docs: add between-year draft/send-to-planning workflow design spec

This commit is contained in:
Nut.ไปเรื่อย
2026-07-06 18:29:15 +07:00
parent 2fbb0b3d51
commit 976710f2da
@@ -0,0 +1,74 @@
# คำขอระหว่างปี — ขั้นตอนบันทึกฉบับร่าง / ส่งกองนโยบายและแผน
## บริบท
เมนู "คำขอระหว่างปี" (`/budget-web/app/request-budget-between-year`, routing `type: 21`) ปัจจุบันเมื่อกด "บันทึก" (ครั้งแรก) ระบบจะสร้างระเบียน `request_budget` (`is_between_year=true`) ด้วย `status_id = 0` และ `is_examine = true` ทันที แล้วพากลับไปหน้ารายการ — ระเบียนนี้จะไปปรากฏในเมนู "ตรวจสอบคำเสนอขอโครงการ" (`check-project-proposal`, `type: 2`) แท็บ "จัดการคำเสนอขอ" โดยอัตโนมัติ เพราะแท็บนั้น query `request_budget_and_strategic?status_id=1`... แต่ทดสอบจริงพบว่าระเบียนที่ `status_id=0` จากฟอร์มก็ไปโผล่ที่นั่นแล้ว (มีสถานะ "รอตรวจสอบ") กล่าวคือ ปัจจุบัน**ไม่มีขั้นตอนกลาง** — บันทึกปุ๊บ = ส่งงานแผนปั๊บ ผู้ใช้ไม่มีโอกาสร่างทิ้งไว้ก่อน
ผู้ใช้ (เจ้าหน้าที่หน่วยงาน/คณะ ผู้กรอกคำขอ) ต้องการขั้นตอนกลาง: ร่างไว้ก่อน แล้วค่อยเลือกส่งเป็นชุดเมื่อพร้อม
โค้ดที่มีอยู่แล้วบางส่วนสะท้อนว่าเคยมีความตั้งใจทำ flow นี้มาก่อนแต่ทำไม่เสร็จ:
- `list-between-year.component.html` มีคอลัมน์สถานะที่ comment ไว้ ตรงกับความหมาย `status_id==0` → "ร่าง", `status_id==1` (และยังไม่ `is_ok`/`is_modify`) → รอดำเนินการ
- `request-budget-statistics.container.ts``getAll()` query เริ่มต้นของ `dataSourceBetweenYear$` มี filter `&status_id=1` ค้างอยู่ (ไม่ตรงกับ `onsearch()` ที่ไม่กรอง) — เป็น bug ที่ต้องแก้ไปพร้อมกัน มิฉะนั้นร่าง (`status_id=0`) จะไม่โผล่ในรายการเริ่มต้นเลย
`check-project-proposal` แท็บ "จัดการคำเสนอขอ" (query `status_id=1`) เป็นเมนูที่งานแผนใช้ตรวจสอบอยู่แล้ว **ไม่ต้องแก้ไขเมนูนั้น** — งานนี้ทำเฉพาะฝั่ง "คำขอระหว่างปี" ให้ควบคุมได้ว่าจะยังไม่ส่ง (ร่าง) หรือส่งแล้ว (`status_id: 1`) เท่านั้น การไปโผล่ที่ `check-project-proposal` เป็นผลพลอยได้อัตโนมัติจากการเปลี่ยน `status_id` ซึ่งมีอยู่แล้ว
## ขอบเขต
เฉพาะเมนู "คำขอระหว่างปี" (list + form ของ between-year เท่านั้น) ไม่แตะเมนู `check-project-proposal` หรือเมนูอื่นที่ใช้ container เดียวกัน (`request-budget-statistics.container.ts`) เกินความจำเป็น (จะเพิ่ม handler ใหม่ 1 ตัวในไฟล์นี้เท่านั้น)
## การไหลของสถานะ
| status_id | is_ok / is_modify | ป้ายในรายการ | แก้ไข/ลบได้ไหม |
|---|---|---|---|
| 0 | - | ร่าง | ได้ปกติ |
| 1 | ทั้งคู่ false/null | ส่งงานแผน | ดูได้อย่างเดียว (form disable, ปุ่มลบปิด) |
| 1 | is_modify = true | ส่งแก้ไข | (ปรับปรุงจากงานแผนผ่านเมนูอื่นอยู่แล้ว นอกขอบเขตนี้) |
| 1 | is_ok = true | ตรวจสอบแล้ว | (นอกขอบเขตนี้) |
หมายเหตุ: แถวสถานะ `is_modify`/`is_ok` เกิดจากการทำงานของงานแผนในเมนู `check-project-proposal` ซึ่งมีอยู่แล้วในโค้ดปัจจุบัน งานนี้แค่แสดงป้ายให้ถูกต้องตามข้อมูลจริงในรายการเดียวกัน ไม่ต้องสร้าง logic ใหม่สำหรับสองแถวนี้
## การเปลี่ยนแปลงที่ต้องทำ
### 1. Bug fix: `request-budget-statistics.container.ts` → `getAll()`
ลบ `&status_id=1` ออกจาก query เริ่มต้นของ `dataSourceBetweenYear$` (บรรทัด ~199) ให้เหลือ `?is_between_year=true` เท่านั้น เพื่อให้ทั้งร่างและที่ส่งแล้วแสดงในรายการเดียวกัน (ตรงกับพฤติกรรมของ `onsearch()` ที่มีอยู่แล้ว)
### 2. ฟอร์ม `request-budget-between-year-form.component.ts`
- ปุ่ม "บันทึก" (label ในเทมเพลต) เปลี่ยนเป็น **"บันทึกฉบับร่าง"**
- ตอน `save()` กรณี `state === 'add'`: คง `status_id = 0` ไว้ (ร่าง) แต่**เอา `is_examine = true` ออก** จาก path นี้ (ย้ายไปตั้งตอนส่งแทน เพราะ "กำลังตรวจสอบ" ควรเป็นจริงก็ต่อเมื่อส่งไปงานแผนแล้วเท่านั้น)
- ตอนโหลดข้อมูลกรณี `state === 'edit'` (ใน `ngOnInit`, หลัง `this.form.patchValue(x)`): ถ้า `x.status_id !== 0` (ถูกส่งไปแล้ว) → เรียก `this.form.disable()` และตั้ง flag `isLocked = true` (component property ใหม่)
- เทมเพลตฟอร์ม: ถ้า `isLocked` ให้ซ่อนปุ่ม "บันทึกฉบับร่าง" (เหลือแต่ "ยกเลิก"/กลับ) ฟิลด์ทั้งหมด disable อยู่แล้วจากการ `form.disable()`
### 3. List `list-between-year.component.ts` / `.html`
- เพิ่ม `selection = new SelectionModel<any>(true, [])` (import จาก `@angular/cdk/collections`) ตามแพทเทิร์นเดียวกับ `list6`/`list24`
- เพิ่มคอลัมน์ checkbox หน้าสุด: เช็คได้เฉพาะแถว `status_id === 0` (ร่าง) แถวอื่น disable ล่วงหน้า (ไม่ให้เลือกส่งซ้ำ)
- Uncomment คอลัมน์สถานะเดิม เพิ่มเข้า columns array ของ header/row/footer และแก้ข้อความ:
- `status_id == 0` → badge "ร่าง"
- `is_modify == true` → badge "ส่งแก้ไข"
- `is_ok == true` → badge "ตรวจสอบแล้ว"
- `status_id == 1` (ไม่เข้าเงื่อนไขบนสองข้อ) → badge **"ส่งงานแผน"** (เดิม comment ไว้ว่า "รอตรวจสอบ" เปลี่ยนข้อความ)
- ไอคอน "ลบ" (`delete`): disable เมื่อ `status_id !== 0`
- ไอคอน "แก้ไข" (`edit`): ไม่ disable — กดได้เสมอ (เข้าไปดู แต่ฟอร์มจะ read-only เองถ้าไม่ใช่ร่าง ตามข้อ 2)
- เพิ่มปุ่ม toolbar ใหม่ **"ส่งกองนโยบายและแผน"** ข้าง "เพิ่มคำขอระหว่างปี": `disabled` เมื่อ `selection.selected.length === 0`, คลิกแล้ว `emit` ผ่าน `@Output() onsend = new EventEmitter()` ส่ง `this.selection.selected` (array ของแถวที่เลือก)
### 4. เดินสาย output ผ่านชั้น presenter กลาง
- `request-budget-statistics-list.component.html` ส่วน `*ngIf="typeUrl == 21"`: เพิ่ม `(onsend)="onsend.emit($event)"` (หรือชื่อ output ที่สอดคล้อง) ให้ bubble ต่อไปยัง container
- `request-budget-statistics.container.html`: เพิ่ม `(onsend)="sendBetweenYearToPlan($event)"` บน `<app-request-budget-statistics-list>`
### 5. Handler ใหม่ใน `request-budget-statistics.container.ts`
เมธอดใหม่ `sendBetweenYearToPlan(selected: any[])`:
- ถ้า `selected.length === 0` return
- confirm ด้วย `this.swSV.confirmSave()` (แพทเทิร์นเดียวกับ handler อื่นในไฟล์นี้)
- เมื่อ confirm: map `selected` เป็น payload ที่ตั้ง `status_id: 1, is_examine: true` ทับค่าเดิมของแต่ละแถว แล้วเรียก `this.requestBudgetSV.updateMany(payload)`
- สำเร็จ: `this.swSV.updateSuccess()` แล้ว `this.getAll()` (หรือเทียบเท่า) เพื่อ refresh `dataSourceBetweenYear$`, `this.cdRef.detectChanges()`
- ผิดพลาด: `catchError``this.swSV.errText(err?.error?.description || 'ส่งไม่สำเร็จ')`
## Error handling
- กดส่งโดยไม่เลือกอะไร: ปุ่มถูก disable ไว้แล้ว ไม่ต้องมี error state เพิ่ม
- API update ล้มเหลว: แสดง error ผ่าน `swSV.errText` ตามแพทเทิร์นเดิมของไฟล์ ไม่ rollback selection (ผู้ใช้กดส่งซ้ำได้)
- แถวที่ถูกล็อกแล้วไม่มีทางถูกส่งซ้ำ เพราะ checkbox ถูก disable ไว้ตั้งแต่ต้น (client-side guard เท่านั้น ไม่ได้เพิ่ม guard ฝั่ง backend เพราะนอกขอบเขตงานนี้)
## Testing
- Manual/browser verification (ตาม superpowers:verify skill ของโปรเจกต์): เพิ่มคำขอระหว่างปีใหม่ → ยืนยันบันทึกเป็น "ร่าง", แก้ไข/ลบได้
- ติ๊ก checkbox 1-2 แถวร่าง กด "ส่งกองนโยบายและแผน" → ยืนยันเปลี่ยนเป็น "ส่งงานแผน", แก้ไข/ลบไม่ได้ (เข้าไปดูฟอร์ม read-only ได้)
- ยืนยันรายการที่ส่งแล้วไปปรากฏในเมนู "ตรวจสอบคำเสนอขอโครงการ" แท็บ "จัดการคำเสนอขอ" เหมือนเดิม (ไม่ต้องแก้โค้ดฝั่งนั้น)
- ยืนยัน checkbox ของแถวที่ส่งแล้วไม่สามารถติ๊กเลือกซ้ำได้