feat: Implement Day End Report feature logic
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user