Phase 12: draft generation (FR-16)

ไฟล์ร่าง (draft) — generate docx จาก template แบบ stateless ไม่สร้าง
Document/version; stamp เฉพาะ custom property TemplateId เพื่อให้ add-in
เห็นเป็น untracked; Start Versioning ส่ง form field templateId ให้ backend
ผูก template ตอน auto-create — เอกสารเริ่มที่ v1 (ไม่มี v0) + field sync เต็มระบบ

- Backend: POST /api/templates/{id}/generate-draft (200 คืนไฟล์) +
  DocxGenerationService.GenerateDraft + แยก TemplateFieldValidation ใช้ร่วมกับ
  generate + UploadVersion รับ templateId (non-GUID/unknown → 400,
  deactivated → ผูกได้, document เดิม → เฉย) + CORS expose Content-Disposition
- Add-in: readTrackedProperties อ่าน TemplateId + ส่ง templateId เฉพาะ
  startVersioning + hint ไฟล์ร่างใน task pane
- Web: ปุ่ม "สร้างไฟล์ร่าง" บนฟอร์มเดิม + downloadFile รับ init (POST → blob)
- Docs: spec v8 (FR-16, AC-19) + Phase 12 + CONTEXT.md + ADR 0002 +
  acceptance-review AC-19
- Tests: backend 82 / add-in 66 / web 59 + build ครบ; QA reviewer PASS
  (2 rounds); ค้าง manual บน Word จริง

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Suphonchai Phoonsawat 2026-09-10 10:51:13 +07:00
parent 0edb70fd38
commit b3c1c667f7
30 changed files with 879 additions and 93 deletions

View file

@ -31,9 +31,11 @@ public record DocDetail(Guid Id, string FileName, DateTimeOffset CreatedAt, Guid
public static class ApiExtensions
{
/// <summary>multipart form ของ POST versions: file + clientVersionId + fields (option) + message (option)</summary>
/// <summary>multipart form ของ POST versions: file + clientVersionId + fields (option) + message (option)
/// + templateId (option — FR-16 ไฟล์ร่าง: ส่งเฉพาะตอน Start Versioning)</summary>
public static MultipartFormDataContent VersionForm(
byte[] docx, string fileName, string clientVersionId, string? fieldsJson = null, string? message = null, byte[]? pdf = null)
byte[] docx, string fileName, string clientVersionId, string? fieldsJson = null, string? message = null,
byte[]? pdf = null, string? templateId = null)
{
var form = new MultipartFormDataContent();
var file = new ByteArrayContent(docx);
@ -50,6 +52,8 @@ public static class ApiExtensions
part.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(part, "pdf", "document.pdf");
}
if (templateId is not null)
form.Add(new StringContent(templateId), "templateId");
return form;
}