Spaces:
Running
Running
| using BakeryErp.Infrastructure.Database; | |
| namespace BakeryErp.Infrastructure.Repositories; | |
| public sealed class OcrDocumentRepository | |
| { | |
| private readonly DatabaseOptions _options; | |
| public OcrDocumentRepository(DatabaseOptions options) | |
| { | |
| _options = options; | |
| } | |
| public IReadOnlyList<LocalOcrDocument> List() | |
| { | |
| return LocalDataStoreFile.Load(_options) | |
| .OcrDocuments | |
| .OrderByDescending(static document => document.CreatedAt) | |
| .ToList(); | |
| } | |
| public LocalOcrDocument? Find(Guid id) | |
| { | |
| return LocalDataStoreFile.Load(_options) | |
| .OcrDocuments | |
| .FirstOrDefault(document => document.Id == id); | |
| } | |
| public OcrReport GetReport() | |
| { | |
| var documents = LocalDataStoreFile.Load(_options).OcrDocuments; | |
| return new OcrReport( | |
| documents.Count, | |
| documents.Count(static document => document.Status == LocalOcrStatus.Uploaded), | |
| documents.Count(static document => document.Status == LocalOcrStatus.Recognized), | |
| documents.Count(static document => document.Status == LocalOcrStatus.MappingRequired), | |
| documents.Count(static document => document.Status == LocalOcrStatus.Confirmed), | |
| documents.Count(static document => document.Status == LocalOcrStatus.ConvertedToReceipt), | |
| documents.Count(static document => document.Status == LocalOcrStatus.Failed), | |
| documents.Sum(static document => document.TotalAmount ?? 0)); | |
| } | |
| public SaveOcrDocumentResult Save(LocalOcrDocument document) | |
| { | |
| var store = LocalDataStoreFile.Load(_options); | |
| var documentNumber = document.DocumentNumber.Trim().ToUpperInvariant(); | |
| if (string.IsNullOrWhiteSpace(documentNumber)) | |
| { | |
| return SaveOcrDocumentResult.ValidationError("單據編號為必填。"); | |
| } | |
| var duplicatedNumber = store.OcrDocuments.Any(existing => | |
| existing.Id != document.Id && | |
| string.Equals(existing.DocumentNumber, documentNumber, StringComparison.OrdinalIgnoreCase)); | |
| if (duplicatedNumber) | |
| { | |
| return SaveOcrDocumentResult.ValidationError("單據編號已存在,請改用其他編號。"); | |
| } | |
| var existingDocument = store.OcrDocuments.FirstOrDefault(existing => existing.Id == document.Id); | |
| if (existingDocument is null) | |
| { | |
| document.Id = document.Id == Guid.Empty ? Guid.NewGuid() : document.Id; | |
| document.DocumentNumber = documentNumber; | |
| document.FileName = BlankToEmpty(document.FileName); | |
| document.SupplierName = BlankToNull(document.SupplierName); | |
| document.RawText = BlankToNull(document.RawText); | |
| document.RecognizedItems = BlankToNull(document.RecognizedItems); | |
| document.ReviewNote = BlankToNull(document.ReviewNote); | |
| document.CreatedAt = DateTime.UtcNow; | |
| store.OcrDocuments.Add(document); | |
| } | |
| else | |
| { | |
| existingDocument.DocumentNumber = documentNumber; | |
| existingDocument.FileName = BlankToEmpty(document.FileName); | |
| existingDocument.SupplierName = BlankToNull(document.SupplierName); | |
| existingDocument.DocumentDate = document.DocumentDate; | |
| existingDocument.Status = document.Status; | |
| existingDocument.TotalAmount = document.TotalAmount; | |
| existingDocument.RawText = BlankToNull(document.RawText); | |
| existingDocument.RecognizedItems = BlankToNull(document.RecognizedItems); | |
| existingDocument.ReviewNote = BlankToNull(document.ReviewNote); | |
| existingDocument.UpdatedAt = DateTime.UtcNow; | |
| } | |
| LocalDataStoreFile.Save(_options, store); | |
| return SaveOcrDocumentResult.Success(); | |
| } | |
| public void Review(Guid id, bool approved) | |
| { | |
| var store = LocalDataStoreFile.Load(_options); | |
| var document = store.OcrDocuments.FirstOrDefault(existing => existing.Id == id); | |
| if (document is null) | |
| { | |
| return; | |
| } | |
| document.Status = approved ? LocalOcrStatus.Confirmed : LocalOcrStatus.MappingRequired; | |
| document.ReviewedAt = DateTime.UtcNow; | |
| document.UpdatedAt = DateTime.UtcNow; | |
| LocalDataStoreFile.Save(_options, store); | |
| } | |
| public void ConvertToReceipt(Guid id) | |
| { | |
| var store = LocalDataStoreFile.Load(_options); | |
| var document = store.OcrDocuments.FirstOrDefault(existing => existing.Id == id); | |
| if (document is null) | |
| { | |
| return; | |
| } | |
| document.Status = LocalOcrStatus.ConvertedToReceipt; | |
| document.UpdatedAt = DateTime.UtcNow; | |
| LocalDataStoreFile.Save(_options, store); | |
| } | |
| public static string GetStatusName(LocalOcrStatus status) | |
| { | |
| return status switch | |
| { | |
| LocalOcrStatus.Uploaded => "已上傳", | |
| LocalOcrStatus.Recognized => "已辨識", | |
| LocalOcrStatus.MappingRequired => "待對應", | |
| LocalOcrStatus.Confirmed => "已確認", | |
| LocalOcrStatus.ConvertedToReceipt => "已轉進貨", | |
| LocalOcrStatus.Failed => "失敗", | |
| _ => status.ToString() | |
| }; | |
| } | |
| private static string BlankToEmpty(string? value) | |
| { | |
| return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); | |
| } | |
| private static string? BlankToNull(string? value) | |
| { | |
| return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); | |
| } | |
| } | |
| public sealed record OcrReport( | |
| int TotalCount, | |
| int UploadedCount, | |
| int RecognizedCount, | |
| int MappingRequiredCount, | |
| int ConfirmedCount, | |
| int ConvertedToReceiptCount, | |
| int FailedCount, | |
| decimal TotalAmount); | |
| public sealed record SaveOcrDocumentResult(bool IsSuccess, string? ErrorMessage) | |
| { | |
| public static SaveOcrDocumentResult Success() => new(true, null); | |
| public static SaveOcrDocumentResult ValidationError(string message) => new(false, message); | |
| } | |