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>
This commit is contained in:
parent
618328ffa7
commit
39d610a781
39 changed files with 1387 additions and 212 deletions
135
docs/plans/version-pdf.md
Normal file
135
docs/plans/version-pdf.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Version PDF + เลข version v0/v1+ + เปิดไฟล์จากเว็บ — FR-15 / AC-18 / Spec v7
|
||||
|
||||
> **Status: เสร็จสมบูรณ์ — implementation + automated QA + manual acceptance ผ่านบน Word for Mac (2026-09-08)** · ค้างเฉพาะ Word for Windows / Word on the web ตามรายการใน [acceptance-review](../acceptance-review.md); spec v7
|
||||
|
||||
## Context
|
||||
|
||||
ระบบเก็บทุก version เป็น .docx ใน MinIO แต่การนำเอกสารไปใช้จริง โดยเฉพาะ**สำเนาเชิงกฎหมาย/ยื่นศาล** ต้องการ PDF ที่ฟอร์แมตเที่ยงเท่ากับที่ Word เอง export — ตัวแปลงกลาง (LibreOffice/Gotenberg, Aspose, Microsoft Graph, Word Interop บน server) ล้วนเป็น layout engine คนละตัวหรือมีต้นทุน/topology ที่ไม่เข้ากัน จึงตัดทิ้งทั้งหมด
|
||||
|
||||
**กลไกที่เลือก (ยืนยันจาก MS Learn):** `Office.context.document.getFileAsync(Office.FileType.Pdf)` — PdfFile requirement set รองรับ Word Windows/Mac/**web**/iPad · add-in ที่รันใน Word สั่ง export ได้โดยตรง และ `fileService` มี slice loop + `sliceDataToBytes()` รองรับ slice หลายรูปแบบอยู่แล้ว (กติกาข้อ 13) · known issue PDF ฝัง JS เป็นของ Excel/PPT เท่านั้น
|
||||
|
||||
พร้อมกันนี้ผู้ใช้ขอปรับสองเรื่อง: **(1) ระบบเลข version** — เว็บ-generate เป็น **v0 ฉบับตั้งต้น** (ไม่มี PDF), ทุกการ save จาก add-in เริ่ม **v1** และมี PDF เสมอ → เลข version บอกที่มา (0 = ฟอร์ม, ≥1 = ผ่าน Word) · **(2) คลิกลิงก์บนหน้าเว็บให้เปิดไฟล์ด้วยแอปของไฟล์นั้น** — PDF เปิดแท็บใหม่ (ตัวดู PDF ของเบราว์เซอร์), docx เปิด Word จริงผ่าน [`ms-word:ofe|u|<url>`](https://learn.microsoft.com/en-us/office/client-developer/office-uri-schemes) (นิ่งบน Windows, Mac อาจต้องยืนยัน/พังบ้าง — มี fallback ดาวน์โหลดเสมอ)
|
||||
|
||||
## ข้อตกลง fixed (ยืนยันครบใน grill session)
|
||||
|
||||
1. **ผู้ผลิต PDF เดียวในระบบ = Word จริง** ผ่าน add-in — ไม่มี server-side converter ไม่ต้องเพิ่ม infra
|
||||
2. **เลข version**: generate เว็บ → **v0** ฉบับตั้งต้น ไม่มี PDF · add-in save (**ทั้ง** Start Versioning และ Save & Version) → **v1, v2, …** มี PDF เสมอ · ไม่ backfill · ยังไม่มี production → ไม่ต้อง migration ข้อมูล dev เดิม (คงเลขเดิมหรือล้าง DB ก็ได้)
|
||||
3. **Best-effort**: PDF capture/upload ล้ม → version ของ docx ผ่านเสมอ (`PdfObjectKey` = null) + MessageBar เตือนใน task pane "บันทึกแล้ว แต่สร้าง PDF ไม่สำเร็จ" — กด Save & Version ใหม่ได้ version ใหม่พร้อม PDF
|
||||
4. **ที่เก็บ**: bucket `docx-versions` เดิม, sibling key `{documentId}/v{N:D4}_{clientVersionId}.pdf` (stem เดียวกับ docx)
|
||||
5. **UI**: ปุ่ม/ลิงก์ PDF เฉพาะ version ที่มี PDF จริง · add-in: presigned + `openBrowserWindow` (กติกาข้อ 12) · เว็บ: PDF แท็บใหม่ / docx ผ่าน `ms-word:ofe|u|` + heuristic ตรวจว่าเปิดจริง (visibilitychange/blur ~1.5s) ไม่เปิด → ลิงก์ presigned ดาวน์โหลดสำรอง
|
||||
6. `pdf` part ที่ส่งมาไม่ใช่ .pdf → **400** (บั๊ก client ควรเห็นชัด) — ต่างจากความล้มสิ่งแวดล้อม (MinIO) ที่ best-effort
|
||||
7. **manifest ไม่ declare `PdfFile`** เป็น requirement (ทำให้ host ไม่รองรับโหลด add-in ไม่ได้ทั้งอัน ขัด best-effort) — runtime-guard ด้วย `Office.context.requirements.isSetSupported("PdfFile", "1.1")` เท่านั้น
|
||||
|
||||
## ขั้นที่ 0 — บันทึกแผน + แก้เอกสาร requirement ก่อน (กติกา repo)
|
||||
|
||||
1. บันทึกแผนนี้ที่ `docs/plans/version-pdf.md` (ธรรมเนียมเดิม) — **ทำแล้ว**
|
||||
2. **[specs/spec-officejs.md](../../specs/spec-officejs.md)** → v7: changelog บรรทัดบนสุด (FR-15 + เลข v0/v1 + เปิดไฟล์จากเว็บ) · **แก้ของเดิม**: FR-5 (response + pdf part + เลขเริ่ม v1 สำหรับ upload / v0 generate), FR-7 (download/download-link `?format=pdf` + presigned ฝังชื่อไฟล์ Content-Disposition), FR-10 ("version 1" → "version 0 ฉบับตั้งต้น"), AC 12/15 ที่อ้าง v1 ของ generate → v0 · `### FR-15: Version PDF — สำเนา PDF ที่ Word ผลิต (เพิ่มเติม v7)` ต่อท้ายหมวด 6: ผลิตจาก Word จริงเท่านั้นใน task pane (Save & Version + Start Versioning) · multipart part `pdf` ทางเลือก · sibling key `.pdf` · best-effort + คำเตือน · download `?format=pdf` (proxy + presigned 404 เมื่อไม่มี) · เว็บเปิด docx ผ่าน `ms-word:ofe|u|` และ PDF แท็บใหม่ · v0 ไม่มี PDF เสมอ · หมวด 7: `DocumentVersion.PdfObjectKey` + ปรับคำอธิบาย `VersionNumber` · หมวด 10: AC-18 (v7) · หมวด 11: task items (v7) ทั้ง 3 list
|
||||
3. **[docs/plans/officejs-implementation-plan.md](officejs-implementation-plan.md)**: `## Phase 11: Version PDF + เลข version v0/v1+ — FR-15 (เพิ่มทีหลัง, v7)` + checklist + แถว `| 11 (ทีหลัง, v7) | FR-15 (+แก้ FR-5/FR-7/FR-10) | AC-18 |`
|
||||
4. **[docs/acceptance-review.md](acceptance-review.md)**: AC-18 (verdict 🔶 รอ implement) + หมายเหตุว่า AC เดิมที่อ้าง "generate = v1" อ่านใหม่ตาม v7
|
||||
5. **สร้าง `CONTEXT.md`** (glossary เท่านั้น ห้ามใส่ implementation): **Version PDF** = สำเนา PDF ที่ Word ผลิตขณะเปิดเอกสาร; ขาดได้แบบ best-effort · **ฉบับตั้งต้น (v0)** = version ที่เว็บสร้างจากฟอร์ม ยังไม่เคยผ่าน Word · **เลข version** = จำนวนรอบที่ผ่าน Word · **ต้นฉบับ (docx)** = ตัว version จริง; PDF เป็น derived artifact
|
||||
6. **สร้าง `docs/adr/0001-version-pdf-from-word-only.md`**: Decision (Word เท่านั้น + v0 ไม่มี PDF + ไม่ backfill) · rejected: Gotenberg/LibreOffice, Aspose, Graph cloud, Word Interop server · consequences (v0/version เก่าไม่มี PDF, request โตขึ้น, ms-word พึ่ง protocol handler ของเครื่องผู้ใช้)
|
||||
|
||||
## ขั้นที่ 1 — Backend (`backend/WordVersioning.Api`)
|
||||
|
||||
1. **เลข version ใหม่**: [DocumentsController.cs](../../backend/WordVersioning.Api/Controllers/DocumentsController.cs) `Generate` — `VersionNumber = 0`, ObjectKey `v0000_{Guid}.docx` (สังเกต: `GenerateDocumentResponse.VersionNumber` คืน 0) · `UploadVersion` ใช้ `max+1` เดิมได้ทันที (0→1 และ ไม่มี→1 ได้ v1 ทั้งคู่) · add-in เดาเลข `latest+1` ใช้ได้กับ v0 ด้วย
|
||||
2. [Models/DocumentVersion.cs](../../backend/WordVersioning.Api/Models/DocumentVersion.cs): `public string? PdfObjectKey { get; set; }` (+ doc comment FR-15: null = version นี้ไม่มี PDF — v0 หรือ capture/upload ไม่สำเร็จ)
|
||||
3. [Data/AppDbContext.cs](../../backend/WordVersioning.Api/Data/AppDbContext.cs): `entity.Property(x => x.PdfObjectKey).HasMaxLength(1024);` · migration `dotnet ef migrations add AddVersionPdf` (tests ใช้ MigrateAsync อยู่แล้ว)
|
||||
4. **สร้าง `Services/PdfUploadValidation.cs`** มิเรอร์ `DocxUploadValidation`: นามสกุล `.pdf` + content-type (`application/pdf`/octet-stream/ว่าง) — ความลึกเท่าของเดิม ไม่ต้อง magic-byte
|
||||
5. [Models/VersionDtos.cs](../../backend/WordVersioning.Api/Models/VersionDtos.cs): `UploadVersionRequest.Pdf` (`IFormFile?` — bind part `pdf`) · `UploadVersionResponse` เพิ่ม `bool HasPdf` ท้าย · `VersionListItem` เพิ่ม `bool HasPdf` ท้าย · `GenerateDocumentResponse` **ไม่แตะ** (v0 ไม่มี PDF — asymmetry ตั้งใจ)
|
||||
6. `UploadVersion`: validate part `pdf` หลัง message check (ไม่ใช่ .pdf/ว่าง → 400, ใหญ่เกิน → 413) · buffer เป็น MemoryStream (มิเรอร์ path docx) · ใน retry loop: `PdfObjectKey = มี pdf ? $"{documentId}/v{nextNumber:D4}_{request.ClientVersionId}.pdf" : null` (คำนวณใน loop ให้ตรงเลขที่จองได้) · **หลัง** docx `PutAsync` (โค้ดเดิมไม่แตะ): put PDF ด้วย `application/pdf` — `AmazonServiceException` → log warning + `version.PdfObjectKey = null; SaveChangesAsync()` (ultra-rare fail → row ชี้ object ไม่มี = 404 ตอนโหลด ยอมรับได้) + **201 ต่อ** · response `UploadVersionResponse(..., PdfObjectKey is not null)` · matrix: docx put ล้ม → 502 rollback เหมือนเดิม / pdf put ล้ม → 201 hasPdf:false
|
||||
7. `GetVersions`: projection เพิ่ม `PdfObjectKey` → `HasPdf` ท้าย VersionListItem
|
||||
8. **Download ทั้งสอง endpoint เพิ่ม `string? format`**: `"pdf"` (case-insensitive; อื่น/null = docx — lenient ตามสไตล์ Clamp) → เลือก key จาก `PdfObjectKey`; ไม่มี → 404 "version นี้ไม่มีสำเนา PDF" · `DownloadVersion`: filename `.pdf` (`Path.ChangeExtension`) + content-type `application/pdf` · `GetVersionDownloadLink`: presign key ที่เลือก + **`ResponseHeaderOverrides.ContentDisposition`** ชื่อสวย (`inline; filename*=UTF-8''{base}_v{N}.pdf` / `attachment; filename*=UTF-8''{base}_v{N}.docx` — RFC 5987 สำหรับชื่อไทย, compose จาก `Document.FileName` + `VersionNumber` ฝั่ง server; add-in เจ้าเดิมก็ได้ชื่อสวยขึ้นไปด้วย) · update Swagger XML comments
|
||||
|
||||
## ขั้นที่ 2 — Add-in (`addin`)
|
||||
|
||||
1. [fileService.ts](../../addin/src/services/fileService.ts): parametrize capture เป็น private `readFileAsBlob(fileType, contentType)` (slice loop + `sliceDataToBytes` + `closeAsync` ใน finally เดิม) · `getCurrentDocumentFile()` public API เดิม · **เพิ่ม `getCurrentDocumentPdf()`** — guard `Office.context.requirements.isSetSupported("PdfFile", "1.1")` (throw ถ้าไม่รองรับ), คืน Blob type `application/pdf` · export `PDF_CONTENT_TYPE` · จับทีละ File object ตามลำดับ (Word เปิดพร้อมกันได้ 2 ตัว)
|
||||
2. [versioningService.ts](../../addin/src/services/versioningService.ts): `uploadVersion(..., pdf?: Blob)` ท้าย signature → `if (pdf) form.append("pdf", pdf, \`${documentId}.pdf\`)` · `buildDownloadUrl`/`getDownloadLink`/`downloadVersion` เพิ่ม `format?: "docx" | "pdf"` → `?format=pdf`
|
||||
3. [models/versionRecord.ts](../../addin/src/models/versionRecord.ts): `UploadVersionResult.hasPdf: boolean` · `VersionRecord.hasPdf?: boolean` (optional กัน payload เก่า)
|
||||
4. [versionFlow.ts](../../addin/src/services/versionFlow.ts) — **ทั้งสอง flow** (v1+ มี PDF ทุกตัว): แก้ `uploadCurrentFile` ให้ทำเองทั้ง capture: docx → progress "กำลังสร้างสำเนา PDF…" → capture PDF ใน try/catch (**พัง = ข้าม + `console.warn` — กติกาข้อ 10, ห้ามบล็อกการบันทึก**) + guard `pdf.size === 0` (build เก่าคืนไฟล์เปล่า — [office-js#5950](https://github.com/OfficeDev/office-js/issues/5950)) → `uploadVersion(..., pdf)` ครั้งเดียว · `SaveAndVersionResult`/`StartVersioningResult` พา `hasPdf` จาก response ตรงๆ (backend echo คือ source เดียวของ warning — ไม่ต้องมี flag ท้องถิ่น) · step แก้เลข XML part หลัง upload เดิมทั้งหมด
|
||||
5. [App.tsx](../../addin/src/taskpane/components/App.tsx): state `flowWarning` (เคลียร์พร้อม `flowSuccess`/`flowError`) — หลัง `saveAndVersion`/`startVersioning` สำเร็จ: `if (!result.hasPdf) setFlowWarning("บันทึกแล้ว แต่สร้าง PDF ไม่สำเร็จ — version นี้ไม่มีสำเนา PDF (กด Save & Version อีกครั้งเพื่อสร้าง version ใหม่ที่มี PDF)")` → render `<MessageBar intent="warning">` ใต้ success (Fluent UI v9) · `handleDownload(version, format)` + state `downloadingPdfVersionId` มิเรอร์ของเดิม (ส่ง format ให้ `getDownloadLink`/`downloadVersion`, ข้อความ/ชื่อไฟล์ fallback ตาม format)
|
||||
6. [VersionHistoryPanel.tsx](../../addin/src/taskpane/components/VersionHistoryPanel.tsx): props เพิ่ม `downloadingPdfVersionId: string | null` + `onDownloadPdf: (version) => void` · แถวที่ `version.hasPdf` → Button `DocumentPdf24Regular` label "PDF" (`size="small" appearance="subtle"`, disabled ระหว่างโหลดตัวใดตัวหนึ่ง, wrap flex column ให้อยู่รอด task pane แคบ)
|
||||
7. **manifest.xml ไม่แก้** (ข้อตกลง #7)
|
||||
|
||||
## ขั้นที่ 3 — Web (`web`)
|
||||
|
||||
1. [types/api.ts](../../web/app/types/api.ts): `VersionListItem.hasPdf: boolean` · `GenerateDocumentResponse` เดิม
|
||||
2. **สร้าง `web/app/utils/versionDownload.ts`** (pure — ทดสอบได้ตาม pattern vitest ที่มี): `versionFileName(fileName, versionNumber, format)` (strip `.docx` + `_v{N}` + `.pdf`/`.docx`) · `versionDownloadPath(documentId, versionId, format)` (`?format=pdf` เมื่อ pdf) · `wordOpenUrl(presignedUrl)` → `ms-word:ofe|u|${url}` (ตรวจระดับ encode กับ [MS-DOCSTRURI](https://learn.microsoft.com/en-us/office/client-developer/office-uri-schemes) ตอน implement)
|
||||
3. [documents/[documentId].vue](../../web/app/pages/documents/[documentId].vue) — **เปลี่ยนพฤติกรรมจาก blob-download เป็นเปิดไฟล์**: `openVersion(v, format)` = `GET download-link` (Bearer) → PDF: `window.open(url, "_blank")` · DOCX: `window.open(wordOpenUrl(url))` + heuristic `visibilitychange`/`blur` ใน ~1.5s — ไม่เกิด = `AlertBox` พร้อมลิงก์ presigned ดาวน์โหลดสำรอง · state `openingVersionId`/`downloadError` แทน `downloadingVersionId` (proxy-blob flow ของหน้านี้เลิกใช้ — endpoint proxy ยังอยู่เพราะ add-in/spec ใช้) · ปุ่ม history (ตาราง + การ์ดมือถือ): "เปิด" ทุก version + "PDF" `v-if="v.hasPdf"` · Badge แสดง v0 ได้ตามข้อมูลจริง
|
||||
4. [documents/create/[templateId].vue](../../web/app/pages/documents/create/[templateId].vue): ปุ่ม success เปลี่ยนเป็น **"เปิดใน Word"** (`ms-word` กับ v0 — next step ธรรมชาติของ flow: เปิด → แก้ → Save & Version เป็น v1) + ลิงก์ดาวน์โหลดสำรอง
|
||||
|
||||
## ขั้นที่ 4 — Tests
|
||||
|
||||
**Backend — ไฟล์ใหม่ `VersionPdfTests.cs`** (pattern `VersionMessageTests`) + **แก้ test เดิมที่ assert เลข version ของ generate** (GenerateTests ฯลฯ คาด v1 → v0, key `v0000_...`):
|
||||
1. ขยาย helper ก่อน: `TestDtos` (+`HasPdf` ทั้ง upload/history, `DownloadLinkResult`) · `ApiExtensions.VersionForm` เพิ่ม `byte[]? pdf = null` (part `pdf` + content-type) · `TestDocx.BuildPdf()` (%PDF-1.7 + payload)
|
||||
2. `Upload_WithPdf_PersistsSiblingKey_AndEchoesHasPdf` (key `{documentId}/v0001_{clientVersionId}.pdf`)
|
||||
3. `Upload_WithoutPdf_StoresNullKey_HasPdfFalse`
|
||||
4. `Upload_InvalidPdfPart_Returns400_NoSideEffect` (ชื่อไฟล์ไม่ใช่ .pdf)
|
||||
5. `DownloadPdf_ReturnsExactBytes_PdfContentType_PdfFilename`
|
||||
6. `DownloadPdf_WhenNoPdf_Returns404` · `DownloadLink_FormatPdf_NoPdf_Returns404`
|
||||
7. `DownloadLink_FormatPdf_PresignedUrlWithFilenameDisposition`
|
||||
8. `Generate_CreatesVersionZero_NoPdf` (VersionNumber == 0, PdfObjectKey null, `v0000_` key) · `UploadAfterGenerate_StartsAtVersionOne_WithPdf`
|
||||
9. Regression: default (ไม่ส่ง format) ยังเป็น docx — ครอบโดย test download เดิม
|
||||
|
||||
**Add-in (jest)**: ขยาย `installOfficeFileMock` (fileService.test) / `installOfficeDocumentMock` (versionFlow.test) เพิ่ม `FileType.Pdf` + ตัวเลือก fail · [fileService.test.ts](../../addin/src/services/fileService.test.ts): `getCurrentDocumentPdf` คืน Blob type ใช่/เรียก FileType.Pdf/slice-shape อีกรูปแบบหนึ่ง/`isSetSupported` false → reject/`closeAsync` ถูกเรียกครั้งเดียวแม้พัง · [versionFlow.test.ts](../../addin/src/services/versionFlow.test.ts): **ทั้ง saveAndVersion และ startVersioning** แนบ part `pdf` ชื่อ `.pdf` + `requestedFileTypes` เรียง compressed → pdf · `failPdf` → ยัง upload สำเร็จ ไม่มี part pdf + `hasPdf:false` · test เดิม 5 ตัวไม่ต้องแก้ (assert เฉพาะ docx) · **สร้าง `versioningService.test.ts`**: `getDownloadLink(id, vid, "pdf")` ชน `?format=pdf` · `uploadVersion` แนบ pdf เมื่อได้รับ
|
||||
|
||||
**Web (vitest)**: `web/test/versionDownload.spec.ts` — ชื่อไฟล์ v0/v1 + format, path `?format=pdf`, `wordOpenUrl` encode ถูก · component เป็น manual verify (ไม่มี component-test infra — ไม่แกล้งทดสอบ)
|
||||
|
||||
## ขั้นที่ 5 — ปิดงาน
|
||||
|
||||
1. รันชุดเต็ม: `dotnet test` (backend) · `npm test` (addin) · `npm test` + `npm run build` (web)
|
||||
2. [CLAUDE.md](../../CLAUDE.md): สถานะ Phase 0–11 · เพิ่มย่อหน้า "Version PDF (FR-15)" ในหมวดสถาปัตยกรรม + ปรับประโยค "เห็น v1" เป็นระบบ v0/v1+ · อัปเดต AGENTS.md ให้ตรงกัน
|
||||
3. [docs/acceptance-review.md](acceptance-review.md): verdict AC-18 + หลักฐาน test + รายการยืนยันมือที่เหลือ
|
||||
|
||||
## ยืนยันมือที่ทำแทนไม่ได้ (ไว้ตรวจตอนปิด phase)
|
||||
|
||||
1. Word Windows: generate จากเว็บ (ได้ **v0** ไม่มี PDF) → "เปิดใน Word" จากหน้า success (ทดสอบ `ms-word` จริง) → แก้ → Save & Version → **v1 + PDF** ไม่มี warning + ปุ่ม PDF ขึ้นเฉพาะ v1
|
||||
2. Start Versioning เอกสารนำเข้า → v1 **มี PDF** ทันที
|
||||
3. **Fidelity**: เทียบ PDF กับ File → Save As PDF ของ Word — ฟอนต์ไทย (TH Sarabun New) render + embed (Acrobat → Properties → Fonts), หัว-ท้ายกระดาษตรง
|
||||
4. **Tracked changes**: เปิด Track Changes → แก้ → Save & Version → PDF มี markup ปนไหม (ไม่มี API คุม — ถ้ามี จดใน AC-18 + spec แนะนำ accept/reject ก่อนบันทึกสำเนาศาล)
|
||||
5. Word Mac + **Word on the web**: ซ้ำข้อ 1 (web = path ใหม่ของ PdfFile; slice shape ต่าง host)
|
||||
6. **Warning path**: บังคับ PDF capture ล้ม → version ผ่าน + MessageBar เตือน + ไม่มีปุ่ม PDF ทั้งสอง UI + บันทึกใหม่ได้ PDF
|
||||
7. **เว็บเปิดไฟล์**: PDF → แท็บใหม่เรนเดอร์ทันที · docx → Word เปิดจริง (Windows) / เครื่องไม่มี handler → เห็นลิงก์สำรอง · ชื่อไฟล์สวยทั้งสองกรณี
|
||||
8. Regression: flow docx เดิม (AC-7/8/13) + history ทุกหน้าเรียง/แสดงเลขถูกตามระบบใหม่
|
||||
|
||||
## ความเสี่ยงที่รับไว้ (ตัดสินแล้ว)
|
||||
|
||||
- 100MB request cap ครอบ docx+pdf รวมกัน — คงตาม spec เดิม, เจอจริงค่อยปรับ
|
||||
- `ms-word:` พึ่ง protocol handler ของเครื่องผู้ใช้ — Windows นิ่ง, Mac อาจต้องยืนยัน/พัง → heuristic fallback เสมอ
|
||||
- apiClient ไม่มี timeout (เป็นมาก่อนกับ docx) — ไม่ขยายงานนี้
|
||||
- Dev data เก่าใช้เลขเดิม (generate=v1) — ยอมรับได้เพราะยังไม่มี production หรือล้าง DB dev
|
||||
|
||||
## Implementation record — 2026-09-08
|
||||
|
||||
- [x] ขั้น 0: spec v7 + Phase 11 + glossary + ADR
|
||||
- [x] Backend: PdfObjectKey migration, optional PDF validation/storage/download, hasPdf, generate v0
|
||||
- [x] Add-in: PDF ทั้งสอง flow + warnings + history PDF; ไม่มี blob fallback ใน task pane
|
||||
- [x] Web: shared `useVersionOpen`, เปิด Word/PDF + fallback + pending-tab cleanup
|
||||
- [x] Automated checks: backend 68 / add-in 63 / web 55; builds ผ่าน, add-in lint ผ่าน
|
||||
- [x] QA รอบยืนยัน — PASS
|
||||
- [x] Manual acceptance บน Word for Mac ผ่าน (2026-09-08) — ผลละเอียดในหมวด Manual acceptance ด้านล่าง; Windows / Word on the web คงค้างตามรายการเดิม
|
||||
|
||||
ข้อปรับรายละเอียดจากแผน: ใช้ `DocumentPdf24Regular` ที่ package มีจริง; คง signed URL โดยไม่ encode ทั้งก้อนตาม [Microsoft Office URI Schemes](https://learn.microsoft.com/en-us/office/client-developer/office-uri-schemes); reserve PDF tab ก่อน await เพื่อรองรับ popup policy; เพิ่ม composable lifecycle tests โดยไม่เพิ่ม component-test framework.
|
||||
|
||||
## QA
|
||||
|
||||
### Dev database follow-up — 2026-09-08
|
||||
|
||||
พบ `42703: column "PdfObjectKey" does not exist` ตอน Generate: migration ถูกสร้างและผ่าน Testcontainers แต่ยังไม่ได้ apply กับฐานข้อมูล dev `wordversioning`. ตรวจซ้ำด้วย `SELECT "PdfObjectKey" FROM "DocumentVersions" LIMIT 0` แล้วได้ error เดียวกัน; migration history มีถึง `AddTemplateFieldEditor` เท่านั้น และ config ชี้ฐานข้อมูลเดียวกัน. ลง `20260908053250_AddVersionPdf` ใน dev แล้วสำเร็จ. ก่อนทดสอบกับฐานข้อมูลเดิมหลังเพิ่ม migration ต้องรัน `dotnet ef database update` ตาม README; automated tests ที่สร้าง DB ใหม่ไม่ยืนยันสถานะ migration ของ dev DB.
|
||||
|
||||
2026-09-08 · fresh-eyes reviewer `qa_fr15` · 2 rounds · พบ 3 ข้อ แก้ครบ 3 ไม่มี rejected findings.
|
||||
|
||||
1. Build: test ใช้ `.at(-1)` เกิน TS target → indexed access; build ผ่าน
|
||||
2. Lifecycle: pending PDF tab ค้างเมื่อออกจากหน้า → ปิดทันทีตอน reset/unmount; test ครอบ cleanup/late response
|
||||
3. Copy: PDF inline แต่ข้อความบอกดู Downloads → ข้อความระบุเปิด PDF ใน browser
|
||||
|
||||
**Final verdict: PASS (implementation/automated QA + manual acceptance บน Word for Mac).** Phase 11 ปิดแล้ว.
|
||||
|
||||
## Manual acceptance — 2026-09-08 (Word for Mac)
|
||||
|
||||
ผู้ใช้ยืนยันผ่านครบตามรายการ "ยืนยันมือที่ทำแทนไม่ได้" บน Mac (ข้อ 1–4, 6–8):
|
||||
|
||||
- **ข้อ 1–2 flow:** เว็บ generate → v0 ไม่มี PDF → เปิดใน Word → Save & Version → **v1 + PDF** ไม่มี warning, ปุ่ม PDF ขึ้นเฉพาะ v1 · Start Versioning เอกสารนำเข้า → v1 มี PDF ทันที
|
||||
- **ข้อ 3 fidelity:** เทียบ PDF จาก add-in กับ File → Save As PDF ของ Word — ฟอนต์ไทย (TH Sarabun New) render + embed ครบ, หัว-ท้ายกระดาษตรง
|
||||
- **ข้อ 4 Track Changes:** เปิด Track Changes ทิ้งไว้ → แก้ → Save & Version → **PDF ไม่มี markup ของ revision ปน** — ไม่มีข้อจำกัดต้องบันทึกเพิ่มใน AC-18/spec
|
||||
- **ข้อ 6 warning path · ข้อ 7 เว็บเปิดไฟล์** (PDF แท็บใหม่, docx ผ่าน `ms-word` พร้อม fallback, ชื่อไฟล์สวย) **· ข้อ 8 regression** flow docx เดิม — ผ่าน
|
||||
- **ค้างตามเดิม (ข้อ 5):** Word for Windows + Word on the web — ต้องมีเครื่อง/บัญชีทดสอบ เป็นข้อค้างเดิมตั้งแต่ Phase 8 (AC-1 ฯลฯ) ไม่ใช่สิ่งที่ Phase 11 เพิ่มเข้ามา
|
||||
Loading…
Add table
Add a link
Reference in a new issue