docs: revise review-stage spec — ส่งแก้ไข reopens the chain's last approver
Per feedback: "ส่งแก้ไข" from central review must route back through the existing income-report chain to its last approver (who can cascade the rejection further back to the creator via the existing mechanism), not create a disconnected new status. income_budget_review is now log/history only — actual in-progress-editing state still comes from request_expense.is_draft/is_approve directly, and re-entering "รอตรวจสอบ" happens automatically via the existing is_approve-completion trigger, so no separate resubmit endpoint or button is needed on income-budget-plan-01. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0153wBt23EjW125ZsADwWVAs
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5f634c1bf8
commit
f834bbefd8
@@ -19,6 +19,11 @@
|
||||
**การค้นหา/กรอง:** ที่ `income-budget-plan-02` ต้องกรองตาม **ปีงบประมาณ + หน่วยงาน/คณะ + พื้นที่**
|
||||
เหมือนหน้า income-report เดิม (ยืนยันจากผู้ใช้)
|
||||
|
||||
**เมื่อคนกลางกด "ส่งแก้ไข":** ต้องส่งกลับไปหา**ผู้อนุมัติคนสุดท้าย**ของเชนเดิม เพื่อเข้าสู่ขั้นตอน
|
||||
"ส่งกลับให้ต้นทาง" แบบเดียวกับที่เชน income-report มีอยู่แล้ว (ผู้อนุมัติคนสุดท้ายเห็นว่าเรื่องถูกตีกลับ
|
||||
แก้ไขได้ตามปกติ หรือจะกดปฏิเสธต่อไปยังลำดับก่อนหน้าจนถึงผู้สร้างก็ได้ตามกลไกเดิมทุกประการ) — ไม่ใช่การ
|
||||
สร้างสถานะ "ส่งแก้ไข" แยกลอยๆ ที่ไม่เชื่อมกับเชนเดิม (ยืนยันจากผู้ใช้)
|
||||
|
||||
## 2. ทำไมไม่ใช้กลไก `round_no` (resubmit) ที่มีอยู่แล้ว
|
||||
|
||||
ระบบมี "ส่งอนุมัติเพิ่มเติม" (`ResubmitGetEntity`, เพิ่ม `round_no`) อยู่แล้ว แต่ **ผูกตายตัวกับ
|
||||
@@ -29,8 +34,9 @@
|
||||
|
||||
## 3. โมเดลข้อมูลใหม่
|
||||
|
||||
ตารางใหม่ `income_budget_review` (1 แถวต่อ "รอบการตรวจสอบ" — มีได้หลายแถวถ้าโดนส่งแก้ไขแล้วส่งใหม่หลายรอบ
|
||||
เพื่อเก็บประวัติ):
|
||||
ตารางใหม่ `income_budget_review` — **ใช้เป็น log/ประวัติเท่านั้น** ไม่ใช่ตัวเก็บ "สถานะปัจจุบัน" — สถานะ
|
||||
จริงที่ว่าเรื่องกำลังอยู่ระหว่างแก้ไขหรือไม่ยังคงมาจาก `request_expense.is_draft`/`is_approve` เดิมเป๊ะ
|
||||
(ดูเหตุผลในข้อ 4):
|
||||
|
||||
```csharp
|
||||
public class income_budget_review : base_table
|
||||
@@ -48,29 +54,84 @@ public class income_budget_review : base_table
|
||||
ในจุดเดียวกับที่ `ApproveGetEntity` (ApproveState.cs) ตั้ง `request_expense.is_approve = true` (เคสที่ไม่มี
|
||||
ลำดับถัดไปแล้ว — ดู [[2026-09-07-approve-state-workflow-reference]] ข้อ 5) ให้เพิ่ม insert
|
||||
`income_budget_review` แถวใหม่ `status_id=1` **เฉพาะตอน `request_approve_type == "04"`** (ไม่กระทบ
|
||||
`invest_asset`/`invest_construct`/`request_expense` code อื่นๆ ที่ใช้ engine เดียวกัน)
|
||||
`invest_asset`/`invest_construct`/`request_expense` code อื่นๆ ที่ใช้ engine เดียวกัน) — จุด trigger นี้จะยิง
|
||||
ซ้ำทุกครั้งที่เชนอนุมัติผ่านครบ **ไม่ว่าจะเป็นครั้งแรกหรือหลังถูกคนกลางตีกลับแล้วแก้ไขจนอนุมัติผ่านใหม่**
|
||||
เพราะเป็น branch เดียวกันของโค้ดเดิม (ดูข้อ 4) — ระบบจึงวนกลับเข้า "รอตรวจสอบ" ให้อัตโนมัติโดยไม่ต้องมีปุ่ม
|
||||
"ส่งตรวจสอบใหม่" แยกต่างหาก
|
||||
|
||||
**สถานะปัจจุบันของเรื่องหนึ่งๆ** = แถว `income_budget_review` ที่มี `created_datetime` ล่าสุดของ
|
||||
`request_expense_uid` นั้น (query `OrderByDescending(created_datetime).First()`)
|
||||
**สถานะปัจจุบันของเรื่องหนึ่งๆ (สำหรับ dashboard/badge):**
|
||||
- ถ้า `request_expense.is_draft == true && is_approve == false` → **"ถูกส่งแก้ไข"** (กำลังอยู่ในเชน
|
||||
income-report รอฝั่งคณะแก้ไข — เช็คจาก `request_expense` โดยตรง ไม่ต้องพึ่ง log)
|
||||
- ไม่งั้น ดูแถว `income_budget_review` ล่าสุด (`OrderByDescending(created_datetime).First()`) ของ
|
||||
`request_expense_uid` นั้น: `status_id=1` → "รอตรวจสอบ", `status_id=3` → "ตรวจสอบแล้ว"
|
||||
|
||||
## 4. Flow
|
||||
## 4. Flow — "ส่งแก้ไข" จากส่วนกลาง = เปิดคิวผู้อนุมัติคนสุดท้ายกลับมาใหม่
|
||||
|
||||
หัวใจของดีไซน์นี้: การกด "ส่งแก้ไข" ที่ `income-budget-plan-02` **ไม่ใช่สถานะแยกลอย** แต่เป็นการจำลอง
|
||||
สิ่งที่จะเกิดขึ้นถ้า "ผู้อนุมัติคนสุดท้าย" ของเชนกด "ส่งแก้ไข" เอง — เปิดแถว `approve_state` ที่
|
||||
`row_order` สูงสุดของรอบล่าสุดกลับมา `is_state=true` อีกครั้ง แล้วปล่อยให้กลไก "ส่งแก้ไข"/"อนุมัติ" เดิมของ
|
||||
income-report ([[2026-09-07-approve-state-workflow-reference]] ข้อ 5-6) ทำงานต่อตามปกติทุกประการ —
|
||||
รวมถึงถ้าผู้อนุมัติคนสุดท้ายกดปฏิเสธต่อ ก็จะไหลกลับไปลำดับก่อนหน้าจนถึงผู้สร้างได้เหมือนเดิม
|
||||
|
||||
```csharp
|
||||
// สรุปพฤติกรรม ReturnForEdit(requestExpenseUid, remark, claimsIdentity) — รายละเอียด field เป๊ะๆ
|
||||
// จะสรุปอีกครั้งตอนทำ implementation plan
|
||||
var entity = await _context.t_request_expense.FirstOrDefaultAsync(c => c.request_expense_uid == requestExpenseUid);
|
||||
if (entity.is_approve != true)
|
||||
return (false, "รายการนี้ยังไม่ผ่านการอนุมัติครบทุกลำดับ ไม่สามารถส่งแก้ไขจากการตรวจสอบได้");
|
||||
|
||||
var currentRound = await _context.t_approve_state.Where(c => c.request_expense_uid == requestExpenseUid)
|
||||
.Select(c => (int?)c.round_no).MaxAsync() ?? 1;
|
||||
var lastRow = await _context.t_approve_state
|
||||
.Where(c => c.request_expense_uid == requestExpenseUid && c.round_no == currentRound)
|
||||
.OrderByDescending(c => c.row_order).FirstOrDefaultAsync(); // ผู้อนุมัติคนสุดท้าย
|
||||
|
||||
lastRow.is_state = true;
|
||||
lastRow.is_approve = null; // เปิดคิวนี้กลับมาใหม่ เหมือนยังไม่เคยอนุมัติ
|
||||
|
||||
entity.is_draft = true;
|
||||
entity.is_approve = false;
|
||||
entity.current_approve_name = lastRow.display_name_th;
|
||||
if (lastRow.row_order == 1) entity.is_sent = false; // เชนมีคนเดียว = เท่ากับตีกลับไปหาผู้สร้างเลย (เหมือน RejectGetEntity index_current==0)
|
||||
|
||||
// บันทึก remark ผ่านกลไกเดิมที่ income-report ใช้แสดงในตาราง "รายละเอียดสำหรับการส่งแก้ไข" (remark_history)
|
||||
await _context.t_remark_history.AddAsync(new t_remark_history {
|
||||
request_expense_uid = requestExpenseUid,
|
||||
name = "ส่งแก้ไขจากการตรวจสอบส่วนกลาง",
|
||||
remark = remark,
|
||||
});
|
||||
|
||||
// log ไว้สำหรับ dashboard/ประวัติที่ plan-02 เท่านั้น — ไม่ใช่ตัวกำหนด state จริง
|
||||
await _context.income_budget_review.AddAsync(new t_income_budget_review {
|
||||
request_expense_uid = requestExpenseUid, status_id = 2, remark = remark,
|
||||
reviewed_by = claimsIdentity?.FindFirst("login_name")?.Value, reviewed_datetime = DateTime.Now,
|
||||
});
|
||||
```
|
||||
|
||||
ผลลัพธ์: ผู้อนุมัติคนสุดท้ายเห็นเรื่องนี้ที่หน้า income-report **เหมือนถูกตีกลับตามปกติทุกประการ**
|
||||
(remark จากส่วนกลางโชว์ในตารางเดิมที่มีอยู่แล้ว) แก้ไข/ส่งอนุมัติใหม่ตามกลไกเดิมจนอนุมัติผ่านครบอีกครั้ง
|
||||
→ trigger เดิมในข้อ 3 จะ log "รอตรวจสอบ" ให้อัตโนมัติ วนกลับไป plan-02
|
||||
|
||||
```
|
||||
income-report เชนอนุมัติผ่านครบ (is_approve=true)
|
||||
│ (auto insert income_budget_review status_id=1)
|
||||
▼
|
||||
income-budget-plan-01 (ฝั่งคณะ) income-budget-plan-02 (ฝั่งส่วนกลาง)
|
||||
เห็นสถานะ "รอตรวจสอบ" เห็นเรื่องนี้ใน dashboard "รอตรวจสอบ"
|
||||
│ │
|
||||
│ ผู้ตรวจสอบกด "ส่งแก้ไข" + พิมพ์ remark
|
||||
│◄───────────────────────────────────────┘
|
||||
เห็นสถานะ "ถูกส่งแก้ไข" + remark
|
||||
กด "ส่งตรวจสอบใหม่" (แก้ไขข้อมูลผ่าน income-report/plan-01 ตามปกติ)
|
||||
│ (insert income_budget_review row ใหม่ status_id=1)
|
||||
▼
|
||||
income-budget-plan-02 เห็นเรื่องนี้กลับมาที่ "รอตรวจสอบ" อีกครั้ง
|
||||
income-budget-plan-02 (ฝั่งส่วนกลาง) เห็นเรื่องนี้ใน dashboard "รอตรวจสอบ"
|
||||
│
|
||||
ผู้ตรวจสอบกด "ตรวจสอบแล้ว" (status_id=3, จบกระบวนการ)
|
||||
┌────┴─────────────────────┐
|
||||
│ │
|
||||
กด "ตรวจสอบแล้ว" กด "ส่งแก้ไข" + remark
|
||||
(status_id=3, จบ) │
|
||||
เปิดแถว approve_state ล่าสุด (row_order สูงสุด) กลับเป็น is_state=true
|
||||
request_expense.is_draft=true, is_approve=false
|
||||
บันทึก remark ผ่าน remark_history เดิม + log status_id=2
|
||||
│
|
||||
เรื่องนี้กลับเข้าเชน income-report ปกติ (ผู้อนุมัติคนสุดท้ายเห็น "ถูกส่งแก้ไข"
|
||||
แก้ไข/ปฏิเสธต่อไปยังลำดับก่อนหน้า/ส่งอนุมัติใหม่ได้ตามกลไกเดิมทุกประการ)
|
||||
│
|
||||
เชนอนุมัติผ่านครบอีกครั้ง (is_approve=true)
|
||||
│
|
||||
(auto insert income_budget_review status_id=1 — วนกลับไป "รอตรวจสอบ")
|
||||
```
|
||||
|
||||
## 5. Backend API (ใหม่)
|
||||
@@ -79,31 +140,35 @@ income-budget-plan-02 เห็นเรื่องนี้กลับมา
|
||||
|
||||
| Endpoint | Method | หน้าที่ |
|
||||
|---|---|---|
|
||||
| `/api/income_budget_review/search` | GET | query `budget_year_uid`, `faculty_uid`, `budget_location_uid` → คืน `request_expense` 1 เรื่อง (ถ้ามี) + สถานะรีวิวล่าสุด + ประวัติ remark ทั้งหมด |
|
||||
| `/api/income_budget_review/dashboard` | GET | นับจำนวนตามสถานะทั่วทั้งระบบ (ไม่กรองคณะ — ผู้ตรวจสอบเป็นส่วนกลาง): รอตรวจสอบ, ส่งแก้ไข(ที่ยังไม่ถูกแก้), ตรวจสอบแล้ว — พร้อม list รายละเอียด (คณะ/พื้นที่/ปีงบ) ต่อหมวด เหมือน `request_expense/type_2/my_dashboard` |
|
||||
| `/api/income_budget_review/return_for_edit/{request_expense_uid}` | POST | body `{ remark }` → insert แถวใหม่ `status_id=2` |
|
||||
| `/api/income_budget_review/mark_reviewed/{request_expense_uid}` | POST | insert แถวใหม่ `status_id=3` — เงื่อนไข: แถวล่าสุดต้องเป็น `status_id=1` เท่านั้น (กันกดซ้ำ/กดข้ามสถานะ) |
|
||||
| `/api/income_budget_review/resubmit/{request_expense_uid}` | POST | ใช้ที่ฝั่งคณะ (plan-01) กด "ส่งตรวจสอบใหม่" หลังแก้ไข — เงื่อนไข: แถวล่าสุดต้องเป็น `status_id=2` → insert แถวใหม่ `status_id=1` |
|
||||
| `/api/income_budget_review/search` | GET | query `budget_year_uid`, `faculty_uid`, `budget_location_uid` → คืน `request_expense` 1 เรื่อง (ถ้ามี) + สถานะปัจจุบัน (คำนวณตามข้อ 3) + ประวัติ `income_budget_review`/`remark_history` ทั้งหมด |
|
||||
| `/api/income_budget_review/dashboard` | GET | นับจำนวนตามสถานะทั่วทั้งระบบ (ไม่กรองคณะ — ผู้ตรวจสอบเป็นส่วนกลาง): รอตรวจสอบ, ถูกส่งแก้ไข(ที่ยังไม่ผ่านการอนุมัติใหม่), ตรวจสอบแล้ว — พร้อม list รายละเอียด (คณะ/พื้นที่/ปีงบ) ต่อหมวด เหมือน `request_expense/type_2/my_dashboard` |
|
||||
| `/api/income_budget_review/return_for_edit/{request_expense_uid}` | POST | body `{ remark }` → เปิดแถวผู้อนุมัติคนสุดท้ายกลับมา + ตั้ง `is_draft`/`is_approve` ตามข้อ 4 + insert `remark_history` + log `status_id=2` |
|
||||
| `/api/income_budget_review/mark_reviewed/{request_expense_uid}` | POST | insert แถวใหม่ `status_id=3` — เงื่อนไข: `request_expense.is_approve==true` และแถว log ล่าสุดต้องเป็น `status_id=1` เท่านั้น (กันกดซ้ำ/กดข้ามสถานะ) |
|
||||
|
||||
ทุก endpoint ตรวจสอบเงื่อนไข "แถวล่าสุดของเรื่องนี้ต้องเป็นสถานะอะไร" ก่อน insert เสมอ กัน race condition/
|
||||
กดปุ่มผิดจังหวะ (เช่น กด "ตรวจสอบแล้ว" ซ้ำ หรือกด "ส่งตรวจสอบใหม่" ทั้งที่ยังไม่เคยถูกส่งแก้ไข)
|
||||
ไม่มี endpoint "resubmit" แยกต่างหากฝั่งคณะ — การกลับเข้า "รอตรวจสอบ" เกิดขึ้นอัตโนมัติผ่าน trigger เดิม
|
||||
(ข้อ 3) เมื่อเชน income-report อนุมัติผ่านครบอีกครั้ง
|
||||
|
||||
ทุก endpoint ตรวจสอบเงื่อนไข "สถานะปัจจุบันของเรื่องนี้ต้องเป็นอะไร" ก่อนทำงานเสมอ กัน race condition/
|
||||
กดปุ่มผิดจังหวะ (เช่น กด "ตรวจสอบแล้ว"/"ส่งแก้ไข" ซ้ำ หรือกดตอนเชนยังไม่อนุมัติผ่านครบ)
|
||||
|
||||
## 6. Frontend
|
||||
|
||||
**`income-budget-plan-02`** (เพิ่มเข้าไปในหน้าเดิม เหนือฟอร์มค้นหา — โครงเดียวกับ dashboard ที่ทำให้
|
||||
income-report):
|
||||
- การ์ด dashboard: chip กดได้ 3 อัน — "รอตรวจสอบ N", "ส่งแก้ไขไปแล้ว N" (ที่ยังไม่ถูกแก้กลับมา),
|
||||
- การ์ด dashboard: chip กดได้ 3 อัน — "รอตรวจสอบ N", "ถูกส่งแก้ไข N" (ที่ยังไม่ผ่านการอนุมัติใหม่),
|
||||
"ตรวจสอบแล้ว N" — กด chip กางลิสต์ (คณะ/พื้นที่/ปีงบ), กดแถวในลิสต์ = pre-fill ช่องค้นหาแล้วค้นหาทันที
|
||||
(รูปแบบเดียวกับ `income-report-approve-dashboard.component.ts` ที่มีอยู่แล้ว — reuse โครงสร้างเดิม)
|
||||
- หลังค้นหาแล้วเจอเรื่อง: แสดง **สถานะปัจจุบันชัดเจน** (badge: "รอตรวจสอบ" / "ส่งแก้ไขแล้ว รอคณะแก้ไข" /
|
||||
- หลังค้นหาแล้วเจอเรื่อง: แสดง **สถานะปัจจุบันชัดเจน** (badge: "รอตรวจสอบ" / "ถูกส่งแก้ไข รอคณะแก้ไข" /
|
||||
"ตรวจสอบแล้ว") + ตาราง**ประวัติการส่งแก้ไข**ทั้งหมดของเรื่องนี้ (วันที่, remark, ผู้ตรวจ) — โครงตาราง
|
||||
เดียวกับ "รายละเอียดสำหรับการส่งแก้ไข" ที่มีอยู่แล้วในหน้า income-report (`remark_history`)
|
||||
เดียวกับ "รายละเอียดสำหรับการส่งแก้ไข" ที่มีอยู่แล้วในหน้า income-report (`remark_history` — ตารางเดียวกัน
|
||||
ที่ผู้อนุมัติคนสุดท้ายจะเห็น remark จากส่วนกลางด้วย)
|
||||
- ปุ่ม "ส่งแก้ไข" (เปิด dialog ให้พิมพ์ remark ก่อนยืนยัน) และ "ตรวจสอบแล้ว" — โชว์เฉพาะตอนสถานะปัจจุบัน
|
||||
เป็น "รอตรวจสอบ" เท่านั้น
|
||||
|
||||
**`income-budget-plan-01`** (เพิ่มเข้าไปในหน้าเดิม):
|
||||
- Badge สถานะเดียวกัน ("รอตรวจสอบ" / "ถูกส่งแก้ไข" + remark ล่าสุด / "ตรวจสอบแล้ว") ต่อเรื่องที่ค้นหาอยู่
|
||||
- ปุ่ม "ส่งตรวจสอบใหม่" — โชว์เฉพาะตอนสถานะเป็น "ถูกส่งแก้ไข" เท่านั้น
|
||||
**`income-budget-plan-01`** (เพิ่มเข้าไปในหน้าเดิม — ฝั่งคณะดูอย่างเดียว ไม่มีปุ่มใหม่):
|
||||
- Badge สถานะเดียวกัน ("รอตรวจสอบ" / "ถูกส่งแก้ไข" + remark ล่าสุด / "ตรวจสอบแล้ว") ต่อเรื่องที่ค้นหาอยู่ —
|
||||
ตอน "ถูกส่งแก้ไข" ฝั่งคณะแก้ไข/ส่งอนุมัติใหม่ผ่านหน้า income-report ตามกลไกเดิมที่มีอยู่แล้ว ไม่ต้องมี
|
||||
ปุ่มใหม่ที่หน้านี้
|
||||
|
||||
## 7. นอกขอบเขตรอบนี้ (YAGNI)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user