eCourt-Doc-Edit/docs/plans/version-message.md
Suphonchai Phoonsawat 7b20f12ede Phase 9: version message (FR-13)
- ใส่คำอธิบายแบบ commit message ได้ทุก flow ที่สร้าง version (Save & Version / Start Versioning / generate จากเว็บ) + แสดงใน version history ทุกหน้า
- backend: DocumentVersion.Message (varchar 2048) + migration AddVersionMessage + BadRequest ตรวจความยาวก่อนแตะ MinIO (กัน 500 + orphan object)
- add-in: ConfirmVersionDialog เพิ่ม textarea (Start Versioning ใช้ dialog ด้วย) + แสดง message ใน VersionHistoryPanel
- web: ฟอร์มสร้างเอกสาร + หน้า detail แสดง message (desktop truncate + tooltip, mobile เต็มบรรทัด)
- sync เอกสารทั้งหมด: spec v5 (FR-13/AC-16) + plan Phase 9 + acceptance-review 1–16 + README + CLAUDE.md + web/README
- tests: backend 52 / addin 50 / web 36 — เขียวทั้งชุด

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 22:05:49 +07:00

12 KiB
Raw Permalink Blame History

Version Message (commit message) — FR-13 / AC-16 / Spec v5

Context

ผู้ใช้ต้องการให้ใส่ "ข้อความอธิบาย" ตอนสร้าง version ใหม่ได้ เหมือน git commit message — เก็บกับแต่ละ version และแสดงใน version history ทุกหน้า ขอบเขต (ยืนยันกับผู้ใช้แล้ว): ทุก flow ที่สร้าง version = add-in Save & Version + add-in Start Versioning (เพิ่ม dialog ใหม่) + ฟอร์ม generate บน web

