# 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     ``` Change to: ```html ส่งกองนโยบายและแผนแล้ว — ไม่สามารถแก้ไขได้     ``` 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()` — 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(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
edit delete ปีงบฯ {{x.budget_year_name_th}} พื้นที่ {{x.budget_location_name_th}} หน่วยงาน/คณะ {{x.responsible_faculty_name_th}} ชื่อโครงการ {{x.project_name_th}} ผลผลิต {{x.budget_project_name_th}} ด้าน {{x.budget_topic_name_th}} ประเภทคำขอ คำขอระหว่างปี วงเงิน (บาท) {{x.total_budget | number:'1.2-2'}}
``` Replace it entirely with: ```html
edit delete delete ปีงบฯ {{x.budget_year_name_th}} พื้นที่ {{x.budget_location_name_th}} หน่วยงาน/คณะ {{x.responsible_faculty_name_th}} ชื่อโครงการ {{x.project_name_th}} ผลผลิต {{x.budget_project_name_th}} ด้าน {{x.budget_topic_name_th}} ประเภทคำขอ คำขอระหว่างปี สถานะ ตรวจสอบแล้ว ส่งแก้ไข ส่งงานแผน ร่าง วงเงิน (บาท) {{x.total_budget | number:'1.2-2'}}
``` - [ ] **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 ``` Change to: ```html ``` - [ ] **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.