# Out-of-Plan Report Submit/Review 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 "ส่งงานแผน" submit action to the out-of-plan report list (`agency-out-of-plan/out-of-plan`) and a new reviewer menu "ตรวจสอบรายงานผล(นอกแผน)" with 3 status tabs, "ส่งกลับแก้ไข", and "ตรวจสอบผ่าน" actions.
**Architecture:** No new tables/endpoints. Everything hinges on the existing `agency_report.status_id` column (0/null=draft, 1=รอตรวจสอบ, 2=ส่งแก้ไข, 3=ตรวจสอบแล้ว) and the existing `out_of_plan_report_detail_uid` link column on `agency_report`. The reviewer menu mirrors the existing `check-project-report` (`typeUrl == 16`) 3-tab pattern exactly, scoped by filtering `out_of_plan_report_detail_uid` client-side (the generic backend query filter is equality-only — it cannot express "not null" — so both the new list and the pre-existing `check-project-report` list filter client-side with RxJS `map()`). The review form reuses the big shared `agency-report-form.component.ts` (already used by 6+ other typeUrl variants), gated behind new `typeUrl` values (29, 29161, 29162, 29163) so no existing flow is touched. Bulk submit reuses the existing `AgencyReportService.updateStatusAgency()` method (already wired to `POST update_status/1`).
**Tech Stack:** Angular 17, Angular Material (`mat-table`, `mat-tab-group`, `mat-checkbox`), Angular CDK `SelectionModel`, RxJS, SweetAlert2 (via `SweetalertService`).
## Global Constraints
- No backend (`rmutr-api`) changes in this plan — the two endpoints needed (`GET .../agency_report?status_id=N`, `POST .../agency_report/update_status/1`) already exist.
- This repo has no meaningful unit-test culture for these Angular components (only CLI-boilerplate `.spec.ts` files exist project-wide). 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.
- Every new branch added to shared files (`request-budget-statistics.container.ts`, `request-budget-statistics-list.component.ts/html`, `agency-report-form.component.ts/html`) must be explicitly guarded by the new `typeUrl` values (29, 29161, 29162, 29163) or by `out_of_plan_report_detail_uid` truthiness — never change behavior for any other `typeUrl`.
- Follow existing code conventions exactly: raw `Swal.fire(...)` / `this.swSV.confirmSave()` patterns already used in the touched files — don't introduce a new dialog library or pattern.
- `AgencyReportService.updateStatusAgency(data)` (POST `{fullUrl}/update_status/1`) is the existing bulk-submit method — reuse it, don't add a new API method.
- Reuse the existing shared search component (`request-budget-statistics.search.component.ts`, fields: `budget_year_name_th`, `project_name_th`, `budget_plan_name_th`, `budget_project_name_th`, `responsible_faculty_name_th`) for the new reviewer menu — do not build new search UI.
---
### Task 1: New routes and menu entry
**Files:**
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/app-routing.module.ts:3011-3016`
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/core/data/navigator.ts:1111-1117`
**Interfaces:**
- Consumes: nothing new
- Produces: route `type` values `29` (list), `29161`/`29162`/`29163` (review-edit sub-routes) and route paths `check-project-report-out-of-plan`, `agency-report-out-of-plan-edit-wait`, `agency-report-out-of-plan-edit-send`, `agency-report-out-of-plan-edit-pass` — every later task's `typeUrl` checks and `router.navigate()` calls target these exact values/paths.
- [ ] **Step 1: Add the 4 new routes**
Find, in `app-routing.module.ts` (the block right after the existing `agency-report-edit-pass` route):
```ts
{
path: 'agency-report-edit-pass',
loadChildren: () => import('./feature/budget-request/request/request-budget-statistics/request-budget-statistics.module')
.then(m => m.RequestBudgetStatisticsModule),
data: {
menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผลโครงการ`,
type: 202163
}
},
{
path: 'original-project-proposal1',
```
Change to:
```ts
{
path: 'agency-report-edit-pass',
loadChildren: () => import('./feature/budget-request/request/request-budget-statistics/request-budget-statistics.module')
.then(m => m.RequestBudgetStatisticsModule),
data: {
menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผลโครงการ`,
type: 202163
}
},
{
path: 'check-project-report-out-of-plan',
loadChildren: () => import('./feature/budget-request/request/request-budget-statistics/request-budget-statistics.module')
.then(m => m.RequestBudgetStatisticsModule),
data: {
menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผล(นอกแผน)`,
type: 29
}
},
{
path: 'agency-report-out-of-plan-edit-wait',
loadChildren: () => import('./feature/budget-request/request/request-budget-statistics/request-budget-statistics.module')
.then(m => m.RequestBudgetStatisticsModule),
data: {
menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผล(นอกแผน)`,
type: 29161
}
},
{
path: 'agency-report-out-of-plan-edit-send',
loadChildren: () => import('./feature/budget-request/request/request-budget-statistics/request-budget-statistics.module')
.then(m => m.RequestBudgetStatisticsModule),
data: {
menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผล(นอกแผน)`,
type: 29162
}
},
{
path: 'agency-report-out-of-plan-edit-pass',
loadChildren: () => import('./feature/budget-request/request/request-budget-statistics/request-budget-statistics.module')
.then(m => m.RequestBudgetStatisticsModule),
data: {
menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผล(นอกแผน)`,
type: 29163
}
},
{
path: 'original-project-proposal1',
```
- [ ] **Step 2: Add the menu entry**
Find, in `navigator.ts`:
```ts
{
id: 'agency-report-out-of-plan',
code: 'administer-011-007-2',
title: 'หน่วยงานทำรายงานผล(นอกแผน)',
type: 'basic',
icon: 'fiber_manual_record',
link: '/app/agency-out-of-plan/out-of-plan',
},
{
id: 'check-project-report',
```
Change to:
```ts
{
id: 'agency-report-out-of-plan',
code: 'administer-011-007-2',
title: 'หน่วยงานทำรายงานผล(นอกแผน)',
type: 'basic',
icon: 'fiber_manual_record',
link: '/app/agency-out-of-plan/out-of-plan',
},
{
id: 'check-project-report-out-of-plan',
code: 'administer-011-007-3',
title: 'ตรวจสอบรายงานผล(นอกแผน)',
type: 'basic',
icon: 'fiber_manual_record',
link: '/app/check-project-report-out-of-plan',
},
{
id: 'check-project-report',
```
- [ ] **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/app-routing.module.ts rmutr-web/src/app/core/data/navigator.ts
git commit -m "feat: add routes and menu entry for out-of-plan report review"
```
---
### Task 2: `list29-1`/`list29-2`/`list29-3` components (review tabs)
**Files:**
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-1/list29-1.component.ts`
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-1/list29-1.component.html`
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-1/list29-1.component.scss`
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-2/list29-2.component.ts`
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-2/list29-2.component.html`
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-3/list29-3.component.ts`
- Create: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-3/list29-3.component.html`
- Modify: `/Users/nut.looknut/Project/rmutr/rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/request-budget-statistics.module.ts:46,190`
**Interfaces:**
- Consumes: `@Input() dataSource: any` — an array of `agency_report` rows (set by Task 3's container wiring). Each row has `agency_report_uid`, `budget_year_name_th`, `project_name_th`, `budget_project_name_th`(displayed via `text_6`... actually mirrors `list16-1` exactly, see below), `budget_topic_name_th`, `responsible_faculty_name_th`.
- Produces: `app-list29-1`, `app-list29-2`, `app-list29-3` selectors — Task 3 wires these into `request-budget-statistics-list.component.html`. Each has `edit(val)` navigating to its dedicated review route from Task 1.
These 3 components are a direct copy of the existing `list16-1`/`list16-2`/`list16-3` pattern (same table shape, same `BaseList`/`updateMatTable` usage), minus the unrelated P/A-send checkbox feature that lives only in `list16-3` (that's a different, unrelated feature bolted onto that specific tab — not part of this workflow). Each adds one static badge column ("นอกแผน") since every row here is out-of-plan by definition (per the approved design spec).
- [ ] **Step 1: Create `list29-1.component.ts`**
```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';
@Component({
selector: 'app-list29-1',
templateUrl: './list29-1.component.html',
styleUrls: ['./list29-1.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class List29_1Component 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(
public router: Router
) {
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 {
}
edit(val) {
this.router.navigate(['app/agency-report-out-of-plan-edit-wait/edit-report', val.agency_report_uid])
}
delete(val) {
this.ondelete.emit(val)
}
change(event: PageEvent) {
let page: number = event.pageIndex + 1
let table: any = {
page: page,
size: event.pageSize
}
this.onchange.emit(table)
}
excel(val) {
this.onexcel.emit(val)
}
}
```
- [ ] **Step 2: Create `list29-1.component.html`**
```html
ประเภท |
นอกแผน
|
ปีงบประมาณ |
{{x.budget_year_name_th}}
|
ชื่อโครงการ |
{{x.project_name_th}}
|
ผลผลิต |
{{x.text_6}}
|
ด้าน |
{{x.text_7}}
|
ชื่อหน่วยงาน |
{{x.responsible_faculty_name_th}}
|
ดูรายงานผล |
create
|
```
- [ ] **Step 3: Create `list29-1.component.scss`**
```scss
```
(empty — no extra styles needed, matches `list16-2`/`list16-3`'s empty/minimal scss)
- [ ] **Step 4: Create `list29-2.component.ts`** (identical to `list29-1` except `edit()` target)
```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';
@Component({
selector: 'app-list29-2',
templateUrl: './list29-2.component.html',
styleUrls: ['./list29-2.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class List29_2Component 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(
public router: Router
) {
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 {
}
edit(val) {
this.router.navigate(['app/agency-report-out-of-plan-edit-send/edit-report', val.agency_report_uid])
}
delete(val) {
this.ondelete.emit(val)
}
change(event: PageEvent) {
let page: number = event.pageIndex + 1
let table: any = {
page: page,
size: event.pageSize
}
this.onchange.emit(table)
}
excel(val) {
this.onexcel.emit(val)
}
}
```
- [ ] **Step 5: Create `list29-2.component.scss`**
```scss
```
(empty file)
- [ ] **Step 6: Create `list29-2.component.html`** (identical to `list29-1.component.html`)
```html
ประเภท |
นอกแผน
|
ปีงบประมาณ |
{{x.budget_year_name_th}}
|
ชื่อโครงการ |
{{x.project_name_th}}
|
ผลผลิต |
{{x.text_6}}
|
ด้าน |
{{x.text_7}}
|
ชื่อหน่วยงาน |
{{x.responsible_faculty_name_th}}
|
ดูรายงานผล |
create
|
```
- [ ] **Step 7: Create `list29-3.component.ts`** (identical, `edit()` targets the "pass" route)
```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';
@Component({
selector: 'app-list29-3',
templateUrl: './list29-3.component.html',
styleUrls: ['./list29-3.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class List29_3Component 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(
public router: Router
) {
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 {
}
edit(val) {
this.router.navigate(['app/agency-report-out-of-plan-edit-pass/edit-report', val.agency_report_uid])
}
delete(val) {
this.ondelete.emit(val)
}
change(event: PageEvent) {
let page: number = event.pageIndex + 1
let table: any = {
page: page,
size: event.pageSize
}
this.onchange.emit(table)
}
excel(val) {
this.onexcel.emit(val)
}
}
```
- [ ] **Step 8: Create `list29-3.component.scss`**
```scss
```
(empty file)
- [ ] **Step 9: Create `list29-3.component.html`**
```html
ประเภท |
นอกแผน
|
ปีงบประมาณ |
{{x.budget_year_name_th}}
|
ชื่อโครงการ |
{{x.project_name_th}}
|
ผลผลิต |
{{x.text_6}}
|
ด้าน |
{{x.text_7}}
|
ชื่อหน่วยงาน |
{{x.responsible_faculty_name_th}}
|
ดูรายงานผล |
create
|
```
- [ ] **Step 10: Register the 3 components in the module**
Find, in `request-budget-statistics.module.ts`:
```ts
import { List16_1Component } from './presenter/list/request-budget-statistics-list/list16-1/list16-1.component';
import { List16_2Component } from './presenter/list/request-budget-statistics-list/list16-2/list16-2.component';
import { List16_3Component } from './presenter/list/request-budget-statistics-list/list16-3/list16-3.component';
import { List20Component } from './presenter/list/request-budget-statistics-list/list20/list20.component';
```
Change to:
```ts
import { List16_1Component } from './presenter/list/request-budget-statistics-list/list16-1/list16-1.component';
import { List16_2Component } from './presenter/list/request-budget-statistics-list/list16-2/list16-2.component';
import { List16_3Component } from './presenter/list/request-budget-statistics-list/list16-3/list16-3.component';
import { List29_1Component } from './presenter/list/request-budget-statistics-list/list29-1/list29-1.component';
import { List29_2Component } from './presenter/list/request-budget-statistics-list/list29-2/list29-2.component';
import { List29_3Component } from './presenter/list/request-budget-statistics-list/list29-3/list29-3.component';
import { List20Component } from './presenter/list/request-budget-statistics-list/list20/list20.component';
```
Find:
```ts
List16_1Component,
List16_2Component,
List16_3Component,
List20Component,
```
Change to:
```ts
List16_1Component,
List16_2Component,
List16_3Component,
List29_1Component,
List29_2Component,
List29_3Component,
List20Component,
```
- [ ] **Step 11: 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 12: Commit**
```bash
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-1/ rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-2/ rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/presenter/list/request-budget-statistics-list/list29-3/ rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/request-budget-statistics.module.ts
git commit -m "feat: add list29-1/2/3 review tab components for out-of-plan reports"
```
---
### Task 3: Wire the new reviewer list end-to-end + fix `check-project-report` leak
**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`
- 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/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`
**Interfaces:**
- Consumes: `app-list29-1/2/3` selectors (Task 2), route path `check-project-report-out-of-plan` (Task 1, `type: 29`)
- Produces: `dataSource29_1$/29_2$/29_3$` observables on the container — final task in this chain, nothing downstream depends on these further.
- [ ] **Step 1: Add the 3 new dataSource properties**
Find, in `request-budget-statistics.container.ts`:
```ts
dataSource16_1$ = new Observable()
dataSource16_2$ = new Observable()
dataSource16_3$ = new Observable()
dataSourceBetweenYear$ = new Observable()
```
Change to:
```ts
dataSource16_1$ = new Observable()
dataSource16_2$ = new Observable()
dataSource16_3$ = new Observable()
dataSource29_1$ = new Observable()
dataSource29_2$ = new Observable()
dataSource29_3$ = new Observable()
dataSourceBetweenYear$ = new Observable()
```
- [ ] **Step 2: Fix `getAll()`'s initial (unfiltered) load — exclude out-of-plan from 16, include only out-of-plan in 29**
Find, in `getAll()`:
```ts
this.dataSource16_1$ = this.AgencyReportSV.queryString(`?status_id=1`)
this.dataSource16_2$ = this.AgencyReportSV.queryString(`?status_id=2`)
this.dataSource16_3$ = this.AgencyReportSV.queryString(`?status_id=3`)
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true`)
```
Change to:
```ts
this.dataSource16_1$ = this.AgencyReportSV.queryString(`?status_id=1`).pipe(map((data: any[]) => (data || []).filter(d => !d.out_of_plan_report_detail_uid)))
this.dataSource16_2$ = this.AgencyReportSV.queryString(`?status_id=2`).pipe(map((data: any[]) => (data || []).filter(d => !d.out_of_plan_report_detail_uid)))
this.dataSource16_3$ = this.AgencyReportSV.queryString(`?status_id=3`).pipe(map((data: any[]) => (data || []).filter(d => !d.out_of_plan_report_detail_uid)))
this.dataSource29_1$ = this.AgencyReportSV.queryString(`?status_id=1`).pipe(map((data: any[]) => (data || []).filter(d => !!d.out_of_plan_report_detail_uid)))
this.dataSource29_2$ = this.AgencyReportSV.queryString(`?status_id=2`).pipe(map((data: any[]) => (data || []).filter(d => !!d.out_of_plan_report_detail_uid)))
this.dataSource29_3$ = this.AgencyReportSV.queryString(`?status_id=3`).pipe(map((data: any[]) => (data || []).filter(d => !!d.out_of_plan_report_detail_uid)))
this.dataSourceBetweenYear$ = this.requestBudgetSV.queryString(`?is_between_year=true`)
```
(`map` is already imported at the top of this file — used by many other branches — no new import needed.)
- [ ] **Step 3: Fix the `onsearch()` query branch for `typeUrl == 16` and add the `typeUrl == 29` branch**
Find, in `onsearch()`:
```ts
else if(this.typeUrl == 16){
this.dataSource16_1$ = this.AgencyReportSV.queryString(`?status_id=1&${queryStr}`)
this.dataSource16_2$ = this.AgencyReportSV.queryString(`?status_id=2&${queryStr}`)
this.dataSource16_3$ = this.AgencyReportSV.queryString(`?status_id=3&${queryStr}`)
}
```
Change to:
```ts
else if(this.typeUrl == 16){
const excludeOutOfPlan = (data: any[]) => (data || []).filter(d => !d.out_of_plan_report_detail_uid)
this.dataSource16_1$ = this.AgencyReportSV.queryString(`?status_id=1&${queryStr}`).pipe(map(excludeOutOfPlan))
this.dataSource16_2$ = this.AgencyReportSV.queryString(`?status_id=2&${queryStr}`).pipe(map(excludeOutOfPlan))
this.dataSource16_3$ = this.AgencyReportSV.queryString(`?status_id=3&${queryStr}`).pipe(map(excludeOutOfPlan))
}
else if(this.typeUrl == 29){
const onlyOutOfPlan = (data: any[]) => (data || []).filter(d => !!d.out_of_plan_report_detail_uid)
this.dataSource29_1$ = this.AgencyReportSV.queryString(`?status_id=1&${queryStr}`).pipe(map(onlyOutOfPlan))
this.dataSource29_2$ = this.AgencyReportSV.queryString(`?status_id=2&${queryStr}`).pipe(map(onlyOutOfPlan))
this.dataSource29_3$ = this.AgencyReportSV.queryString(`?status_id=3&${queryStr}`).pipe(map(onlyOutOfPlan))
}
```
- [ ] **Step 4: Bind the 3 new observables in the container template**
Find, in `request-budget-statistics.container.html`:
```html
[dataSource16_1]="dataSource16_1$ | async"
[dataSource16_2]="dataSource16_2$ | async"
[dataSource16_3]="dataSource16_3$ | async"
```
Change to:
```html
[dataSource16_1]="dataSource16_1$ | async"
[dataSource16_2]="dataSource16_2$ | async"
[dataSource16_3]="dataSource16_3$ | async"
[dataSource29_1]="dataSource29_1$ | async"
[dataSource29_2]="dataSource29_2$ | async"
[dataSource29_3]="dataSource29_3$ | async"
```
- [ ] **Step 5: Add the 3 new `@Input()`s to the presenter list component**
Find, in `request-budget-statistics-list.component.ts`:
```ts
@Input() dataSource16_1: any = [];
@Input() dataSource16_2: any = [];
@Input() dataSource16_3: any = [];
```
Change to:
```ts
@Input() dataSource16_1: any = [];
@Input() dataSource16_2: any = [];
@Input() dataSource16_3: any = [];
@Input() dataSource29_1: any = [];
@Input() dataSource29_2: any = [];
@Input() dataSource29_3: any = [];
```
- [ ] **Step 6: Add the `typeUrl == 29` tab block to the presenter list template**
Find, in `request-budget-statistics-list.component.html`:
```html
```
Change to:
```html
```
- [ ] **Step 7: 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 8: Commit**
```bash
git add rmutr-web/src/app/feature/budget-request/request/request-budget-statistics/container/request-other-expenses/request-budget-statistics.container.ts 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/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
git commit -m "feat: wire out-of-plan reviewer list (typeUrl 29) and stop out-of-plan reports leaking into check-project-report"
```
---
### Task 4: Report form — lock, badge, review buttons, `close()` routing
**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`
- 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.html`
**Interfaces:**
- Consumes: `typeUrl` values `29161`/`29162`/`29163` (Task 1), `agency_report.out_of_plan_report_detail_uid`/`status_id` (existing fields, no new backend needed)
- Produces: `isLocked: boolean` class field — the template reads it to hide the save button and show a locked banner. `close()` now routes `29161/29162/29163` back to `check-project-report-out-of-plan`.
- [ ] **Step 1: Add the `isLocked` field**
Find:
```ts
urlPath
typeUrl
tabIndex
```
Change to:
```ts
urlPath
typeUrl
tabIndex
isLocked: boolean = false
```
- [ ] **Step 2: Route the 3 new review typeUrls through the existing `typeUrl == 282` load branch, and lock the form when the loaded report is already submitted or done**
Find the full `else if (this.typeUrl == 282)` block:
```ts
// type 282: เปิดฟอร์มจากรายงานที่มีอยู่แล้ว → โหลด agency_report ตรงๆ แล้ว state='edit'
else if (this.typeUrl == 282) {
this.AgencyReportSV.get(this.uniquekey).pipe(
tap((x: any) => {
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()
}
Swal.close()
}),
catchError(err => {
Swal.close()
return of(null)
})
).subscribe()
}
```
Change to:
```ts
// type 282/29161/29162/29163: เปิดฟอร์มจากรายงานที่มีอยู่แล้ว → โหลด agency_report ตรงๆ แล้ว state='edit'
// (29161/29162/29163 = งานแผนเปิดดูจากเมนู "ตรวจสอบรายงานผล(นอกแผน)" — ใช้ loader เดียวกับ 282)
else if (this.typeUrl == 282 || this.typeUrl == 29161 || this.typeUrl == 29162 || this.typeUrl == 29163) {
this.AgencyReportSV.get(this.uniquekey).pipe(
tap((x: any) => {
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.out_of_plan_report_detail_uid && (x.status_id === 1 || x.status_id === 3)) {
this.isLocked = true
this.form.disable()
}
Swal.close()
}),
catchError(err => {
Swal.close()
return of(null)
})
).subscribe()
}
```
- [ ] **Step 3: Route `close()` for the 3 new review typeUrls back to the new menu**
Find:
```ts
close() {
if(this.typeUrl == 202161 || this.typeUrl == 202163 ){
this.router.navigate(['app/check-project-report'])
}
else if(this.typeUrl == 202162){
this.router.navigate(['app/send-edit-agency-report'])
}
else if(this.typeUrl == 281 || this.typeUrl == 282){
this.router.navigate(['app/agency-out-of-plan/out-of-plan'])
}
else{
this.router.navigate(['app/agency-report'])
}
}
```
Change to:
```ts
close() {
if(this.typeUrl == 202161 || this.typeUrl == 202163 ){
this.router.navigate(['app/check-project-report'])
}
else if(this.typeUrl == 202162){
this.router.navigate(['app/send-edit-agency-report'])
}
else if(this.typeUrl == 29161 || this.typeUrl == 29162 || this.typeUrl == 29163){
this.router.navigate(['app/check-project-report-out-of-plan'])
}
else if(this.typeUrl == 281 || this.typeUrl == 282){
this.router.navigate(['app/agency-out-of-plan/out-of-plan'])
}
else{
this.router.navigate(['app/agency-report'])
}
}
```
- [ ] **Step 4: Add the "โครงการ"/"นอกแผน" badge at the top of the form**
Find, at the very top of `agency-report-form.component.html`:
```html