ข้อตกลง fixed:

  • Field ชื่อ message — nullable string, max 2048, normalize IsNullOrWhiteSpace → null + .Trim() (นโยบายเดียวกับ Template.Description เป๊ะ)
  • Optional ทุกที่ ไม่บังคับกรอก
  • Non-goal: ไม่ฝังลง docx (custom properties / Custom XML part) — backend คือ source of truth (กติกา #5) และเลี่ยง parse-compat กับไฟล์เก่า (XmlVersionEntry ไม่แตะ)
  • ตามกติกา repo: แก้ spec ก่อน แล้ว sync plan

ขั้นที่ 0 — บันทึกแผน + แก้เอกสาร requirement ก่อน

  1. บันทึกแผนนี้ที่ docs/plans/version-message.md (ธรรมเนียมเดิม)
  2. specs/spec-officejs.md: เพิ่ม changelog v5 บรรทัดบนสุด · ### FR-13: คำอธิบายต่อ version — commit message (เพิ่มเติม v5) ต่อท้าย section 6 (หลัง FR-12) ตาม format เดิม — ระบุ field message + ทั้ง 2 endpoint + นโยบาย null/trim + แสดงผลทุก history + Start Versioning ได้ dialog · section 7 DocumentVersion POCO เพิ่ม public string? Message { get; set; } · section 10 เพิ่ม AC-16 (v5) · section 11 เพิ่ม task items (v5) ทั้ง 3 list
  3. docs/plans/officejs-implementation-plan.md: เพิ่ม ## Phase 9: Version message — FR-13 (เพิ่มทีหลัง, v5) + checklist + แถวตาราง mapping | 9 (v5) | FR-13 | AC-16 |
  4. docs/acceptance-review.md: 1–15 → 1–16 + section AC-16 (🔶 ส่วน dialog ใน Word ต้องยืนยันมือ)

ขั้นที่ 1 — Backend (backend/WordVersioning.Api)

  1. Models/DocumentVersion.cs: public string? Message { get; set; } (+ doc comment FR-13)
  2. Data/AppDbContext.cs: ใน block DocumentVersion เพิ่ม entity.Property(x => x.Message).HasMaxLength(2048); (มิเรอร์ Template.Description)
  3. Migration: dotnet ef migrations add AddVersionMessage → expect AddColumn<string>("Message", "DocumentVersions", nullable, maxLength 2048) (tests รัน MigrateAsync อยู่แล้ว = migration ถูก exercise)
  4. Models/VersionDtos.cs: UploadVersionRequest.Message (string? หลัง Fields) · GenerateDocumentRequest.Message (string? หลัง Values) · VersionListItem เพิ่ม string? Message ท้าย record
  5. Controllers/DocumentsController.cs:
    • เพิ่ม MaxVersionMessageLength = 2048 + NormalizeMessage() helper (static private — มี pattern ParseSnapshot/IsUniqueViolation อยู่แล้ว)
    • ตัดสินใจ: ใส่ BadRequest check ความยาว (ต่างจาก Template.Description ที่ไม่มี) เพราะ: ถ้าไม่เช็ค = 500 จาก DB และใน Generate จะเกิด 500 หลัง MinIO upload แล้ว = orphan object ใน bucket โดยเปล่า + UploadVersion เป็น action ที่ validate เองด้วยข้อความไทยอยู่แล้ว
    • UploadVersion: normalize + check หลัง validation ของ fields → Message = message ใน new DocumentVersion ใน retry loop (ทุกรอบได้ค่าเดียวกัน)
    • Generate: normalize + check ก่อน ทำ MinIO/template IO → Message = message ใน v1 initializer
    • GetVersions: เพิ่ม v.Message ใน projection + , r.Message ตอน construct VersionListItem
    • Rollback path ไม่ต้องแตะเพิ่ม (MinIO fail → Remove ทั้ง row อยู่แล้ว / Generate detach ก่อน SaveChanges)

ขั้นที่ 2 — Add-in (addin) — Fluent UI v9

  1. ConfirmVersionDialog.tsx — generalize ด้วย optional props (ไม่สร้าง component ใหม่): title?, description?, confirmLabel? + onConfirm: (message: string) => void; เพิ่ม Field+Textarea (label "คำอธิบาย version (ไม่บังคับ)", placeholder "เช่น แก้ไขข้อ 3 สัญญาจ้าง", maxLength 2048, rows 3, resize vertical, disabled={busy}); local state message + useEffect เคลียร์เมื่อ !open (FC ถูก mount ค้างไว้ — ห้ามพึ่ง remount)
  2. versionFlow.ts — optional positional param วางก่อน callback (callback-last): uploadCurrentFile(documentId, clientVersionId, fields, message?) · startVersioning(message?, onProgress?) · saveAndVersion(documentId, latestVersionNumber, message?, onProgress?) — test เดิม 5 ตัวเรียก 2 args / 0 args คอมไพล์ผ่านไม่ต้องแก้
  3. versioningService.ts uploadVersion — เพิ่ม message? ท้าย signature + const trimmed = message?.trim(); if (trimmed) form.append("message", trimmed) (มิเรอร์ guard ของ fields)
  4. versionRecord.ts — VersionRecord เพิ่ม message: string | null
  5. VersionHistoryPanel.tsx — แสดง version.message ใต้บรรทัด "โดย …" (แบบเต็ม, pre-wrap, muted เล็ก) — เพิ่ม style messageRow
  6. App.tsx — handleSaveAndVersion(message) / handleStartVersioning(message) ส่งต่อ; state startConfirmOpen; ปุ่ม Start Versioning เปลี่ยนเป็นเปิด dialog; render ConfirmVersionDialog ตัวที่สอง (title "เริ่มติดตามเอกสารนี้?", confirmLabel "เริ่มติดตาม (version 1)")

ขั้นที่ 3 — Web (web)

  1. documents/create/[templateId].vue — const message = ref(''); textarea ทางเลือก (label/input class, rows 2, maxlength 2048) หลัง field loop; payload เพิ่ม message: message.value.trim() || null; resetForm เคลียร์; re-generate ไม่ prefill message (เอกสารใหม่ได้ข้อความใหม่ — ใส่ comment กำกับ)
  2. types/api.ts — VersionListItem เพิ่ม message: string | null
  3. documents/[documentId].vue — ไม่เพิ่มคอลัมน์ใหม่ (colspan="4" ใน expanded row + คอลัมน์ action จะบีบ): desktop แสดงใต้ v{N} เป็น truncate text-xs text-muted + :title tooltip; mobile card แสดงเต็ม break-words

ขั้นที่ 4 — Tests

Backend — ไฟล์ใหม่ VersionMessageTests.cs (ตาม pattern UploadVersionTests — VersionForm, fixture.CreateDb(), AsNoTracking):

  1. Upload_WithMessage_PersistsRow_AndEchoesInHistory
  2. [Theory] Upload_BlankOrMissingMessage_StoredAsNull (null / "" / " ")
  3. Upload_TrimsSurroundingWhitespace
  4. Upload_OversizeMessage_Returns400_NoSideEffect (2049 ตัว, ไม่มี row หลงเหลือ)
  5. Upload_MessageExactly2048_Succeeds (boundary)
  6. SecondUpload_KeepsPerVersionMessages (v1=A, v2=B → history [B,A])
  7. Generate_WithMessage_CreatesV1WithMessage
  8. Generate_OversizeMessage_Returns400 (เช็คว่า 400 เกิดก่อน MinIO)

แก้ helper: TestDtos.cs — VersionItem เพิ่ม string? Message, VersionForm(..., string? message = null); TestFlow.GenerateDocumentAsync(..., string? message = null)

Add-in — versionFlow.test.ts: เพิ่ม 3 test — form.get("message") หลัง saveAndVersion(id, 1, "…"), whitespace → null, startVersioning("…") แนบ v1 (test เดิมไม่ต้องแก้)

Web: type-only — ไม่มี component test ตาม convention (เช็คด้วย build)

Verification

# ลำดับ: docs → backend → add-in → web
cd backend/WordVersioning.Api && dotnet ef migrations add AddVersionMessage
cd backend && dotnet test --filter VersionMessageTests   # ต้องมี Docker (Testcontainers)
cd backend && dotnet test                                 # ทั้งชุด
cd addin && npm run build && npm test
cd web && npm run build && npm test

Manual E2E (หลัง docker compose up -d + backend + npm run dev/npm start):

  1. Web generate พร้อม message → หน้า detail แสดงใต้ v1 (desktop + mobile card); ไม่กรอก → ไม่แสดง
  2. Re-generate จากเอกสารที่มี message → textarea ว่าง
  3. Add-in: untracked → Start Versioning เปิด dialog → พิมพ์ message → v1 + panel แสดง
  4. Add-in: Save & Version พร้อม message → history refresh แสดง
  5. เปิด dialog → พิมพ์ → ยกเลิก → เปิดใหม่ → textarea ต้องว่าง
  6. Word sideload caveat: ห้าม cp ทับ manifest ใน wef (symlink), kill dev server เก่าก่อน npm start

Risks / gotchas

  • Param เพิ่มท้าย + callback-last = test เดิมคอมไพล์ผ่าน (verify แล้วว่าเรียก 2/0 args)
  • เคลียร์ dialog state ด้วย useEffect ตาม open — อย่าพึ่ง unmount
  • Postgres varchar นับเป็น characters (ไทย 3 bytes/char ไม่มีปัญหา); JS maxLength กับ C# .Length นับ UTF-16 code units เหมือนกัน
  • GenerateDocumentRequest.Message เป็น plain string — JSON ส่ง type ผิดได้ 400 อัตโนมัติจาก [ApiController] (body อังกฤษ — ยอมรับได้)
  • AC-16 ส่วน dialog/textarea ใน Word webview ต้องยืนยันมือ (🔶 ใน acceptance-review)
  • อย่าลืมอัปเดตตัวเลข test count ใน docs (47/42/36) หลังรันจริง

สถานะ: ✅ implement เสร็จ (2026-08-20)

ผลรันจริงทั้งชุด: backend dotnet test 52/52 · addin npm test 50/50 + build + lint + manifest validate ผ่าน · web npm test 36/36 + build ผ่าน — รายละเอียด checkbox Phase 9 + หมายเหตุจากการทำจริงอยู่ที่ officejs-implementation-plan.md · ผล AC-16 ที่ acceptance-review.md · เหลือยืนยันด้วยมือเฉพาะ dialog/textarea ใน Word จริง