Neil67's picture
Deploy Bakery ERP Space
13240d3
Raw
History Blame Contribute Delete
7.76 kB
using BakeryErp.Infrastructure.Database;
namespace BakeryErp.Infrastructure.Repositories;
public sealed class PurchaseRepository
{
private readonly DatabaseOptions _options;
public PurchaseRepository(DatabaseOptions options)
{
_options = options;
}
public IReadOnlyList<LocalPurchaseOrder> List()
{
return LocalDataStoreFile.Load(_options)
.PurchaseOrders
.OrderByDescending(static order => order.OrderDate)
.ThenBy(static order => order.OrderNumber)
.ToList();
}
public LocalPurchaseOrder? Find(Guid id)
{
return LocalDataStoreFile.Load(_options)
.PurchaseOrders
.FirstOrDefault(order => order.Id == id);
}
public PurchaseReport GetReport()
{
var orders = LocalDataStoreFile.Load(_options).PurchaseOrders;
return new PurchaseReport(
orders.Count,
orders.Count(static order => order.Status == LocalPurchaseOrderStatus.PendingApproval),
orders.Count(static order => order.Status == LocalPurchaseOrderStatus.Approved),
orders.Count(static order => order.Status == LocalPurchaseOrderStatus.MonthlyClosed),
orders.Where(static order => order.Status != LocalPurchaseOrderStatus.Cancelled).Sum(static order => order.LineAmount),
orders.Where(static order => order.Status == LocalPurchaseOrderStatus.Approved).Sum(static order => order.LineAmount),
orders.Where(static order => order.Status == LocalPurchaseOrderStatus.MonthlyClosed).Sum(static order => order.LineAmount));
}
public IReadOnlyList<SupplierPurchaseSummary> GetSupplierSummaries()
{
return LocalDataStoreFile.Load(_options)
.PurchaseOrders
.Where(static order => order.Status != LocalPurchaseOrderStatus.Cancelled)
.GroupBy(static order => order.SupplierName)
.Select(group => new SupplierPurchaseSummary(
group.Key,
group.Count(),
group.Sum(static order => order.LineAmount),
group.Count(static order => order.Status == LocalPurchaseOrderStatus.MonthlyClosed)))
.OrderByDescending(static summary => summary.TotalAmount)
.ToList();
}
public SavePurchaseOrderResult Save(LocalPurchaseOrder order)
{
var store = LocalDataStoreFile.Load(_options);
var orderNumber = order.OrderNumber.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(orderNumber) ||
string.IsNullOrWhiteSpace(order.SupplierName) ||
string.IsNullOrWhiteSpace(order.ItemName) ||
string.IsNullOrWhiteSpace(order.Unit))
{
return SavePurchaseOrderResult.ValidationError("採購單號、供應商、品項與單位為必填。");
}
if (order.Quantity <= 0 || order.UnitPrice < 0)
{
return SavePurchaseOrderResult.ValidationError("數量需大於 0,單價不可小於 0。");
}
var duplicatedNumber = store.PurchaseOrders.Any(existing =>
existing.Id != order.Id &&
string.Equals(existing.OrderNumber, orderNumber, StringComparison.OrdinalIgnoreCase));
if (duplicatedNumber)
{
return SavePurchaseOrderResult.ValidationError("採購單號已存在,請改用其他單號。");
}
var existingOrder = store.PurchaseOrders.FirstOrDefault(existing => existing.Id == order.Id);
if (existingOrder is null)
{
order.Id = order.Id == Guid.Empty ? Guid.NewGuid() : order.Id;
order.OrderNumber = orderNumber;
order.SupplierName = order.SupplierName.Trim();
order.ItemSku = BlankToEmpty(order.ItemSku).ToUpperInvariant();
order.ItemName = order.ItemName.Trim();
order.Unit = order.Unit.Trim();
order.Note = BlankToNull(order.Note);
order.Status = LocalPurchaseOrderStatus.PendingApproval;
order.CreatedAt = DateTime.UtcNow;
store.PurchaseOrders.Add(order);
}
else
{
if (existingOrder.Status is LocalPurchaseOrderStatus.Approved or LocalPurchaseOrderStatus.MonthlyClosed)
{
return SavePurchaseOrderResult.ValidationError("已審核或已月結的採購單不可編輯。");
}
existingOrder.OrderNumber = orderNumber;
existingOrder.SupplierName = order.SupplierName.Trim();
existingOrder.OrderDate = order.OrderDate;
existingOrder.ItemSku = BlankToEmpty(order.ItemSku).ToUpperInvariant();
existingOrder.ItemName = order.ItemName.Trim();
existingOrder.Quantity = order.Quantity;
existingOrder.Unit = order.Unit.Trim();
existingOrder.UnitPrice = order.UnitPrice;
existingOrder.Note = BlankToNull(order.Note);
existingOrder.Status = LocalPurchaseOrderStatus.PendingApproval;
existingOrder.UpdatedAt = DateTime.UtcNow;
}
LocalDataStoreFile.Save(_options, store);
return SavePurchaseOrderResult.Success();
}
public void Approve(Guid id)
{
UpdateStatus(id, LocalPurchaseOrderStatus.Approved);
}
public void CloseMonthly(Guid id)
{
UpdateStatus(id, LocalPurchaseOrderStatus.MonthlyClosed);
}
public void Cancel(Guid id)
{
UpdateStatus(id, LocalPurchaseOrderStatus.Cancelled);
}
public static string GetStatusName(LocalPurchaseOrderStatus status)
{
return status switch
{
LocalPurchaseOrderStatus.Draft => "草稿",
LocalPurchaseOrderStatus.PendingApproval => "待審核",
LocalPurchaseOrderStatus.Approved => "已審核",
LocalPurchaseOrderStatus.MonthlyClosed => "已月結",
LocalPurchaseOrderStatus.Cancelled => "已取消",
_ => status.ToString()
};
}
private void UpdateStatus(Guid id, LocalPurchaseOrderStatus status)
{
var store = LocalDataStoreFile.Load(_options);
var order = store.PurchaseOrders.FirstOrDefault(existing => existing.Id == id);
if (order is null)
{
return;
}
if (status == LocalPurchaseOrderStatus.Cancelled && order.Status == LocalPurchaseOrderStatus.MonthlyClosed)
{
return;
}
order.Status = status;
order.UpdatedAt = DateTime.UtcNow;
if (status == LocalPurchaseOrderStatus.Approved)
{
order.ApprovedAt = DateTime.UtcNow;
}
if (status == LocalPurchaseOrderStatus.MonthlyClosed)
{
order.ClosedAt = 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 PurchaseReport(
int OrderCount,
int PendingApprovalCount,
int ApprovedCount,
int MonthlyClosedCount,
decimal TotalAmount,
decimal PayableAmount,
decimal ClosedAmount);
public sealed record SupplierPurchaseSummary(
string SupplierName,
int OrderCount,
decimal TotalAmount,
int ClosedCount);
public sealed record SavePurchaseOrderResult(bool IsSuccess, string? ErrorMessage)
{
public static SavePurchaseOrderResult Success() => new(true, null);
public static SavePurchaseOrderResult ValidationError(string message) => new(false, message);
}