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

@ -9,12 +9,15 @@ public class DocumentVersion
public Document? Document { get; set; }
/// <summary>เริ่มที่ 1 — auto increment ต่อ document (unique index กัน race)</summary>
/// <summary>generate เริ่ม 0; add-in เริ่ม 1 — auto increment ต่อ document (unique index กัน race)</summary>
public int VersionNumber { get; set; }
/// <summary>MinIO object key: {documentId}/v{VersionNumber:D4}_{clientVersionId}.docx</summary>
public string ObjectKey { get; set; } = string.Empty;
/// <summary>FR-15: null เมื่อไม่มี PDF (v0 หรือ capture/upload ล้ม)</summary>
public string? PdfObjectKey { get; set; }
/// <summary>SHA256 hex ของไฟล์ (ตรวจ integrity ภายหลัง)</summary>
public string ContentHash { get; set; } = string.Empty;

View file

@ -9,6 +9,8 @@ public class UploadVersionRequest
[Required]
public IFormFile File { get; set; } = null!;
public IFormFile? Pdf { get; set; }
/// <summary>GUID ที่ client generate ต่อ version — ใช้เป็นส่วนหนึ่งของ object key</summary>
[Required]
public string ClientVersionId { get; set; } = string.Empty;
@ -20,7 +22,7 @@ public class UploadVersionRequest
public string? Message { get; set; }
}
public record UploadVersionResponse(Guid VersionId, int VersionNumber);
public record UploadVersionResponse(Guid VersionId, int VersionNumber, bool HasPdf);
/// <summary>body ของ POST /api/documents/generate (FR-10)</summary>
public class GenerateDocumentRequest
@ -45,7 +47,8 @@ public record VersionListItem(
string CreatedByUserId,
string ContentHash,
object? Fields,
string? Message);
string? Message,
bool HasPdf);
/// <summary>response ของ GET .../download-link — presigned URL อายุสั้นให้เปิดในเบราว์เซอร์ของผู้ใช้</summary>
public record VersionDownloadLinkResponse(string Url, DateTimeOffset ExpiresAt);