Files
DeepnaPooja e175a7a040
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Successful in 2m38s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 13m9s
feat: Implement Day-End Sales report fe
2026-10-23 14:54:40 +05:30

209 lines
6.9 KiB
C#

using Distribution.Features.DayEndReport;
using Distribution.Models;
using Moq;
using Xunit;
namespace Distribution.Tests.Services;
public class DayEndReportServiceTests
{
private static readonly DateTime Day = new(2026, 10, 20);
private readonly Mock<IDayEndReportRepository> _repo = new();
private readonly DayEndReportService _sut;
public DayEndReportServiceTests()
{
_sut = new DayEndReportService(_repo.Object);
}
private void Returns(DayEndData data) =>
_repo.Setup(r => r.GetDataAsync(It.IsAny<DayEndFilter>())).ReturnsAsync(data);
private static DayEndData Data(
List<DayEndInvoiceRow>? invoices = null,
List<DayEndPaymentRow>? payments = null,
List<DayEndReturnRow>? returns = null,
List<DayEndExpenseRow>? expenses = null) =>
new(invoices ?? [], payments ?? [], returns ?? [], expenses ?? []);
private static DayEndExpenseRow Expense(DateTime billDate, decimal amount, PaymentMethod? method = null, DateTime? paidAt = null) =>
new(Guid.NewGuid(), billDate, paidAt, method, amount, "Utilities", "Payee", null);
// Validate / BuildFilter
[Fact]
public void Validate_ReturnsError_WhenToBeforeFrom()
{
var f = new DayEndFilter(Day, Day.AddDays(-1));
Assert.NotNull(DayEndReportService.Validate(f));
}
[Fact]
public void Validate_ReturnsError_WhenRangeTooLong()
{
var f = new DayEndFilter(Day, Day.AddDays(DayEndReportService.MaxRangeDays));
Assert.NotNull(DayEndReportService.Validate(f));
}
[Fact]
public void Validate_ReturnsNull_ForMaxAllowedRange()
{
var f = new DayEndFilter(Day, Day.AddDays(DayEndReportService.MaxRangeDays - 1));
Assert.Null(DayEndReportService.Validate(f));
}
// GetDayAsync
[Fact]
public async Task GetDayAsync_ComputesSalesCollectionsAndIncome()
{
var inv1 = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(10), 1000m);
var inv2 = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(15), 500m);
Returns(Data(
invoices: [inv1, inv2],
payments:
[
new(inv1.Id, PaymentMethod.Cash, 600m, Day.AddHours(11)),
new(inv1.Id, PaymentMethod.Cheque, 400m, Day.AddHours(12)),
new(inv2.Id, PaymentMethod.Credit, 500m, Day.AddHours(16)),
],
returns: [new(Day.AddHours(9), 100m)],
expenses: [Expense(Day, 250m)]));
var result = await _sut.GetDayAsync(Day);
var s = result.Summary;
Assert.Equal(1500m, s.TotalSales);
Assert.Equal(1000m, s.Collected);
Assert.Equal(500m, s.Due);
Assert.Equal(600m, s.Cash);
Assert.Equal(400m, s.Cheque);
Assert.Equal(500m, s.Credit);
Assert.Equal(100m, s.Returns);
Assert.Equal(250m, s.Expenses);
Assert.Equal(1150m, s.Income);
Assert.Equal(2, s.InvoiceCount);
Assert.Equal(3, s.PaymentCount);
}
[Fact]
public async Task GetDayAsync_CreditPayment_DoesNotReduceDue()
{
var inv = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(10), 800m);
Returns(Data(invoices: [inv], payments: [new(inv.Id, PaymentMethod.Credit, 800m, Day.AddHours(10))]));
var result = await _sut.GetDayAsync(Day);
Assert.Equal(800m, result.Summary.Due);
Assert.Equal(0m, result.Summary.Collected);
}
[Fact]
public async Task GetDayAsync_PaymentAfterDayEnd_DoesNotReduceThatDaysDue()
{
var inv = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(10), 800m);
Returns(Data(invoices: [inv], payments: [new(inv.Id, PaymentMethod.Cash, 800m, Day.AddDays(1).AddHours(1))]));
var result = await _sut.GetDayAsync(Day);
Assert.Equal(800m, result.Summary.Due);
Assert.Equal(0m, result.Summary.Collected);
}
[Fact]
public async Task GetDayAsync_InstalmentOnOlderInvoice_CountsAsCollectedNotSales()
{
var older = Guid.NewGuid();
Returns(Data(payments: [new(older, PaymentMethod.Cash, 300m, Day.AddHours(9))]));
var result = await _sut.GetDayAsync(Day);
Assert.Equal(0m, result.Summary.TotalSales);
Assert.Equal(300m, result.Summary.Collected);
}
[Fact]
public async Task GetDayAsync_Overpayment_DueIsNeverNegative()
{
var inv = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(10), 100m);
Returns(Data(invoices: [inv], payments: [new(inv.Id, PaymentMethod.Cash, 150m, Day.AddHours(11))]));
var result = await _sut.GetDayAsync(Day);
Assert.Equal(0m, result.Summary.Due);
}
[Fact]
public async Task GetDayAsync_OnlyCountsExpensesBilledThatDay()
{
Returns(Data(expenses:
[
Expense(Day, 100m),
Expense(Day.AddDays(-1), 999m, PaymentMethod.Cash, paidAt: Day.AddHours(8)),
]));
var result = await _sut.GetDayAsync(Day);
Assert.Equal(100m, result.Summary.Expenses);
Assert.Single(result.Expenses);
}
[Fact]
public async Task GetDayAsync_NetCash_IsCashCollectedMinusCashExpensesPaid()
{
var inv = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(10), 1000m);
Returns(Data(
invoices: [inv],
payments:
[
new(inv.Id, PaymentMethod.Cash, 700m, Day.AddHours(11)),
new(inv.Id, PaymentMethod.Cheque, 300m, Day.AddHours(11)),
],
expenses:
[
Expense(Day, 200m, PaymentMethod.Cash, paidAt: Day.AddHours(14)),
Expense(Day, 50m, PaymentMethod.Cheque, paidAt: Day.AddHours(14)),
]));
var result = await _sut.GetDayAsync(Day);
Assert.Equal(700m, result.Summary.CashCollected);
Assert.Equal(200m, result.Summary.CashExpensesPaid);
Assert.Equal(500m, result.Summary.NetCash);
}
[Fact]
public async Task GetDayAsync_NoActivity_ReturnsZeros()
{
Returns(Data());
var result = await _sut.GetDayAsync(Day);
Assert.Equal(0m, result.Summary.Income);
Assert.Equal(3, result.ByMethod.Count);
Assert.Empty(result.Expenses);
}
// GetDailyAsync
[Fact]
public async Task GetDailyAsync_FillsEmptyDays_AndTotalsMatchSumOfDays()
{
var inv = new DayEndInvoiceRow(Guid.NewGuid(), Day.AddHours(10), 1000m);
Returns(Data(
invoices: [inv],
payments: [new(inv.Id, PaymentMethod.Cash, 1000m, Day.AddHours(11))],
expenses: [Expense(Day.AddDays(2), 300m)]));
var result = await _sut.GetDailyAsync(new DayEndFilter(Day, Day.AddDays(2)));
Assert.Equal(3, result.Days.Count);
Assert.Equal(0m, result.Days[1].TotalSales);
Assert.Equal(1000m, result.Totals.TotalSales);
Assert.Equal(300m, result.Totals.Expenses);
Assert.Equal(700m, result.Totals.Income);
Assert.Equal(result.Days.Sum(d => d.Income), result.Totals.Income);
}
}