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:
Suphonchai Phoonsawat 2026-09-08 14:01:13 +07:00
parent 618328ffa7
commit 39d610a781
39 changed files with 1387 additions and 212 deletions

View file

@ -1,7 +1,7 @@
/* global Word, Office */
/* global Word, Office, console */
import * as authService from "./authService";
import { getCurrentDocumentFile } from "./fileService";
import { getCurrentDocumentFile, getCurrentDocumentPdf } from "./fileService";
import {
generateGuid,
readContentControlFields,
@ -27,12 +27,14 @@ import { uploadVersion } from "./versioningService";
*/
export interface StartVersioningResult {
hasPdf: boolean;
documentId: string;
versionId: string;
versionNumber: number;
}
export interface SaveAndVersionResult {
hasPdf: boolean;
versionId: string;
versionNumber: number;
}
@ -71,10 +73,29 @@ async function uploadCurrentFile(
documentId: string,
clientVersionId: string,
fields: Record<string, string>,
message?: string
): Promise<{ versionId: string; versionNumber: number }> {
message?: string,
onProgress?: (progress: string) => void
): Promise<SaveAndVersionResult> {
const file = await getCurrentDocumentFile();
return uploadVersion(documentId, file, clientVersionId, fields, guessDocumentFileName(), message);
let pdf: Blob | undefined;
onProgress?.("กำลังสร้างสำเนา PDF…");
try {
pdf = await getCurrentDocumentPdf();
if (pdf.size === 0) throw new Error("Word คืนไฟล์ PDF ว่าง");
} catch (error) {
console.warn("PDF capture failed:", error);
pdf = undefined;
}
onProgress?.("กำลังอัปโหลด version…");
return uploadVersion(
documentId,
file,
clientVersionId,
fields,
guessDocumentFileName(),
message,
pdf
);
}
/** FR-1 — เริ่ม track เอกสารปัจจุบัน: stamp รอบแรก + upload เป็น version 1
@ -109,7 +130,7 @@ export async function startVersioning(
// 4) upload version แรก
onProgress?.("กำลังอัปโหลด version 1…");
const result = await uploadCurrentFile(documentId, clientVersionId, fields, message);
const result = await uploadCurrentFile(documentId, clientVersionId, fields, message, onProgress);
return { documentId, ...result };
}
@ -147,7 +168,7 @@ export async function saveAndVersion(
// 4) upload
onProgress?.("กำลังอัปโหลด version ใหม่…");
const result = await uploadCurrentFile(documentId, clientVersionId, fields, message);
const result = await uploadCurrentFile(documentId, clientVersionId, fields, message, onProgress);
// 5) เลขจริงจาก backend ต่างจากที่เดา (มี client อื่นแทรก) → แก้ XML part ให้ตรง (best effort)
if (result.versionNumber !== predictedNumber) {