209 lines
6.9 KiB
C#
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);
|
|
}
|
|
}
|