- เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
94 lines
3.8 KiB
C#
94 lines
3.8 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using WordVersioning.Api.Models;
|
|
|
|
namespace WordVersioning.Api.Data;
|
|
|
|
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
|
|
{
|
|
public DbSet<Document> Documents => Set<Document>();
|
|
|
|
public DbSet<DocumentVersion> DocumentVersions => Set<DocumentVersion>();
|
|
|
|
public DbSet<Template> Templates => Set<Template>();
|
|
|
|
public DbSet<TemplateField> TemplateFields => Set<TemplateField>();
|
|
|
|
public DbSet<DocumentFieldValue> DocumentFieldValues => Set<DocumentFieldValue>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.Entity<Document>(entity =>
|
|
{
|
|
entity.HasKey(x => x.Id);
|
|
entity.Property(x => x.FileName).HasMaxLength(512);
|
|
entity.Property(x => x.AddInSignature).HasMaxLength(64);
|
|
|
|
// FR-10: เอกสารผูกกับ template — Restrict เพราะ template ต้อง soft-delete เท่านั้น
|
|
// (แถว Template ไม่ควรถูกลบจริงตรงๆ แต่กันไว้เผื่อมือสั่น)
|
|
entity.HasOne(x => x.Template)
|
|
.WithMany(t => t.Documents)
|
|
.HasForeignKey(x => x.TemplateId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
|
|
modelBuilder.Entity<DocumentVersion>(entity =>
|
|
{
|
|
entity.HasKey(x => x.Id);
|
|
|
|
// กัน versionNumber ซ้ำตอน upload พร้อมกัน (retry ที่ controller เมื่อชน constraint)
|
|
entity.HasIndex(x => new { x.DocumentId, x.VersionNumber }).IsUnique();
|
|
|
|
entity.Property(x => x.ObjectKey).HasMaxLength(1024);
|
|
entity.Property(x => x.PdfObjectKey).HasMaxLength(1024);
|
|
entity.Property(x => x.ContentHash).HasMaxLength(64);
|
|
entity.Property(x => x.Message).HasMaxLength(2048); // FR-13 commit message — นโยบายเดียวกับ Template.Description
|
|
|
|
entity.HasOne(x => x.Document)
|
|
.WithMany(d => d.Versions)
|
|
.HasForeignKey(x => x.DocumentId);
|
|
});
|
|
|
|
modelBuilder.Entity<Template>(entity =>
|
|
{
|
|
entity.HasKey(x => x.Id);
|
|
entity.Property(x => x.Name).HasMaxLength(256);
|
|
entity.Property(x => x.Description).HasMaxLength(2048);
|
|
entity.Property(x => x.ObjectKey).HasMaxLength(1024);
|
|
|
|
entity.HasMany(x => x.Documents)
|
|
.WithOne(d => d.Template)
|
|
.HasForeignKey(d => d.TemplateId);
|
|
});
|
|
|
|
modelBuilder.Entity<TemplateField>(entity =>
|
|
{
|
|
entity.HasKey(x => x.Id);
|
|
|
|
// Key = SDT tag ต้อง unique ต่อ template (spec ส่วน 9 เพิ่มเติม v2)
|
|
entity.HasIndex(x => new { x.TemplateId, x.Key }).IsUnique();
|
|
|
|
entity.Property(x => x.Key).HasMaxLength(256);
|
|
entity.Property(x => x.Label).HasMaxLength(512);
|
|
entity.Property(x => x.Type).HasMaxLength(16);
|
|
entity.Property(x => x.Editor).HasMaxLength(16);
|
|
|
|
entity.HasOne(x => x.Template)
|
|
.WithMany(t => t.Fields)
|
|
.HasForeignKey(x => x.TemplateId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
modelBuilder.Entity<DocumentFieldValue>(entity =>
|
|
{
|
|
// latest value ต่อ (document, field) — ตาม spec ส่วน 7 ไม่มี Id แยก
|
|
entity.HasKey(x => new { x.DocumentId, x.FieldKey });
|
|
|
|
entity.Property(x => x.FieldKey).HasMaxLength(256);
|
|
|
|
entity.HasOne<Document>()
|
|
.WithMany()
|
|
.HasForeignKey(x => x.DocumentId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
}
|
|
}
|