/* global Word, Office, console */ import * as authService from "./authService"; import { getCurrentDocumentFile, getCurrentDocumentPdf } from "./fileService"; import { generateGuid, readContentControlFields, readVersionHistoryFromXml, stampTrackedProperties, writeVersionHistoryToXml, XmlVersionEntry, } from "./metadataService"; import { uploadVersion } from "./versioningService"; /** * Orchestration ของ flow หลักสองอันของ add-in (แยกจาก UI เพื่อ unit test ได้): * * - `startVersioning` (FR-1 "Start Versioning This Document") — เอกสารใหม่ที่ยังไม่ถูก track * - `saveAndVersion` (FR-2) — ไล่ตามลำดับใน spec แบบเคร่งครัด: * stamp properties → XML part (delete-then-add, fail ได้เงียบๆ) → document.save() * → ดึงไฟล์ + extract fields (FR-11) → POST version → คืนผลให้ UI แสดง/refresh history * * หมายเหตุค่า id ใน metadata: `LastVersionId` และ `` ใน XML part เก็บ * **clientVersionId** (GUID ที่ client generate) เพราะต้อง stamp ลงไฟล์ *ก่อน* upload — * versionId ของ backend จะได้มาหลัง upload ซึ่งไฟล์ถูก capture ไปแล้ว * (backend คือ source of truth ของ id จริงอยู่ดี) */ export interface StartVersioningResult { hasPdf: boolean; documentId: string; versionId: string; versionNumber: number; } export interface SaveAndVersionResult { hasPdf: boolean; versionId: string; versionNumber: number; } /** เลข version ถัดไปตามที่ client เดา (history ล่าสุด + 1) — ใช้เขียน XML part ก่อนรู้ค่าจริงจาก backend */ function predictNextVersionNumber(latestVersionNumber: number): number { return latestVersionNumber + 1; } /** ชื่อไฟล์จาก URL ของ document ถ้าดึงได้ (desktop มักเป็น path จริง) — ใช้เป็นชื่อตอน upload */ function guessDocumentFileName(): string | undefined { try { const url = Office.context.document.url; if (!url) { return undefined; } const name = url.split(/[\\/]/).pop(); if (!name) { return undefined; } return name.toLowerCase().endsWith(".docx") ? name : `${name}.docx`; } catch { return undefined; } } /** `context.document.save(Word.SaveBehavior.save)` — trigger native save ตาม FR-2 ขั้นตอนที่ 4 */ async function saveDocument(): Promise { await Word.run(async (context) => { context.document.save(Word.SaveBehavior.save); await context.sync(); }); } async function uploadCurrentFile( documentId: string, clientVersionId: string, fields: Record, message?: string, onProgress?: (progress: string) => void ): Promise { const file = await getCurrentDocumentFile(); 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 * (FR-13: message คือ commit message ทางเลือกจาก dialog ยืนยัน) */ export async function startVersioning( message?: string, onProgress?: (progress: string) => void ): Promise { const documentId = generateGuid(); const clientVersionId = generateGuid(); const timestamp = new Date().toISOString(); // 1) stamp รอบแรก — ได้ทั้ง SavedByAddIn และ DocumentTrackingId ใหม่ onProgress?.("กำลังบันทึก metadata ของเอกสาร…"); await stampTrackedProperties({ documentTrackingId: documentId, lastVersionId: clientVersionId, lastVersionTimestamp: timestamp, }); // 2) XML part (secondary — fail ได้เงียบๆ) onProgress?.("กำลังอัปเดต version history ในไฟล์…"); await writeVersionHistoryToXml([ { id: clientVersionId, number: 1, timestamp, user: authService.getUsername() ?? "unknown" }, ]); // 3) native save แล้ว capture ไฟล์ + fields onProgress?.("กำลังบันทึกไฟล์…"); await saveDocument(); onProgress?.("กำลังอ่านค่า fields จากเอกสาร…"); const fields = await readContentControlFields(); // 4) upload version แรก onProgress?.("กำลังอัปโหลด version 1…"); const result = await uploadCurrentFile(documentId, clientVersionId, fields, message, onProgress); return { documentId, ...result }; } /** FR-2 — Save & Version ของเอกสารที่ track อยู่แล้ว (เรียกหลังผู้ใช้กดยืนยันใน dialog แล้ว) * (FR-13: message คือ commit message ทางเลือกจาก dialog ยืนยัน) */ export async function saveAndVersion( documentId: string, latestVersionNumber: number, message?: string, onProgress?: (progress: string) => void ): Promise { const clientVersionId = generateGuid(); const timestamp = new Date().toISOString(); const predictedNumber = predictNextVersionNumber(latestVersionNumber); const user = authService.getUsername() ?? "unknown"; // 1) stamp properties (SavedByAddIn + DocumentTrackingId ถูก stamp ไว้แล้วจากรอบแรก — เขียนทับไม่เสียหาย) onProgress?.("กำลังบันทึก metadata ของ version ใหม่…"); await stampTrackedProperties({ lastVersionId: clientVersionId, lastVersionTimestamp: timestamp, }); // 2) XML part: เพิ่ม entry ของ version ใหม่ไว้ต้นสุด=ใหม่→เก่า ตามรูปใน spec (เดาเลขก่อน upload) — fail ได้เงียบๆ onProgress?.("กำลังอัปเดต version history ในไฟล์…"); const entries = await readVersionHistoryFromXml(); entries.unshift({ id: clientVersionId, number: predictedNumber, timestamp, user }); await writeVersionHistoryToXml(entries); // 3) native save → capture ไฟล์ + fields ปัจจุบัน (FR-11) onProgress?.("กำลังบันทึกไฟล์…"); await saveDocument(); onProgress?.("กำลังอ่านค่า fields จากเอกสาร…"); const fields = await readContentControlFields(); // 4) upload onProgress?.("กำลังอัปโหลด version ใหม่…"); const result = await uploadCurrentFile(documentId, clientVersionId, fields, message, onProgress); // 5) เลขจริงจาก backend ต่างจากที่เดา (มี client อื่นแทรก) → แก้ XML part ให้ตรง (best effort) if (result.versionNumber !== predictedNumber) { const corrected = await readVersionHistoryFromXml(); const entry = corrected.find((item: XmlVersionEntry) => item.id === clientVersionId); if (entry) { entry.number = result.versionNumber; corrected.sort((a, b) => b.number - a.number); // คงลำดับใหม่→เก่าหลังแก้เลข await writeVersionHistoryToXml(corrected); } } return result; }