Adds plan docs that were pending in the working tree, matching the existing docs/superpowers/plans/ pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf6qFtiQoRuSh9zQSPF1wS
966 lines
43 KiB
Markdown
966 lines
43 KiB
Markdown
# Between-Year Draft → Send-to-Planning Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add a draft/send-to-planning workflow to the "คำขอระหว่างปี" (between-year budget request) menu — save as draft, then explicitly select rows and click "ส่งกองนโยบายและแผน" to send them to the planning office, locking them from further edits.
|
|
|
|
**Architecture:** No new tables/endpoints. Everything hinges on the existing `request_budget.status_id` column (0 = draft, 1 = sent) on the SAME record. The list already shows both states (once we remove a stale query filter); a new checkbox + bulk-send button flips `status_id` 0→1 via the existing generic `RequestBudgetService.updateMany()`. The list-level UI lives in `list-between-year.component.ts/html`; the record-level form lives in a large shared component (`RequestOtherExpensesFormComponent`) reused by many other project-proposal types, so every change there is gated behind `this.typeUrl == 21` (the between-year route's type) to avoid touching other flows.
|
|
|
|
**Tech Stack:** Angular 17, Angular Material (`mat-table`, `mat-checkbox`), Angular CDK `SelectionModel`, RxJS, SweetAlert2 (via `SweetalertService`).
|
|
|
|
## Global Constraints
|
|
|
|
- Scope is strictly the "คำขอระหว่างปี" menu (`typeUrl == 21` / `is_between_year`). Do not change behavior for any other `typeUrl` value in shared files (`request-budget-statistics.container.ts`, `request-budget-statistics-list.component.ts/html`, `request-other-expenses-form.component.ts/html`) — every new branch must be explicitly guarded by `typeUrl == 21`.
|
|
- This repo has no meaningful unit-test culture for these Angular components (only CLI-boilerplate `.spec.ts` files exist project-wide; none for the files touched here). Per-task verification therefore uses `ng build --configuration=production` (from `/Users/nut.looknut/Project/rmutr/rmutr-web`) to catch compile/template errors, plus a manual browser check via `npm start` (dev server with HMR) — this matches how this project has actually been verified throughout this session (Playwright-driven manual checks), not fabricated unit tests.
|
|
- Follow existing code conventions exactly: raw `Swal.fire(...)` in `request-other-expenses-form.component.ts` (it does NOT use `SweetalertService`), vs. `this.swSV.confirmSave()/.updateSuccess()/.errText()` in `request-budget-statistics.container.ts` (it DOES use `SweetalertService`). Don't introduce a new pattern in either file.
|
|
- `BaseService.updateMany(data)` (PUT `{fullUrl}/s`) is the existing generic bulk-update method — reuse it, don't add a new API method.
|
|
|
|
---
|
|
|
|
### Task 1: Fix stale `status_id=1` filter blocking drafts from the list
|
|
|
|
**Files:**
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts:199`
|
|
|
|
**Interfaces:**
|
|
- Consumes: nothing new
|
|
- Produces: `dataSourceBetweenYear$` now returns both draft (`status_id=0`) and sent (`status_id=1`) rows — later tasks (checkbox/status badge in Task 4) rely on both being present in the same list.
|
|
|
|
- [ ] **Step 1: Read the current line to confirm context**
|
|
|
|
The `getAll()` method currently has (line 199):
|
|
```ts
|
|
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true&status_id=1`)
|
|
```
|
|
This hardcodes `status_id=1`, meaning newly-created drafts (`status_id=0`, added by Task 2) would never show up in the list at all. The search path (`onsearch()`, same file, `typeUrl == 21` branch) already has no such filter — this line is inconsistent with it and must be fixed.
|
|
|
|
- [ ] **Step 2: Edit the line**
|
|
|
|
Change:
|
|
```ts
|
|
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true&status_id=1`)
|
|
```
|
|
to:
|
|
```ts
|
|
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true`)
|
|
```
|
|
|
|
- [ ] **Step 3: Build to confirm no errors**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
|
Expected: build succeeds with no new errors.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts
|
|
git commit -m "fix: stop filtering out draft between-year requests from the list"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Set `status_id=0` on create + lock the form once sent (shared form component)
|
|
|
|
**Files:**
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.ts`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `this.typeUrl` (already set at line 391 from route data; between-year route resolves this to `21`, confirmed live via browser console), `this.state` (`'add'`/`'edit'`, set by `BaseFormV2`)
|
|
- Produces: `isLocked: boolean` class field — Task 3 (template) reads this to hide the save button and show a locked banner.
|
|
|
|
- [ ] **Step 1: Add the `isLocked` field**
|
|
|
|
Find (around line 199):
|
|
```ts
|
|
is_plan_log: boolean = false
|
|
```
|
|
Change to:
|
|
```ts
|
|
is_plan_log: boolean = false
|
|
isLocked: boolean = false
|
|
```
|
|
|
|
- [ ] **Step 2: Set `status_id = 0` on create, scoped to `typeUrl == 21`**
|
|
|
|
Find, at the top of `save()` (around line 4455-4458):
|
|
```ts
|
|
save() {
|
|
console.log('ททท',this.request_budget_type_url)
|
|
console.log(this.typeUrl)
|
|
|
|
if(this.typeUrl == 2001){
|
|
```
|
|
Change to:
|
|
```ts
|
|
save() {
|
|
console.log('ททท',this.request_budget_type_url)
|
|
console.log(this.typeUrl)
|
|
|
|
if (this.typeUrl == 21 && this.state === 'add') {
|
|
this.form.get('status_id').setValue(0)
|
|
}
|
|
|
|
if(this.typeUrl == 2001){
|
|
```
|
|
This only ever fires for the between-year route (`typeUrl == 21`) on first creation, leaving every other `typeUrl` branch (including the `typeUrl == 2001` review flow right below, which manages its own `status_id` independently) untouched. On `'edit'` saves of an existing draft, `status_id` is left as whatever was loaded (already `0`), so this is a no-op there too.
|
|
|
|
- [ ] **Step 3: Lock the form when an already-sent between-year record loads**
|
|
|
|
Find, in `setUpform(res)` (around line 975-990):
|
|
```ts
|
|
setUpform(res) {
|
|
console.log(res)
|
|
this.change_year(res.budget_year_uid)
|
|
if (res.budget_strategy_uid) this.change_budget_strategy_edit(res.budget_strategy_uid)
|
|
if (res.budget_strategy_target_uid) this.change_budget_strategy_target_edit(res.budget_strategy_target_uid)
|
|
if (res.budget_reform_uid) this.change_budget_reform_edit(res.budget_reform_uid)
|
|
if (res.budget_reform_target_uid) this.change_budget_reform_target_edit(res.budget_reform_target_uid)
|
|
if (res.budget_economic_uid) this.change_budget_economic_edit(res.budget_economic_uid)
|
|
if (res.budget_economic_target_uid) this.change_budget_economic_target_edit(res.budget_economic_target_uid)
|
|
if (res.budget_strategy_faculty_uid) this.change_budget_strategy_faculty_strategic_edit(res.budget_strategy_faculty_uid)
|
|
if (res.budget_unit_uid) this.change_budget_unit_edit(res.budget_unit_uid)
|
|
if (res.service_insurance_level_uid) this.on_search_Insurance_fac(res.service_insurance_level_uid)
|
|
if (res.service_insurance_faculty_uid) this.on_search_Insurance(res.service_insurance_faculty_uid)
|
|
this.click_faculty_edit(res)
|
|
this.form.patchValue(res)
|
|
```
|
|
Change the last line (`this.form.patchValue(res)`) to add the lock check right after it:
|
|
```ts
|
|
this.click_faculty_edit(res)
|
|
this.form.patchValue(res)
|
|
|
|
if (this.typeUrl == 21) {
|
|
this.isLocked = res.status_id !== 0
|
|
if (this.isLocked) {
|
|
this.form.disable()
|
|
}
|
|
}
|
|
```
|
|
`setUpform` is shared by every project type, so this must stay guarded by `typeUrl == 21` — for any other type, `isLocked` stays `false` and `form.disable()` never runs, preserving current behavior exactly.
|
|
|
|
- [ ] **Step 4: Build to confirm no errors**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
|
Expected: build succeeds with no new errors.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.ts
|
|
git commit -m "feat: save between-year requests as draft (status_id=0) and lock the form once sent"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: Split the save button into "บันทึก" / "บันทึกฉบับร่าง" and show a locked banner
|
|
|
|
**Files:**
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.html:876-877`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `typeUrl`, `isLocked` (from Task 2), `save()` (unchanged method — same call for both buttons)
|
|
- Produces: nothing new for later tasks (this is the leaf-most template change)
|
|
|
|
- [ ] **Step 1: Edit the button block**
|
|
|
|
Find (lines 876-877):
|
|
```html
|
|
<button mat-raised-button cdkFocusInitial (click)="close_request_budget()">ยกเลิก</button>
|
|
<button mat-raised-button class="bg-bpi-primary-color" (click)="save()" *ngIf="typeUrl != 2001" color="primary">บันทึก</button>
|
|
```
|
|
Change to:
|
|
```html
|
|
<span *ngIf="typeUrl == 21 && isLocked" style="color:#ef4444;font-weight:600;margin-right:12px;">ส่งกองนโยบายและแผนแล้ว — ไม่สามารถแก้ไขได้</span>
|
|
<button mat-raised-button cdkFocusInitial (click)="close_request_budget()">ยกเลิก</button>
|
|
<button mat-raised-button class="bg-bpi-primary-color" (click)="save()" *ngIf="typeUrl != 2001 && typeUrl != 21" color="primary">บันทึก</button>
|
|
<button mat-raised-button class="bg-bpi-primary-color" (click)="save()" *ngIf="typeUrl == 21 && !isLocked" color="primary">บันทึกฉบับร่าง</button>
|
|
```
|
|
The original button's `*ngIf` now excludes `typeUrl == 21` (so between-year never shows the generic "บันทึก" label), and a new button shows "บันทึกฉบับร่าง" only for between-year and only while unlocked (draft). When locked, neither save button renders — only the banner and "ยกเลิก".
|
|
|
|
- [ ] **Step 2: Build to confirm no errors**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
|
Expected: build succeeds with no new errors.
|
|
|
|
- [ ] **Step 3: Manual verification**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && npm start`, then in a browser:
|
|
- Open the "คำขอระหว่างปี" menu → "เพิ่มคำขอระหว่างปี" (add). Confirm the button reads "บันทึกฉบับร่าง" (not "บันทึก").
|
|
- Save it, then edit that same record. Confirm "บันทึกฉบับร่าง" still shows (not locked yet — Task 5 is what flips it to sent).
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add rmutr-web/src/app/feature/budget-request/request/request-other-expenses/presenter/form/request-other-expenses-form/request-other-expenses-form.component.html
|
|
git commit -m "feat: label between-year save button as draft and show locked banner when sent"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Checkbox column, status badge, and locked delete icon in the list
|
|
|
|
**Files:**
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/list-between-year.component.ts`
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/list-between-year.component.html`
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/list-between-year.component.scss`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `dataSource` input rows now include both `status_id: 0` (draft) and `status_id: 1` (sent) rows (Task 1), each row has `is_ok`/`is_modify`/`status_id` fields already returned by the API.
|
|
- Produces: `@Output() onsend = new EventEmitter<any[]>()` — emits the array of selected draft rows when "ส่งกองนโยบายและแผน" is clicked. Task 5 wires this output up through the presenter list and container.
|
|
|
|
- [ ] **Step 1: Add `SelectionModel` and the send output to the component class**
|
|
|
|
Find the full current file:
|
|
```ts
|
|
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
|
|
import { PageEvent } from '@angular/material/paginator';
|
|
import { Router } from '@angular/router';
|
|
import { BaseList } from 'src/app/core/base/base-list';
|
|
import { RequestPlanUidService } from 'src/app/core/service/request-budget/request-plan-uid.service';
|
|
|
|
@Component({
|
|
selector: 'app-list-between-year',
|
|
templateUrl: './list-between-year.component.html',
|
|
styleUrls: ['./list-between-year.component.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
})
|
|
export class ListBetweenYearComponent extends BaseList implements OnInit, OnChanges {
|
|
|
|
@Input() dataSource: any = [];
|
|
@Output() onchange = new EventEmitter();
|
|
@Output() onedit = new EventEmitter();
|
|
@Output() ondelete = new EventEmitter();
|
|
@Output() onexcel = new EventEmitter();
|
|
data
|
|
|
|
constructor(
|
|
private router: Router,
|
|
private RequestPlanUidSV: RequestPlanUidService,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if ('dataSource' in changes && changes?.dataSource?.currentValue) {
|
|
this.data = this.dataSource;
|
|
this.dataSource = this.updateMatTable(this.dataSource ? this.dataSource : []);
|
|
}
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
}
|
|
|
|
add() {
|
|
this.RequestPlanUidSV.is_between_year$.next(true);
|
|
this.RequestPlanUidSV.request_budget_type$.next('ae9afcdb-a8d6-4be6-b440-931e7a27fa73');
|
|
this.router.navigate(['app/request-budget-between-year/add-between-year/add']);
|
|
}
|
|
|
|
edit(val) {
|
|
this.router.navigate(['app/request-budget-between-year/add-between-year/edit', val.request_budget_uid]);
|
|
}
|
|
|
|
delete(val) {
|
|
this.ondelete.emit(val);
|
|
}
|
|
|
|
change(event: PageEvent) {
|
|
const page: number = event.pageIndex + 1;
|
|
const table: any = { page, size: event.pageSize };
|
|
this.onchange.emit(table);
|
|
}
|
|
|
|
excel(val) {
|
|
this.onexcel.emit(val);
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
Replace it entirely with:
|
|
```ts
|
|
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
|
|
import { SelectionModel } from '@angular/cdk/collections';
|
|
import { PageEvent } from '@angular/material/paginator';
|
|
import { Router } from '@angular/router';
|
|
import { BaseList } from 'src/app/core/base/base-list';
|
|
import { RequestPlanUidService } from 'src/app/core/service/request-budget/request-plan-uid.service';
|
|
|
|
@Component({
|
|
selector: 'app-list-between-year',
|
|
templateUrl: './list-between-year.component.html',
|
|
styleUrls: ['./list-between-year.component.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
})
|
|
export class ListBetweenYearComponent extends BaseList implements OnInit, OnChanges {
|
|
|
|
@Input() dataSource: any = [];
|
|
@Output() onchange = new EventEmitter();
|
|
@Output() onedit = new EventEmitter();
|
|
@Output() ondelete = new EventEmitter();
|
|
@Output() onexcel = new EventEmitter();
|
|
@Output() onsend = new EventEmitter();
|
|
selection = new SelectionModel<any>(true, []);
|
|
data
|
|
|
|
constructor(
|
|
private router: Router,
|
|
private RequestPlanUidSV: RequestPlanUidService,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if ('dataSource' in changes && changes?.dataSource?.currentValue) {
|
|
this.data = this.dataSource;
|
|
this.dataSource = this.updateMatTable(this.dataSource ? this.dataSource : []);
|
|
this.selection.clear();
|
|
}
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
}
|
|
|
|
add() {
|
|
this.RequestPlanUidSV.is_between_year$.next(true);
|
|
this.RequestPlanUidSV.request_budget_type$.next('ae9afcdb-a8d6-4be6-b440-931e7a27fa73');
|
|
this.router.navigate(['app/request-budget-between-year/add-between-year/add']);
|
|
}
|
|
|
|
edit(val) {
|
|
this.router.navigate(['app/request-budget-between-year/add-between-year/edit', val.request_budget_uid]);
|
|
}
|
|
|
|
delete(val) {
|
|
this.ondelete.emit(val);
|
|
}
|
|
|
|
change(event: PageEvent) {
|
|
const page: number = event.pageIndex + 1;
|
|
const table: any = { page, size: event.pageSize };
|
|
this.onchange.emit(table);
|
|
}
|
|
|
|
excel(val) {
|
|
this.onexcel.emit(val);
|
|
}
|
|
|
|
private draftRows(): any[] {
|
|
return (this.data || []).filter((x: any) => x.status_id === 0);
|
|
}
|
|
|
|
isAllSelected(): boolean {
|
|
const draft = this.draftRows();
|
|
return draft.length > 0 && this.selection.selected.length === draft.length;
|
|
}
|
|
|
|
masterToggle(): void {
|
|
if (this.isAllSelected()) {
|
|
this.selection.clear();
|
|
return;
|
|
}
|
|
this.selection.select(...this.draftRows());
|
|
}
|
|
|
|
checkboxLabel(row?: any): string {
|
|
if (!row) {
|
|
return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
|
|
}
|
|
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row`;
|
|
}
|
|
|
|
sendToStrategic(): void {
|
|
this.onsend.emit(this.selection.selected);
|
|
}
|
|
|
|
}
|
|
```
|
|
Selection is cleared on every `dataSource` change (Angular gives new row object instances on every refresh, so stale selected references would otherwise linger); only draft (`status_id === 0`) rows are selectable, matching the same "already-sent rows can't be selected" pattern used by `list6`/`list24` elsewhere in this codebase.
|
|
|
|
- [ ] **Step 2: Add the checkbox column, fix the status column, lock the delete icon, add the send button**
|
|
|
|
Find the full current file:
|
|
```html
|
|
<div class="list-card">
|
|
<div class="toolbar">
|
|
<button mat-raised-button color="primary" class="btn-add" (click)="add()">
|
|
<mat-icon>add</mat-icon>
|
|
เพิ่มคำขอระหว่างปี
|
|
</button>
|
|
</div>
|
|
|
|
<div class="table-wrap">
|
|
<table mat-table [dataSource]="dataSource" class="list-table">
|
|
<tr mat-header-row
|
|
*matHeaderRowDef="['action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','total_budget']">
|
|
</tr>
|
|
<tr mat-row
|
|
*matRowDef="let myRowData; columns: ['action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','total_budget']">
|
|
</tr>
|
|
<tr mat-footer-row
|
|
*matFooterRowDef="['action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','total_budget']">
|
|
</tr>
|
|
|
|
<!-- Action -->
|
|
<ng-container matColumnDef="action">
|
|
<th mat-header-cell *matHeaderCellDef class="col-action"></th>
|
|
<td mat-cell *matCellDef="let x" class="col-action">
|
|
<span class="material-icons action-icon" (click)="edit(x)" title="แก้ไข">edit</span>
|
|
<span class="material-icons action-icon action-icon--delete" (click)="delete(x)" title="ลบ">delete</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ปีงบประมาณ -->
|
|
<ng-container matColumnDef="budget_year_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-year">ปีงบฯ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-year">
|
|
<span class="badge-year">{{x.budget_year_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- พื้นที่ -->
|
|
<ng-container matColumnDef="budget_location_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-location">พื้นที่</th>
|
|
<td mat-cell *matCellDef="let x" class="col-location">
|
|
<span class="text-clip">{{x.budget_location_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- หน่วยงาน/คณะ -->
|
|
<ng-container matColumnDef="faculty_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-faculty">หน่วยงาน/คณะ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-faculty">
|
|
<span class="text-clip">{{x.responsible_faculty_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ชื่อโครงการ -->
|
|
<ng-container matColumnDef="project_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-project">ชื่อโครงการ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-project">
|
|
<span class="text-clip project-name">{{x.project_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ผลผลิต -->
|
|
<ng-container matColumnDef="budget_project_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-project-type">ผลผลิต</th>
|
|
<td mat-cell *matCellDef="let x" class="col-project-type">
|
|
<span class="text-clip">{{x.budget_project_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ด้าน -->
|
|
<ng-container matColumnDef="budget_topic_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-topic">ด้าน</th>
|
|
<td mat-cell *matCellDef="let x" class="col-topic">
|
|
<span class="text-clip">{{x.budget_topic_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ประเภทคำขอ -->
|
|
<ng-container matColumnDef="request_type">
|
|
<th mat-header-cell *matHeaderCellDef class="col-request-type">ประเภทคำขอ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-request-type">
|
|
<span class="badge badge-between">คำขอระหว่างปี</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- สถานะ -->
|
|
<!-- <ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef class="col-status">สถานะ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-status">
|
|
<span *ngIf="x.is_ok == true" class="badge badge-success">ตรวจสอบแล้ว</span>
|
|
<span *ngIf="x.is_modify == true" class="badge badge-warning">ส่งแก้ไข</span>
|
|
<span *ngIf="!x.is_ok && !x.is_modify && x.status_id == 1" class="badge badge-pending">รอตรวจสอบ</span>
|
|
<span *ngIf="x.status_id == 0" class="badge badge-draft">ร่าง</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef class="footer-label">จำนวนเงินรวม</td>
|
|
</ng-container> -->
|
|
|
|
<!-- วงเงิน -->
|
|
<ng-container matColumnDef="total_budget">
|
|
<th mat-header-cell *matHeaderCellDef class="col-amount">วงเงิน (บาท)</th>
|
|
<td mat-cell *matCellDef="let x" class="col-amount">
|
|
{{x.total_budget | number:'1.2-2'}}
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef class="col-amount footer-total">
|
|
{{data | total:'total_budget' | number:'1.2-2'}}
|
|
</td>
|
|
</ng-container>
|
|
</table>
|
|
</div>
|
|
|
|
<mat-paginator [pageSizeOptions]="[20, 50, 100]" showFirstLastButtons (page)="change($event)"></mat-paginator>
|
|
</div>
|
|
```
|
|
|
|
Replace it entirely with:
|
|
```html
|
|
<div class="list-card">
|
|
<div class="toolbar">
|
|
<button mat-raised-button color="primary" class="btn-add" (click)="add()">
|
|
<mat-icon>add</mat-icon>
|
|
เพิ่มคำขอระหว่างปี
|
|
</button>
|
|
<button mat-raised-button color="accent" class="btn-send" [disabled]="selection.selected.length === 0" (click)="sendToStrategic()">
|
|
<mat-icon>send</mat-icon>
|
|
ส่งกองนโยบายและแผน
|
|
</button>
|
|
</div>
|
|
|
|
<div class="table-wrap">
|
|
<table mat-table [dataSource]="dataSource" class="list-table">
|
|
<tr mat-header-row
|
|
*matHeaderRowDef="['checkbox','action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','status','total_budget']">
|
|
</tr>
|
|
<tr mat-row
|
|
*matRowDef="let myRowData; columns: ['checkbox','action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','status','total_budget']">
|
|
</tr>
|
|
<tr mat-footer-row
|
|
*matFooterRowDef="['checkbox','action','budget_year_name_th','budget_location_name_th','faculty_name_th','project_name_th','budget_project_name_th','budget_topic_name_th','request_type','status','total_budget']">
|
|
</tr>
|
|
|
|
<!-- เลือก -->
|
|
<ng-container matColumnDef="checkbox">
|
|
<th mat-header-cell *matHeaderCellDef class="col-checkbox">
|
|
<mat-checkbox
|
|
(change)="$event ? masterToggle() : null"
|
|
[checked]="selection.hasValue() && isAllSelected()"
|
|
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
|
</mat-checkbox>
|
|
</th>
|
|
<td mat-cell *matCellDef="let x" class="col-checkbox">
|
|
<mat-checkbox
|
|
(click)="$event.stopPropagation()"
|
|
(change)="$event ? selection.toggle(x) : null"
|
|
[checked]="selection.isSelected(x)"
|
|
[aria-label]="checkboxLabel(x)"
|
|
[disabled]="x.status_id !== 0">
|
|
</mat-checkbox>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- Action -->
|
|
<ng-container matColumnDef="action">
|
|
<th mat-header-cell *matHeaderCellDef class="col-action"></th>
|
|
<td mat-cell *matCellDef="let x" class="col-action">
|
|
<span class="material-icons action-icon" (click)="edit(x)" title="แก้ไข">edit</span>
|
|
<span *ngIf="x.status_id === 0" class="material-icons action-icon action-icon--delete" (click)="delete(x)" title="ลบ">delete</span>
|
|
<span *ngIf="x.status_id !== 0" class="material-icons action-icon action-icon--disabled" title="ส่งไปงานแผนแล้ว ลบไม่ได้">delete</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ปีงบประมาณ -->
|
|
<ng-container matColumnDef="budget_year_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-year">ปีงบฯ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-year">
|
|
<span class="badge-year">{{x.budget_year_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- พื้นที่ -->
|
|
<ng-container matColumnDef="budget_location_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-location">พื้นที่</th>
|
|
<td mat-cell *matCellDef="let x" class="col-location">
|
|
<span class="text-clip">{{x.budget_location_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- หน่วยงาน/คณะ -->
|
|
<ng-container matColumnDef="faculty_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-faculty">หน่วยงาน/คณะ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-faculty">
|
|
<span class="text-clip">{{x.responsible_faculty_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ชื่อโครงการ -->
|
|
<ng-container matColumnDef="project_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-project">ชื่อโครงการ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-project">
|
|
<span class="text-clip project-name">{{x.project_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ผลผลิต -->
|
|
<ng-container matColumnDef="budget_project_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-project-type">ผลผลิต</th>
|
|
<td mat-cell *matCellDef="let x" class="col-project-type">
|
|
<span class="text-clip">{{x.budget_project_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ด้าน -->
|
|
<ng-container matColumnDef="budget_topic_name_th">
|
|
<th mat-header-cell *matHeaderCellDef class="col-topic">ด้าน</th>
|
|
<td mat-cell *matCellDef="let x" class="col-topic">
|
|
<span class="text-clip">{{x.budget_topic_name_th}}</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- ประเภทคำขอ -->
|
|
<ng-container matColumnDef="request_type">
|
|
<th mat-header-cell *matHeaderCellDef class="col-request-type">ประเภทคำขอ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-request-type">
|
|
<span class="badge badge-between">คำขอระหว่างปี</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- สถานะ -->
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef class="col-status">สถานะ</th>
|
|
<td mat-cell *matCellDef="let x" class="col-status">
|
|
<span *ngIf="x.is_ok == true" class="badge badge-success">ตรวจสอบแล้ว</span>
|
|
<span *ngIf="x.is_modify == true" class="badge badge-warning">ส่งแก้ไข</span>
|
|
<span *ngIf="!x.is_ok && !x.is_modify && x.status_id == 1" class="badge badge-pending">ส่งงานแผน</span>
|
|
<span *ngIf="x.status_id == 0" class="badge badge-draft">ร่าง</span>
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef></td>
|
|
</ng-container>
|
|
|
|
<!-- วงเงิน -->
|
|
<ng-container matColumnDef="total_budget">
|
|
<th mat-header-cell *matHeaderCellDef class="col-amount">วงเงิน (บาท)</th>
|
|
<td mat-cell *matCellDef="let x" class="col-amount">
|
|
{{x.total_budget | number:'1.2-2'}}
|
|
</td>
|
|
<td mat-footer-cell *matFooterCellDef class="col-amount footer-total">
|
|
{{data | total:'total_budget' | number:'1.2-2'}}
|
|
</td>
|
|
</ng-container>
|
|
</table>
|
|
</div>
|
|
|
|
<mat-paginator [pageSizeOptions]="[20, 50, 100]" showFirstLastButtons (page)="change($event)"></mat-paginator>
|
|
</div>
|
|
```
|
|
|
|
- [ ] **Step 3: Add the checkbox column width and locked-icon styles**
|
|
|
|
Find, in the `.scss` file:
|
|
```scss
|
|
.action-icon {
|
|
font-size: 17px !important;
|
|
width: 17px !important;
|
|
height: 17px !important;
|
|
color: #f8a300;
|
|
cursor: pointer;
|
|
display: inline-block;
|
|
margin: 0 4px;
|
|
transition: opacity 0.15s;
|
|
vertical-align: middle;
|
|
|
|
&:hover { opacity: 0.7; }
|
|
|
|
&--delete {
|
|
color: #ef4444;
|
|
}
|
|
}
|
|
```
|
|
Change to:
|
|
```scss
|
|
.action-icon {
|
|
font-size: 17px !important;
|
|
width: 17px !important;
|
|
height: 17px !important;
|
|
color: #f8a300;
|
|
cursor: pointer;
|
|
display: inline-block;
|
|
margin: 0 4px;
|
|
transition: opacity 0.15s;
|
|
vertical-align: middle;
|
|
|
|
&:hover { opacity: 0.7; }
|
|
|
|
&--delete {
|
|
color: #ef4444;
|
|
}
|
|
|
|
&--disabled {
|
|
color: #d1d5db;
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
|
|
.col-checkbox {
|
|
width: 44px !important;
|
|
min-width: 44px !important;
|
|
max-width: 44px !important;
|
|
text-align: center !important;
|
|
}
|
|
```
|
|
Also find, near the top of the file:
|
|
```scss
|
|
.btn-add {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
```
|
|
Change to:
|
|
```scss
|
|
.btn-add {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-send {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Build to confirm no errors**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
|
Expected: build succeeds with no new errors.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list-between-year/
|
|
git commit -m "feat: add checkbox selection, status badge, and locked delete icon to between-year list"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: Wire the send action end-to-end and implement the bulk status update
|
|
|
|
**Files:**
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.ts`
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.html`
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.html`
|
|
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `list-between-year`'s `@Output() onsend` (Task 4) — emits `any[]` of selected draft rows.
|
|
- Produces: nothing further downstream — this is the final task; the sent rows automatically appear in the existing "ตรวจสอบคำเสนอขอโครงการ" menu (`check-project-proposal`) because that menu already queries `status_id=1` on the same table (`request_budget_and_strategic`), unchanged by this plan.
|
|
|
|
- [ ] **Step 1: Add the output + forwarding method to the presenter list component**
|
|
|
|
Find, in `request-budget-statistics-list.component.ts`:
|
|
```ts
|
|
@Output() onsave22 = new EventEmitter();
|
|
@Output() onsendPlan22 = new EventEmitter();
|
|
```
|
|
Change to:
|
|
```ts
|
|
@Output() onsave22 = new EventEmitter();
|
|
@Output() onsendPlan22 = new EventEmitter();
|
|
@Output() onsendBetweenYear = new EventEmitter();
|
|
```
|
|
|
|
Find:
|
|
```ts
|
|
save22(val){
|
|
this.onsave22.emit(val)
|
|
}
|
|
|
|
sendPlan22(val){
|
|
this.onsendPlan22.emit(val)
|
|
}
|
|
```
|
|
Change to:
|
|
```ts
|
|
save22(val){
|
|
this.onsave22.emit(val)
|
|
}
|
|
|
|
sendPlan22(val){
|
|
this.onsendPlan22.emit(val)
|
|
}
|
|
|
|
sendBetweenYear(val){
|
|
this.onsendBetweenYear.emit(val)
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Wire the child `(onsend)` into the new method in the presenter list's template**
|
|
|
|
Find, in `request-budget-statistics-list.component.html`:
|
|
```html
|
|
<ng-container *ngIf="typeUrl == 21">
|
|
<app-list-between-year
|
|
[dataSource]="dataSourceBetweenYear"
|
|
(ondelete)="onDelete($event)"
|
|
(onchange)="change($event)"
|
|
(onexcel)="excel($event)"
|
|
></app-list-between-year>
|
|
</ng-container>
|
|
```
|
|
Change to:
|
|
```html
|
|
<ng-container *ngIf="typeUrl == 21">
|
|
<app-list-between-year
|
|
[dataSource]="dataSourceBetweenYear"
|
|
(ondelete)="onDelete($event)"
|
|
(onchange)="change($event)"
|
|
(onexcel)="excel($event)"
|
|
(onsend)="sendBetweenYear($event)"
|
|
></app-list-between-year>
|
|
</ng-container>
|
|
```
|
|
|
|
- [ ] **Step 3: Wire the presenter list's `(onsendBetweenYear)` into the container's template**
|
|
|
|
Find, in `request-budget-statistics.container.html`:
|
|
```html
|
|
(onedit)="onedit($event)"
|
|
(onedit_request_budget)="onedit_request_budget($event)"
|
|
(ondelete)="ondelete($event)"
|
|
(onchange)="onchange($event)"
|
|
```
|
|
Change to:
|
|
```html
|
|
(onedit)="onedit($event)"
|
|
(onedit_request_budget)="onedit_request_budget($event)"
|
|
(ondelete)="ondelete($event)"
|
|
(onchange)="onchange($event)"
|
|
(onsendBetweenYear)="sendBetweenYearToStrategic($event)"
|
|
```
|
|
|
|
- [ ] **Step 4: Implement the bulk-send handler in the container**
|
|
|
|
Find, in `request-budget-statistics.container.ts`, the `ondelete(value)` method:
|
|
```ts
|
|
ondelete(value){
|
|
// console.log(value)
|
|
this.swSV.confirm().pipe(
|
|
filter(x => x.isConfirmed),
|
|
concatMap(() => {
|
|
const delete$ = (this.typeUrl == 7 || this.typeUrl == 22 || this.typeUrl == 9 || this.typeUrl == 11)
|
|
? this.requestBudgetAnalysis5SV.deleteData(value.request_budget_analysis_5_uid)
|
|
: this.requestBudgetSV.deleteData(value.request_budget_uid)
|
|
return delete$.pipe(
|
|
tap(() => this.swSV.removeSuccess()),
|
|
tap(() => this.ngOnInit()),
|
|
tap(() => this.cdRef.detectChanges())
|
|
)
|
|
})
|
|
).subscribe()
|
|
}
|
|
```
|
|
Add a new method directly after it:
|
|
```ts
|
|
ondelete(value){
|
|
// console.log(value)
|
|
this.swSV.confirm().pipe(
|
|
filter(x => x.isConfirmed),
|
|
concatMap(() => {
|
|
const delete$ = (this.typeUrl == 7 || this.typeUrl == 22 || this.typeUrl == 9 || this.typeUrl == 11)
|
|
? this.requestBudgetAnalysis5SV.deleteData(value.request_budget_analysis_5_uid)
|
|
: this.requestBudgetSV.deleteData(value.request_budget_uid)
|
|
return delete$.pipe(
|
|
tap(() => this.swSV.removeSuccess()),
|
|
tap(() => this.ngOnInit()),
|
|
tap(() => this.cdRef.detectChanges())
|
|
)
|
|
})
|
|
).subscribe()
|
|
}
|
|
|
|
sendBetweenYearToStrategic(value: any[]){
|
|
const selected = (value || []).filter(v => v?.status_id === 0)
|
|
if (selected.length === 0) {
|
|
this.swSV.errText('กรุณาเลือกรายการที่ต้องการส่ง')
|
|
return
|
|
}
|
|
this.swSV.confirmSave('ต้องการส่งรายการที่เลือกไปกองนโยบายและแผนหรือไม่?').pipe(
|
|
filter(x => x.isConfirmed),
|
|
concatMap(() => {
|
|
const putData = selected.map(v => ({ ...v, status_id: 1, is_examine: true }))
|
|
return this.requestBudgetSV.updateMany(putData).pipe(
|
|
catchError(err => {
|
|
this.swSV.errText(err?.error?.description || 'ส่งไม่สำเร็จ')
|
|
return throwError(err)
|
|
})
|
|
)
|
|
}),
|
|
tap(() => this.swSV.updateSuccess()),
|
|
tap(() => this.getAll()),
|
|
tap(() => this.cdRef.detectChanges())
|
|
).subscribe()
|
|
}
|
|
```
|
|
`filter`, `concatMap`, `tap`, `catchError`, and `throwError` are already imported at the top of this file (used throughout the other handlers above), so no new imports are needed.
|
|
|
|
- [ ] **Step 5: Build to confirm no errors**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && ng build --configuration=production`
|
|
Expected: build succeeds with no new errors.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.ts rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/request-budget-statistics-list.component.html rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.html rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts
|
|
git commit -m "feat: wire checkbox send action to bulk-update between-year requests to sent status"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: End-to-end manual verification
|
|
|
|
**Files:** none (verification only)
|
|
|
|
**Interfaces:** none
|
|
|
|
- [ ] **Step 1: Start the dev server**
|
|
|
|
Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && npm start`
|
|
|
|
- [ ] **Step 2: Draft creation**
|
|
|
|
In the browser, open "แผนงานและโครงการ > คำขอระหว่างปี" → "เพิ่มคำขอระหว่างปี" → fill required fields → click "บันทึกฉบับร่าง".
|
|
Expected: record appears in the list with badge "ร่าง", checkbox enabled, both "แก้ไข" and "ลบ" icons active.
|
|
|
|
- [ ] **Step 3: Edit a draft**
|
|
|
|
Click "แก้ไข" on the drafted row.
|
|
Expected: form loads fully editable, button still reads "บันทึกฉบับร่าง", no locked banner.
|
|
|
|
- [ ] **Step 4: Send to planning**
|
|
|
|
Back in the list, tick the checkbox on 1-2 draft rows → click "ส่งกองนโยบายและแผน" → confirm the dialog.
|
|
Expected: success toast, rows now show badge "ส่งงานแผน", checkbox greyed out and unchecked, "ลบ" icon greyed out/disabled, "แก้ไข" icon still clickable.
|
|
|
|
- [ ] **Step 5: View a sent (locked) record**
|
|
|
|
Click "แก้ไข" on a now-sent row.
|
|
Expected: form loads with all fields disabled, red banner "ส่งกองนโยบายและแผนแล้ว — ไม่สามารถแก้ไขได้" visible, no save button, only "ยกเลิก".
|
|
|
|
- [ ] **Step 6: Confirm it reached the planning menu**
|
|
|
|
Open "แผนงานและโครงการ > ตรวจสอบคำเสนอขอโครงการ" → tab "จัดการคำเสนอขอ".
|
|
Expected: the just-sent project(s) appear there (this is existing, unmodified behavior — confirms `status_id=1` propagation works end-to-end).
|
|
|
|
- [ ] **Step 7: Regression check on an unrelated menu**
|
|
|
|
Open "แผนงานและโครงการ > ต้นฉบับเสนอโครงการ ง.5" (typeUrl 1, uses the same shared form component) → add or edit a record.
|
|
Expected: behaves exactly as before — plain "บันทึก" button (not "บันทึกฉบับร่าง"), no locked banner, saves normally. This confirms the `typeUrl == 21` guards in Task 2/3 didn't leak into other project types.
|