100 lines
4.6 KiB
C#
100 lines
4.6 KiB
C#
|
|
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));
|
||
|
|
}
|
||
|
|
}
|