Files
ERP_Distribution/Distribution/Features/DayEndReport/DayEndReportService.cs
T

97 lines
4.2 KiB
C#

using Distribution.Models;
namespace Distribution.Features.DayEndReport;
public class DayEndReportService(IDayEndReportRepository repo)
{
public const int MaxRangeDays = 92;
public static DayEndFilter BuildFilter(DateTime? from, DateTime? to)
{
var end = (to ?? DateTime.Now).Date;
var start = (from ?? end.AddDays(-29)).Date;
return new DayEndFilter(start, end);
}
public static string? Validate(DayEndFilter f)
{
if (f.To < f.From) return "'to' must not be before 'from'.";
if ((f.To - f.From).TotalDays >= MaxRangeDays) return $"Date range cannot exceed {MaxRangeDays} days.";
return null;
}
public async Task<DayEndResponse> GetDayAsync(DateTime date)
{
var day = date.Date;
var data = await repo.GetDataAsync(new DayEndFilter(day, day));
var row = BuildRow(day, data);
var payments = data.Payments.Where(p => p.CollectedAt.Date == day).ToList();
var byMethod = Enum.GetValues<PaymentMethod>().Select(m =>
{
var rows = payments.Where(p => p.Method == m).ToList();
return new DayEndMethod(m.ToString(), rows.Count, rows.Sum(p => p.Amount));
}).ToList();
var expenses = data.Expenses
.Where(e => e.BillDate.Date == day)
.Select(e => new DayEndExpense(e.Id, e.Category, e.Payee, e.Description, e.Method?.ToString(), e.Amount))
.ToList();
return new DayEndResponse(row.Date, row, byMethod, expenses);
}
public async Task<DayEndDailyResponse> GetDailyAsync(DayEndFilter f)
{
var data = await repo.GetDataAsync(f);
var days = new List<DayEndRow>();
for (var d = f.From.Date; d <= f.To.Date; d = d.AddDays(1))
days.Add(BuildRow(d, data));
var totals = new DayEndRow(
"Total",
days.Sum(r => r.TotalSales), days.Sum(r => r.Collected), days.Sum(r => r.Due),
days.Sum(r => r.Cash), days.Sum(r => r.Cheque), days.Sum(r => r.Credit),
days.Sum(r => r.Returns), days.Sum(r => r.Expenses), days.Sum(r => r.Income),
days.Sum(r => r.InvoiceCount), days.Sum(r => r.PaymentCount),
days.Sum(r => r.CashCollected), days.Sum(r => r.CashExpensesPaid), days.Sum(r => r.NetCash));
return new DayEndDailyResponse(f.From, f.To, days, totals);
}
// Accrual basis: Income = sales (finalized invoices) - returns - expenses billed that day.
// Due is the unpaid part of that day's invoices as at the end of the day; Credit payments never count as paid.
private static DayEndRow BuildRow(DateTime day, DayEndData data)
{
var next = day.AddDays(1);
var invoices = data.Invoices.Where(i => i.FinalizedAt.Date == day).ToList();
var invoiceIds = invoices.Select(i => i.Id).ToHashSet();
var paidByInvoice = data.Payments
.Where(p => invoiceIds.Contains(p.InvoiceId) && p.Method != PaymentMethod.Credit && p.CollectedAt < next)
.GroupBy(p => p.InvoiceId)
.ToDictionary(g => g.Key, g => g.Sum(p => p.Amount));
var totalSales = invoices.Sum(i => i.Total);
var due = invoices.Sum(i => Math.Max(0m, i.Total - paidByInvoice.GetValueOrDefault(i.Id)));
var payments = data.Payments.Where(p => p.CollectedAt.Date == day).ToList();
var cash = payments.Where(p => p.Method == PaymentMethod.Cash).Sum(p => p.Amount);
var cheque = payments.Where(p => p.Method == PaymentMethod.Cheque).Sum(p => p.Amount);
var credit = payments.Where(p => p.Method == PaymentMethod.Credit).Sum(p => p.Amount);
var returns = data.Returns.Where(r => r.At.Date == day).Sum(r => r.Amount);
var expenses = data.Expenses.Where(e => e.BillDate.Date == day).Sum(e => e.Amount);
var cashExpensesPaid = data.Expenses
.Where(e => e.Method == PaymentMethod.Cash && e.PaidAt.HasValue && e.PaidAt.Value.Date == day)
.Sum(e => e.Amount);
return new DayEndRow(
day.ToString("yyyy-MM-dd"),
totalSales, cash + cheque, due, cash, cheque, credit,
returns, expenses, totalSales - returns - expenses,
invoices.Count, payments.Count,
cash, cashExpensesPaid, cash - cashExpensesPaid);
}
}