eCourt-Doc-Edit/README.md

360 lines
44 KiB
Markdown
Raw Normal View History

2026-08-19 23:06:57 +07:00
# e-Court Documents Versioning (Word Add-in)
2026-08-18 12:52:40 +07:00
ระบบจัดการ version history ของเอกสาร Word — สร้างเอกสารจาก template ผ่านเว็บ ติดตามทุกการแก้ไขผ่าน add-in ใน Word และ sync ข้อมูลสำคัญกลับฐานข้อมูลอัตโนมัติ
## คุณสมบัติหลัก
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
- 📝 **สร้างเอกสารจาก template** — กรอก form บนเว็บ → ระบบ generate .docx จาก template (แทนที่ค่าใน content controls) พร้อม stamp metadata และขึ้นเป็น **version 0 ฉบับตั้งต้น**
2026-08-18 12:52:40 +07:00
- 🗂️ **Template management** — upload ไฟล์ .docx ที่มี content controls แล้วระบบ **detect fields อัตโนมัติ** ตั้งชื่อ/ชนิด/required ต่อ field ผ่านหน้าเว็บ
- 📋 **รายการเอกสารต่อ template + detail** — ดูเอกสารทุกฉบับที่สร้างจาก template, ค่า fields ปัจจุบัน และ version history (ขยายแต่ละ version เห็นค่าตอนนั้น) บนหน้าเว็บ
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
- ⟳ **Re-generate** — สร้างเอกสารใหม่จากค่าปัจจุบันของเอกสารเดิม (แก้ไขค่าได้ก่อน) — เป็น v0 ของเอกสารใหม่ **เอกสารต้นทางและ version history ไม่ถูกแตะ**
2026-08-18 12:52:40 +07:00
- 📌 **ตรวจจับ tracked document** — เปิดไฟล์ใน Word (Desktop Win/Mac หรือ Online) แล้ว add-in รู้ทันทีว่าไฟล์นี้อยู่ในระบบ พร้อมแสดง version history
- 💾 **Save & Version** — ปุ่มใน task pane สร้าง version ใหม่ (stamp metadata + อัปโหลด snapshot ไป backend)
- 💬 **คำอธิบายต่อ version (commit message)** — พิมพ์ข้อความอธิบายการเปลี่ยนแปลงได้ทุกครั้งที่สร้าง version (Save & Version, Start Versioning, สร้างจากเว็บ) แล้วแสดงกับ version นั้นใน history ทุกหน้า — เว้นว่างได้
2026-08-18 12:52:40 +07:00
- 🔄 **Field sync** — ทุกครั้งที่สร้าง version ระบบ extract ค่าจาก content controls ของเอกสารไป update fields ใน DB ให้ sync กับเนื้อหาจริงเสมอ
- ⬇️ **ดาวน์โหลด version ย้อนหลัง** — ทุก version เก็บครบพร้อม SHA256 hash ตรวจสอบความถูกต้องได้
## สถาปัตยกรรม
| ส่วน | Stack | หน้าที่ |
|---|---|---|
| **Add-in** (`/addin`) | Office.js, TypeScript, React, Fluent UI | Task pane ใน Word — ตรวจจับ document, Save & Version, version history, field extraction |
| **Backend** (`/backend`) | ASP.NET Core 8, EF Core, PostgreSQL, MinIO, OpenXML | Version API, template system, docx generation, JWT validation |
| **Web Portal** (`/web`) | Nuxt.js, TailwindCSS | จัดการ template + สร้างเอกสารจาก form + ดูรายการเอกสาร/detail + re-generate (light/dark theme) |
2026-08-18 12:52:40 +07:00
| **Auth** | Keycloak (OIDC + PKCE) | Public clients ทั้ง add-in และเว็บ, backend validate ผ่าน JWKS |
| **Storage** | MinIO (S3-compatible) | bucket `docx-versions` (snapshot ต่อ version) + `docx-templates` (ไฟล์ template) — private ทั้งคู่ |
**Flow หลัก:**
1. Admin upload template (.docx ที่มี content controls) → ระบบ detect fields อัตโนมัติ
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
2. ผู้ใช้กรอก form บนเว็บ → ได้ไฟล์ .docx ที่เป็น version 0 แล้ว → เปิดใน Word
2026-08-18 12:52:40 +07:00
3. Add-in รู้จักเอกสารทันที → แก้ไขเนื้อหา → กด **Save & Version** → เกิด version ใหม่ + fields ใน DB sync ตามเนื้อหาล่าสุด
4. กลับมาที่เว็บ anytime — ดูรายการเอกสารของ template / detail (ค่า fields + ทุก version) / **re-generate** เป็นเอกสารใหม่จากค่าปัจจุบัน
2026-08-18 12:52:40 +07:00
## สถานะโปรเจกต์
2026-08-19 10:58:37 +07:00
| Phase | เนื้อหา | สถานะ |
|---|---|---|
| 0 | Scaffold 3 โปรเจกต์ (addin / backend / web) | ✅ เสร็จ |
| 1 | Local dev infra — docker-compose + Keycloak (realm, 3 clients + PKCE) + MinIO (buckets) | ✅ เสร็จ |
| 2 | Backend core — version API (FR-5–7) + JWT auth (FR-8) | ✅ เสร็จ |
2026-08-19 12:15:20 +07:00
| 3 | Template system + docx generation + field sync (FR-9–11) | ✅ เสร็จ |
2026-08-19 21:35:07 +07:00
| 4 | Add-in services layer — auth (PKCE + Dialog API), metadata, file/API, field extraction (FR-8, FR-11) | ✅ เสร็จ |
2026-08-19 23:06:57 +07:00
| 5 | Task pane UI — sign-in, Start Versioning, Save & Version, history (FR-1–4) | ✅ เสร็จ — ทดสอบจริงบน Word for Mac แล้ว (login/tracking/Save & Version ผ่าน, แก้ปัญหาที่เจอ 4 ประเด็น — ดู Troubleshooting) เหลือยืนยัน download + AC เต็มชุด |
| 6 | Web Portal UI (FR-9–10) | ✅ เสร็จ — login ผ่าน Keycloak, template management, dynamic form, theme toggle |
| 6.4 | รายการเอกสารต่อ template + document detail + re-generate (FR-12) | ✅ เสร็จ (เพิ่มทีหลัง v4) |
| 7 | Tests ครบชุด (backend integration + web Vitest + addin Jest) | ✅ เสร็จ — 125 tests ผ่านทั้งหมดตอนนั้น (ดูหัวข้อ Tests สำหรับตัวเลขปัจจุบัน) |
2026-08-20 13:51:42 +07:00
| 8 | ไล่ acceptance criteria เต็มชุด | ✅ เสร็จ — ผลครบ AC-1 ถึง 15 ที่ [docs/acceptance-review.md](docs/acceptance-review.md) |
| — | Web Portal redesign — design system (token layer สี/ฟอนต์/radius) + layout responsive ทุกหน้า | ✅ เสร็จ (นอก spec — แผนที่ [docs/plans/web-portal-redesign.md](docs/plans/web-portal-redesign.md)) |
| 9 | Version message — commit message ต่อ version ทุก flow (FR-13) | ✅ เสร็จ (เพิ่มทีหลัง v5 — แผนที่ [docs/plans/version-message.md](docs/plans/version-message.md)) |
| 10 | Field editor — เลือกรูปแบบช่องกรอกต่อ field (FR-14) | ✅ เสร็จ (เพิ่มทีหลัง v6 — แผนที่ [docs/plans/field-editor.md](docs/plans/field-editor.md)) |
2026-08-19 10:58:37 +07:00
**ใช้ได้แล้วตอนนี้:** ครบทั้ง 3 ส่วน — backend API ครบทุก endpoint (version + template + generate + field sync + รายการเอกสาร/detail + commit message + field editor), add-in ครบทั้ง services และ task pane UI ทดสอบจริงบน Word for Mac แล้ว, web portal ใช้งานได้ทุกหน้า (login → จัดการ template → สร้างเอกสาร → ดูรายการ/detail → re-generate) บน design system + responsive layout ใหม่ · **tests ครบชุด 156 ตัว** (backend integration 60 รันบน infra จริง + addin 50 + web 46) · ไล่ AC 1–17 ครบแล้ว — เหลือยืนยันด้วยมือเฉพาะงานที่ทำแทนไม่ได้ (Word Online/Windows, click-through บน browser จริง — ดูท้าย [acceptance-review.md](docs/acceptance-review.md))
2026-08-18 12:52:40 +07:00
## เอกสาร
- [specs/spec-officejs.md](specs/spec-officejs.md) — spec เต็มรูปแบบ: functional requirements (FR-1–14), data models, acceptance criteria (1–17)
- [docs/plans/officejs-implementation-plan.md](docs/plans/officejs-implementation-plan.md) — implementation plan แบ่ง 11 phases (0–10) พร้อม definition of done
- [docs/acceptance-review.md](docs/acceptance-review.md) — ผลตรวจ acceptance criteria ทั้ง 17 ข้อ พร้อมปัญหาที่เจอและรายการที่เหลือต้องยืนยันด้วยมือ
- [docs/plans/version-message.md](docs/plans/version-message.md) — แผน Phase 9: commit message ต่อ version (FR-13)
- [docs/plans/field-editor.md](docs/plans/field-editor.md) — แผน Phase 10: editor ต่อ field (FR-14)
- [docs/plans/web-portal-redesign.md](docs/plans/web-portal-redesign.md) — แผน redesign web portal (design system + responsive, นอก spec)
2026-08-18 12:52:40 +07:00
สำหรับผู้เริ่มพัฒนา: อ่าน spec ก่อน แล้วตามด้วย plan (เริ่มจาก Phase 0)
2026-08-19 10:58:37 +07:00
## การพัฒนา (local)
### Prerequisites
- Docker Desktop
- .NET 8 SDK + `dotnet-ef` tool (สำหรับ migration):
```bash
dotnet tool install --global dotnet-ef --version 8.0.11
```
2026-08-19 21:35:07 +07:00
- Node.js LTS (add-in และ web)
2026-08-19 23:06:57 +07:00
- **Dev cert สำหรับ localhost** (ต้องทำ**ก่อน** `docker compose up` — Keycloak mount cert จากที่นี่):
```bash
cd addin && npx office-addin-dev-certs install
```
ทำครั้งเดียวต่อเครื่อง: สร้าง CA + cert ที่ trust ไว้ใน keychain ใช้ทั้ง add-in dev server (https://localhost:3000) และ Keycloak (https://localhost:8443)
2026-08-19 10:58:37 +07:00
### 1) รัน infrastructure (PostgreSQL + MinIO + Keycloak)
2026-08-18 12:52:40 +07:00
```bash
2026-08-19 10:58:37 +07:00
cp .env.example .env # ครั้งแรกครั้งเดียว — แก้ password ได้ตามต้องการ
2026-08-18 12:52:40 +07:00
docker compose up -d
2026-08-19 10:58:37 +07:00
```
เมื่อขึ้นครบแล้ว:
| บริการ | URL | เข้าด้วย |
|---|---|---|
2026-08-19 23:06:57 +07:00
| Keycloak admin console | https://localhost:8443/admin/ | `KC_BOOTSTRAP_ADMIN_USERNAME` / `KC_BOOTSTRAP_ADMIN_PASSWORD` จาก `.env` |
| MinIO console | http://localhost:39001 | `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` จาก `.env` |
| PostgreSQL | `localhost:35432` | `POSTGRES_USER` / `POSTGRES_PASSWORD` จาก `.env`, ฐานข้อมูล `wordversioning` |
2026-08-19 10:58:37 +07:00
2026-08-19 23:06:57 +07:00
> **ทำไม Keycloak ต้องเป็น HTTPS:** Office Dialog API ที่ add-in ใช้เปิดหน้า login **ปฏิเสธ http:// โดยสมบูรณ์** (error: "The HTTP protocol is not supported. Use HTTPS instead") — dev จึงให้ Keycloak เสิร์ฟ HTTPS ที่ port 8443 ด้วย cert ของ `office-addin-dev-certs` (CA ถูก trust ในเครื่องแล้ว Word/webview จึงเชื่อ) · ตัว issuer ใน token จะเป็น `https://localhost:8443/realms/word-versioning` ซึ่ง backend ตั้ง `Authentication:Authority` ตรงกัน
2026-08-19 10:58:37 +07:00
Keycloak จะ import realm `word-versioning` อัตโนมัติ (clients 3 ตัว + test user) จาก [keycloak/realm-export.json](keycloak/realm-export.json) — ไฟล์นี้คือ source of truth ของ realm ฝั่ง dev: แก้ realm ให้แก้ที่ไฟล์นี้แล้วรัน `docker compose rm -sf keycloak && docker compose up -d` เพื่อ import ใหม่ (การแก้ผ่าน admin console จะหายเมื่อ recreate container)
**Test user สำหรับล็อกอิน:** `testuser` / `Test1234!` (เปลี่ยนได้ใน realm-export.json)
MinIO จะมี bucket `docx-versions` และ `docx-templates` (private ทั้งคู่) สร้างให้อัตโนมัติ
2026-08-18 12:52:40 +07:00
2026-08-19 10:58:37 +07:00
### 2) Backend API
```bash
2026-08-18 12:52:40 +07:00
cd backend/WordVersioning.Api
2026-08-19 10:58:37 +07:00
dotnet ef database update # ครั้งแรก / เมื่อมี migration ใหม่
2026-08-19 23:06:57 +07:00
dotnet run # รันทั้ง http://localhost:5075 และ https://localhost:7232 (Swagger ใช้ https ก็ได้)
2026-08-19 10:58:37 +07:00
```
2026-08-19 23:06:57 +07:00
> **ทำไม dev ต้องมี endpoint HTTPS ด้วย:** task pane ของ add-in เป็นหน้า `https://localhost:3000` และ **webview ของ Word (WKWebView บน Mac) บล็อก fetch จากหน้า https ไป http แบบเงียบๆ** — error ที่เห็นคือ "Load failed" — ส่วน CORS ก็ไม่ช่วยเพราะ request ไม่เคยถึง server · dev จึงเปิด endpoint `https://localhost:7232` ด้วย cert ชุดเดียวกับ Keycloak (`~/.office-addin-dev-certs`, ปิดได้ผ่าน `DevHttps:Enabled=false` ใน `appsettings.Development.json`) · add-in ชี้ API ที่ https ตัวนี้อัตโนมัติ
2026-08-19 10:58:37 +07:00
ค่าเชื่อมต่อ (DB/MinIO/Keycloak) อยู่ใน `appsettings.json` + `appsettings.Development.json` — override ผ่าน env ได้ทั้งหมด เช่น `ConnectionStrings__Default`, `MinIO__Endpoint`, `Authentication__Authority`
2026-08-18 12:52:40 +07:00
2026-08-19 10:58:37 +07:00
**API ที่มีตอนนี้** (ทุก endpoint ต้องแนบ `Authorization: Bearer <token>`):
2026-08-18 12:52:40 +07:00
```
# Documents / versions (FR-5–7, 11, 12, 13)
POST /api/documents/{documentId}/versions # อัปโหลด version ใหม่ + field sync + commit message
GET /api/documents/{documentId} # detail: ข้อมูลเอกสาร + template + ค่าล่าสุดทุก field (FR-12)
GET /api/documents/{documentId}/versions # version history ใหม่ → เก่า (snapshot fields + message ต่อ version)
2026-08-19 23:06:57 +07:00
GET /api/documents/{documentId}/versions/{versionId}/download # ดาวน์โหลด version (proxy stream จาก MinIO)
GET /api/documents/{documentId}/versions/{versionId}/download-link # presigned URL อายุสั้น (ให้เบราว์เซอร์โหลดตรงจาก MinIO)
2026-08-19 12:15:20 +07:00
# Generate จาก template (FR-10, 13)
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
POST /api/documents/generate # { templateId, values, message? } → เอกสาร v0 พร้อม metadata
2026-08-19 12:15:20 +07:00
# Templates (FR-9, 12)
2026-08-19 12:15:20 +07:00
GET /api/templates # รายการ (?includeInactive=true สำหรับ admin)
GET /api/templates/{id} # รายละเอียด + field definitions
GET /api/templates/{id}/documents # รายการเอกสารที่สร้างจาก template นี้ (FR-12)
2026-08-19 12:15:20 +07:00
POST /api/templates # upload .docx → detect fields อัตโนมัติ
PUT /api/templates/{id} # แก้ metadata + field definitions (label/type/editor/required/ลำดับ)
2026-08-19 12:15:20 +07:00
PUT /api/templates/{id}/file # แทนที่ไฟล์ → re-detect (บอก tag ที่เพิ่ม/หาย)
DELETE /api/templates/{id} # deactivate (soft-delete)
2026-08-19 10:58:37 +07:00
```
- Upload เป็น multipart/form-data: `file` (.docx เท่านั้น, จำกัด 100MB) + `clientVersionId` (GUID) + `fields` (optional, JSON map tag→text สำหรับ field sync) + `message` (optional, commit message ยาวไม่เกิน 2048 อักขระ — FR-13 · ว่าง/whitespace ล้วน = ไม่เก็บ)
2026-08-19 10:58:37 +07:00
- `documentId` ยังไม่มีในระบบ = สร้างใหม่อัตโนมัติ · `versionNumber` เพิ่มอัตโนมัติต่อเอกสาร · เก็บ SHA256 ของทุก version
2026-08-19 12:15:20 +07:00
- Field sync policy (FR-11): field ที่ส่งมาขาด (ผู้ใช้ลบ content control ใน Word) → เก็บค่าเดิม + mark missing ใน snapshot **ไม่ fail request** · ส่งเกินนิยาม → ignore
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
- Generate (FR-10): backend เปิด template ด้วย OpenXML → แทนที่ค่าใน SDT ที่ **run level** (รักษา formatting) → stamp custom properties (`SavedByAddIn`, `DocumentTrackingId`) → upload เป็น v0 → response `{ documentId, versionId, versionNumber, downloadUrl }` — เปิดไฟล์ใน Word แล้ว add-in (Phase 4–5) จะรู้จักทันที
2026-08-19 10:58:37 +07:00
### ขอ token ทดสอบ (dev)
2026-08-19 23:06:57 +07:00
ทุก client ใน realm บังคับ PKCE (ปิด direct grants ตาม spec) และ token ของ `admin-cli` **ไม่มี claim `aud`** จึงเรียก backend ไม่ผ่าน (401) — ใช้สคริปต์นี้เดิน PKCE flow จริงแทน (login ด้วย test user ให้อัตโนมัติ):
2026-08-19 10:58:37 +07:00
```bash
2026-08-19 23:06:57 +07:00
python3 scripts/dev-token.py # ปริ้น access token
python3 scripts/dev-token.py --clip # หรือคัดลอกเข้า clipboard
TOKEN=$(python3 scripts/dev-token.py) # เก็บใส่ตัวแปร
2026-08-19 10:58:37 +07:00
```
แล้วลอง upload:
```bash
DOC_ID=$(uuidgen | tr 'A-Z' 'a-z')
curl -X POST http://localhost:5075/api/documents/$DOC_ID/versions \
-H "Authorization: Bearer $TOKEN" \
-F "file=@เอกสาร.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \
-F "clientVersionId=$(uuidgen)"
# → { "versionId": "...", "versionNumber": 1 }
```
### ลองเล่น template → generate → field sync (ผ่าน curl — ใช้ UI บนเว็บก็ได้ ดูหัวข้อ Web Portal)
> ขั้นตอนเดียวกันนี้ทำผ่านหน้าเว็บได้สะดวกกว่า (ดูหัวข้อ "4) Web Portal") — ตัวอย่าง curl นี้เผื่อใช้กับ Swagger/automation
2026-08-19 12:15:20 +07:00
**0) เตรียมไฟล์ template** — เขียน .docx ใน Word แล้วแทรก content controls ในจุดที่จะเป็น field (Developer tab → Rich Text Content Control → Properties ตั้ง **Tag** ให้ unique ต่อไฟล์ เช่น `employee_name`, `salary`)
**1) Upload template → ระบบ detect fields อัตโนมัติ:**
```bash
curl -X POST http://localhost:5075/api/templates \
-H "Authorization: Bearer $TOKEN" \
-F "name=สัญญาจ้างงาน" \
-F "file=@template.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document"
# → { "id": "...", "fields": [ { "key": "employee_name", ... }, ... ] }
```
**2) ตั้งค่า field definitions** (label ที่แสดงในฟอร์ม / ชนิด `text|number|date` / required / ลำดับ):
```bash
curl -X PUT http://localhost:5075/api/templates/$TPL_ID \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"fields": [
{ "key": "employee_name", "label": "ชื่อ-นามสกุล", "type": "text", "isRequired": true, "sortOrder": 0 },
{ "key": "salary", "label": "เงินเดือน", "type": "number", "isRequired": true, "sortOrder": 1 } ]}'
```
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
**3) Generate เอกสาร — ได้ .docx ที่เป็น version 0 พร้อม metadata** (แนบ `message` ได้เลย — เป็น commit message ของ v0):
2026-08-19 12:15:20 +07:00
```bash
curl -X POST http://localhost:5075/api/documents/generate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"templateId\":\"$TPL_ID\",\"values\":{\"employee_name\":\"สมชาย ใจดี\",\"salary\":\"50000\"},\"message\":\"ฉบับเสนอลงนามครั้งแรก\"}"
2026-08-19 12:15:20 +07:00
# → { "documentId": "...", "versionId": "...", "versionNumber": 1, "downloadUrl": "/api/documents/.../download" }
curl -H "Authorization: Bearer $TOKEN" -o ไฟล์ที่สร้าง.docx \
"http://localhost:5075$DOWNLOAD_URL"
```
เปิดไฟล์ที่ได้ตรวจได้ว่า: ค่าถูกทุก field (formatting ของ template คงเดิม) และมี custom properties `SavedByAddIn` / `DocumentTrackingId` (unzip ดู `docProps/custom.xml` หรือ Word → File → Info → Properties → Advanced Properties)
**4) Field sync ตอนสร้าง version ถัดไป** — upload v2 พร้อมแนบ `fields` (map tag→text ที่ extract จาก content controls — ฝั่ง add-in ทำให้อัตโนมัติตอนกด Save & Version แล้ว) และ `message` (commit message — FR-13):
2026-08-19 12:15:20 +07:00
```bash
curl -X POST http://localhost:5075/api/documents/$DOC_ID/versions \
-H "Authorization: Bearer $TOKEN" \
-F "file=@ไฟล์แก้แล้ว.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \
-F "clientVersionId=$(uuidgen)" \
-F 'fields={"employee_name":"สมชาย ใจดี มากกว่าเดิม","salary":"60000"}' \
-F "message=ปรับเงินเดือนตามผลประเมิน"
2026-08-19 12:15:20 +07:00
```
แล้วดู `GET /api/documents/$DOC_ID/versions` — แต่ละ version มี snapshot fields และ `message` ของตัวเอง หากลบ content control ออกจาก Word ก่อน upload field นั้นจะเห็น `"missing": true` โดยค่าเดิมยังคงอยู่ (ไม่ error)
2026-08-19 12:15:20 +07:00
2026-08-19 23:06:57 +07:00
### 3) Add-in (ครบทั้ง services layer และ task pane UI)
2026-08-19 10:58:37 +07:00
```bash
2026-08-19 21:35:07 +07:00
cd addin
npx office-addin-dev-certs install # ครั้งแรกครั้งเดียว (https localhost)
npm run build # webpack build + type-check ทั้ง program
npm test # Jest — unit tests (50 ตัว)
2026-08-19 21:35:07 +07:00
npm run lint # eslint (ใช้ eslint.config.mjs ของโปรเจกต์)
npm run validate # ตรวจ manifest.xml
npm start # dev server https://localhost:3000 — sideload ผ่าน manifest.xml
2026-08-19 10:58:37 +07:00
```
2026-08-19 21:35:07 +07:00
**Services layer ที่มีแล้ว** ([addin/src/services/](addin/src/services/)) — ทุกตัวมี unit tests ครอบ (mock `Word.run`, `Office.context.ui`, `fetch`):
| ไฟล์ | หน้าที่ |
|---|---|
| `authService.ts` | OIDC Authorization Code + PKCE (S256) ผ่าน Office Dialog API, token ใน memory เท่านั้น, silent refresh, state กัน CSRF |
| `metadataService.ts` | อ่าน/stamp custom properties (`SavedByAddIn` ฯลฯ), Custom XML Part แบบ delete-then-add (fail เงียบๆ ตาม known bug บน Mac), `readContentControlFields()` (FR-11) |
| `fileService.ts` | ดึงไฟล์ปัจจุบันผ่าน File/Slice API ประกอบเป็น Blob — เรียก `closeAsync` เสมอ |
| `apiClient.ts` | fetch wrapper แนบ Bearer, โดน 401 → refresh + retry ครั้งเดียว, ยัง fail → `AuthRequiredError` ให้ UI กลับหน้า sign-in |
| `versioningService.ts` | uploadVersion (แนบ `fields` + `message`), getVersions, downloadVersion + getDownloadLink (presigned) |
| `versionFlow.ts` | orchestration ของ Start Versioning / Save & Version ตามลำดับ 7 ขั้นตอนของ spec + ส่ง commit message (FR-13) |
2026-08-19 21:35:07 +07:00
หน้า login ของ add-in คือ [addin/src/taskpane/auth-callback.html](addin/src/taskpane/auth-callback.html) — redirect target ของ Keycloak (ตรง `https://localhost:3000/auth-callback.html` ใน realm-export.json)
**Config ฝั่ง add-in** ฉีดตอน build ผ่าน webpack DefinePlugin (อ่านจาก env — default ชี้ dev ในพื้นที่):
```bash
2026-08-19 23:06:57 +07:00
KEYCLOAK_AUTHORITY=https://localhost:8443/realms/word-versioning \
API_BASE_URL=https://localhost:7232 \
2026-08-19 21:35:07 +07:00
npm run build
```
(ตัวแปรที่เหลือ: `KEYCLOAK_CLIENT_ID` — default `word-addin-officejs`)
2026-08-19 23:06:57 +07:00
**Task pane UI (Phase 5)** — state machine `checking → signed-out → untracked → tracking`:
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
- **Sign-in gate (FR-1)** — ยังไม่ login → ปุ่ม Sign in (เปิด Keycloak ผ่าน Office Dialog API) · login แล้วตรวจ property `SavedByAddIn` ในไฟล์: ตรง → โหลด version history ทันที (ไฟล์ที่สร้างจากเว็บเข้า state นี้เลย เห็น v0 โดยไม่ต้องกดอะไร) / ไม่ตรง → ปุ่ม **Start Versioning This Document** (กดแล้วเปิด dialog ยืนยันพร้อมช่องคำอธิบายก่อนสร้าง v1 — FR-13)
- **Save & Version (FR-2)** — confirm dialog พร้อมช่อง **คำอธิบาย version แบบ commit message** ทางเลือก (FR-13) → stamp properties → อัปเดต Custom XML Part → native save → ดึงไฟล์ + extract fields (FR-11) → upload → refresh history · ทุกขั้นมี loading + error ไม่มี unhandled rejection
2026-08-19 23:06:57 +07:00
- **Soft reminder (FR-3)** — ระหว่าง tracking ถามทุก 30 วิ ว่ามีแก้ไขที่ยังไม่ได้ version → banner เตือน (ไม่ block)
- **Version history (FR-4)** — รายการ v เวลา/ผู้สร้าง + คำอธิบาย version (ถ้ามี) + ปุ่มดาวน์โหลด version นั้น (ขอ presigned URL อายุ 2 นาทีแล้วเปิดในเบราว์เซอร์ของเครื่อง — webview ของ Word ทิ้ง blob download แบบเงียบๆ ไฟล์จึงต้องมาจากเบราว์เซอร์จริง แล้วหาได้ในโฟลเดอร์ Downloads ของเบราว์เซอร์)
2026-08-19 23:06:57 +07:00
- **Session หมดอายุ (AC-9)** — 401 → silent refresh → ยังไม่ได้ → กลับหน้า sign-in พร้อมข้อความ
**Sideload เพื่อทดสอบใน Word จริง** (ต้องรัน infra + backend + `npm start` ก่อน):
- **Word Desktop (Mac/Windows)**: `cd addin && npm start` (ใช้ office-addin-debugging sideload ให้อัตโนมัติ) หรือ insert manual: Word → Insert → My Add-ins → Upload My Add-in → เลือก `addin/manifest.xml`
- **Word Online**: ไฟล์ใดก็ได้ใน OneDrive/SharePoint → Insert → Office Add-ins → Upload My Add-in → เลือก `addin/manifest.xml`
- ไล่ทดสอบตาม acceptance criteria: login ผ่าน dialog → เปิดไฟล์ใหม่เห็นปุ่ม Start Versioning → กดแล้วได้ v1 → แก้ไขแล้ว Save & Version → history เพิ่ม + ดาวน์โหลด version เก่าเปิดได้
- ⚠️ **ห้าม `cp` ทับ manifest ใน `~/Library/Containers/com.microsoft.Word/Data/Documents/wef/`** — `npm start` สร้าง symlink ไว้ การทับทำให้ครั้งถัดไปขึ้น `EEXIST` — ถาวรแก้ที่ `addin/manifest.xml` ต้นทางแล้ว restart Word (ถ้าเจอ EEXIST แล้ว: ลบไฟล์ใน wef ทิ้งแล้ว `npm start` ใหม่)
2026-08-19 21:35:07 +07:00
### 4) Web Portal
2026-08-19 21:35:07 +07:00
```bash
cd web && npm run dev # http://localhost:5173 (ห้ามใช้ 3000 — ชน add-in)
cd web && npm test # Vitest — unit tests (46 ตัว)
2026-08-19 21:35:07 +07:00
```
เปิด <http://localhost:5173> แล้วกดเข้าสู่ระบบด้วย `testuser` / `Test1234!` (ผ่าน Keycloak — client `word-versioning-web` ใช้ PKCE เหมือนกันทั้งระบบ) หน้าที่มีให้ใช้:
| หน้า | ทำอะไร |
|---|---|
| `/` หน้าหลัก | card รายการ template ที่ active → เข้าฟอร์มสร้างเอกสาร หรือดูเอกสารที่สร้างแล้วของ template นั้น |
| `/templates` | ตาราง template ทั้งหมด (แสดงตัวที่ deactivate ได้) — แก้ไข / activate / deactivate / ดูรายการเอกสาร |
| `/templates/new` | upload template → เห็น field list ที่ระบบ detect อัตโนมัติทันที |
| `/templates/{id}/edit` | แก้ metadata + ตั้ง label/type (`text\|number\|date`)/editor (FR-14 — `text` เลือก textbox/textarea ได้, number/date ใช้ control ตาม type)/required/ลำดับ + แทนที่ไฟล์ (บอก tag ที่เพิ่ม/หาย) |
| `/templates/{id}/documents` | **รายการเอกสารที่สร้างจาก template** — จำนวน version, กิจกรรมล่าสุด, เข้าไปดู detail |
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
| `/documents/create/{templateId}` | dynamic form ตาม field definitions — control ต่อ field ตาม editor (FR-14: textarea สำหรับข้อความยาว · ช่องตัวเลข · date picker) + validate ฝั่ง client + backend + ช่อง **คำอธิบาย version** ทางเลือก (FR-13) → สร้างเป็น v0 + เปิดใน Word |
| `/documents/{documentId}` | **detail เอกสาร** — ค่า fields ปัจจุบัน + version history พร้อมคำอธิบายของแต่ละ version (กดขยายแถวเห็นค่า fields ตอนนั้น + ดาวน์โหลดต่อ version) + ปุ่ม **⟳ re-generate** |
**สร้าง template แรกจากไฟล์จริง:** เขียน .docx ใน Word แล้วแทรก content controls ในจุดที่จะเป็น field (Developer tab → Rich Text Content Control → Properties ตั้ง **Tag** ให้ unique ต่อไฟล์ เช่น `employee_name`, `salary`) → หน้า `/templates/new` upload ไฟล์ → ระบบ detect tags ออกมาเป็นรายการ fields → กด "ตั้งค่าต่อ" ไปใส่ label/type/editor/required ให้เรียบร้อย (field ข้อความยาวเช่นที่อยู่/รายละเอียด เลือก editor เป็น `textarea` ได้ — FR-14) → กลับมาหน้าหลักเพื่อสร้างเอกสาร
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
**Re-generate (สร้างเอกสารใหม่จากข้อมูลเดิม):** ที่หน้า detail ของเอกสาร กด **"⟳ สร้างเอกสารใหม่จากข้อมูลนี้"** → เข้าฟอร์มสร้างเอกสารโดย prefill ค่าปัจจุบันของทุก field ไว้ให้แก้ไขก่อน (ช่องคำอธิบาย version ว่าง — เป็นเอกสารใหม่ได้ข้อความใหม่) → กดสร้าง → ได้**เอกสารใหม่เป็น v0** (documentId ใหม่) โดยเอกสารต้นทางและ version history ของมันไม่ถูกแตะ · template ที่ deactivate ไปแล้วจะกดไม่ได้ (ปุ่มถูก disable พร้อมเหตุผล)
Theme toggle (light/dark) อยู่มุมขวาบนของทุกหน้า (header ติด sticky) — จำค่าไว้หลัง reload
**Design system + responsive (redesign ล่าสุด):** สี/ฟอนต์/radius/shadow ทั้งหมดเป็น token ผ่าน CSS variables ใน [web/app/assets/css/tailwind.css](web/app/assets/css/tailwind.css) (derive จาก scheme ของ admincourt.go.th — ฟอนต์ Sukhumvit Set/Sarabun, primary `#0000EE`, elevated ครีม) + component classes กลาง (`.btn*`/`.input`/`.card`/`.table-card` ฯลฯ) · ทุกหน้า responsive ครบ — ตารางทุกตัวเปลี่ยนเป็น card list บนจอเล็กกว่า `md`, sticky header + backdrop blur, ฟอร์ม stack บนมือถือ
2026-08-18 12:52:40 +07:00
**Config:** ค่า default ชี้ dev ในพื้นที่อยู่ใน `nuxt.config.ts` — override ผ่าน env ได้ทั้งหมด (ดู [web/.env.example](web/.env.example)): `NUXT_PUBLIC_API_BASE_URL`, `NUXT_PUBLIC_KEYCLOAK_AUTHORITY`, `NUXT_PUBLIC_KEYCLOAK_CLIENT_ID`
2026-08-18 12:52:40 +07:00
2026-08-20 13:51:42 +07:00
## Tests
ชุด test ทั้งหมดของระบบ — รวม **156 ตัว** ครอบทุก flow ตาม acceptance criteria:
2026-08-20 13:51:42 +07:00
```bash
# 1) backend integration (60 ตัว) — ต้องมี Docker รันอยู่
2026-08-20 13:51:42 +07:00
cd backend
dotnet test
dotnet test --filter "FullyQualifiedName~FieldSyncTests" # รันเฉพาะชุด (Auth/UploadVersion/Template/Generate/FieldSync/VersionMessage/FieldEditor)
2026-08-20 13:51:42 +07:00
# 2) add-in unit (50 ตัว) — mock Word.run / Office.context.ui / fetch / Keycloak
2026-08-20 13:51:42 +07:00
cd addin && npm test
npx jest src/services/versionFlow.test.ts # รันเดี่ยว
# 3) web unit (46 ตัว)
2026-08-20 13:51:42 +07:00
cd web && npm test
npx vitest run test/useTheme.spec.ts # รันเดี่ยว
```
**backend integration** (`backend/WordVersioning.Api.IntegrationTests`) — ชุดใหญ่สุด: ยก **PostgreSQL + MinIO + Keycloak จริงผ่าน Testcontainers** ทุกครั้งที่รัน (~1 นาทีรวมการยก infra — ไม่แตะของ dev ใน docker-compose เลย) แล้วยิง API ผ่าน `WebApplicationFactory` พร้อม token จริงจาก Keycloak (validate JWKS/issuer/audience เต็มขั้นตอน):
- **AuthTests** — ไม่แนบ token / token ขยะ → 401 ครบทุก endpoint (AC-3)
- **UploadVersionTests** — upload v1/v2 + versionNumber เพิ่ม + history เรียงใหม่→เก่า + download ได้ bytes ตรงเป๊ะ + ไฟล์ไม่ใช่ docx → 400 + `CreatedByUserId` ตรง `sub` ใน JWT (AC-5, 6, 8)
- **TemplateTests** — detect SDT tags ครบทุกรูปแบบ (run เดี่ยว/multi-run/block/tag ซ้ำ) + บันทึก field defs + replace ไฟล์ (tag เพิ่ม/หาย) + deactivate แล้ว generate ไม่ได้แต่เอกสารเก่ายังใช้ได้ (AC-11)
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
- **GenerateTests** — generate แล้วค่าครบทุก field + multi-run รวมเป็น run เดียวรักษา formatting + stamp `SavedByAddIn`/`DocumentTrackingId` ลงไฟล์ + v0 + field values ใน DB + required/type validation + FR-12 (detail merge orphan สองทาง, รายการเอกสารต่อ template) (AC-12, 15)
2026-08-20 13:51:42 +07:00
- **FieldSyncTests** — Save & Version พร้อม `fields`: latest values อัปเดต + snapshot ต่อ version (AC-13) · ลบ content control → request สำเร็จ ค่าเดิมคงอยู่ + mark missing (AC-14)
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
- **VersionMessageTests** — commit message ต่อ version (FR-13/AC-16): เก็บ + echo ใน history · ว่าง/whitespace → null · trim · เกิน 2048 → 400 ไม่มี side effect · ยาวพอดี 2048 ผ่าน · message ต่อ version เป็นของตัวเอง · generate v0 พร้อม message · generate oversize → 400 ก่อนแตะ MinIO
- **FieldEditorTests** — editor ต่อ field (FR-14/AC-17): PUT editor=textarea → persist + echo · ไม่ส่ง → resolve default ตาม type + DB เก็บ null · ค่า = default → normalize เป็น null · editor ไม่รู้จัก/ไม่เข้ากับ type → 400 · tag ใหม่จาก replace ได้ default
2026-08-20 13:51:42 +07:00
ไฟล์ .docx ที่ใช้ตอน test สร้างใน memory ด้วย OpenXML (`TestDocx.cs`) — ไม่มี binary ติดใน repo · realm ของ Keycloak ทดสอบเป็นสำเนาของ dev (`Fixtures/realm-word-versioning-test.json`) ที่เปิด password grant ให้ client `word-versioning-web` เฉพาะตอน test เพื่อขอ token ได้โดยไม่ต้องเดิน PKCE
**web unit** (`web/test/`) — `fieldValidation.spec.ts` (validate ฟอร์มสร้างเอกสาร: required/number/date ครบ edge cases) · `useTheme.spec.ts` (toggle + persist + system default) · `snapshot.spec.ts` (`normalizeSnapshot` รองรับ snapshot ทั้ง 2 รูปแบบที่หน้า detail ใช้) — รันบน happy-dom โดย stub Nuxt auto-imports เบาๆ (`test/setup.ts`)
**addin unit** (`addin/src/services/*.test.ts`) — ตามที่มาจาก Phase 4–5: PKCE flow, metadata stamp + XML part fail-แบบ-ไม่-throw, apiClient retry-on-401, fileService ประกอบ slice ครบ 4 รูปแบบ, versionFlow 7 ขั้น + ส่ง commit message ของทั้ง Save & Version และ Start Versioning (FR-13)
2026-08-20 13:51:42 +07:00
2026-08-19 23:06:57 +07:00
## Troubleshooting (จากการทดสอบจริงบน Word for Mac)
ปัญหาที่เจอตอน sideload test รอบแรกๆ พร้อมสาเหตุ/วิธีแก้ — แก้ไขในโค้ดแล้วทั้งหมด เก็บไว้เป็นบทเรียน:
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| กด Sign in แล้ว "The HTTP protocol is not supported. Use HTTPS instead" | Office Dialog API **ปฏิเสธ http:// โดยสมบูรณ์** แต่ Keycloak dev เดิมรัน http://8080 | Keycloak เสิร์ฟ HTTPS ที่ `https://localhost:8443` ด้วย cert ของ office-addin-dev-certs (docker-compose mount `~/.office-addin-dev-certs` เข้า container) |
| Login ผ่านแต่ทุก API call ขึ้น "Load failed" | **WKWebView บล็อก fetch จากหน้า https ไป http (mixed content)** — task pane เป็น `https://localhost:3000` แต่ API เป็น `http://localhost:5075` (CORS ไม่เกี่ยว — ตรวจแล้วเรียบร้อย) | backend เปิด endpoint `https://localhost:7232` ด้วย cert ชุดเดียวกัน (`DevHttps:*` ใน `appsettings.Development.json`) — add-in ชี้อันนี้อัตโนมัติ |
| กด Start Versioning แล้ว "รูปแบบข้อมูลของ slice ไม่รองรับ" | host แต่ละตัวส่ง `slice.data` คนละรูป (Win = ArrayBuffer/view, **Mac = base64 string หรือ number[]**) | `fileService.sliceDataToBytes()` normalize ครบทั้ง 4 รูปแบบ |
| กดดาวน์โหลด version แล้ว**ไม่มีไฟล์โผล่ที่ไหนเลย** | **webview ของ Word ทิ้ง blob download (`a[download]`) แบบเงียบๆ** | ขอ presigned URL อายุ 2 นาทีจาก `download-link` แล้วเปิดใน**เบราว์เซอร์ของเครื่อง** (`openBrowserWindow`) — ไฟล์ลงโฟลเดอร์ Downloads ของเบราว์เซอร์ |
| แก้ webpack.config.js แล้วค่าเก่ายังติดอยู่ | webpack **ไม่ restart เองเมื่อ config เปลี่ยน** + dev-server ตัวเก่าถือ port 3000 อยู่ (ตัวใหม่ขึ้นไม่ได้) | kill dev-server เดิม (`lsof -ti :3000 | xargs kill`) แล้ว `npm start` ใหม่ทุกครั้งที่แก้ config |
| `npm start` ขึ้น `EEXIST ... wef/...manifest.xml` | มีการ `cp` ทับ symlink ของ sideload จนกลายเป็นไฟล์ธรรมดา | ลบไฟล์นั้นใน wef folder ทิ้งแล้ว `npm start` ใหม่ (ดูคำเตือนในหัวข้อ Sideload) |
| รัน backend ด้วย `dotnet run --no-launch-profile` แล้ว API ตอบ **500** (`No password has been provided`) | `--no-launch-profile` ตัด `ASPNETCORE_ENVIRONMENT=Development` (ปกติตั้งโดย launchSettings) ทิ้ง → ไปอ่าน `appsettings.json` ที่ DB password ว่าง | ใส่ env เอง: `ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5075 dotnet run --no-launch-profile` หรือรัน `dotnet run` เฉยๆ |
2026-08-19 23:06:57 +07:00
| curl token จาก `admin-cli` แล้วเรียก backend ได้ 401 | token ของ admin-cli **ไม่มี claim `aud`** (ไม่ได้อยู่ใน realm-export จึงไม่มี audience mapper) | ใช้ `python3 scripts/dev-token.py` (เดิน PKCE จริงด้วย client `word-versioning-web`) |
**กติกาที่สรุปได้:** ใน dev ทุก service ที่ task pane (หน้า https) จะเรียก **ต้องเป็น HTTPS ด้วย cert จาก office-addin-dev-certs ทั้งหมด** — Keycloak (8443) และ backend (7232)
2026-08-18 12:52:40 +07:00
## เทคโนโลยี
- **Add-in**: Office.js (WordApi 1.5+), TypeScript, React, Fluent UI, webpack — scaffold ด้วย `yo office`
- **Backend**: .NET 8, EF Core + Npgsql (PostgreSQL), AWSSDK.S3 → MinIO (`ForcePathStyle = true`), DocumentFormat.OpenXml, JwtBearer
- **Web**: Nuxt.js (Vue 3), TailwindCSS (`darkMode: 'class'`)
- **Auth**: Keycloak — realm `word-versioning`, clients: `word-addin-officejs`, `word-versioning-web`, `word-addin-vsto` (public + PKCE S256 ทุกตัว)
Phase 11: version PDF (FR-15) - เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00
## Version PDF (FR-15 / spec v7)
เว็บ generate/re-generate เป็น **v0 ฉบับตั้งต้น** ไม่มี PDF. Start Versioning และ Save & Version สร้าง v1+ พร้อม PDF จาก Word (`PdfFile 1.1` runtime guard; ไม่เพิ่ม requirement ใน manifest). PDF capture/upload เป็น best-effort: docx สำเร็จได้แม้ PDF ล้ม และ task pane เตือนตาม `hasPdf` จาก backend.
`DocumentVersion.PdfObjectKey` nullable เก็บ sibling `.pdf` ใน bucket เดิม; history มี `hasPdf`. `/download` และ `/download-link` รับ `?format=pdf`, ไม่มี PDF → 404. Presigned URLs กำหนดชื่อไฟล์ไทย + `_vN`; PDF inline, docx attachment. เว็บเปิด Word ผ่าน `ms-word:ofe|u|<url>` พร้อมดาวน์โหลดสำรอง; PDF เปิดแท็บใหม่. Add-in ใช้ `openBrowserWindow` เท่านั้น. ข้อมูลเก่าไม่ backfill หรือเปลี่ยนเลข.