eCourt-Doc-Edit/CLAUDE.md
2026-08-19 23:06:57 +07:00

8.8 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

โปรเจกต์นี้คืออะไร

ระบบ Word Versioning (ชื่อที่แสดงใน UI: e-Court Documents Versioning): จัดการ version history ของไฟล์ .docx — สร้างเอกสารจาก template ผ่านเว็บ, ตรวจจับ tracked document ใน Word, บันทึก version ผ่านปุ่ม Save & Version และ sync ค่า fields กลับ DB ประกอบด้วย 3 ส่วน:

ส่วน Stack หน้าที่
/addin Office.js + TypeScript + React + Fluent UI (yo office) Task pane ใน Word Desktop/Online — detect, Save & Version, history, field extraction
/backend ASP.NET Core 8 + EF Core + PostgreSQL + MinIO (AWSSDK.S3) + OpenXML Version API, template system, docx generation, JWT validation
/web Nuxt.js + TailwindCSS (light/dark theme) จัดการ template + กรอก form สร้างเอกสารเป็น version 1 อัตโนมัติ

Infra ทั้งหมดรันผ่าน docker-compose.yml: PostgreSQL + MinIO + Keycloak (OIDC, 3 public clients พร้อม PKCE: word-addin-officejs, word-versioning-web, word-addin-vsto)

สถานะปัจจุบัน: Phase 0–5 เสร็จ (backend ครบ + add-in ครบทั้ง services และ task pane UI — sideload ทดสอบจริงบน Word for Mac แล้ว แก้ปัญหาตกมุม 4 รอบ) — งานต่อคือ phase 6 (web portal), 7 (tests ครบชุด), 8 (ไล่ acceptance criteria) ตามแผน

เอกสารหลัก (อ่านก่อนเริ่มงานใดๆ)

  • specs/spec-officejs.md — source of truth ของ requirements (FR-1–11, acceptance criteria 1–14, data models, platform constraints)
  • docs/plans/officejs-implementation-plan.md — implementation plan แบ่ง 9 phases พร้อม checklist, DoD, dependency ระหว่าง phase และตารา mapping Phase↔FR↔AC

เมื่อ requirement เปลี่ยน: แก้ spec ก่อน แล้ว sync ไปยัง plan

กฎที่ห้ามละเมิด (ข้อจำกัดแพลตฟอร์มจริง — สรุปจาก spec ส่วน 2 และ 9)

  1. ห้ามออกแบบ flow ที่ดัก native save (Ctrl+S / File>Save) — Office.js ไม่มี before-save event ที่ cancel ได้ ใช้ปุ่มใน task pane เท่านั้น
  2. Custom Document Properties = primary flag ตรวจจับ tracked document; Custom XML Part = secondary เท่านั้น (ต้อง delete แล้ว add ใหม่ทุกครั้ง, มี known bug บน Mac บาง build — ต้อง catch แบบไม่ throw)
  3. ห้าม redirect iframe ของ task pane ไป Keycloak ตรงๆ — ต้องใช้ Office Dialog API (displayDialogAsync + messageParent); ส่วน /web เป็น top-level page ใช้ OIDC redirect ปกติ
  4. Token เก็บใน memory เท่านั้น — ห้าม localStorage (theme preference ของ /web ยกเว้นได้)
  5. Backend คือ source of truth ของ version history และ field values
  6. Field key = content control (SDT) tag ใช้ค่าเดียวกันทั้ง generate (backend) / extract (add-in) / sync (DB)
  7. Docx generation + metadata stamping ทำฝั่ง backend เท่านั้น (OpenXML, แก้ที่ run level ใน SDT เพื่อรักษา formatting)
  8. Ports: add-in = https://localhost:3000, web = http://localhost:5173 (ห้ามชนกัน), backend CORS ต้องอนุญาตทั้งคู่
  9. Config (DB/MinIO/Keycloak) แยกเป็น env/appsettings เท่านั้น — ห้าม hardcode; userId เอาจาก JWT claims เท่านั้น ห้ามรับจาก client
  10. ทุก Office.js call ต้อง catch — ห้ามปล่อย unhandled promise rejection
  11. Dev: ทุก service ที่ task pane (หน้า https) เรียกต้องเป็น HTTPS ด้วย cert ของ office-addin-dev-certs — Keycloak = https://localhost:8443 (Office Dialog API ปฏิเสธ http:// โดยสมบูรณ์), backend = https://localhost:7232 + http://localhost:5075 (WKWebView บล็อก mixed content → "Load failed") — รัน npx office-addin-dev-certs install ก่อน docker compose up
  12. ดาวน์โหลดไฟล์จาก add-in ใช้ presigned URL + Office.context.ui.openBrowserWindow เท่านั้น — webview ของ Word ทิ้ง blob download (a[download]) แบบเงียบๆ ไฟล์ไม่ถูกเขียนที่ไหนเลย; และห้าม cp ทับ manifest ใน wef folder (npm start ใช้ symlink — ทับแล้วครั้งหน้า EEXIST, ให้แก้ที่ addin/manifest.xml ต้นทาง)
  13. slice.data จาก getSliceAsync มีหลายรูปแบบตาม host (ArrayBuffer / view / base64 string / number[]) — ต้องผ่าน fileService.sliceDataToBytes() เสมอ ห้าม assume รูปเดียว

