Neil67's picture
Deploy Bakery ERP Space
13240d3
Raw
History Blame Contribute Delete
8.92 kB
using BakeryErp.Infrastructure.Database;
namespace BakeryErp.Infrastructure.Repositories;
public sealed class PettyCashRepository
{
private readonly DatabaseOptions _options;
public PettyCashRepository(DatabaseOptions options)
{
_options = options;
}
public IReadOnlyList<LocalPettyCashTransaction> List()
{
return LocalDataStoreFile.Load(_options)
.PettyCashTransactions
.OrderByDescending(static transaction => transaction.TransactionDate)
.ThenBy(static transaction => transaction.TransactionNumber)
.ToList();
}
public LocalPettyCashTransaction? Find(Guid id)
{
return LocalDataStoreFile.Load(_options)
.PettyCashTransactions
.FirstOrDefault(transaction => transaction.Id == id);
}
public PettyCashReport GetReport()
{
var transactions = LocalDataStoreFile.Load(_options).PettyCashTransactions;
var approvedExpenses = transactions.Where(static transaction =>
transaction.TransactionType == LocalPettyCashTransactionType.Expense &&
transaction.Status is LocalPettyCashStatus.Approved or LocalPettyCashStatus.MonthlyClosed);
var replenishments = transactions.Where(static transaction =>
transaction.TransactionType == LocalPettyCashTransactionType.Replenishment &&
transaction.Status is LocalPettyCashStatus.Approved or LocalPettyCashStatus.Replenished);
return new PettyCashReport(
transactions.Count,
transactions.Count(static transaction => transaction.Status == LocalPettyCashStatus.PendingFinanceApproval),
approvedExpenses.Sum(static transaction => transaction.Amount),
transactions.Count(static transaction => transaction.Status == LocalPettyCashStatus.MonthlyClosed),
replenishments.Sum(static transaction => transaction.Amount),
replenishments.Sum(static transaction => transaction.Amount) - approvedExpenses.Sum(static transaction => transaction.Amount));
}
public IReadOnlyList<StorePettyCashSummary> GetStoreSummaries()
{
return LocalDataStoreFile.Load(_options)
.PettyCashTransactions
.Where(static transaction => transaction.Status != LocalPettyCashStatus.Cancelled)
.GroupBy(static transaction => transaction.StoreName)
.Select(group => new StorePettyCashSummary(
group.Key,
group.Count(),
group.Where(static transaction => transaction.TransactionType == LocalPettyCashTransactionType.Expense).Sum(static transaction => transaction.Amount),
group.Where(static transaction => transaction.TransactionType == LocalPettyCashTransactionType.Replenishment).Sum(static transaction => transaction.Amount)))
.OrderByDescending(static summary => summary.ExpenseAmount)
.ToList();
}
public SavePettyCashResult Save(LocalPettyCashTransaction transaction)
{
var store = LocalDataStoreFile.Load(_options);
var transactionNumber = transaction.TransactionNumber.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(transactionNumber) ||
string.IsNullOrWhiteSpace(transaction.StoreName) ||
string.IsNullOrWhiteSpace(transaction.ExpenseCategory))
{
return SavePettyCashResult.ValidationError("零用金單號、門市與分類為必填。");
}
if (transaction.Amount <= 0)
{
return SavePettyCashResult.ValidationError("金額需大於 0。");
}
var duplicatedNumber = store.PettyCashTransactions.Any(existing =>
existing.Id != transaction.Id &&
string.Equals(existing.TransactionNumber, transactionNumber, StringComparison.OrdinalIgnoreCase));
if (duplicatedNumber)
{
return SavePettyCashResult.ValidationError("零用金單號已存在,請改用其他單號。");
}
var existingTransaction = store.PettyCashTransactions.FirstOrDefault(existing => existing.Id == transaction.Id);
if (existingTransaction is null)
{
transaction.Id = transaction.Id == Guid.Empty ? Guid.NewGuid() : transaction.Id;
transaction.TransactionNumber = transactionNumber;
transaction.StoreName = transaction.StoreName.Trim();
transaction.ExpenseCategory = transaction.ExpenseCategory.Trim();
transaction.Description = BlankToNull(transaction.Description);
transaction.Status = LocalPettyCashStatus.PendingFinanceApproval;
transaction.CreatedAt = DateTime.UtcNow;
store.PettyCashTransactions.Add(transaction);
}
else
{
if (existingTransaction.Status is LocalPettyCashStatus.Approved or LocalPettyCashStatus.MonthlyClosed or LocalPettyCashStatus.Replenished)
{
return SavePettyCashResult.ValidationError("已審核、已月結或已撥補的零用金紀錄不可編輯。");
}
existingTransaction.TransactionNumber = transactionNumber;
existingTransaction.StoreName = transaction.StoreName.Trim();
existingTransaction.TransactionDate = transaction.TransactionDate;
existingTransaction.TransactionType = transaction.TransactionType;
existingTransaction.ExpenseCategory = transaction.ExpenseCategory.Trim();
existingTransaction.Amount = transaction.Amount;
existingTransaction.Description = BlankToNull(transaction.Description);
existingTransaction.ReceiptImagePath = BlankToNull(transaction.ReceiptImagePath) ?? existingTransaction.ReceiptImagePath;
existingTransaction.Status = LocalPettyCashStatus.PendingFinanceApproval;
existingTransaction.UpdatedAt = DateTime.UtcNow;
}
LocalDataStoreFile.Save(_options, store);
return SavePettyCashResult.Success();
}
public void Approve(Guid id)
{
UpdateStatus(id, LocalPettyCashStatus.Approved);
}
public void Reject(Guid id)
{
UpdateStatus(id, LocalPettyCashStatus.Rejected);
}
public void CloseMonthly(Guid id)
{
UpdateStatus(id, LocalPettyCashStatus.MonthlyClosed);
}
public void Replenish(Guid id)
{
UpdateStatus(id, LocalPettyCashStatus.Replenished);
}
public static string GetTransactionTypeName(LocalPettyCashTransactionType type)
{
return type switch
{
LocalPettyCashTransactionType.Expense => "門市支出",
LocalPettyCashTransactionType.Replenishment => "零用金撥補",
LocalPettyCashTransactionType.Adjustment => "調整",
_ => type.ToString()
};
}
public static string GetStatusName(LocalPettyCashStatus status)
{
return status switch
{
LocalPettyCashStatus.Draft => "草稿",
LocalPettyCashStatus.PendingFinanceApproval => "待財務審核",
LocalPettyCashStatus.Approved => "已審核",
LocalPettyCashStatus.MonthlyClosed => "已月結",
LocalPettyCashStatus.Replenished => "已撥補",
LocalPettyCashStatus.Rejected => "已退回",
LocalPettyCashStatus.Cancelled => "已取消",
_ => status.ToString()
};
}
private void UpdateStatus(Guid id, LocalPettyCashStatus status)
{
var store = LocalDataStoreFile.Load(_options);
var transaction = store.PettyCashTransactions.FirstOrDefault(existing => existing.Id == id);
if (transaction is null)
{
return;
}
transaction.Status = status;
transaction.UpdatedAt = DateTime.UtcNow;
if (status == LocalPettyCashStatus.Approved)
{
transaction.ApprovedAt = DateTime.UtcNow;
}
if (status == LocalPettyCashStatus.MonthlyClosed)
{
transaction.ClosedAt = DateTime.UtcNow;
}
LocalDataStoreFile.Save(_options, store);
}
private static string? BlankToNull(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
}
public sealed record PettyCashReport(
int TransactionCount,
int PendingFinanceApprovalCount,
decimal ApprovedExpenseAmount,
int MonthlyClosedCount,
decimal ReplenishmentAmount,
decimal EstimatedBalance);
public sealed record StorePettyCashSummary(
string StoreName,
int TransactionCount,
decimal ExpenseAmount,
decimal ReplenishmentAmount);
public sealed record SavePettyCashResult(bool IsSuccess, string? ErrorMessage)
{
public static SavePettyCashResult Success() => new(true, null);
public static SavePettyCashResult ValidationError(string message) => new(false, message);
}