From a5de73a39ca90fc91da74c3a2a2299f72c13c624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nut=2E=E0=B9=84=E0=B8=9B=E0=B9=80=E0=B8=A3=E0=B8=B7?= =?UTF-8?q?=E0=B9=88=E0=B8=AD=E0=B8=A2?= Date: Wed, 22 Jul 2026 18:22:32 +0700 Subject: [PATCH] docs: add design spec for expense-project-research edit-history view Ports the already-built change-history diff/session UI (backend + shared Angular logic) from change-project-research-form to expense-project-research, showing project detail fields (name, plan, output, etc.) instead of a/p metrics per the requested screenshot layout. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01613NaY9LAvy2wFXEeyE5eR --- ...expense-project-research-history-design.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-22-expense-project-research-history-design.md diff --git a/docs/superpowers/specs/2026-07-22-expense-project-research-history-design.md b/docs/superpowers/specs/2026-07-22-expense-project-research-history-design.md new file mode 100644 index 0000000..d6f876b --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-expense-project-research-history-design.md @@ -0,0 +1,89 @@ +# ประวัติการแก้ไขโครงการวิจัย — หน้า expense-project-research + +## บริบท + +เมนู "เปลี่ยนแปลงโครงการ(วิจัย)" (`change-project-research-form/list-all`, `typeUrl: 12`) ให้แก้ไขข้อมูลโครงการวิจัย กดปุ่ม "ส่งข้อมูล" (`sendResearchDataListAll()`) แล้วรายการจะไปโผล่ที่เมนู "หน่วยงานทำรายงานผล(วิจัย)" — route `expense-project-research` (`typeUrl: 26`, `status_id=2`) แสดงด้วย `List14ResearchComponent` + +ฟีเจอร์ "ดูประวัติการแก้ไข" มีอยู่แล้วสมบูรณ์ทั้ง backend และ frontend — แต่ใช้งานได้เฉพาะที่หน้า `change-project-research-form/list-all` (typeUrl 12) เท่านั้น ผ่าน component ใหญ่ตัวเดียวที่ใช้ร่วมกันหลายเมนู `make-year-plant-form.component.ts`: + +- Backend: `ChangeProjectResearchService.UpdateEntity()` (`rmutr-api/Modules/RequestBudgets/Services/ChangeProjectResearch.cs:25-89`) ทุกครั้งที่บันทึก จะ diff field ที่เปลี่ยนผ่าน `DiffDetail()` (บรรทัด 119-181) เขียนลงตาราง `t_change_project_research_history` ถ้ายังไม่เคยมี history ของ detail นี้มาก่อน จะสร้าง snapshot ต้นฉบับก่อนด้วย `BuildOriginalSnapshot()` (บรรทัด 186+, ใส่ `change_remark = "ต้นฉบับ"`) — endpoint อ่าน: `GET api/request_budget/change_project_research_detail/{uid}/history` +- **`DiffDetail()`/`BuildOriginalSnapshot()` diff ทั้งฟิลด์รายละเอียดโครงการ** (`project_name_th`, `budget_project_name_th`, `budget_plan_name_th`, `budget_topic_name_th`, `budget_strategy_name_th`, `responsible_faculty_name_th`, `budget_come_from`, `budget_location_name_th`) **และ** ฟิลด์ตัวชี้วัด `a_1`-`a_13`/`p_1`-`p_13` — แต่หน้าจอเดิม (typeUrl 12) เอามาแสดงแค่กลุ่ม a/p เท่านั้น (ดูตารางในแถวขยายที่ `make-year-plan-form.component.html:1406-1487`) ฟิลด์รายละเอียดโครงการที่ diff ไว้ **ไม่เคยถูกแสดงที่ไหนเลย** +- Frontend logic ที่มีอยู่แล้วและนำมาใช้ซ้ำได้ตรงๆ ใน `make-year-plant-form.component.ts`: `buildHistorySessions()` (บรรทัด 791-805, group ประวัติเป็น session ตาม timestamp แล้วเรียง `[...edits, ...originals]` — ต้นฉบับอยู่ท้ายเสมอ), `countEditSessions()` (บรรทัด 670-678), `toggleResearchHistory()`/`getResearchEditCount()`/`isResearchHistoryLoading()`/`getResearchHistorySessions()` (บรรทัด 2694-2726) + +หน้า `expense-project-research` (typeUrl 26) แสดงด้วยคนละ component (`List14ResearchComponent`, `.../list14-research/list14-research.component.ts`) ซึ่ง**ไม่มี history เลย** — ไม่ fetch, ไม่มี state, ไม่มี UI (ยืนยันด้วย grep ไม่พบคำว่า "history"/"เปลี่ยนแปลง" ในไฟล์นี้) + +เป้าหมายงานนี้: พอร์ตความสามารถ "ดูประวัติการแก้ไข" มาที่หน้า `expense-project-research` โดยแสดง**ฟิลด์รายละเอียดโครงการ**แทนกลุ่ม a/p (ตามภาพตัวอย่างที่ผู้ใช้ให้ไว้ — คอลัมน์ วันเวลาที่แก้ไข, แผนงาน, ชื่อโครงการ, ผลผลิต, ประเด็นยุทธศาสตร์, ลักษณะโครงการ ฯลฯ) ไม่ต้องแก้ backend เลยเพราะข้อมูลถูก diff ไว้ครบอยู่แล้ว + +## ขอบเขต + +- แก้เฉพาะฝั่ง frontend (`rmutr-web`), เฉพาะ `List14ResearchComponent` + extract util ใหม่จาก `make-year-plant-form.component.ts` +- ไม่แตะ backend/endpoint ใดๆ (ข้อมูลพร้อมใช้อยู่แล้ว) +- ไม่แตะพฤติกรรมเดิมของหน้า `change-project-research-form/list-all` (typeUrl 12) — ยกเว้น refactor ดึง `buildHistorySessions()`/`countEditSessions()` ออกเป็น shared util (พฤติกรรมต้องเหมือนเดิมทุกประการ ไม่ใช่ redesign) +- ไม่รวมฟิลด์ตัวชี้วัด a/p ในตารางประวัติของหน้านี้ (ต่างจากหน้าเดิม) — ตามที่ผู้ใช้ยืนยันจากภาพตัวอย่าง + +## การเปลี่ยนแปลงที่ต้องทำ + +### 1. Extract shared util — ไฟล์ใหม่ `src/app/core/utils/change-history-session.util.ts` + +- ย้าย `buildHistorySessions()` (ปัจจุบัน private method ที่ `make-year-plant-form.component.ts:791-805`) ออกมาเป็น exported pure function ชื่อเดิม รับ `records: any[]` คืน `Array<{datetime: string, isOriginal: boolean, vals: Record}>` — คัดลอก logic มาทั้งหมดโดยไม่แก้ไข (group ด้วย `created_datetime.substring(0,19)`, แถว `change_remark === 'ต้นฉบับ'` แยกกลุ่มเป็น `__original__`, คืนค่า `[...edits, ...originals]`) +- ย้าย `countEditSessions()` (บรรทัด 670-678) ออกมาเป็น exported pure function ชื่อเดิม เช่นกัน +- แก้ `make-year-plant-form.component.ts`: import 2 ฟังก์ชันนี้จาก util ใหม่ ลบ private method เดิมทิ้ง แล้วแก้จุดเรียกทั้งหมด (`this.buildHistorySessions(...)` → `buildHistorySessions(...)`, `this.countEditSessions(...)` → `countEditSessions(...)`) — จุดเรียกใช้งานจริงรวม 8 จุด: `buildHistorySessions` ที่บรรทัด 749, 832, 2712, 2758 และ `countEditSessions` ที่บรรทัด 267, 746, 2660, 2755 พฤติกรรมต้องเหมือนเดิม 100% + +### 2. `List14ResearchComponent` (`list14-research.component.ts`) — เพิ่ม history state + fetch + +- inject `ChangeProjectResearchDetailService` (มีอยู่แล้ว `getHistory(uid)` ที่ `change-project-research-detail.service.ts:14`) และ `ChangeDetectorRef` (component ใช้ `ChangeDetectionStrategy.OnPush` อยู่แล้วที่บรรทัด 9 — ต้องเรียก `detectChanges()` เองทุกจุดที่ callback แบบ async อัปเดต state) +- เพิ่ม properties: `editCountMap = new Map()`, `historySessionsMap = new Map>()`, `historyLoadingUids = new Set()`, `expandedHistoryUid: string | null = null` +- ใน `ngOnChanges` หลัง set `this.details = [...this.dataSource]` (บรรทัด 26-27): ถ้ามีข้อมูล, `forkJoin` เรียก `getHistory(d.change_project_research_detail_uid)` ทุกแถว (`catchError(() => of([]))` ต่อรายการ) แล้วคำนวณ `editCountMap` ด้วย `countEditSessions()` จาก util ใหม่ — ยังไม่ build session เต็ม ตอนนี้ (lazy โหลดตอน toggle เพื่อลด payload ตอนโหลดหน้าแรก เหมือน pattern เดิมที่ `toggleResearchHistory()` ทำ) +- เพิ่มเมธอด มิเรอร์ `toggleResearchHistory`/`isResearchHistoryLoading`/`getResearchEditCount`/`getResearchHistorySessions` จาก `make-year-plant-form.component.ts:2694-2726` ทุกตัวอักษร (เปลี่ยนแค่ชื่อ map/property ให้ตรงกับของ component นี้, เรียก util ที่ extract แทน `this.` method เดิม): + - `toggleHistory(uid: string)` + - `isHistoryLoading(uid: string): boolean` + - `getEditCount(uid: string): number` + - `getHistorySessions(uid: string)` + +### 3. Field set ของตารางประวัติ — ไม่ใช้ a/p + +Mapping คงที่ (field_key ตรงกับที่ backend เขียนจริงใน `DiffDetail()`/`BuildOriginalSnapshot()`): + +| field_key | หัวคอลัมน์ | +|---|---| +| `budget_plan_name_th` | แผนงาน | +| `project_name_th` | ชื่อโครงการ | +| `budget_project_name_th` | ผลผลิต | +| `budget_strategy_name_th` | ประเด็นยุทธศาสตร์ | +| `budget_topic_name_th` | ลักษณะโครงการ | +| `responsible_faculty_name_th` | หน่วยงานที่รับผิดชอบ | +| `budget_come_from` | แหล่งที่มาของเงิน | + +ลำดับคอลัมน์อ้างอิงหัวตารางหลักของ `list14-research.component.html:29-37` ให้ตรงกัน (ต่อจากคอลัมน์ "วันเวลาที่แก้ไข" ที่นำหน้าเสมอ) + +ไม่รวม `budget_location_name_th` แม้ backend diff ไว้ — เพราะไม่มีคอลัมน์นี้แสดงในตารางหลักของหน้านี้อยู่แล้ว (นอกสโคป ถ้าต้องการภายหลังแจ้งเพิ่มได้) และไม่รวม `a_1`-`a_13`/`p_1`-`p_13` ตามที่ตกลงกันไว้ + +### 4. Template `list14-research.component.html` — ไอคอน + badge + แถวขยาย + +- คอลัมน์ "ลำดับ" (บรรทัด 81, ปัจจุบันแสดงแค่ `{{i+1}}` เฉยๆ): เปลี่ยนเป็นโครงสร้างเดียวกับ seq-cell ที่ `make-year-plan-form.component.html:1268-1288` แต่**ไม่มีปุ่มบันทึก** (หน้านี้ read-only ไม่มีฟอร์มแก้ไข) — แสดงเลขลำดับ + ปุ่มไอคอน history (`*ngIf="getEditCount(detail.change_project_research_detail_uid) > 0"`, สลับ `history`/`expand_less` ตามสถานะเปิด, `title="ดูประวัติการแก้ไข"`) + badge `เปลี่ยนแปลง #{{getEditCount(...)}}` +- หลังแถว `tr_a` (บรรทัด 116-131) เพิ่มแถวขยาย `` (24 = จำนวนคอลัมน์ทั้งหมดตาม `` บรรทัด 3-27) — โครงสร้างเดียวกับ `make-year-plan-form.component.html:1406-1487`: + - หัวข้อ "ประวัติการแก้ไข", loading spinner ระหว่าง `isHistoryLoading(uid)` + - ตาราง 1 session = 1 แถว (ต่างจากต้นฉบับที่แยก P/A เป็น 2 แถวย่อย เพราะ field ชุดใหม่นี้เป็นค่าเดียวต่อ detail ไม่ผูกกับ P-row) หัวตาราง: วันเวลาที่แก้ไข + 7 คอลัมน์ตามข้อ 3 + - แถว "ต้นฉบับ" ใช้ badge สีส้มเดิม (`background:#ff8f00`) แทนวันที่ — อยู่แถวสุดท้ายเสมอ (การันตีจาก `buildHistorySessions()`) + - ไฮไลต์เซลล์ `background:#fff8e1` เมื่อ `!session.isOriginal && session.vals[''] != null` (กติกาเดิมเป๊ะ — ถ้า field ไม่ได้ถูกแก้ใน session นั้น `field_key` จะไม่อยู่ใน `vals` เลย เพราะ `DiffDetail()` สร้างแถว history เฉพาะ field ที่ค่าเปลี่ยนจริงเท่านั้น) + - แถว empty state `ไม่มีประวัติการแก้ไข` เมื่อ `getHistorySessions(uid).length === 0` + +## Error handling + +- `getHistory()` ล้มเหลว (ทั้งตอนโหลดหน้าแรกและตอน toggle): `catchError(() => of([]))` เหมือนทุกจุดในโปรเจกต์ — แถวนั้นจะไม่มี badge/ไอคอน (เหมือนไม่มีประวัติ) ไม่มี error popup รบกวน (หน้านี้เป็น read-only report ไม่ใช่ฟอร์มที่ต้อง block การทำงาน) +- ไม่มีแถวไหนมีประวัติเลยทั้งหน้า: ไม่แสดงไอคอน/badge (ปกติ ไม่ต้องมี state พิเศษ) + +## Known limitation (สืบทอดจากของเดิม ไม่ใช่สิ่งที่งานนี้สร้างขึ้น) + +- `budget_location_name_th` ถูก diff ไว้ใน backend แต่ยังไม่มีหน้าไหนแสดงเลย (ทั้งของเดิมและงานนี้) — คงสถานะเดิม +- field p_1-p_13 ของหลายแถว P ในรายละเอียดเดียวกัน backend ใช้ field_key ซ้ำกัน ("p_1" ทุกแถว) ทำให้ session ประวัติที่มี P มากกว่า 1 แถวจะเห็นค่าทับกันถ้าดูผ่านตาราง a/p เดิม — ไม่กระทบงานนี้เพราะไม่แสดง a/p ในตารางประวัติของหน้านี้ + +## Testing (manual/browser) + +1. เข้าเมนู "เปลี่ยนแปลงโครงการ(วิจัย)" (`change-project-research-form/list-all`) แก้ไขโครงการวิจัยที่มีสถานะรอส่ง (เปลี่ยน "ชื่อโครงการ" และ "แผนงาน") กด "ส่งข้อมูล" +2. เข้าเมนู `expense-project-research` — ยืนยันแถวโครงการที่เพิ่งแก้ไขมีไอคอน history + badge "เปลี่ยนแปลง #1" ในคอลัมน์ลำดับ +3. กดไอคอน — ยืนยันแถวขยายออกมาแสดง 2 แถว (session ที่เพิ่งแก้ + ต้นฉบับ) ต้นฉบับอยู่ล่างสุดเสมอ คอลัมน์ "ชื่อโครงการ"/"แผนงาน" ไฮไลต์สีเหลืองเฉพาะแถว session ที่แก้จริง ไม่ไฮไลต์ในแถวต้นฉบับ คอลัมน์อื่นที่ไม่ได้แก้ (เช่น ผลผลิต) ว่างเปล่าไม่ไฮไลต์ +4. แก้ไขโครงการเดิมซ้ำอีกครั้งที่เมนู `change-project-research-form/list-all` (คนละ field เช่น "ผลผลิต") — กลับมาที่ `expense-project-research` ยืนยัน badge เปลี่ยนเป็น "#2" และแถวขยายมี 3 แถว (2 session + ต้นฉบับ) เรียงใหม่→เก่า ต้นฉบับยังคงอยู่ล่างสุด +5. โครงการที่ไม่เคยถูกแก้ไขเลย — ยืนยันไม่มีไอคอน/badge ในคอลัมน์ลำดับ +6. เปิดหน้า `change-project-research-form/list-all` (typeUrl 12) อีกครั้งหลัง refactor util — ยืนยัน badge/แถวขยาย/ไฮไลต์ของกลุ่ม a/p ยังทำงานเหมือนเดิมทุกจุด (regression check การ extract util) +7. throttle network แล้วกดไอคอน history ที่หน้า `expense-project-research` — เห็น spinner "กำลังโหลด..." ก่อนแถวข้อมูลมา