Refactor document versioning to extract fields from uploaded .docx files on the server side

- Removed the `fields` parameter from the version upload form, as it is now extracted from the uploaded .docx file using the `extractContentControls` function.
- Updated related types and interfaces to reflect the new structure of version fields.
- Modified the OpenAPI specification to document the changes in the versioning process.
- Adjusted tests to ensure compliance with the new behavior, including validation for the absence of the `fields` parameter.
- Implemented a new function `extractDocumentSnapshot` to handle the extraction of content controls and document properties from .docx files.
- Updated metadata and versioning services to align with the new extraction logic.
This commit is contained in:
Suphonchai Phoonsawat 2026-10-31 10:21:17 +07:00
parent bc61f0fd2e
commit cdae1ceedd
17 changed files with 380 additions and 179 deletions

View file

@ -10,6 +10,8 @@
* ไว้เพื่อ signature เดิมเท่านั้น ไม่ถูกส่งถึง server · templateId ก็เช่นกัน (มาจาก claims
* ของ token — ส่งมาใน form = 400)
* - **ห้ามส่ง userId/userName/templateId ใน FormData** (contract 2026-10-22 — ส่งมา = 400)
* - **ห้ามส่ง fields ใน FormData เช่นกัน** (contract 2026-10-30) — server แกะค่า Content
* Control จากไฟล์ .docx ที่อัปโหลดเองด้วยตัวเดียวกับ /api/template/extract)
* - mock ยัง persist metadata ลง localStorage ต่อ documentId เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่
* ในโหมด demo — blob ไฟล์จริงเฉพาะ session; localStorage เข้าไม่ได้ (webview บางตัว block)
* = ใช้ memory อย่างเดียว ไม่ throw
@ -18,13 +20,14 @@
import * as authService from "./authService";
import { generateGuid } from "./metadataService";
import { ApiError, apiGetJson, apiSendExpectJson } from "./apiClient";
import { UploadVersionResult, VersionRecord } from "../models/versionRecord";
import { ExtractSnapshotFields, UploadVersionResult, VersionRecord } from "../models/versionRecord";
// ---------------------------------------------------------------------------
// ของจริง — กลุ่ม docver (Bearer จาก ticket auth แนบอัตโนมัติโดย apiClient)
// ---------------------------------------------------------------------------
/** DTO ของ GET /api/documents/versions (ใหม่ → เก่า) — PLAN-DOCVER.md §1 */
/** DTO ของ GET /api/documents/versions (ใหม่ → เก่า) — PLAN-DOCVER.md §1
* fields = ก้อนเดียวกับ return ของ /api/template/extract (server แกะจากไฟล์เอง) */
interface DocumentVersionDTO {
versionId: string;
versionNumber: number;
@ -32,14 +35,16 @@ interface DocumentVersionDTO {
createdByUserId: string;
createdByUserName: string;
contentHash: string;
fields: Record<string, string> | null;
fields: ExtractSnapshotFields | null;
message: string | null;
hasPdf: boolean;
}
/** FR-5 — อัปโหลดไฟล์ปัจจุบันเป็น version ใหม่ (multipart POST)
* + FR-13: message คือ commit message ทางเลือก
* + FR-16: templateId เก็บไว้ตาม signature เดิม — ไม่ส่งถึง server (มาจาก claims ของ token) */
* + FR-16: templateId เก็บไว้ตาม signature เดิม — ไม่ส่งถึง server (มาจาก claims ของ token)
* + fields ก็เก็บไว้ตาม signature เดิมเช่นกัน — ไม่ส่งถึง server (contract 2026-10-30:
* server แกะจากไฟล์ .docx เองด้วยตัวเดียวกับ /api/template/extract ส่งมา = 400) */
export async function uploadVersion(
documentId: string,
file: Blob,
@ -52,6 +57,7 @@ export async function uploadVersion(
): Promise<UploadVersionResult> {
if (!authService.isDemoMode()) {
void templateId; // contract 2026-10-22 — ห้ามส่ง (identity ทั้งหมดจาก Bearer token)
void fields; // contract 2026-10-30 — ห้ามส่ง (server แกะจากไฟล์เอง ดูด้านบน)
const form = new FormData();
form.append("file", file, fileName ?? "document.docx");
// pdf optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มีสำเนา PDF
@ -61,9 +67,6 @@ export async function uploadVersion(
if (clientVersionId) {
form.append("clientVersionId", clientVersionId);
}
if (fields && Object.keys(fields).length > 0) {
form.append("fields", JSON.stringify(fields));
}
const trimmedMessage = message?.trim();
if (trimmedMessage) {
form.append("message", trimmedMessage);