Phase 12: draft generation (FR-16)

ไฟล์ร่าง (draft) — generate docx จาก template แบบ stateless ไม่สร้าง
Document/version; stamp เฉพาะ custom property TemplateId เพื่อให้ add-in
เห็นเป็น untracked; Start Versioning ส่ง form field templateId ให้ backend
ผูก template ตอน auto-create — เอกสารเริ่มที่ v1 (ไม่มี v0) + field sync เต็มระบบ

- Backend: POST /api/templates/{id}/generate-draft (200 คืนไฟล์) +
  DocxGenerationService.GenerateDraft + แยก TemplateFieldValidation ใช้ร่วมกับ
  generate + UploadVersion รับ templateId (non-GUID/unknown → 400,
  deactivated → ผูกได้, document เดิม → เฉย) + CORS expose Content-Disposition
- Add-in: readTrackedProperties อ่าน TemplateId + ส่ง templateId เฉพาะ
  startVersioning + hint ไฟล์ร่างใน task pane
- Web: ปุ่ม "สร้างไฟล์ร่าง" บนฟอร์มเดิม + downloadFile รับ init (POST → blob)
- Docs: spec v8 (FR-16, AC-19) + Phase 12 + CONTEXT.md + ADR 0002 +
  acceptance-review AC-19
- Tests: backend 82 / add-in 66 / web 59 + build ครบ; QA reviewer PASS
  (2 rounds); ค้าง manual บน Word จริง

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Suphonchai Phoonsawat 2026-09-10 10:51:13 +07:00
parent 0edb70fd38
commit b3c1c667f7
30 changed files with 879 additions and 93 deletions

View file

