Phase 3
This commit is contained in:
parent
9321f5a370
commit
25c2ce53cd
23 changed files with 1746 additions and 68 deletions
99
backend/WordVersioning.Api/Services/FieldSyncService.cs
Normal file
99
backend/WordVersioning.Api/Services/FieldSyncService.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WordVersioning.Api.Data;
|
||||
using WordVersioning.Api.Models;
|
||||
|
||||
namespace WordVersioning.Api.Services;
|
||||
|
||||
/// <summary>entry หนึ่ง field ใน `DocumentVersion.FieldsSnapshotJson` (เอกสารที่ผูก template)</summary>
|
||||
public record FieldSnapshotEntry(string Value, bool Missing);
|
||||
|
||||
public record FieldSyncResult(string? SnapshotJson);
|
||||
|
||||
public interface IFieldSyncService
|
||||
{
|
||||
/// <summary>FR-11 — เทียบ fields ที่ส่งมากับ TemplateField ของ template ที่ document ผูกอยู่:
|
||||
/// stage การ upsert `DocumentFieldValue` (latest value) ลง change tracker (**caller เป็นคน SaveChanges**)
|
||||
/// แล้วคืน snapshot JSON ของ version นี้ไปเก็บใน `DocumentVersion.FieldsSnapshotJson`
|
||||
///
|
||||
/// policy ตาม spec: field ขาด (ผู้ใช้ลบ content control) → เก็บค่าเดิม + mark missing **ไม่ fail**;
|
||||
/// field เกินนิยาม → ignore; `UpdatedByUserId` จาก JWT เสมอ</summary>
|
||||
Task<FieldSyncResult> StageFieldSyncAsync(
|
||||
Document document,
|
||||
Guid sourceVersionId,
|
||||
IReadOnlyDictionary<string, string?> sentFields,
|
||||
DateTimeOffset now,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public class FieldSyncService(AppDbContext db, ICurrentUser currentUser) : IFieldSyncService
|
||||
{
|
||||
// camelCase ให้ตรงกับ JSON ที่ API คืนทั้งระบบ
|
||||
private static readonly JsonSerializerOptions SnapshotJsonOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
public async Task<FieldSyncResult> StageFieldSyncAsync(
|
||||
Document document,
|
||||
Guid sourceVersionId,
|
||||
IReadOnlyDictionary<string, string?> sentFields,
|
||||
DateTimeOffset now,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (document.TemplateId is null)
|
||||
{
|
||||
// เอกสารไม่ผูก template (track เองจาก add-in) — ไม่มีนิยามให้เทียบ
|
||||
// เก็บ map ดิบตามที่ส่งมาใน snapshot ไว้ดู history ได้ (Phase 2 behavior)
|
||||
return sentFields.Count == 0
|
||||
? new FieldSyncResult(null)
|
||||
: new FieldSyncResult(JsonSerializer.Serialize(sentFields, SnapshotJsonOptions));
|
||||
}
|
||||
|
||||
var fieldDefs = await db.TemplateFields.AsNoTracking()
|
||||
.Where(f => f.TemplateId == document.TemplateId)
|
||||
.OrderBy(f => f.SortOrder)
|
||||
.ToListAsync(ct);
|
||||
if (fieldDefs.Count == 0)
|
||||
return new FieldSyncResult(null);
|
||||
|
||||
// tracked — update ทีหลังให้เข้า SaveChanges เดียวกับ version insert
|
||||
var existingValues = await db.DocumentFieldValues
|
||||
.Where(v => v.DocumentId == document.Id)
|
||||
.ToDictionaryAsync(v => v.FieldKey, ct);
|
||||
|
||||
var snapshot = new Dictionary<string, FieldSnapshotEntry>(fieldDefs.Count);
|
||||
foreach (var def in fieldDefs)
|
||||
{
|
||||
if (sentFields.TryGetValue(def.Key, out var sentValue))
|
||||
{
|
||||
sentValue ??= string.Empty;
|
||||
if (existingValues.TryGetValue(def.Key, out var row))
|
||||
{
|
||||
row.Value = sentValue;
|
||||
row.SourceVersionId = sourceVersionId;
|
||||
row.UpdatedByUserId = currentUser.UserId;
|
||||
row.UpdatedAt = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
db.DocumentFieldValues.Add(new DocumentFieldValue
|
||||
{
|
||||
DocumentId = document.Id,
|
||||
FieldKey = def.Key,
|
||||
Value = sentValue,
|
||||
SourceVersionId = sourceVersionId,
|
||||
UpdatedByUserId = currentUser.UserId,
|
||||
UpdatedAt = now,
|
||||
});
|
||||
}
|
||||
snapshot[def.Key] = new FieldSnapshotEntry(sentValue, Missing: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// field หายจากเอกสาร (ผู้ใช้ลบ content control) — เก็บค่าเดิม + mark missing ไม่ fail (AC-14)
|
||||
var oldValue = existingValues.TryGetValue(def.Key, out var row) ? row.Value : string.Empty;
|
||||
snapshot[def.Key] = new FieldSnapshotEntry(oldValue, Missing: true);
|
||||
}
|
||||
}
|
||||
|
||||
return new FieldSyncResult(JsonSerializer.Serialize(snapshot, SnapshotJsonOptions));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue