Files
rmutr/docs/superpowers/plans/2026-07-22-in-plan-report-submit-review-fix.md
2026-07-22 08:01:08 +07:00

20 KiB

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 <span> 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:

  save(){
    console.log(this.typeUrl)

    if(this.isListAllMode()){

Change to:

  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
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:

  isListAllMode(): boolean {
    return this.state === 'add' && (this.typeUrl === 12 || this.typeUrl === 14 || this.typeUrl === 20)
  }

Change to:

  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 <span> in the template

Find, in make-year-plan-form.component.html:

                                  <td class="td_class" [attr.rowspan]="cpPDetailsArray(i).length + cpADetailsArray(i).length" [hidden]="typeUrl != 20 && !(typeUrl == 12 && isListAllMode())">
                                    <div class="select-cell">
                                      <mat-checkbox
                                        (click)="$event.stopPropagation()"
                                        (change)="$event ? selection.toggle(item.value) : null"
                                        [checked]="selection.isSelected(item.value)"
                                        [aria-label]="checkboxLabel(item.value)">
                                      </mat-checkbox>
                                      <ng-container *ngIf="typeUrl == 12 && getSentCount(item.value) > 0">
                                        <span style="font-size:10px; background:#e65100; color:#fff; padding:2px 6px; border-radius:10px; display:block; text-align:center; margin-top:3px; white-space:nowrap;">แจ้งแล้ว {{getSentCount(item.value)}} ครั้ง</span>
                                      </ng-container>
                                      <button mat-icon-button *ngIf="typeUrl == 20 && item.value.agency_report?.agency_report_uid == null"
                                              (click)="addFormAgencyReport(item.value)"
                                              class="report-btn report-btn--add" title="เพิ่มรายงาน">
                                        <mat-icon>note_add</mat-icon>
                                      </button>
                                      <button mat-icon-button *ngIf="typeUrl == 20 && item.value.agency_report?.agency_report_uid != null"
                                              (click)="addFormAgencyReport(item.value)"
                                              class="report-btn report-btn--edit" title="แก้ไขรายงาน">
                                        <mat-icon>edit_document</mat-icon>
                                      </button>
                                    </div>
                                  </td>

Change to:

                                  <td class="td_class" [attr.rowspan]="cpPDetailsArray(i).length + cpADetailsArray(i).length" [hidden]="typeUrl != 20 && !(typeUrl == 12 && isListAllMode())">
                                    <div class="select-cell">
                                      <mat-checkbox
                                        (click)="$event.stopPropagation()"
                                        (change)="$event ? selection.toggle(item.value) : null"
                                        [checked]="selection.isSelected(item.value)"
                                        [disabled]="typeUrl == 20 && (item.value.agency_report?.agency_report_uid == null || item.value.agency_report?.status_id === 1 || item.value.agency_report?.status_id === 3)"
                                        [aria-label]="checkboxLabel(item.value)">
                                      </mat-checkbox>
                                      <ng-container *ngIf="typeUrl == 12 && getSentCount(item.value) > 0">
                                        <span style="font-size:10px; background:#e65100; color:#fff; padding:2px 6px; border-radius:10px; display:block; text-align:center; margin-top:3px; white-space:nowrap;">แจ้งแล้ว {{getSentCount(item.value)}} ครั้ง</span>
                                      </ng-container>
                                      <button mat-icon-button *ngIf="typeUrl == 20 && item.value.agency_report?.agency_report_uid == null"
                                              (click)="addFormAgencyReport(item.value)"
                                              class="report-btn report-btn--add" title="เพิ่มรายงาน">
                                        <mat-icon>note_add</mat-icon>
                                      </button>
                                      <button mat-icon-button *ngIf="typeUrl == 20 && item.value.agency_report?.agency_report_uid != null"
                                              (click)="addFormAgencyReport(item.value)"
                                              class="report-btn report-btn--edit" title="แก้ไขรายงาน">
                                        <mat-icon>edit_document</mat-icon>
                                      </button>
                                      <span *ngIf="typeUrl == 20 && item.value.agency_report?.agency_report_uid != null"
                                            [style.background]="reportStatusBadge(item.value.agency_report?.status_id).bg"
                                            [style.color]="reportStatusBadge(item.value.agency_report?.status_id).color"
                                            style="display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;margin-top:3px;white-space:nowrap;">
                                        {{reportStatusBadge(item.value.agency_report?.status_id).label}}
                                      </span>
                                    </div>
                                  </td>
  • 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
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:

        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:

        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
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.