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()(POSTupdate_status/1) andAgencyReportService.get()already exist and already work (used today by the out-of-plan flow and bycheck-project-report). - typeUrl 12 and typeUrl 14 behavior in
make-year-plant-form.component.tsmust be completely unaffected — every change in this plan is gated so it only fires fortypeUrl === 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 vianpm startfor 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-existingelse if(this.typeUrl == 20)block (ts:2439-2508) becomes reachable; itscase 'add':sub-branch (ts:2457-2478) is what will now run when the button is clicked fromlist-all(route has no:id, sothis.stateis 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 nearisListAllMode(), 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 theagency_reportFormGroup builder) -
Produces:
reportStatusBadge(status_id: number | null): {label: string, bg: string, color: string}— a new public method onMakeYearPlanFormComponent/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(existingagency_reportfield, already loaded byAgencyReportSV.get()) -
Produces: nothing new —
isLockedbeingtrueis already read by the existing template bindings atagency-report-form.component.html:428(locked banner) and:430(hides the "บันทึก" button), both unconditional ontypeUrl, so they activate automatically for typeUrl202/202163once this task sets the flag. Thesave_send()/save_pass()buttons (gated bytypeUrl == 202161) are untouched by this task and keep working because both methods callgetRawValue(), which reads values from disabled controls. -
Step 1: Add the lock check to the
typeUrl == 202branch
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-reportregression-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.