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
17 KiB
ประวัติการแก้ไขโครงการวิจัย — หน้า 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หลัง setthis.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): booleangetEditCount(uid: string): numbergetHistorySessions(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
- หัวข้อ "ประวัติการแก้ไข", loading spinner ระหว่าง
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)
- เข้าเมนู "เปลี่ยนแปลงโครงการ(วิจัย)" (
change-project-research-form/list-all) แก้ไขโครงการวิจัยที่มีสถานะรอส่ง (เปลี่ยน "ชื่อโครงการ" และ "แผนงาน") กด "ส่งข้อมูล" - เข้าเมนู
expense-project-research— ยืนยันแถวโครงการที่เพิ่งแก้ไขมีไอคอน history + badge "เปลี่ยนแปลง #1" ในคอลัมน์ลำดับ - กดไอคอน — ยืนยันแถวขยายออกมาแสดง 2 แถว (session ที่เพิ่งแก้ + ต้นฉบับ) ต้นฉบับอยู่ล่างสุดเสมอ คอลัมน์ "ชื่อโครงการ"/"แผนงาน" ไฮไลต์สีเหลืองเฉพาะแถว session ที่แก้จริง ไม่ไฮไลต์ในแถวต้นฉบับ คอลัมน์อื่นที่ไม่ได้แก้ (เช่น ผลผลิต) ว่างเปล่าไม่ไฮไลต์
- แก้ไขโครงการเดิมซ้ำอีกครั้งที่เมนู
change-project-research-form/list-all(คนละ field เช่น "ผลผลิต") — กลับมาที่expense-project-researchยืนยัน badge เปลี่ยนเป็น "#2" และแถวขยายมี 3 แถว (2 session + ต้นฉบับ) เรียงใหม่→เก่า ต้นฉบับยังคงอยู่ล่างสุด - โครงการที่ไม่เคยถูกแก้ไขเลย — ยืนยันไม่มีไอคอน/badge ในคอลัมน์ลำดับ
- เปิดหน้า
change-project-research-form/list-all(typeUrl 12) อีกครั้งหลัง refactor util — ยืนยัน badge/แถวขยาย/ไฮไลต์ของกลุ่ม a/p ยังทำงานเหมือนเดิมทุกจุด (regression check การ extract util) - throttle network แล้วกดไอคอน history ที่หน้า
expense-project-research— เห็น spinner "กำลังโหลด..." ก่อนแถวข้อมูลมา