Phase 12 v9: draft opens in Word + Thai word-breaking fix (FR-16)
แก้ตามผลใช้งานจริง 2 ประเด็น: 1. เปิดใน Word ทันที — generate-draft เปลี่ยนจากคืนไฟล์ตรงๆ เป็นเก็บ ชั่วคราวใต้ drafts/ (lifecycle ลบรายวัน, config Minio__DraftRetentionDays) + คืน presigned URL ให้เว็บเปิดผ่าน ms-word:ofe พร้อมลิงก์สำรอง (useVersionOpen.openDocxUrl แยกแกนเปิด docx URL ใช้ร่วมกับ flow v0) 2. ตัดคำไทย — T14 แตก placeholder เป็น [ / ข้อความ / ] โดย run ข้อความ ถือ w:hint="cs" + w:cs เท่านั้น; เดิมเขียนค่าลง run ปีกกาทำให้ Word จัดหน้าไทยผิด — แก้เป็นเขียนที่ run ข้อความยาวสุด + SDT เปล่าสืบทอด rPr จาก sdtPr (ใช้ร่วมทั้ง generate และ generate-draft) - ถอดของที่เลิกใช้: CORS expose Content-Disposition, downloadFile init, useApi.spec · Spec v9 + ADR 0002 Amendment + acceptance-review AC-19 - Tests: backend 84 / web 56 + build; QA reviewer PASS (2 rounds, 10 findings แก้ครบ — รวม bug จริงที่ test ใหม่จับได้: lifecycle Rules null ทำ startup ตั้ง rule ล้มแบบเงียบ) · ค้าง manual บน Word จริง Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
b3c1c667f7
commit
70f71f4c5c
23 changed files with 408 additions and 241 deletions
|
|
@ -305,11 +305,12 @@ public class TemplatesController(
|
|||
return Ok(new ReplaceTemplateFileResponse(template.Id, FieldDtos(template), addedTags, removedTags));
|
||||
}
|
||||
|
||||
/// <summary>FR-16 — ไฟล์ร่าง (draft): generate docx จาก template แบบ stateless — ไม่สร้าง
|
||||
/// Document/DocumentVersion/DocumentFieldValue และไม่เขียน MinIO; คืนไฟล์ทันที
|
||||
/// (ผู้ใช้เปิดใน Word แล้วกด Start Versioning เพื่อเริ่ม track เป็น v1 พร้อมผูกกลับมาที่ template นี้)</summary>
|
||||
/// <summary>FR-16 — ไฟล์ร่าง (draft): generate docx จาก template โดยไม่สร้าง
|
||||
/// Document/DocumentVersion/DocumentFieldValue ใน DB — เก็บไฟล์ชั่วคราวใน bucket versions ใต้ prefix
|
||||
/// `drafts/` (หมดอายุรายวันตาม lifecycle) แล้วคืน presigned URL อายุสั้นให้เว็บเปิดใน Word
|
||||
/// ผ่าน ms-word:ofe พร้อมลิงก์ดาวน์โหลดสำรอง (v9 — ตอนแรกคืนไฟล์ตรงๆ แต่ผู้ใช้ต้องเปิด Word เอง)</summary>
|
||||
[HttpPost("{id:guid}/generate-draft")]
|
||||
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(GenerateDraftResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status502BadGateway)]
|
||||
|
|
@ -366,11 +367,32 @@ public class TemplatesController(
|
|||
return StatusCode(StatusCodes.Status502BadGateway, "ไฟล์ template เสียหาย (เปิดด้วย OpenXML ไม่ได้)");
|
||||
}
|
||||
|
||||
logger.LogInformation("Draft generated templateId={TemplateId} userId={UserId} bytes={Size}",
|
||||
template.Id, currentUser.UserId, fileBytes.Length);
|
||||
// เก็บชั่วคราวใต้ drafts/ — lifecycle ลบให้เอง (Program.cs ตั้ง rule ตอน startup)
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var objectKey = $"drafts/{template.Id}/{now:yyyyMMddHHmmss}_{Guid.NewGuid():N}.docx";
|
||||
try
|
||||
{
|
||||
await storage.PutAsync(minioOptions.Value.VersionsBucket, objectKey,
|
||||
new MemoryStream(fileBytes), DocxUploadValidation.DocxContentType, ct);
|
||||
}
|
||||
catch (AmazonServiceException ex)
|
||||
{
|
||||
logger.LogError(ex, "Draft upload failed templateId={TemplateId} userId={UserId}",
|
||||
template.Id, currentUser.UserId);
|
||||
return StatusCode(StatusCodes.Status502BadGateway, "อัปโหลดไฟล์ขึ้น storage ไม่สำเร็จ");
|
||||
}
|
||||
|
||||
return File(fileBytes, DocxUploadValidation.DocxContentType,
|
||||
DocxGenerationService.BuildGeneratedFileName(template.Name, DateTimeOffset.UtcNow));
|
||||
// presigned อายุสั้น + ชื่อไฟล์ไทยตาม RFC 5987 (นโยบายเดียวกับ download-link ของ version)
|
||||
var expirySeconds = Math.Clamp(minioOptions.Value.PresignedExpirySeconds, 30, 600);
|
||||
var fileName = DocxGenerationService.BuildGeneratedFileName(template.Name, now);
|
||||
var url = storage.GetPresignedGetUrl(
|
||||
minioOptions.Value.VersionsBucket, objectKey, TimeSpan.FromSeconds(expirySeconds),
|
||||
$"attachment; filename*=UTF-8''{Uri.EscapeDataString(fileName)}");
|
||||
|
||||
logger.LogInformation("Draft generated templateId={TemplateId} userId={UserId} bytes={Size} key={ObjectKey}",
|
||||
template.Id, currentUser.UserId, fileBytes.Length, objectKey);
|
||||
|
||||
return Ok(new GenerateDraftResponse(url, now.AddSeconds(expirySeconds)));
|
||||
}
|
||||
|
||||
/// <summary>deactivate (soft-delete) — document เก่าที่อ้างอยู่ยังใช้ได้ต่อ แต่สร้างเอกสารใหม่ไม่ได้</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue