Spaces:
Sleeping
Sleeping
| using BakeryErp.Infrastructure.Database; | |
| namespace BakeryErp.Infrastructure.Repositories; | |
| public sealed class TransferRepository | |
| { | |
| private readonly DatabaseOptions _options; | |
| public TransferRepository(DatabaseOptions options) | |
| { | |
| _options = options; | |
| } | |
| public IReadOnlyList<LocalTransferOrder> 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<LocalTransferOrder> 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); | |
| } | |