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:
parent
0edb70fd38
commit
b3c1c667f7
30 changed files with 879 additions and 93 deletions
|
|
@ -19,6 +19,8 @@ export const SAVED_BY_ADD_IN_PROPERTY = "SavedByAddIn";
|
|||
export const DOCUMENT_TRACKING_ID_PROPERTY = "DocumentTrackingId";
|
||||
export const LAST_VERSION_ID_PROPERTY = "LastVersionId";
|
||||
export const LAST_VERSION_TIMESTAMP_PROPERTY = "LastVersionTimestamp";
|
||||
/** FR-16 — stamp เฉพาะไฟล์ร่าง (draft) ที่เว็บสร้าง: template ต้นทาง (ค่าเดียวกับ AddInMetadata.TemplateIdPropertyName ฝั่ง backend) */
|
||||
export const TEMPLATE_ID_PROPERTY = "TemplateId";
|
||||
|
||||
/** ค่า property ที่หาไม่เจอเป็น `undefined` (ไม่ใช่ throw) */
|
||||
export interface TrackedDocumentProperties {
|
||||
|
|
@ -26,6 +28,8 @@ export interface TrackedDocumentProperties {
|
|||
documentTrackingId?: string;
|
||||
lastVersionId?: string;
|
||||
lastVersionTimestamp?: string;
|
||||
/** มีเฉพาะไฟล์ร่าง (FR-16) — ส่งต่อให้ backend ตอน Start Versioning เพื่อผูก Document กับ template */
|
||||
templateId?: string;
|
||||
}
|
||||
|
||||
/** item ใน Custom XML Part `<VersionHistory>` */
|
||||
|
|
@ -36,7 +40,7 @@ export interface XmlVersionEntry {
|
|||
user: string;
|
||||
}
|
||||
|
||||
/** อ่าน custom properties ทั้ง 4 ตัวที่ระบบสนใจ */
|
||||
/** อ่าน custom properties ทั้ง 5 ตัวที่ระบบสนใจ (4 ตัวของ tracked + TemplateId ของไฟล์ร่าง FR-16) */
|
||||
export async function readTrackedProperties(): Promise<TrackedDocumentProperties> {
|
||||
return Word.run(async (context) => {
|
||||
const properties = context.document.properties.customProperties;
|
||||
|
|
@ -44,11 +48,13 @@ export async function readTrackedProperties(): Promise<TrackedDocumentProperties
|
|||
const documentTrackingId = properties.getItemOrNullObject(DOCUMENT_TRACKING_ID_PROPERTY);
|
||||
const lastVersionId = properties.getItemOrNullObject(LAST_VERSION_ID_PROPERTY);
|
||||
const lastVersionTimestamp = properties.getItemOrNullObject(LAST_VERSION_TIMESTAMP_PROPERTY);
|
||||
const templateId = properties.getItemOrNullObject(TEMPLATE_ID_PROPERTY);
|
||||
// load "value" ของ null-object proxy ก็ทำให้ isNullObject แม่นหลัง sync
|
||||
savedByAddIn.load("value");
|
||||
documentTrackingId.load("value");
|
||||
lastVersionId.load("value");
|
||||
lastVersionTimestamp.load("value");
|
||||
templateId.load("value");
|
||||
await context.sync();
|
||||
|
||||
const read = (property: Word.CustomProperty): string | undefined =>
|
||||
|
|
@ -61,11 +67,15 @@ export async function readTrackedProperties(): Promise<TrackedDocumentProperties
|
|||
documentTrackingId: read(documentTrackingId),
|
||||
lastVersionId: read(lastVersionId),
|
||||
lastVersionTimestamp: read(lastVersionTimestamp),
|
||||
templateId: read(templateId),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** FR-1 — ไฟล์นี้ผ่าน add-in นี้หรือเปล่า (รวมไฟล์ที่ generate จากเว็บ ที่ backend stamp ไว้แล้ว) */
|
||||
/** FR-1 — ไฟล์นี้ผ่าน add-in นี้หรือเปล่า (รวมไฟล์ที่ generate จากเว็บ ที่ backend stamp ไว้แล้ว)
|
||||
* หมายเหตุ: App.detect() ทำนายค่านี้ inline จาก readTrackedProperties() อยู่แล้ว (ต้องอ่าน properties
|
||||
* ครั้งเดียวทั้งชุดเพื่อให้ไฟล์ร่าง untracked ได้อ่าน TemplateId ด้วย — FR-16) ฟังก์ชันนี้จึงเหลือเป็น
|
||||
* service API ที่มี test คุม semantics ของ FR-1 ไว้ */
|
||||
export async function isTrackedDocument(): Promise<boolean> {
|
||||
const properties = await readTrackedProperties();
|
||||
return properties.savedByAddIn === ADDIN_SIGNATURE_GUID;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue