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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01613NaY9LAvy2wFXEeyE5eR
This commit is contained in:
@@ -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<string,string>}>` — คัดลอก 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<string, number>()`, `historySessionsMap = new Map<string, Array<{datetime,isOriginal,vals}>>()`, `historyLoadingUids = new Set<string>()`, `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) เพิ่มแถวขยาย `<tr *ngIf="expandedHistoryUid === detail.change_project_research_detail_uid"><td colspan="24" ...>` (24 = จำนวนคอลัมน์ทั้งหมดตาม `<colgroup>` บรรทัด 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['<field_key>'] != 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 "กำลังโหลด..." ก่อนแถวข้อมูลมา
|
||||||
Reference in New Issue
Block a user