diff --git a/docs/superpowers/plans/2026-07-22-in-plan-report-submit-review-fix.md b/docs/superpowers/plans/2026-07-22-in-plan-report-submit-review-fix.md new file mode 100644 index 0000000..4456348 --- /dev/null +++ b/docs/superpowers/plans/2026-07-22-in-plan-report-submit-review-fix.md @@ -0,0 +1,333 @@ +# In-Plan Report Submit/Review Bug-Fix 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:** Fix the dead "ส่งให้กองแผนตรวจสอบ" button on `agency-report-form/list-all` (typeUrl 20) so it actually submits selected reports to the planning division, add a status badge + checkbox guard to that list, and lock the report form for the agency once it's submitted — mirroring the pattern already shipped for out-of-plan reports. + +**Architecture:** No new tables/endpoints/components. Three surgical edits to existing files: (1) one added boolean condition in `make-year-plant-form.component.ts`'s `save()` so typeUrl 20 stops being intercepted by the generic `isListAllMode()` branch and instead reaches its own already-correct `else if(this.typeUrl == 20)` block; (2) a `[disabled]` binding plus a new `reportStatusBadge()` helper and badge `` in the list-all checkbox column; (3) one added `if` block in `agency-report-form.component.ts`'s existing `typeUrl == 202` branch, copied from the identical pattern already present in the `typeUrl == 282` branch two branches below it. + +**Tech Stack:** Angular 17, Angular Material (`mat-checkbox`), RxJS (`tap`). + +## Global Constraints + +- No backend (`rmutr-api`) changes — `AgencyReportService.updateStatusAgency()` (POST `update_status/1`) and `AgencyReportService.get()` already exist and already work (used today by the out-of-plan flow and by `check-project-report`). +- typeUrl 12 and typeUrl 14 behavior in `make-year-plant-form.component.ts` must be **completely unaffected** — every change in this plan is gated so it only fires for `typeUrl === 20`. +- Do not touch `check-project-report` (typeUrl 16) or any out-of-plan code (typeUrl 28x/29x) — both already work correctly per prior investigation and must not regress. +- This repo has no meaningful unit-test culture for these Angular components. Per-task verification 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` for the final task. +- Follow existing code conventions exactly: raw `Swal`/`this.swSV.confirmSave()`/`this.swSV.errText()` patterns already used in these files — don't introduce a new dialog library. + +--- + +### Task 1: Fix the `save()` dispatch bug for typeUrl 20 + +**Files:** +- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/make-year-plan-form/make-year-plant-form.component.ts:2380` + +**Interfaces:** +- Consumes: nothing new — `this.typeUrl` (existing component field), `this.isListAllMode()` (existing method, ts:159-161) +- Produces: nothing new — this task only changes which existing branch of `save()` executes for typeUrl 20. The already-existing `else if(this.typeUrl == 20)` block (ts:2439-2508) becomes reachable; its `case 'add':` sub-branch (ts:2457-2478) is what will now run when the button is clicked from `list-all` (route has no `:id`, so `this.state` is always `'add'` there). + +- [ ] **Step 1: Change the guard condition** + +Find, in `make-year-plant-form.component.ts`: +```ts + save(){ + console.log(this.typeUrl) + + if(this.isListAllMode()){ +``` +Change to: +```ts + save(){ + console.log(this.typeUrl) + + if(this.isListAllMode() && this.typeUrl !== 20){ +``` + +- [ ] **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: Commit** + +```bash +git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/make-year-plan-form/make-year-plant-form.component.ts +git commit -m "fix: route typeUrl 20 list-all submit through the correct save() branch" +``` + +--- + +### Task 2: Checkbox guard + status badge on the `list-all` report column + +**Files:** +- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/make-year-plan-form/make-year-plant-form.component.ts` (add helper method near `isListAllMode()`, ts:159-161) +- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/make-year-plan-form/make-year-plan-form.component.html:472-493` + +**Interfaces:** +- Consumes: `item.value.agency_report.agency_report_uid` / `item.value.agency_report.status_id` (existing form data, already patched from backend — confirmed present at ts:1446, 1448 in the `agency_report` FormGroup builder) +- Produces: `reportStatusBadge(status_id: number | null): {label: string, bg: string, color: string}` — a new public method on `MakeYearPlanFormComponent`/`MakeYearPlantFormComponent`, called only from the template in this task. No other task depends on it. + +- [ ] **Step 1: Add the `reportStatusBadge()` helper method** + +Find, in `make-year-plant-form.component.ts`: +```ts + isListAllMode(): boolean { + return this.state === 'add' && (this.typeUrl === 12 || this.typeUrl === 14 || this.typeUrl === 20) + } +``` +Change to: +```ts + isListAllMode(): boolean { + return this.state === 'add' && (this.typeUrl === 12 || this.typeUrl === 14 || this.typeUrl === 20) + } + + reportStatusBadge(status_id: number | null): { label: string, bg: string, color: string } { + switch (status_id) { + case 1: return { label: 'รอตรวจสอบ', bg: '#fef3c7', color: '#92400e' } + case 2: return { label: 'ส่งแก้ไข', bg: '#fee2e2', color: '#991b1b' } + case 3: return { label: 'ตรวจสอบแล้ว', bg: '#dcfce7', color: '#166534' } + default: return { label: 'ร่าง', bg: '#e5e7eb', color: '#374151' } + } + } +``` + +- [ ] **Step 2: Add `[disabled]` to the checkbox and the badge `` in the template** + +Find, in `make-year-plan-form.component.html`: +```html + +
+ + + + แจ้งแล้ว {{getSentCount(item.value)}} ครั้ง + + + +
+ +``` +Change to: +```html + +
+ + + + แจ้งแล้ว {{getSentCount(item.value)}} ครั้ง + + + + + {{reportStatusBadge(item.value.agency_report?.status_id).label}} + +
+ +``` + +- [ ] **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/presenter/form/make-year-plan-form/make-year-plant-form.component.ts rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/make-year-plan-form/make-year-plan-form.component.html +git commit -m "feat: add status badge and re-submit guard to list-all report checkboxes" +``` + +--- + +### Task 3: Lock the in-plan report form once submitted + +**Files:** +- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/agency-report-form/agency-report-form.component.ts:469-508` + +**Interfaces:** +- Consumes: `this.isLocked` (existing field, ts:180, already declared — no new field needed), `x.status_id` (existing `agency_report` field, already loaded by `AgencyReportSV.get()`) +- Produces: nothing new — `isLocked` being `true` is already read by the existing template bindings at `agency-report-form.component.html:428` (locked banner) and `:430` (hides the "บันทึก" button), both unconditional on `typeUrl`, so they activate automatically for typeUrl `202`/`202163` once this task sets the flag. The `save_send()`/`save_pass()` buttons (gated by `typeUrl == 202161`) are untouched by this task and keep working because both methods call `getRawValue()`, which reads values from disabled controls. + +- [ ] **Step 1: Add the lock check to the `typeUrl == 202` branch** + +Find, in `agency-report-form.component.ts`: +```ts + else if(this.typeUrl == 202 || this.typeUrl == 202161 || this.typeUrl == 202162 || this.typeUrl == 202163) { + this.AgencyReportSV.get(this.uniquekey).pipe( + tap((x:any)=>{ + console.log(x) + this.form.patchValue(x) + this.change_year(x.budget_year_name_th) + if (x.agency_report_items) { + const formArray = this.form.get('agency_report_items') as FormArray + x.agency_report_items.forEach((agency_report_item) => { + formArray.push(this.agency_report_item_form(agency_report_item)) + }); + } + if (x.agency_report_details) { + x.agency_report_details.forEach((agency_report_detail) => { + let form_agency_report_details = this.form.get('agency_report_details') as FormArray + form_agency_report_details.push(this.agency_report_details_form(agency_report_detail)) + }); + const firstIssue = x.agency_report_details.find(d => d.type == 1 && d.text_1) + if (firstIssue) this.change_budget_strategy_faculty_strategic_edit(firstIssue.text_1) + } + if (x.agency_report_popularities) { + x.agency_report_popularities.forEach((p) => { + const arr = this.form.get('agency_report_popularities') as FormArray + arr.push(this.fb.group(p)) + }) + this.cdRef.detectChanges() + } + }), + concatMap((x: any) => x.change_project_detail_uid + ? this.ChangeProjectDetailSV.get(x.change_project_detail_uid) + : of(null) + ), + tap((changeDetail: any) => { + if (changeDetail?.project_name_th) { + this.form.get('project_name_th').setValue(changeDetail.project_name_th) + } + Swal.close() + }) + ).subscribe() + } +``` +Change to: +```ts + else if(this.typeUrl == 202 || this.typeUrl == 202161 || this.typeUrl == 202162 || this.typeUrl == 202163) { + this.AgencyReportSV.get(this.uniquekey).pipe( + tap((x:any)=>{ + console.log(x) + this.form.patchValue(x) + this.change_year(x.budget_year_name_th) + if (x.agency_report_items) { + const formArray = this.form.get('agency_report_items') as FormArray + x.agency_report_items.forEach((agency_report_item) => { + formArray.push(this.agency_report_item_form(agency_report_item)) + }); + } + if (x.agency_report_details) { + x.agency_report_details.forEach((agency_report_detail) => { + let form_agency_report_details = this.form.get('agency_report_details') as FormArray + form_agency_report_details.push(this.agency_report_details_form(agency_report_detail)) + }); + const firstIssue = x.agency_report_details.find(d => d.type == 1 && d.text_1) + if (firstIssue) this.change_budget_strategy_faculty_strategic_edit(firstIssue.text_1) + } + if (x.agency_report_popularities) { + x.agency_report_popularities.forEach((p) => { + const arr = this.form.get('agency_report_popularities') as FormArray + arr.push(this.fb.group(p)) + }) + this.cdRef.detectChanges() + } + if (x.status_id === 1 || x.status_id === 3) { + this.isLocked = true + this.form.disable() + } + }), + concatMap((x: any) => x.change_project_detail_uid + ? this.ChangeProjectDetailSV.get(x.change_project_detail_uid) + : of(null) + ), + tap((changeDetail: any) => { + if (changeDetail?.project_name_th) { + this.form.get('project_name_th').setValue(changeDetail.project_name_th) + } + Swal.close() + }) + ).subscribe() + } +``` + +- [ ] **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: Commit** + +```bash +git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/form/agency-report-form/agency-report-form.component.ts +git commit -m "feat: lock in-plan report form once submitted or reviewed" +``` + +--- + +### Task 4: End-to-end manual verification + +**Files:** none (verification only) + +**Interfaces:** +- Consumes: all of Tasks 1-3 +- Produces: nothing — this is the final confirmation task for this plan + +- [ ] **Step 1: Start the dev server** + +Run: `cd /Users/nut.looknut/Project/rmutr/rmutr-web && npm start` +Expected: compiles, dev server serves on the configured local port. + +- [ ] **Step 2: Verify the list-all badge and checkbox guard** + +In the browser, log in and navigate to `app/agency-report-form/list-all` (typeUrl 20). Confirm: +- Rows with a saved report but not yet submitted show a gray "ร่าง" badge and their checkbox is selectable. +- Rows with no report at all show no badge and their checkbox is disabled. + +- [ ] **Step 3: Verify submit actually reaches the backend** + +Select 1-2 "ร่าง" rows → click "ส่งให้กองแผนตรวจสอบ" → confirm the dialog. Confirm: +- No JS error in the browser console. +- After the save completes and the list reloads, those rows' badges change to yellow "รอตรวจสอบ" and their checkboxes become disabled. + +- [ ] **Step 4: Verify the empty-selection error still fires** + +Deselect everything (or select only rows without a report) → click "ส่งให้กองแผนตรวจสอบ". Confirm the existing error message "กรุณาเลือกรายการที่ต้องการส่ง" appears and no request is sent. + +- [ ] **Step 5: Verify the report form locks for the agency once submitted** + +From `list-all`, open (แก้ไขรายงาน) one of the rows now in "รอตรวจสอบ" status. Confirm all fields are disabled and the "บันทึก" button is hidden, replaced by the "รายงานนี้ถูกล็อก ไม่สามารถแก้ไขได้" banner. + +- [ ] **Step 6: Verify `check-project-report` regression-free (planning division side)** + +Log in as (or switch to) a planning-division account and open `app/check-project-report`. Confirm the rows submitted in Step 3 appear under the "รอตรวจสอบ" tab. Click into one and click "ส่งแก้ไข". Confirm: +- Status becomes 2 (ส่งแก้ไข). +- Back on the agency's `list-all`, that row's badge is now red "ส่งแก้ไข" and its checkbox is selectable again. +- Opening the report form for that row is now editable (not locked), since status_id is 2. + +- [ ] **Step 7: Verify "ตรวจสอบผ่าน" end state** + +Re-submit the same row from `list-all` (repeat Step 3). As the planning division, open it from `check-project-report`'s "รอตรวจสอบ" tab and click "ตรวจสอบผ่าน". Confirm status becomes 3, the row moves to the "ตรวจสอบแล้ว" tab, and on the agency's `list-all` the badge is now green "ตรวจสอบแล้ว" with the form permanently locked when reopened. + +- [ ] **Step 8: Regression check typeUrl 12/14 unaffected** + +If test access to a typeUrl 12 or 14 `list-all`-equivalent view is available, open it and confirm the existing save behavior (grouped `ChangeProjectSV.put()` per `change_project_uid`) is unchanged — no badge, no `[disabled]` guard, same button/label as before this plan.