docs: add out-of-plan report submit/review design spec
Design for adding a "ส่งงานแผน" submit flow to the out-of-plan report list plus a new reviewer menu "ตรวจสอบรายงานผล(นอกแผน)", mirroring the existing check-project-report status_id flow with no backend changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01613NaY9LAvy2wFXEeyE5eR
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# รายงานผล(นอกแผน) — ส่งงานแผน / เมนูตรวจสอบ
|
||||
|
||||
## บริบท
|
||||
|
||||
หน้า "หน่วยงานทำรายงานผล(นอกแผน)" (`/app/agency-out-of-plan/out-of-plan`, `type: 28`) ให้หน่วยงานกรอกรายงานผลโครงการนอกแผนเป็นตารางรายการ (`out_of_plan_report_detail`) แต่ละแถวเชื่อมกับรายงาน (`agency_report`, ผ่าน `out_of_plan_report_detail_uid`) ที่กรอกในฟอร์มแยก (`agency-report-form.component.ts`, เปิดผ่าน `openReport()` ที่ `agency-out-of-plan-form.component.ts:107-117`)
|
||||
|
||||
ปัจจุบัน**ไม่มีขั้นตอนส่งงานแผน** สำหรับรายงานนอกแผน — ฟอร์มมีแต่ปุ่ม "บันทึก" (`save()` case `281`/`282` ที่ `agency-report-form.component.ts:4015-4045`) ซึ่งไม่แตะ `status_id` เลย ต่างจากรายงานในแผนปกติที่มี flow ผ่านเมนู "ตรวจสอบรายงานผลโครงการ" (`check-project-report`, `type: 16`) อยู่แล้ว:
|
||||
|
||||
- 3 แท็บ กรองด้วย `agency_report.status_id`: รอตรวจสอบ (1) / ส่งแก้ไข (2) / ตรวจสอบแล้ว (3) — `request-budget-statistics-list.component.html:297-330`, query ที่ `request-budget-statistics.container.ts:401-406`
|
||||
- ปุ่มตรวจสอบ "ส่งกลับแก้ไข" (`save_send()` → status 2) และ "ตรวจสอบแล้ว" (`save_pass()` → status 3) มีอยู่แล้วใน `agency-report-form.component.ts:4116-4179` (ใช้ร่วมกันได้ทุก typeUrl เพราะไม่ได้ gate ด้วย typeUrl ภายในเมธอด)
|
||||
- Backend endpoint `POST /request_budget/agency_report/update_status/{status_id}` (`rmutr-api/Modules/RequestBudgets/Controllers/AgencyReport.cs:72-87`) รับ list ของ `t_agency_report` แล้วตั้ง `status_id` ให้ทุกตัว — รองรับทั้ง 1/2/3 อยู่แล้วเพราะ `status_id` เป็น route parameter ทั่วไป ไม่ผูกกับค่าใดค่าหนึ่ง
|
||||
|
||||
เป้าหมายงานนี้คือต่อยอด pattern เดิมที่มีอยู่แล้ว มาสโคปเฉพาะรายงานที่มี `out_of_plan_report_detail_uid != null` โดย**ไม่ต้องแก้ backend**
|
||||
|
||||
ข้อจำกัดสำคัญ: filter mechanism ทั่วไปของ `BaseUidController`/`EntityUidService` (`rmutr-api/Databases/Features/EntityUidService.cs:125-128`) รองรับแค่ equality/substring ต่อ field เท่านั้น **ไม่รองรับ "field IS NOT NULL"** ผ่าน query string ดังนั้นการแยกรายงานนอกแผนออกจากรายงานในแผนต้องกรองฝั่ง client (Angular) หลัง fetch — ตรงกับ pattern ที่ `agency-out-of-plan-form.component.ts:54-94` ทำอยู่แล้วสำหรับ field เดียวกันนี้ และตรงกับ pattern การกรอง client-side ด้วย `map(data => data.filter(...))` ที่มีอยู่แล้วหลายจุดใน `request-budget-statistics.container.ts` (เช่น `typeUrl == 14/15/20/26/27`)
|
||||
|
||||
## ขอบเขต
|
||||
|
||||
- ฝั่งหน่วยงาน: หน้า `agency-out-of-plan/out-of-plan` (list) + ฟอร์มรายงาน (`agency-report-form.component.ts` เฉพาะ typeUrl ที่เกี่ยวกับนอกแผน)
|
||||
- เมนูใหม่: "ตรวจสอบรายงานผล(นอกแผน)" — list 3 แท็บ + routing + menu entry
|
||||
- ป้าย "โครงการ" / "นอกแผน" บนหัวฟอร์มรายงาน (ทุก typeUrl ที่ใช้ฟอร์มนี้ ไม่ใช่แค่นอกแผน) และบนเมนูใหม่
|
||||
- ไม่แตะ UI/เมนู `check-project-report` เดิม ไม่แตะ backend ไม่แตะ flow ของรายงานในแผนปกติ — **ยกเว้น** เพิ่ม client-side filter บรรทัดเดียวใน `typeUrl == 16` query (container เดียวกับที่แก้ในงานนี้อยู่แล้ว) เพื่อไม่ให้รายงานนอกแผนไปโผล่ซ้ำในเมนูเดิม (ดูเหตุผลข้อ 7)
|
||||
|
||||
## การไหลของสถานะ (บน `agency_report.status_id`)
|
||||
|
||||
| status_id | ป้าย | ใครทำ / เกิดอะไรขึ้น |
|
||||
|---|---|---|
|
||||
| 0 / null | ร่าง | หน่วยงานกรอก/แก้ไขได้ปกติ (ฟอร์ม enable) |
|
||||
| 1 | รอตรวจสอบ | หลังกด "ส่งงานแผน" — ฟอร์ม**ล็อก** (`form.disable()`) จนกว่าจะถูกส่งกลับ |
|
||||
| 2 | ส่งแก้ไข | งานแผนกด "ส่งกลับแก้ไข" (`save_send()`) — ฟอร์มปลดล็อกให้หน่วยงานแก้ไขอีกครั้ง |
|
||||
| 3 | ตรวจสอบแล้ว | งานแผนกด "ตรวจสอบแล้ว" (`save_pass()`) — จบ flow, ฟอร์มยังคงล็อก |
|
||||
|
||||
## การเปลี่ยนแปลงที่ต้องทำ
|
||||
|
||||
### 1. ฝั่งหน่วยงาน — list `agency-out-of-plan-form.component.ts` / `.html`
|
||||
|
||||
- เพิ่ม `selection = new SelectionModel<any>(true, [])` (import `@angular/cdk/collections`) ตาม pattern `list-between-year`
|
||||
- เพิ่มคอลัมน์ checkbox หน้าสุด: เช็คได้เฉพาะแถวที่ `_agency_report_uid` ไม่ null (กรอกรายงานแล้ว) **และ** `status_id` เป็น `0`/`null` เท่านั้น (ยังไม่เคยส่ง) แถวอื่น disable checkbox ไว้ล่วงหน้า — ต้องดึง `status_id` ของ `agency_report` มาเก็บใน `allDetails`/`details` ตอน `load()` ด้วย (ปัจจุบันเก็บแค่ `_agency_report_uid` ที่บรรทัด 89 — เพิ่ม `_status_id: agencyByDetail.get(uid)?.status_id` โดยเปลี่ยน map จาก uid string เป็น object `{uid, status_id}`)
|
||||
- เพิ่มคอลัมน์/badge สถานะแสดง "ร่าง"/"รอตรวจสอบ"/"ส่งแก้ไข"/"ตรวจสอบแล้ว" ตามตาราง state flow ด้านบน (ใช้ `_status_id` ที่เพิ่มมา)
|
||||
- เพิ่มปุ่ม toolbar "ส่งงานแผน": `disabled` เมื่อ `selection.selected.length === 0`, คลิกแล้ว confirm ผ่าน `swSV.confirmSave()` (pattern เดียวกับที่อื่นในไฟล์นี้) แล้วเรียก `agencyReportSV.updateStatusAgency(selection.selected.map(s => ({agency_report_uid: s._agency_report_uid})))` (service method ที่มีอยู่แล้ว `agency-report.service.ts` ยิง `POST update_status/1`) สำเร็จแล้ว `load()` ใหม่ + เคลียร์ selection
|
||||
|
||||
### 2. ฝั่งหน่วยงาน — ฟอร์ม `agency-report-form.component.ts` (typeUrl 281/282)
|
||||
|
||||
- ตอนโหลด (`ngOnInit`, ภายใน branch `typeUrl == 282` ที่บรรทัด 527-559 — branch `281` เป็นรายงานใหม่ ไม่มีปัญหานี้เพราะ `status_id` ถูก set เป็น `0` เสมอที่บรรทัด 512): หลัง `patchValue(x)` เช็คถ้า `x.status_id === 1 || x.status_id === 3` → `this.form.disable()` และตั้ง flag `isLocked = true` (component property ใหม่)
|
||||
- Template: ถ้า `isLocked` ซ่อนปุ่ม "บันทึก" ปกติ (ฟิลด์ทั้งหมด disable อยู่แล้วจาก `form.disable()`)
|
||||
|
||||
### 3. ป้าย "โครงการ" / "นอกแผน" บนฟอร์ม
|
||||
|
||||
- `agency-report-form.component.html` หัวฟอร์ม: เพิ่ม badge/chip อ่านจาก `form.value.out_of_plan_report_detail_uid` — มีค่า → "นอกแผน", ไม่มี (null) → "โครงการ" ใช้ได้กับทุก typeUrl เพราะ field นี้มีอยู่แล้วใน `agency_report` (จาก migration `20260624000002_AddOutOfPlanLinkToAgencyReport.cs`) และฟอร์มนี้ใช้ร่วมกันทั้งสองประเภทรายงาน
|
||||
|
||||
### 4. Routing ใหม่ (`app-routing.module.ts`)
|
||||
|
||||
เพิ่มต่อจากกลุ่ม route เดิมของ out-of-plan (ใกล้บรรทัด 2945-2971) และของ check-project-report review (ใกล้บรรทัด 2991-3016):
|
||||
|
||||
```ts
|
||||
{
|
||||
path: 'check-project-report-out-of-plan',
|
||||
loadChildren: () => import('.../request-budget-statistics.module').then(m => m.RequestBudgetStatisticsModule),
|
||||
data: { menuName: `การบริหารและรายงานผล${seperation}ตรวจสอบรายงานผล(นอกแผน)`, type: 29 }
|
||||
},
|
||||
{
|
||||
path: 'agency-report-out-of-plan-edit-wait', // เปิดจากแท็บ "รอตรวจสอบ"
|
||||
data: { ..., type: 29161 }
|
||||
},
|
||||
{
|
||||
path: 'agency-report-out-of-plan-edit-send', // เปิดจากแท็บ "ส่งแก้ไข"
|
||||
data: { ..., type: 29162 }
|
||||
},
|
||||
{
|
||||
path: 'agency-report-out-of-plan-edit-pass', // เปิดจากแท็บ "ตรวจสอบแล้ว"
|
||||
data: { ..., type: 29163 }
|
||||
},
|
||||
```
|
||||
|
||||
(เลข type 29/29161/29162/29163 ยืนยันแล้วว่าไม่ชนกับ type ที่มีอยู่เดิมทั้งหมดใน `app-routing.module.ts`)
|
||||
|
||||
### 5. Menu entry (`navigator.ts`)
|
||||
|
||||
เพิ่มต่อจาก entry `agency-report-out-of-plan` (บรรทัด 1111-1117):
|
||||
|
||||
```ts
|
||||
{
|
||||
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',
|
||||
},
|
||||
```
|
||||
|
||||
### 6. List 3 แท็บใหม่ (มิเรอร์ `list16-1/2/3`)
|
||||
|
||||
- สร้าง component ใหม่ `list29-1`, `list29-2`, `list29-3` (copy โครงสร้างจาก `list16-1/2/3` รวม `SelectionModel`/`BaseList` pattern เดิม) — ต่างกันแค่ `edit(val)` navigate ไปที่ route ใหม่ตามข้อ 4 (`agency-report-out-of-plan-edit-wait/edit-report/:uid` เป็นต้น) และแสดง badge "นอกแผน" กำกับทุกแถว (แม้ในลิสต์นี้จะเป็นนอกแผนทั้งหมดอยู่แล้ว — ใส่เพื่อความสอดคล้องกับหัวฟอร์ม)
|
||||
- `request-budget-statistics-list.component.html`: เพิ่ม `<ng-container *ngIf="typeUrl == 29">` มิเรอร์ block ของ `typeUrl == 16` (บรรทัด 297-330) แต่ผูกกับ `dataSource29_1/2/3` และ component `app-list29-1/2/3`
|
||||
|
||||
### 7. Query data (`request-budget-statistics.container.ts`)
|
||||
|
||||
เพิ่ม branch ใหม่ต่อจาก `typeUrl == 16` (บรรทัด 401-406) — filter "not null" ทำฝั่ง client เพราะ backend ไม่รองรับ (ดูบริบทด้านบน):
|
||||
|
||||
```ts
|
||||
else if(this.typeUrl == 29){
|
||||
const filterOutOfPlan = (data: any[]) => (data || []).filter(d => !!d.out_of_plan_report_detail_uid)
|
||||
this.dataSource29_1$ = this.AgencyReportSV.queryString(`?status_id=1&${queryStr}`).pipe(map(filterOutOfPlan))
|
||||
this.dataSource29_2$ = this.AgencyReportSV.queryString(`?status_id=2&${queryStr}`).pipe(map(filterOutOfPlan))
|
||||
this.dataSource29_3$ = this.AgencyReportSV.queryString(`?status_id=3&${queryStr}`).pipe(map(filterOutOfPlan))
|
||||
}
|
||||
```
|
||||
|
||||
**ต้องแก้ควบคู่กัน**: query ของ `typeUrl == 16` (บรรทัด 401-406) ปัจจุบันไม่กรอง `out_of_plan_report_detail_uid` เลย — เพราะ `agency_report` เป็นตารางเดียวกันทั้งรายงานในแผนและนอกแผน (แยกกันแค่ field นี้) เมื่อรายงานนอกแผนถูก "ส่งงานแผน" (`status_id=1`) มันจะ**ตรงเงื่อนไข query เดิมของ `check-project-report` ด้วยเช่นกัน** ทำให้ไปโผล่ซ้ำสองเมนู ต้องเพิ่ม client-side filter แบบเดียวกัน (กรอง**ออก**) ในบล็อกเดิม:
|
||||
|
||||
```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))
|
||||
}
|
||||
```
|
||||
|
||||
ค้นหา (ปีงบประมาณ/ชื่อโครงการ/แผนงบประมาณ/หน่วยงาน) ใช้ `<app-request-budget-statistics-search>` เดิมที่ container ผูกไว้แล้วแบบไม่ขึ้นกับ typeUrl (`request-budget-statistics.container.html:1-12`) — **ไม่ต้องเพิ่ม UI ค้นหาใหม่** field ที่มีอยู่แล้ว (`request-budget-statistics.search.component.ts:26-32`) ครอบคลุมสิ่งที่ผู้ใช้ขอ (ปีงบประมาณ, ชื่อโครงการ) และเกินไปอีก (แผนงบประมาณ, หน่วยงานรับผิดชอบ)
|
||||
|
||||
ต้องเพิ่ม `dataSource29_1$/29_2$/29_3$ = new Observable<any>()` (properties, มิเรอร์บรรทัด 75-77) และค่าเริ่มต้นตอน `getAll()` (มิเรอร์บรรทัด 196-198)
|
||||
|
||||
### 8. ปุ่มตรวจสอบในฟอร์ม (`agency-report-form.component.html`)
|
||||
|
||||
ปุ่ม "ส่งกลับแก้ไข"/"ตรวจสอบแล้ว" มีอยู่แล้ว gate ด้วย `*ngIf="typeUrl == 202161"` (ตำแหน่งใกล้บรรทัด 435-442) — เพิ่ม `|| typeUrl == 29161` เข้าไปในเงื่อนไขเดิม (ใช้เมธอด `save_send()`/`save_pass()` เดิม ไม่ต้อง fork ใหม่)
|
||||
|
||||
### 9. `close()` (`agency-report-form.component.ts:3811-3825`)
|
||||
|
||||
เพิ่ม branch: `typeUrl == 29161 || typeUrl == 29162 || typeUrl == 29163` → `router.navigate(['app/check-project-report-out-of-plan'])`
|
||||
|
||||
## Error handling
|
||||
|
||||
- กดส่งงานแผนโดยไม่เลือกอะไร: ปุ่ม disabled ไว้แล้ว
|
||||
- API `update_status`/`save_send`/`save_pass` ล้มเหลว: error ผ่าน pattern `Swal.fire(err.error.description, '', 'error')` เดิมของไฟล์ ไม่ rollback selection
|
||||
- guard การเลือกแถวส่งซ้ำเป็น client-side เท่านั้น (checkbox disable ไว้ล่วงหน้า) — ไม่มี guard ฝั่ง backend เพิ่มเติม สอดคล้องกับ pattern เดิมของโปรเจกต์ (ดู `between-year-draft-send-design.md`)
|
||||
|
||||
## Testing (manual/browser)
|
||||
|
||||
1. หน่วยงานกรอกรายงานนอกแผนใหม่ → บันทึก → เห็นสถานะ "ร่าง" ในลิสต์ ยังแก้ไข/ลบได้
|
||||
2. ติ๊ก checkbox แถวร่างที่มีรายงานแล้ว 1-2 แถว → กด "ส่งงานแผน" → สถานะเปลี่ยนเป็น "รอตรวจสอบ" → เปิดฟอร์มดูอีกครั้ง ยืนยัน field ทั้งหมด disable
|
||||
3. ยืนยัน checkbox ของแถวที่ส่งแล้วไม่สามารถติ๊กเลือกซ้ำได้ และแถวที่ยังไม่มีรายงาน (`_agency_report_uid` null) ก็เลือกไม่ได้เช่นกัน
|
||||
4. เข้าเมนูใหม่ "ตรวจสอบรายงานผล(นอกแผน)" ในฐานะงานแผน → เห็นรายการที่ส่งมาในแท็บ "รอตรวจสอบ" พร้อม badge "นอกแผน" → ค้นหาด้วยปีงบประมาณ และค้นหาด้วยชื่อโครงการ ยืนยันกรองถูกต้อง
|
||||
5. เปิดรายงานจากแท็บ "รอตรวจสอบ" → กด "ส่งกลับแก้ไข" → ยืนยันสถานะเป็น "ส่งแก้ไข" และกลับไปเมนูตรวจสอบนอกแผน (ไม่ใช่เมนู check-project-report เดิม)
|
||||
6. หน่วยงานเปิดรายงานที่ถูกส่งกลับ (status 2) → ยืนยันฟอร์มแก้ไขได้อีกครั้ง → แก้ไข+ส่งงานแผนใหม่ (ทำซ้ำข้อ 2)
|
||||
7. งานแผนกด "ตรวจสอบแล้ว" ในแท็บ "รอตรวจสอบ" → ยืนยันสถานะเป็น "ตรวจสอบแล้ว" ย้ายไปแท็บ "ตรวจสอบแล้ว"
|
||||
8. ยืนยันรายการนอกแผน (ทุกสถานะ) **ไม่** ไปโผล่ในเมนู `check-project-report` เดิม หลังเพิ่ม client-side filter ตามข้อ 7 (ก่อนแก้จะโผล่ซ้ำแน่นอน เพราะ query เดิมกรองแค่ `status_id`) และยืนยันว่ารายงานในแผนปกติยังโผล่ใน `check-project-report` ตามเดิมไม่หายไป (regression check)
|
||||
9. เปิดรายงานในแผนปกติ (ไม่ใช่นอกแผน) ผ่านเมนูเดิม → ยืนยันหัวฟอร์มขึ้นป้าย "โครงการ" ถูกต้อง (regression check สำหรับข้อ 3 ที่แก้ template ที่ใช้ร่วมกัน)
|
||||
Reference in New Issue
Block a user