@ -1,5 +1,6 @@
# Spec: Word Versioning Add-in (Office.js Cross-Platform + .NET Backend)
> ฉบับแก้ไข v8 (2026-09-10): เพิ่ม FR-16 — ไฟล์ร่าง (draft): สร้าง .docx จาก template แบบ stateless (ไม่สร้าง Document/version, stamp property `TemplateId` ตัวเดียว) + ผูก template ตอน Start Versioning ผ่าน form field `templateId` ทางเลือก — ไฟล์ร่างเริ่ม versioning ที่ v1 โดยไม่มี v0
> ฉบับแก้ไข v7 (2026-09-08): FR-15 — PDF จาก Word แบบ best-effort; generate เป็น v0, add-in เริ่ม v1; เว็บเปิด Word/PDF พร้อมดาวน์โหลดสำรอง
> ฉบับแก้ไข v6 (2026-08-22): เพิ่ม FR-14 — editor ต่อ field (รูปแบบช่องกรอกในฟอร์มสร้างเอกสาร): `text` เลือก `textbox`/`textarea` ได้, `number`/`date` ใช้ control ตาม type — presentation metadata เท่านั้น ค่า field ยังเป็น string ทั้ง flow
> ฉบับแก้ไข v5 (2026-08-20): เพิ่ม FR-13 — คำอธิบายต่อ version ("commit message") พิมพ์ได้ตอนสร้าง version ทุก flow (Save & Version, Start Versioning, สร้างจากเว็บ) + แสดงใน version history ทุกหน้า
@ -292,6 +293,14 @@ POST /api/documents/generate { templateId, values: { <fieldKey>: <value> } }
- เว็บ history ทั้ง desktop/mobile เปิด docx ผ่าน `ms-word:ofe|u|<presigned-url>` (ไม่ encode URL ทั้งก้อนซ้ำตาม Microsoft Office URI Schemes); PDF แท็บใหม่; มีลิงก์ดาวน์โหลดสำรองและ heuristic blur/visibilitychange ~1.5s ซึ่งไม่ใช่หลักฐานว่า Word เปิดสำเร็จ
- หน้า generate success เปิด v0 ใน Word พร้อมลิงก์ดาวน์โหลดสำรอง; PDF fidelity และ tracked changes ต้องยืนยันบน Word จริง
### FR-16: ไฟล์ร่าง (draft) — สร้าง .docx จาก template โดยไม่สร้าง Document (เพิ่มเติม v8)
- **`POST /api/templates/{templateId}/generate-draft`** body `{ values }` — **stateless**: ไม่สร้าง `Document`/`DocumentVersion`/`DocumentFieldValue` และไม่เขียน MinIO; คืนไฟล์ .docx ทันที (200 + attachment, ชื่อ `ชื่อtemplate_yyyyMMdd-HHmmss.docx` — ไม่ใช่ 201 เพราะไม่มีอะไรถูกสร้าง)
- validate เหมือน FR-10 (required + type + format; ส่งเกินนิยาม ignore) · template ไม่พบ → 404, deactivate → 400 ตามนโยบาย FR-10 · ไม่มี `message` (ไม่มี version จึงไม่มี commit message)
- stamp custom property **`TemplateId` = GUID ของ template ตัวเดียว** — ไม่ stamp `SavedByAddIn`/`DocumentTrackingId` ไฟล์ร่างจึงเป็น untracked ในสายตา add-in (เลี่ยงสถานะ "tracked แต่ history 404")
- **ผูก template ตอน Start Versioning:** `POST /api/documents/{documentId}/versions` รับ `templateId` (multipart form field ทางเลือก) — ใช้เฉพาะตอน auto-create `Document` (field sync ทำงานเต็มระบบที่ v1); document มีอยู่แล้ว → เฉย (แต่ validate ทุกครั้งที่ส่งมา) · ไม่ใช่ GUID / ไม่พบใน DB → 400 · template ที่ deactivate → **ผูกได้** (ไฟล์ร่างถูกสร้างตอน template ยัง active — นโยบายเดียวกับ FR-9/FR-12) · upload แรกของ Document ใหม่ = **version 1** (max+1 — ไม่มี v0)
- Add-in: อ่าน property `TemplateId` ตอน untracked แล้วแนบ `templateId` **เฉพาะตอน Start Versioning** (Save & Version ไม่ส่ง) + แสดง hint ว่าไฟล์นี้เป็นไฟล์ร่างจาก template
- Web Portal: ปุ่ม "สร้างไฟล์ร่าง" ที่สองบนฟอร์มสร้างเอกสารเดิม → ดาวน์โหลดไฟล์ทันที (browser download ผ่าน Bearer — ชื่อไฟล์อ่านจาก `Content-Disposition` ที่ CORS ต้อง expose); ฟอร์มอยู่ต่อ ไม่มี documentId/หน้าผลลัพธ์/ปุ่มเปิดใน Word (ไม่มี URL ให้ `ms-word:ofe`) · ช่องคำอธิบาย version ใช้กับ flow สร้างเอกสาร v0 เท่านั้น · flow v0 (FR-10) และ re-generate (FR-12) คงเดิม
## 7. Data Model (Backend)
```csharp
@ -362,6 +371,7 @@ Custom Document Properties:
| `DocumentTrackingId` | GUID เฉพาะของเอกสาร (ผูกกับ `documentId` ฝั่ง backend) |
| `LastVersionId` | GUID ของ version ล่าสุด — คือ `clientVersionId` ที่ client generate (ต้อง stamp ลงไฟล์*ก่อน* save/upload จึงจะติดไปกับ snapshot; versionId จริงของ backend ได้มาหลังไฟล์ถูก capture ไปแล้ว — backend คือ source of truth ของ id จริง) |
| `LastVersionTimestamp` | ISO-8601 UTC timestamp |
| `TemplateId` | GUID ของ template ต้นทาง — มีเฉพาะไฟล์ร่าง (FR-16); backend stamp ตัวเดียวตอน generate-draft, add-in อ่านตอน untracked เพื่อแนบ `templateId` ตอน Start Versioning (เมื่อเริ่ม track แล้วค่านี้ยังอยู่ในไฟล์แต่ไม่ถูกใช้) |
Custom XML Part (namespace `urn:myaddin:versioning`) — เก็บ compact history เสริม (ไม่ใช่ source of truth, backend คือ source of truth):
```xml
@ -419,6 +429,7 @@ Custom XML Part (namespace `urn:myaddin:versioning`) — เก็บ compact hi
---
18. (v7, AC-18) generate ได้ v0 ไม่มี PDF → Save & Version ได้ v1 พร้อม PDF; Start Versioning ได้ v1 พร้อม PDF; PDF capture/storage ล้มยังบันทึก docx สำเร็จพร้อมคำเตือน; history แสดง PDF เฉพาะที่มี; download/proxy/presigned PDF ถูก bytes/type/ชื่อ และไม่มี → 404; เว็บเปิด Word/PDF พร้อม fallback; ตรวจ fidelity/markup บน Word จริง
19. (v8, AC-19) สร้างไฟล์ร่างจากฟอร์มเว็บ → ได้ .docx ค่าครบ ชื่อไทยถูกต้อง โดย**ไม่เกิด Document/version ใดๆ ในระบบ** → เปิดใน Word แล้ว add-in แสดงสถานะ untracked พร้อม hint ไฟล์ร่าง → กด Start Versioning → Document ถูกสร้าง ผูก template ต้นทาง + **version 1** + field values sync ครบ → templateId ไม่ใช่ GUID/ไม่พบ → 400 · template deactivate ผูกได้ · Save & Version ถัดไปได้ v2, v3… โดยไม่ส่ง templateId · flow สร้างเอกสาร v0 (FR-10) ยังทำงานได้คู่กัน
## 11. งานที่ Claude Code ควรทำ
@ -463,3 +474,8 @@ Custom XML Part (namespace `urn:myaddin:versioning`) — เก็บ compact hi
- Add-in: PDF capture + multipart + warning + history PDF
- Backend: migration PdfObjectKey + validation/storage/download + hasPdf + generate v0
- Web: เปิด Word/PDF + fallback + history v0/v1+
### เพิ่มเติม v8 (FR-16)
- Backend: `POST /api/templates/{id}/generate-draft` (stateless) + `DocxGenerationService.GenerateDraft` (stamp เฉพาะ `TemplateId`) + แยก `TemplateFieldValidation` ใช้ร่วมกับ generate + form field `templateId` ทางเลือกของ upload (ใช้ตอน auto-create) + CORS expose `Content-Disposition`
- Add-in: อ่าน `TemplateId` ใน `readTrackedProperties` + ส่ง `templateId` เฉพาะ `startVersioning` + hint ไฟล์ร่างใน task pane
- Web: ปุ่ม "สร้างไฟล์ร่าง" บนฟอร์มเดิม + `downloadFile` รับ `init` (POST + JSON → blob)