docs: add design spec for evaluate-collect approve workflow
2-level self-service approval (mirroring request-budget type_4) plus a central-review stage on evaluate-follow, following the existing approve_state/remark_history reuse pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf6qFtiQoRuSh9zQSPF1wS
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4471b224cf
commit
4aa4f0449e
@@ -0,0 +1,262 @@
|
||||
# evaluate-collect → evaluate-follow: เพิ่มขั้นตอนอนุมัติ 2 ลำดับ + Central Review (Design)
|
||||
|
||||
**สถานะ:** ผ่าน design review แล้ว รอเขียน implementation plan
|
||||
**อ้างอิง:**
|
||||
- `docs/superpowers/specs/2026-09-07-approve-state-workflow-reference.md` — กลไก approve_state 2 ลำดับ + ส่งอนุมัติ (code "04") ต้นแบบหลัก
|
||||
- `docs/superpowers/specs/2026-09-07-invest-asset-self-service-approval-design.md` — ตัวอย่างการพอร์ต pattern เดียวกันไปยัง entity ใหม่ (invest_asset) ล่าสุด
|
||||
- `RequestBudgetReviewStatusComponent`/`RequestBudgetReviewController` (คำขอเงินรายได้ type_4) — ต้นแบบ central-review stage หลัง 2 ลำดับอนุมัติผ่านครบ
|
||||
|
||||
## 1. บริบทและปัญหา
|
||||
|
||||
หน้า `/app/research-and-evaluation/evaluate-collect` (entity `evaluate_form`) ปัจจุบันเป็น flow ระดับเดียว:
|
||||
กรอกข้อมูล (`onSubmit` → PUT) → "ส่งผล" (`onSend` → PUT แล้วเรียก `GET final_active/{uid}` ทันที ตั้ง
|
||||
`is_active=true`) ไม่มีแนวคิดผู้อนุมัติหรือสถานะรออนุมัติเลย — ใครก็กด "ส่งผล" ให้ finalize ได้ทันที
|
||||
|
||||
หน้า `/app/research-and-evaluation/evaluate-follow` (list จาก `agency_evaluate/final_active`) ปัจจุบันมีแค่
|
||||
ปุ่ม "ส่งกลับแก้ไข" (`onSendBack`) ที่เขียนคอมเมนต์ราย indicator ผ่าน `evaluate_follow_comment` แล้วเรียก
|
||||
`GET send_back/{uid}` (ตั้ง `is_active=false`) — ไม่มีปุ่ม "อนุมัติ/ตรวจสอบแล้ว" ที่ปิดจบกระบวนการ
|
||||
|
||||
ผู้ใช้ต้องการเพิ่ม:
|
||||
1. ปุ่ม "กำหนดผู้อนุมัติ" ที่ evaluate-collect → popup เลือกผู้อนุมัติ 2 ลำดับ (label เดียวกับคำขอเงินรายได้)
|
||||
2. ปุ่ม "ส่งอนุมัติ"
|
||||
3. ผู้อนุมัติแต่ละลำดับ login เข้ามากด "อนุมัติ" หรือ "ส่งแก้ไข" (ต้องกรอกรายละเอียด)
|
||||
4. เมื่อผู้อนุมัติลำดับสุดท้ายอนุมัติ → เข้า evaluate-follow โดยอัตโนมัติ ที่นั่นเจ้าหน้าที่กด "ส่งแก้ไข"
|
||||
หรือ "ตรวจสอบแล้ว" (จบ process)
|
||||
|
||||
## 2. ของเดิมที่ยังใช้ได้ (ไม่แตะ)
|
||||
|
||||
- `evaluate_form_target_indicator_agency` และกลไกกรอกข้อมูลราย indicator/หน่วยงาน — ไม่เกี่ยวกับงานนี้
|
||||
- ปุ่ม "ส่งกลับหน่วยงาน (n)" (`onSendBackAgency` → `POST send_back_agency/{uid}`) ที่ evaluate-collect —
|
||||
เป็นการส่งกลับเฉพาะบาง agency ให้กรอกใหม่ **ก่อน** เข้ากระบวนการอนุมัติทั้งฟอร์ม คนละระดับกับงานนี้
|
||||
ยังใช้ได้ปกติ แต่จะถูก disable ระหว่างฟอร์มอยู่ในสถานะรออนุมัติ (ดูข้อ 6)
|
||||
- ตาราง/endpoint `evaluate_form_active`, `evaluate_form_display` และ CRUD สายที่สอง (`create_form`/
|
||||
`update_form`) — ไม่แตะ
|
||||
- `GET final_active/{uid}` (ตั้ง `is_active=true, active_date=now`) และ `GET send_back/{uid}` (ตั้ง
|
||||
`is_active=false, active_date=null`) — logic เดิม **ยังใช้อยู่** แต่จะถูกเรียกจากจุดใหม่ (อัตโนมัติตอน
|
||||
อนุมัติผ่านครบ / ตอนกด "ส่งแก้ไข" ที่ evaluate-follow) แทนที่จะเรียกตรงจากปุ่ม "ส่งผล"/"ส่งกลับแก้ไข" เดิม
|
||||
- `evaluate_follow_comment` (ตาราง + service) — ยังอยู่ในระบบ ไม่ลบ แต่จะไม่มี action ใหม่เขียนเข้าไปอีก
|
||||
(ดูข้อ 8 เรื่อง dead code)
|
||||
|
||||
## 3. โมเดลข้อมูลใหม่
|
||||
|
||||
### 3.1 `approve_state` / `remark_history` — เพิ่มคอลัมน์ FK ใหม่
|
||||
|
||||
ตาม pattern "wide nullable FK ต่อ entity" เดิม (ห้าม reuse `request_expense_uid` เพราะ `evaluate_form`
|
||||
เป็นคนละตาราง):
|
||||
|
||||
```csharp
|
||||
// t_approve_state, t_remark_history — เพิ่มคอลัมน์เดียวกันทั้งสองตาราง
|
||||
public Guid? evaluate_form_uid { get; set; }
|
||||
```
|
||||
|
||||
อัปเดต `v_approve_state` (และ view ของ remark_history ถ้ามี) ให้รวมคอลัมน์ใหม่ + nav property
|
||||
`evaluate_form` แบบเดียวกับ `invest_asset`/`invest_construct`/`request_expense`
|
||||
|
||||
### 3.2 `evaluate_form` — เพิ่ม 3 ฟิลด์สถานะ
|
||||
|
||||
ใช้ semantics แบบ `request_expense` code "04"/"08" (pristine = null/null) ไม่ใช้แบบ `invest_asset`
|
||||
(`is_draft` ตรงข้าม) เพราะ `evaluate_form` ไม่มีแนวคิด draft เดิมอยู่แล้ว เพิ่มเองใหม่ทั้งหมดจึงเลือก
|
||||
รูปแบบที่ตรงไปตรงมากว่า:
|
||||
|
||||
```csharp
|
||||
public bool? is_sent { get; set; } // true = กด "ส่งอนุมัติ" แล้วจริง
|
||||
public bool? is_approve { get; set; } // true = อนุมัติผ่านครบทุกลำดับ (รอบปัจจุบัน)
|
||||
public string current_approve_name { get; set; } // denormalize ชื่อผู้อนุมัติที่กำลังรอคิว (โชว์ในตาราง)
|
||||
```
|
||||
|
||||
สถานะที่เป็นไปได้ (mirror ตารางในเอกสารอ้างอิงข้อ 2 ทุกช่อง — ไม่มี `is_draft` เพราะไม่ต้องใช้):
|
||||
|
||||
| is_sent | is_approve | ความหมาย |
|
||||
|---|---|---|
|
||||
| null | null | ยังไม่กด "ส่งอนุมัติ" (ต่อให้ตั้งผู้อนุมัติไว้แล้วก็ตาม) |
|
||||
| true | null | ส่งอนุมัติแล้ว กำลังรอผู้อนุมัติแถวที่ `is_state=true` |
|
||||
| false | false | ถูกส่งแก้ไขจากลำดับ 1 กลับมาหาผู้กรอกข้อมูล — แก้แล้วกด "ส่งอนุมัติ" ใหม่ได้ |
|
||||
| true | false | ถูกส่งแก้ไขจากลำดับ >1 กลับมาลำดับก่อนหน้า — ยังไม่ถึงมือผู้กรอกข้อมูล |
|
||||
| — | true | อนุมัติผ่านครบทุกลำดับแล้ว (จุดที่ trigger `final_active` + สร้าง `evaluate_form_review`) |
|
||||
|
||||
### 3.3 ตารางใหม่ `evaluate_form_review` — central review stage (mirror `request_budget_review`)
|
||||
|
||||
```csharp
|
||||
public class evaluate_form_review : base_table
|
||||
{
|
||||
[Key] public Guid? evaluate_form_review_uid { get; set; }
|
||||
public Guid? evaluate_form_uid { get; set; }
|
||||
public short review_status_id { get; set; } // 1=รอตรวจสอบ, 2=ถูกส่งแก้ไข, 3=ตรวจสอบแล้ว
|
||||
public string remark { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
Append-only log (เหมือนต้นแบบ) — สถานะปัจจุบันคือแถวล่าสุดตาม `created_datetime` ของ `evaluate_form_uid`
|
||||
นั้น ไม่มีฟิลด์ status เดี่ยวบน `evaluate_form` เอง
|
||||
|
||||
Migration: SQL ใหม่ `Migrations/add_evaluate_form_review.sql` (ตาราง + view) ตามสไตล์ migration อื่นๆ
|
||||
ใน `Modules/Evaluate/`
|
||||
|
||||
## 4. Flow 1 — กำหนดผู้อนุมัติ (`SetCustomApprovers` สำหรับ evaluate_form)
|
||||
|
||||
Mirror `RequestExpenseService.SetCustomApprovers` (เอกสารอ้างอิงข้อ 3) ทุกจุด เปลี่ยนแค่ entity/FK:
|
||||
|
||||
- โหลด `t_evaluate_form` ตาม uid, ไม่พบ → error "ไม่พบรายการ"
|
||||
- Guard: `entity.is_sent != null` **หรือ** `entity.is_approve != null` → error "รายการนี้เข้าสู่
|
||||
กระบวนการอนุมัติไปแล้ว ไม่สามารถกำหนดผู้อนุมัติใหม่ได้" (pristine check — null/null เท่านั้น)
|
||||
- Guard: มีแถว `t_approve_state` ที่ `evaluate_form_uid==uid && is_approve==true` อยู่แล้ว → error เดียวกัน
|
||||
- กรองแถวว่างทิ้ง, ไม่มีเหลือ → error "กรุณาระบุผู้อนุมัติอย่างน้อย 1 คน"
|
||||
- ลบคิวเก่า (ถ้ามี, เผื่อกำหนดซ้ำก่อนส่ง) → สร้างใหม่ทั้งหมด `row_order = idx+1`,
|
||||
`is_state = idx==0`, `request_approve_type = "<รหัสใหม่ — ดูข้อ 9>"`, `evaluate_form_uid = uid`
|
||||
- เซ็ต `entity.current_approve_name` เป็นชื่อคนแรก, save
|
||||
|
||||
**Endpoint:** `POST /api/evaluate/evaluate_form/set_approvers/{uid}`
|
||||
Body: `[{ personnel_uid, display_name_th }, ...]`
|
||||
|
||||
**Frontend:** `evaluate-collect-send.component` เพิ่มปุ่ม "กำหนดผู้อนุมัติ" เปิด
|
||||
`SetApproversDialogComponent` เดิม (reuse ตรงๆ ไม่ต้อง copy component ใหม่ — component นี้ generic อยู่แล้ว
|
||||
รับ/คืนแค่ `{personnel_uid, display_name_th}[]`) `approverLabels = ['หัวหน้าหน่วยงาน', 'รองอธิการบดี']`
|
||||
ลำดับ 1 required, ลำดับ 2 optional (ตามต้นแบบ)
|
||||
|
||||
## 5. Flow 2 — ส่งอนุมัติ (`SendApprove` สำหรับ evaluate_form)
|
||||
|
||||
Mirror `SendApprove2` (เอกสารอ้างอิงข้อ 4):
|
||||
|
||||
- โหลด entity, ไม่พบ → error
|
||||
- `is_approve == true` → error "รายการนี้เข้าสู่กระบวนการอนุมัติไปแล้ว"
|
||||
- ส่งได้ 2 กรณีเท่านั้น: pristine (`is_sent==null && is_approve==null`) หรือเพิ่งถูกส่งแก้ไขกลับมาถึงผู้กรอก
|
||||
ข้อมูล (`is_sent==false && is_approve==false`) — อย่างอื่น block
|
||||
- ต้องมีคิว `row_order==1` ตั้งไว้แล้ว (`is_state==true`) ไม่งั้น error "ยังไม่พร้อมส่งอนุมัติ กรุณา
|
||||
กำหนดผู้อนุมัติก่อน"
|
||||
- เซ็ต `is_sent=true, is_approve=null`, save
|
||||
|
||||
**Endpoint:** `POST /api/evaluate/evaluate_form/send_approve/{uid}` (body ว่าง, ใครก็เรียกได้)
|
||||
|
||||
**Frontend:** ปุ่ม "ส่งอนุมัติ" แทนที่ปุ่ม "ส่งผล" เดิม (ปุ่มเดิมที่เรียก `final_active` ตรงๆ ถูกลบออก —
|
||||
การ finalize จริงย้ายไปเกิดอัตโนมัติตอนอนุมัติผ่านครบทุกลำดับ ดูข้อ 6)
|
||||
|
||||
## 6. Flow 3 — อนุมัติ 2 ลำดับ (ขยาย `ApproveGetEntity` ใน `ApproveState.cs`)
|
||||
|
||||
เพิ่ม branch ใหม่ในคอนโทรลเลอร์กลาง `Modules/Setting/Controllers/ApproveState.cs`
|
||||
(`GET /api/setting/approve_state/approve/{uid}`) คู่ขนานกับ branch `request_expense`/`invest_asset`/
|
||||
`invest_construct` เดิม โดย:
|
||||
|
||||
```csharp
|
||||
if (evaluate_form != null && getResult != null)
|
||||
{
|
||||
// ต่างจากต้นแบบเดิม (ที่เช็คสิทธิ์เฉพาะ row_order==1): เช็คตัวตนผู้อนุมัติทุก row_order
|
||||
// — ใช้ fix ล่าสุดที่ระบบทำกับ request_expense (commit c12b93bd) เป็นค่าเริ่มต้นตั้งแต่วันแรก
|
||||
// ไม่ต้องรอเจอบั๊กเดียวกันซ้ำ
|
||||
if (getResult.row_order == 1 && getResult.request_approve_type == "<รหัสใหม่>"
|
||||
&& evaluate_form.is_sent != true)
|
||||
{
|
||||
return BadRequest(new { description = "ยังไม่ได้ส่งอนุมัติ กรุณากด \"ส่งอนุมัติ\" ก่อน" });
|
||||
}
|
||||
var currentDisplayName = (User.Identity as ClaimsIdentity)?.FindFirst("display_name")?.Value;
|
||||
if (string.IsNullOrEmpty(currentDisplayName) || currentDisplayName != getResult.display_name_th)
|
||||
return StatusCode(403, new { description = "ไม่มีสิทธิ์อนุมัติ (ต้องเป็นผู้ที่ถูกกำหนดเป็นผู้อนุมัติลำดับนี้เท่านั้น)" });
|
||||
|
||||
// ปิดคิวปัจจุบัน = อนุมัติผ่าน
|
||||
getResult.is_state = null; getResult.is_approve = true;
|
||||
evaluate_form.current_approve_name = null;
|
||||
// ... save ...
|
||||
|
||||
var remaining = /* แถวที่ evaluate_form_uid==uid && round_no==currentRound && is_approve!=true */;
|
||||
if (!remaining.Any())
|
||||
{
|
||||
evaluate_form.is_approve = true;
|
||||
evaluate_form.is_active = true; // = logic เดิมของ final_active
|
||||
evaluate_form.active_date = DateTime.Now;
|
||||
// สร้าง evaluate_form_review ใหม่ review_status_id=1 (รอตรวจสอบ)
|
||||
}
|
||||
else
|
||||
{
|
||||
// เดินหน้า is_state=true ไปแถว row_order+1, เซ็ต current_approve_name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Endpoint:** ใช้ตัวเดิม `GET /api/setting/approve_state/approve/{uid}` (endpoint กลาง ไม่เพิ่มใหม่)
|
||||
|
||||
**Frontend:** `evaluate-collect-send.component` เพิ่มปุ่ม "อนุมัติ" / "ส่งแก้ไข" แสดงเฉพาะเมื่อ
|
||||
`current_approve_name` ตรงกับชื่อผู้ login อยู่ (เทียบแบบ `request-expense-approval.ts`, port มาเป็นไฟล์
|
||||
util คู่ขนานสำหรับ `evaluate_form`) — ปุ่ม "อนุมัติ" เรียก endpoint approve ตรงๆ, ปุ่ม "ส่งแก้ไข" เปิด Swal
|
||||
textarea บังคับกรอก แล้วบันทึก `t_remark_history` (`evaluate_form_uid`, `round_no` ปัจจุบัน) ก่อนเรียก
|
||||
reject (ข้อ 7) — mirror `onApprove4`/`onReject4` ทุกจุด รวมตาราง remark history ใต้ปุ่มด้วย
|
||||
|
||||
## 7. Flow 4 — ส่งแก้ไขระหว่างอนุมัติ (ขยาย `RejectGetEntity`)
|
||||
|
||||
ตามที่เลือกไว้ ("ย้อนไปทีละลำดับ") — mirror `RejectGetEntity` branch `request_expense` เป๊ะ
|
||||
(เอกสารอ้างอิงข้อ 6):
|
||||
|
||||
- หา index ของแถว `is_state==true` ปัจจุบันในคิว (เรียงตาม `row_order`)
|
||||
- ถ้า `index > 0`: ปิดแถวปัจจุบัน (`is_state=null, is_approve=null`), เปิดแถวก่อนหน้า
|
||||
(`is_state=true, is_approve=null`) — ยังไม่ถึงมือผู้กรอกข้อมูล
|
||||
- ถ้า `index == 0` (ลำดับ 1 ปฏิเสธเอง): **ไม่แตะ** `approve_state` (ตาม pattern เดิม ทิ้ง `is_state`
|
||||
ของแถวลำดับ 1 ค้างไว้เป็น true — โค้ดฝั่งอ่านสถานะต้องเช็ค `is_sent` ควบคู่เสมอ ตามคำเตือนในเอกสาร
|
||||
อ้างอิงข้อ 2/6/7)
|
||||
- เซ็ต `evaluate_form.is_approve=false`; ถ้า `index==0` เพิ่ม `evaluate_form.is_sent=false` ด้วย
|
||||
(คืนสถานะ "ยังไม่ได้ส่งอนุมัติ" ให้ส่งใหม่ได้หลังแก้ไข) ถ้า `index>0` ไม่แตะ `is_sent` (ยังเป็น true)
|
||||
- อัปเดต `current_approve_name` จากแถวที่ `is_state==true` ใหม่ (ถ้ามี)
|
||||
|
||||
**Endpoint:** ใช้ตัวเดิม `GET /api/setting/approve_state/reject/{uid}`
|
||||
|
||||
## 8. Flow 5 — Central Review Stage (evaluate-follow) — mirror `request_budget_review`
|
||||
|
||||
**Controller/Service ใหม่** `Modules/Evaluate/Controllers/EvaluateFormReview.cs` +
|
||||
`Modules/Evaluate/Services/EvaluateFormReview.cs`, route `api/evaluate/evaluate_form_review`:
|
||||
|
||||
| Endpoint | พฤติกรรม |
|
||||
|---|---|
|
||||
| `GET status/{evaluate_form_uid}` | derive `not_ready`/`pending`/`returned`/`reviewed` จาก `evaluate_form.is_approve` + แถวล่าสุดใน `evaluate_form_review`; คืน `history[]` จาก `remark_history` ที่ `evaluate_form_uid` นี้ (แบบ `by`=`updated_name`, `detail`=`remark_history_name_th`) |
|
||||
| `GET dashboard` | group รายการที่ `is_approve==true` เป็น `pending`/`returned`/`reviewed` ตาม `evaluate_form_review` ล่าสุด |
|
||||
| `POST return_for_edit/{uid}` (body `{remark}`) | **"ส่งแก้ไข"**: require `is_approve==true` && remark ไม่ว่าง → เปิด `is_state=true, is_approve=null` ที่แถว **`row_order` สูงสุด** (ผู้อนุมัติคนสุดท้าย — ไม่ย้อนกลับไปลำดับ 1) → เซ็ต `evaluate_form.is_approve=false, current_approve_name=<ชื่อแถวนั้น>` (ไม่แตะ `is_sent`, ยังเป็น true) → insert `t_remark_history` (comment) + `t_evaluate_form_review` (`review_status_id=2`) |
|
||||
| `POST mark_reviewed/{uid}` | **"ตรวจสอบแล้ว"**: require `is_approve==true` && สถานะล่าสุดเป็น pending (หรือยังไม่มี log เลย สำหรับ record เก่า) → insert `t_evaluate_form_review` (`review_status_id=3`) — จบ process |
|
||||
|
||||
**Frontend:** สร้าง `EvaluateFormReviewService` (มิเรอร์ `RequestBudgetReviewService` 4 endpoint) และ
|
||||
คอมโพเนนต์ใหม่มิเรอร์ `RequestBudgetReviewStatusComponent` ฝังใน `evaluate-follow-send.component` —
|
||||
**แทนที่** ปุ่ม "ส่งกลับแก้ไข" เดิมทั้งหมด (ลบการเรียก `evaluate_follow_comment.add` + `send_back` ออกจาก
|
||||
ปุ่มนี้) ด้วย:
|
||||
- ปุ่ม "ส่งแก้ไข" (แสดงเมื่อ status `pending`) → Swal textarea บังคับกรอก → `returnForEdit`
|
||||
- ปุ่ม "ตรวจสอบแล้ว" (แสดงเมื่อ status `pending`) → confirm "ยืนยันว่าตรวจสอบแล้วใช่หรือไม่" → `markReviewed`
|
||||
- ตาราง history ยุบ/ขยายได้ (mirror `toggleHistory()`)
|
||||
|
||||
`evaluate-follow-index.component` เพิ่ม bucket/tab สถานะ (รอตรวจสอบ/ถูกส่งแก้ไข/ตรวจสอบแล้ว) มิเรอร์
|
||||
`RequestBudgetReviewDashboardComponent` — list source ยังคงอ่านจาก `agency_evaluate/final_active` เดิม
|
||||
(รายการเข้ามาที่นี่เมื่อไรก็ต่อเมื่อ `evaluate_form.is_approve=true` ซึ่งตอนนี้ trigger `is_active=true`
|
||||
ให้อัตโนมัติจาก flow อนุมัติแล้ว — ไม่ต้องเปลี่ยน query เดิม)
|
||||
|
||||
## 9. รหัส `request_approve_type` ใหม่ — ต้องยืนยันสดก่อนเขียนโค้ด
|
||||
|
||||
โค้ดที่ใช้แล้ว ณ ตอนเขียนเอกสารนี้: `"01"`-`"08"` (ดูตารางในเอกสารอ้างอิง + type_4 คำขอแผ่นดิน) คาดว่า
|
||||
รหัสถัดไปคือ `"09"` แต่**ต้องเช็คแบบสดกับฐานข้อมูล production เป็น task แรกของ implementation plan**
|
||||
(`SELECT DISTINCT request_approve_type FROM approve_state` + ตาราง `request_approve_type` ถ้ามี) ก่อน
|
||||
เขียนโค้ดจริง เหมือนที่ทำกับ invest-asset self-service (ข้อ 4 ของสเปกนั้น)
|
||||
|
||||
## 10. ความเสี่ยง/จุดต้องระวัง (สืบทอดจากเอกสารอ้างอิง)
|
||||
|
||||
1. **`display_name_th` string matching แบบ exact** — ระบบเดิม match ตัวตนผู้อนุมัติด้วยการเทียบ string
|
||||
ล้วนๆ (เจอเคสจริงที่ชื่อเดียวกันมี/ไม่มีวรรคแล้ว match ไม่ติด) เวอร์ชันใหม่นี้จะ**ตามระบบเดิมไปก่อน**
|
||||
เพื่อความสอดคล้อง ไม่เปลี่ยนกลไกนี้เฉพาะจุด — ถ้าเจอปัญหาจริงหลัง deploy ค่อยพิจารณาย้ายไป match ด้วย
|
||||
`personnel_uid` ทั้งระบบแยกเป็นงานอื่น (นอกขอบเขต)
|
||||
2. **เช็คสิทธิ์ทุก `row_order` ตั้งแต่วันแรก** (ไม่ใช่แค่ row_order==1 แบบต้นแบบเดิมก่อนแก้) — ตามที่ระบุ
|
||||
ในข้อ 6 ป้องกันบั๊กเดิมที่เพิ่งแก้ไปกับ `request_expense`
|
||||
3. **ปุ่ม "ส่งกลับหน่วยงาน" ที่ evaluate-collect ต้อง disable ระหว่างรออนุมัติ** (`is_sent==true &&
|
||||
is_approve!=true`) — ไม่งั้นข้อมูลจะถูกแก้ระหว่างที่กำลังอยู่ในคิวอนุมัติ ทำให้ข้อมูลที่อนุมัติไปแล้ว
|
||||
กับข้อมูลจริงไม่ตรงกัน
|
||||
4. **`evaluate_follow_comment` กลายเป็น dead write-path** จากงานนี้ (ยังอ่านแสดงได้ ถ้ามีข้อมูลเก่า แต่ไม่มี
|
||||
action ใหม่เขียนเพิ่ม) — ไม่ลบทิ้งในรอบนี้ ตามที่ตกลงไว้ นอกขอบเขต cleanup
|
||||
5. **ต้องเพิ่ม guard `is_sent` ในจุดเช็คของ `ApproveGetEntity`** ให้ครอบคลุม code ใหม่ตั้งแต่แรก — ถ้าลืม
|
||||
ระบบจะข้ามการเช็ค "ส่งอนุมัติหรือยัง" ไปเลย เพราะ `SetCustomApprovers` seed `is_state=true` ที่แถวแรก
|
||||
ตั้งแต่ก่อนกด "ส่งอนุมัติ" ด้วยซ้ำ
|
||||
6. **ห้ามไว้ใจฟิลด์ denormalize ชื่อหน่วยงาน/ปีงบบน `evaluate_form` เอง** ถ้ามี — join จากตารางต้นทางเสมอ
|
||||
เวลาทำ dashboard (บทเรียนเดียวกับ `request_expense.faculty_name_th`)
|
||||
|
||||
## 11. Testing
|
||||
|
||||
Methodology เดียวกับที่ใช้ทำ income-budget-review-stage/request-budget-approve-workflow ใน session ก่อน
|
||||
หน้า: สร้างข้อมูลทดสอบสังเคราะห์ผ่าน SQL (evaluate_form + agency data ที่ครบเงื่อนไข), ยิง endpoint จริง
|
||||
ด้วย JWT ทดสอบของผู้ใช้ 3 บทบาท (ผู้กรอกข้อมูล, ผู้อนุมัติลำดับ 1, ผู้อนุมัติลำดับ 2/เจ้าหน้าที่ follow),
|
||||
ตรวจผลผ่าน SQL หลังแต่ละ step (`approve_state`, `evaluate_form.is_sent/is_approve/is_active`,
|
||||
`evaluate_form_review`, `remark_history`), ลบข้อมูลทดสอบทิ้งหลังตรวจเสร็จ — ไม่มี automated test suite
|
||||
ในโปรเจกต์นี้
|
||||
|
||||
Path ที่ต้องครอบคลุม: (1) happy path ครบ 2 ลำดับ → evaluate-follow → ตรวจสอบแล้ว, (2) ลำดับ 2 ส่งแก้ไข
|
||||
→ กลับลำดับ 1, (3) ลำดับ 1 ส่งแก้ไข (ปฏิเสธตัวเอง) → กลับผู้กรอกข้อมูล → ส่งอนุมัติใหม่, (4) evaluate-follow
|
||||
ส่งแก้ไข → เปิดคิวลำดับสุดท้ายใหม่ → อนุมัติอีกรอบ → กลับเข้า evaluate-follow อีกครั้ง, (5) guard: กด
|
||||
approve/reject โดยไม่ใช่ชื่อที่ตรงคิว → ต้อง 403, (6) กด approve ก่อนกด "ส่งอนุมัติ" → ต้อง 400
|
||||
Reference in New Issue
Block a user