สถาปัตยกรรม — flow หลักที่ต้องเข้าใจ

Create → v1 (FR-10): web form → POST /api/documents/generate → OpenXML แทนที่ค่าใน SDT + stamp SavedByAddIn/DocumentTrackingId ลงไฟล์ → สร้าง Document + DocumentFieldValue + upload v1 → ผู้ใช้โหลดไฟล์ไปเปิดใน Word → add-in ตรวจพบ tracked doc ทันทีเห็น v1

Save & Version (FR-2): ปุ่มใน task pane → stamp properties → อัปเดต XML part (delete-then-add, fail ได้แบบ silent) → document.save() → getFileAsync → POST version พร้อม fields map จาก content controls (FR-11) → backend update DocumentFieldValue (latest) + FieldsSnapshotJson (ต่อ version)

Field sync policy (FR-11): field ขาด (ผู้ใช้ลบ control) → เก็บค่าเดิม + mark missing ไม่ fail request; ส่งมาเกินนิยาม → ignore

MinIO: bucket docx-versions (snapshot ต่อ version, key {documentId}/v{N:D4}_{clientVersionId}.docx) + docx-templates (ไฟล์ template) — private ทั้งคู่, เข้าถึงผ่าน backend เท่านั้น (proxy stream หรือ presigned URL อายุสั้นจาก download-link — ซึ่ง add-in ใช้เปิดในเบราว์เซอร์ของเครื่อง)

Template: template.docx author ด้วย Word (ใส่ content controls) → upload → backend parse SDT tags → admin ตั้ง label/type/required → delete = deactivate (soft) เพราะ document เก่ายังอ้างอยู่

คำสั่ง (ใช้ได้หลัง scaffold ใน Phase 0)

docker compose up -d                                # infra: PostgreSQL + MinIO + Keycloak(https:8443)
python3 scripts/dev-token.py                        # token PKCE จริงสำหรับทดสอบ backend/Swagger (admin-cli ไม่มี aud ใช้ไม่ได้)

# backend (http://localhost:5075 + https://localhost:7232 — dev เปิด https อัตโนมัติด้วย dev cert)
cd backend/WordVersioning.Api
dotnet run                                          # API + swagger
dotnet ef migrations add <Name>
dotnet test
dotnet test --filter "FullyQualifiedName~GenerateTests"   # รัน test เดี่ยว

# add-in (https://localhost:3000)
cd addin
npx office-addin-dev-certs install                  # ครั้งแรกครั้งเดียว — ทำก่อน docker compose up
npm run build                                       # webpack build
npm start                                           # dev server — sideload ผ่าน manifest (symlink)
npm test
npx jest src/services/metadataService.test.ts       # รัน test เดี่ยว

# web portal (http://localhost:5173 — ห้ามใช้ 3000)
cd web
npm run dev
npm test
npx vitest run composables/useTheme.spec.ts         # รัน test เดี่ยว