using BakeryErp.Infrastructure.Database; namespace BakeryErp.Infrastructure.Repositories; public sealed class TransferRepository { private readonly DatabaseOptions _options; public TransferRepository(DatabaseOptions options) { _options = options; } public IReadOnlyList List() { return LocalDataStoreFile.Load(_options) .TransferOrders .OrderByDescending(static order => order.RequestDate) .ThenBy(static order => order.TransferNumber) .ToList(); } public LocalTransferOrder? Find(Guid id) { return LocalDataStoreFile.Load(_options) .TransferOrders .FirstOrDefault(order => order.Id == id); } public TransferReport GetReport() { var orders = LocalDataStoreFile.Load(_options).TransferOrders; return new TransferReport( orders.Count, orders.Count(static order => order.Status == LocalTransferStatus.StoreRequested), orders.Count(static order => order.Status == LocalTransferStatus.Prepared), orders.Count(static order => order.Status == LocalTransferStatus.InTransit), orders.Count(static order => order.Status == LocalTransferStatus.Completed), orders.Count(static order => order.Status == LocalTransferStatus.Discrepancy), orders.Sum(static order => Math.Abs(order.DifferenceQuantity))); } public SaveTransferResult Save(LocalTransferOrder order) { var store = LocalDataStoreFile.Load(_options); var transferNumber = order.TransferNumber.Trim().ToUpperInvariant(); if (string.IsNullOrWhiteSpace(transferNumber) || string.IsNullOrWhiteSpace(order.StoreName) || string.IsNullOrWhiteSpace(order.FromWarehouseName) || string.IsNullOrWhiteSpace(order.ToWarehouseName) || string.IsNullOrWhiteSpace(order.ItemName) || string.IsNullOrWhiteSpace(order.Unit)) { return SaveTransferResult.ValidationError("調撥單號、門市、倉庫、品項與單位為必填。"); } if (order.RequestedQuantity <= 0) { return SaveTransferResult.ValidationError("叫貨數量需大於 0。"); } var duplicatedNumber = store.TransferOrders.Any(existing => existing.Id != order.Id && string.Equals(existing.TransferNumber, transferNumber, StringComparison.OrdinalIgnoreCase)); if (duplicatedNumber) { return SaveTransferResult.ValidationError("調撥單號已存在,請改用其他單號。"); } var existingOrder = store.TransferOrders.FirstOrDefault(existing => existing.Id == order.Id); if (existingOrder is null) { order.Id = order.Id == Guid.Empty ? Guid.NewGuid() : order.Id; order.TransferNumber = transferNumber; order.StoreName = order.StoreName.Trim(); order.FromWarehouseName = order.FromWarehouseName.Trim(); order.ToWarehouseName = order.ToWarehouseName.Trim(); order.ItemSku = BlankToEmpty(order.ItemSku).ToUpperInvariant(); order.ItemName = order.ItemName.Trim(); order.Unit = order.Unit.Trim(); order.Note = BlankToNull(order.Note); order.Status = LocalTransferStatus.StoreRequested; order.CreatedAt = DateTime.UtcNow; store.TransferOrders.Add(order); } else { if (existingOrder.Status is LocalTransferStatus.InTransit or LocalTransferStatus.Completed) { return SaveTransferResult.ValidationError("配送中或已完成的調撥單不可編輯。"); } existingOrder.TransferNumber = transferNumber; existingOrder.StoreName = order.StoreName.Trim(); existingOrder.FromWarehouseName = order.FromWarehouseName.Trim(); existingOrder.ToWarehouseName = order.ToWarehouseName.Trim(); existingOrder.ItemSku = BlankToEmpty(order.ItemSku).ToUpperInvariant(); existingOrder.ItemName = order.ItemName.Trim(); existingOrder.RequestedQuantity = order.RequestedQuantity; existingOrder.PreparedQuantity = order.PreparedQuantity; existingOrder.ShippedQuantity = order.ShippedQuantity; existingOrder.ReceivedQuantity = order.ReceivedQuantity; existingOrder.DifferenceQuantity = order.DifferenceQuantity; existingOrder.Unit = order.Unit.Trim(); existingOrder.RequestDate = order.RequestDate; existingOrder.Note = BlankToNull(order.Note); existingOrder.UpdatedAt = DateTime.UtcNow; } LocalDataStoreFile.Save(_options, store); return SaveTransferResult.Success(); } public void Prepare(Guid id, decimal preparedQuantity) { Update(id, order => { order.PreparedQuantity = preparedQuantity <= 0 ? order.RequestedQuantity : preparedQuantity; order.Status = LocalTransferStatus.Prepared; order.PreparedAt = DateTime.UtcNow; }); } public void Ship(Guid id) { Update(id, order => { order.ShippedQuantity = order.PreparedQuantity <= 0 ? order.RequestedQuantity : order.PreparedQuantity; order.Status = LocalTransferStatus.InTransit; order.ShippedAt = DateTime.UtcNow; }); } public void Receive(Guid id, decimal receivedQuantity) { Update(id, order => { order.ReceivedQuantity = receivedQuantity; order.DifferenceQuantity = order.ReceivedQuantity - order.ShippedQuantity; order.Status = order.DifferenceQuantity == 0 ? LocalTransferStatus.Completed : LocalTransferStatus.Discrepancy; order.ReceivedAt = DateTime.UtcNow; }); } public void Cancel(Guid id) { Update(id, order => { if (order.Status != LocalTransferStatus.Completed) { order.Status = LocalTransferStatus.Cancelled; } }); } public static string GetStatusName(LocalTransferStatus status) { return status switch { LocalTransferStatus.Draft => "草稿", LocalTransferStatus.StoreRequested => "門市叫貨", LocalTransferStatus.Prepared => "已備貨", LocalTransferStatus.InTransit => "配送中", LocalTransferStatus.Completed => "已完成", LocalTransferStatus.Discrepancy => "有差異", LocalTransferStatus.Cancelled => "已取消", _ => status.ToString() }; } private void Update(Guid id, Action update) { var store = LocalDataStoreFile.Load(_options); var order = store.TransferOrders.FirstOrDefault(existing => existing.Id == id); if (order is null) { return; } update(order); order.UpdatedAt = DateTime.UtcNow; LocalDataStoreFile.Save(_options, store); } 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 TransferReport( int OrderCount, int StoreRequestedCount, int PreparedCount, int InTransitCount, int CompletedCount, int DiscrepancyCount, decimal DifferenceQuantityTotal); public sealed record SaveTransferResult(bool IsSuccess, string? ErrorMessage) { public static SaveTransferResult Success() => new(true, null); public static SaveTransferResult ValidationError(string message) => new(false, message); }