bakery-erp / src /BakeryErp.Infrastructure /Repositories /ReportCenterRepository.cs
Neil67's picture
Deploy Bakery ERP Space
13240d3
Raw
History Blame Contribute Delete
10.1 kB
using System.Text;
using BakeryErp.Infrastructure.Database;
namespace BakeryErp.Infrastructure.Repositories;
public sealed class ReportCenterRepository
{
private readonly DatabaseOptions _options;
public ReportCenterRepository(DatabaseOptions options)
{
_options = options;
}
public IReadOnlyList<LocalReportDefinition> List()
{
return LocalDataStoreFile.Load(_options)
.ReportDefinitions
.OrderBy(static report => report.ReportCategory)
.ThenBy(static report => report.ReportCode)
.ToList();
}
public LocalReportDefinition? Find(Guid id)
{
return LocalDataStoreFile.Load(_options)
.ReportDefinitions
.FirstOrDefault(report => report.Id == id);
}
public ReportCenterSummary GetSummary()
{
var store = LocalDataStoreFile.Load(_options);
return new ReportCenterSummary(
store.ReportDefinitions.Count,
store.ReportDefinitions.Count(static report => report.Status == LocalReportStatus.PendingApproval),
store.ReportDefinitions.Count(static report => report.Status == LocalReportStatus.Approved),
store.StockBalances.Count,
store.PurchaseOrders.Count,
store.PettyCashTransactions.Count,
store.TransferOrders.Count,
store.StoreReturns.Count);
}
public SaveReportDefinitionResult Save(LocalReportDefinition report)
{
var store = LocalDataStoreFile.Load(_options);
var reportCode = report.ReportCode.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(reportCode) ||
string.IsNullOrWhiteSpace(report.ReportName) ||
string.IsNullOrWhiteSpace(report.ReportCategory))
{
return SaveReportDefinitionResult.ValidationError("報表代號、名稱與分類為必填。");
}
var duplicatedCode = store.ReportDefinitions.Any(existing =>
existing.Id != report.Id &&
string.Equals(existing.ReportCode, reportCode, StringComparison.OrdinalIgnoreCase));
if (duplicatedCode)
{
return SaveReportDefinitionResult.ValidationError("報表代號已存在,請改用其他代號。");
}
var existingReport = store.ReportDefinitions.FirstOrDefault(existing => existing.Id == report.Id);
if (existingReport is null)
{
report.Id = report.Id == Guid.Empty ? Guid.NewGuid() : report.Id;
report.ReportCode = reportCode;
report.ReportName = report.ReportName.Trim();
report.ReportCategory = report.ReportCategory.Trim();
report.Description = BlankToNull(report.Description);
report.Status = LocalReportStatus.PendingApproval;
report.CreatedAt = DateTime.UtcNow;
store.ReportDefinitions.Add(report);
}
else
{
if (existingReport.Status == LocalReportStatus.Approved)
{
return SaveReportDefinitionResult.ValidationError("已審核報表不可編輯。");
}
existingReport.ReportCode = reportCode;
existingReport.ReportName = report.ReportName.Trim();
existingReport.ReportCategory = report.ReportCategory.Trim();
existingReport.Description = BlankToNull(report.Description);
existingReport.Status = LocalReportStatus.PendingApproval;
existingReport.UpdatedAt = DateTime.UtcNow;
}
LocalDataStoreFile.Save(_options, store);
return SaveReportDefinitionResult.Success();
}
public void Approve(Guid id)
{
Update(id, report =>
{
report.Status = LocalReportStatus.Approved;
report.ApprovedAt = DateTime.UtcNow;
});
}
public void Cancel(Guid id)
{
Update(id, report => report.Status = LocalReportStatus.Cancelled);
}
public CsvExport Export(string reportCode)
{
var store = LocalDataStoreFile.Load(_options);
return reportCode.ToUpperInvariant() switch
{
"RPT-INVENTORY" => ExportInventory(store),
"RPT-PURCHASE" => ExportPurchases(store),
"RPT-PETTYCASH" => ExportPettyCash(store),
"RPT-TRANSFERS" => ExportTransfers(store),
"RPT-RETURNS" => ExportReturns(store),
_ => ExportReportDefinitions(store)
};
}
public static string GetStatusName(LocalReportStatus status)
{
return status switch
{
LocalReportStatus.Draft => "草稿",
LocalReportStatus.PendingApproval => "待審核",
LocalReportStatus.Approved => "已審核",
LocalReportStatus.Cancelled => "已取消",
_ => status.ToString()
};
}
private static CsvExport ExportInventory(LocalDataStore store)
{
var rows = store.StockBalances.Select(stock => new[]
{
stock.WarehouseName,
stock.ItemSku,
stock.ItemName,
stock.QuantityOnHand.ToString("0.##"),
stock.SafetyStockQuantity.ToString("0.##"),
stock.Unit
});
return Csv("inventory-report.csv", ["倉庫", "品項代號", "品項名稱", "現有庫存", "安全庫存", "單位"], rows);
}
private static CsvExport ExportPurchases(LocalDataStore store)
{
var rows = store.PurchaseOrders.Select(order => new[]
{
order.OrderNumber,
order.SupplierName,
order.OrderDate.ToString("yyyy-MM-dd"),
order.ItemName,
order.Quantity.ToString("0.##"),
order.UnitPrice.ToString("0.##"),
order.LineAmount.ToString("0.##"),
PurchaseRepository.GetStatusName(order.Status)
});
return Csv("purchase-report.csv", ["採購單號", "供應商", "日期", "品項", "數量", "單價", "金額", "狀態"], rows);
}
private static CsvExport ExportPettyCash(LocalDataStore store)
{
var rows = store.PettyCashTransactions.Select(transaction => new[]
{
transaction.TransactionNumber,
transaction.StoreName,
transaction.TransactionDate.ToString("yyyy-MM-dd"),
PettyCashRepository.GetTransactionTypeName(transaction.TransactionType),
transaction.ExpenseCategory,
transaction.Amount.ToString("0.##"),
PettyCashRepository.GetStatusName(transaction.Status)
});
return Csv("petty-cash-report.csv", ["單號", "門市", "日期", "類型", "分類", "金額", "狀態"], rows);
}
private static CsvExport ExportTransfers(LocalDataStore store)
{
var rows = store.TransferOrders.Select(order => new[]
{
order.TransferNumber,
order.StoreName,
order.ItemName,
order.RequestedQuantity.ToString("0.##"),
order.ShippedQuantity.ToString("0.##"),
order.ReceivedQuantity.ToString("0.##"),
order.DifferenceQuantity.ToString("0.##"),
TransferRepository.GetStatusName(order.Status)
});
return Csv("transfer-report.csv", ["調撥單號", "門市", "品項", "叫貨", "出庫", "收貨", "差異", "狀態"], rows);
}
private static CsvExport ExportReturns(LocalDataStore store)
{
var rows = store.StoreReturns.Select(item => new[]
{
item.ReturnNumber,
item.StoreName,
item.ItemName,
item.Quantity.ToString("0.##"),
item.Reason,
StoreReturnRepository.GetStatusName(item.Status),
StoreReturnRepository.GetDispositionName(item.Disposition)
});
return Csv("store-return-report.csv", ["退貨單號", "門市", "品項", "數量", "原因", "狀態", "處理方式"], rows);
}
private static CsvExport ExportReportDefinitions(LocalDataStore store)
{
var rows = store.ReportDefinitions.Select(report => new[]
{
report.ReportCode,
report.ReportName,
report.ReportCategory,
ReportCenterRepository.GetStatusName(report.Status)
});
return Csv("report-definitions.csv", ["報表代號", "報表名稱", "分類", "狀態"], rows);
}
private static CsvExport Csv(string fileName, IReadOnlyList<string> headers, IEnumerable<IReadOnlyList<string>> rows)
{
var builder = new StringBuilder();
builder.AppendLine(string.Join(",", headers.Select(EscapeCsv)));
foreach (var row in rows)
{
builder.AppendLine(string.Join(",", row.Select(EscapeCsv)));
}
return new CsvExport(fileName, "\uFEFF" + builder);
}
private static string EscapeCsv(string value)
{
var normalized = value.Replace("\"", "\"\"", StringComparison.Ordinal);
return $"\"{normalized}\"";
}
private void Update(Guid id, Action<LocalReportDefinition> update)
{
var store = LocalDataStoreFile.Load(_options);
var report = store.ReportDefinitions.FirstOrDefault(existing => existing.Id == id);
if (report is null)
{
return;
}
update(report);
report.UpdatedAt = DateTime.UtcNow;
LocalDataStoreFile.Save(_options, store);
}
private static string? BlankToNull(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
}
public sealed record ReportCenterSummary(
int ReportCount,
int PendingApprovalCount,
int ApprovedCount,
int InventoryRows,
int PurchaseRows,
int PettyCashRows,
int TransferRows,
int ReturnRows);
public sealed record CsvExport(string FileName, string Content);
public sealed record SaveReportDefinitionResult(bool IsSuccess, string? ErrorMessage)
{
public static SaveReportDefinitionResult Success() => new(true, null);
public static SaveReportDefinitionResult ValidationError(string message) => new(false, message);
}