212 lines
8.9 KiB
C#
212 lines
8.9 KiB
C#
using ERPCore.Domain.Enums;
|
|
using ERPCore.Dtos.Accounts;
|
|
using ERPCore.System.Errors;
|
|
using ERPCore.Tests.TestSupport;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using static ERPCore.Tests.TestSupport.BankDepositTestSupport;
|
|
|
|
namespace ERPCore.Tests;
|
|
|
|
/// <summary>Recording and cancelling bank deposits (docs/BANK_DEPOSIT_MODULE_PLAN.md §7.2, §11).</summary>
|
|
public class BankDepositServiceTests
|
|
{
|
|
private static readonly DateOnly Today = Clock.Today;
|
|
private static readonly DateOnly Start = Today.AddDays(-10);
|
|
private static readonly DateOnly Day = Today.AddDays(-2);
|
|
|
|
[Fact]
|
|
public async Task Create_PostsBankDebitCashCredit_AndLowersCashInHand()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000));
|
|
await db.SaveChangesAsync();
|
|
var gl = new FakeGeneralLedger();
|
|
|
|
var dto = await DepositService(db, Start, gl).CreateAsync(Request(600, depositDate: Day.AddDays(1)));
|
|
|
|
Assert.Equal(BankDepositStatus.Posted, dto.Status);
|
|
Assert.Equal(("BOC Current", "Bank of Ceylon", "0001234567"), (dto.ToBankAccountName, dto.ToBankName, dto.ToAccountNumber));
|
|
Assert.Equal("JE-1", dto.GlJournalNo);
|
|
|
|
var entry = Assert.Single(gl.Posted);
|
|
Assert.Equal(Day.AddDays(1), entry.EntryDate);
|
|
Assert.Equal("BANK_DEPOSIT", entry.SourceModule);
|
|
Assert.Collection(entry.Lines,
|
|
l => Assert.Equal(("1010-01", 600m, 0m), (l.AccountCode, l.DebitAmount, l.CreditAmount)),
|
|
l => Assert.Equal(("1000-01", 0m, 600m), (l.AccountCode, l.DebitAmount, l.CreditAmount)));
|
|
|
|
var position = await CashService(db, Start).GetPositionAsync(Day, Branch1);
|
|
Assert.Equal((600m, 400m), (position.Position.Deposited, position.Position.Closing));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_MoreThanCashInHand_IsRejected()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000, card: 5000));
|
|
await db.SaveChangesAsync();
|
|
var gl = new FakeGeneralLedger();
|
|
|
|
var ex = await Assert.ThrowsAsync<DomainException>(() => DepositService(db, Start, gl).CreateAsync(Request(1000.01m)));
|
|
|
|
Assert.Equal(ErrorCodes.BankDepositExceedsCashInHand, ex.Code);
|
|
Assert.Empty(gl.Posted);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_BackDatedDepositThatOverdrawsALaterDay_IsRejected()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000));
|
|
db.BankDeposits.Add(Deposit(Day.AddDays(1), 900)); // the next day already banked most of it
|
|
await db.SaveChangesAsync();
|
|
|
|
var ex = await Assert.ThrowsAsync<DomainException>(() =>
|
|
DepositService(db, Start, new FakeGeneralLedger()).CreateAsync(Request(500)));
|
|
|
|
Assert.Equal(ErrorCodes.BankDepositExceedsCashInHand, ex.Code);
|
|
Assert.Contains("100.00", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Cancel_ReversesGl_AndPutsTheCashBack()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000));
|
|
await db.SaveChangesAsync();
|
|
var gl = new FakeGeneralLedger();
|
|
var service = DepositService(db, Start, gl);
|
|
var created = await service.CreateAsync(Request(1000));
|
|
|
|
var cancelled = await service.CancelAsync(created.BankDepositId, new CancelBankDepositRequest { Reason = " wrong bank " });
|
|
|
|
Assert.Equal(BankDepositStatus.Cancelled, cancelled.Status);
|
|
Assert.Equal("wrong bank", cancelled.CancelReason);
|
|
Assert.Equal("JE-2", cancelled.GlReversalJournalNo);
|
|
Assert.Collection(gl.Posted[1].Lines,
|
|
l => Assert.Equal(("1000-01", 1000m, 0m), (l.AccountCode, l.DebitAmount, l.CreditAmount)),
|
|
l => Assert.Equal(("1010-01", 0m, 1000m), (l.AccountCode, l.DebitAmount, l.CreditAmount)));
|
|
Assert.Equal(1000m, (await CashService(db, Start).GetPositionAsync(Day, Branch1)).Position.Closing);
|
|
|
|
var again = await Assert.ThrowsAsync<DomainException>(() =>
|
|
service.CancelAsync(created.BankDepositId, new CancelBankDepositRequest { Reason = "again" }));
|
|
Assert.Equal(ErrorCodes.BankDepositNotCancellable, again.Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(FakeGeneralLedger.BankAccountId, FakeGeneralLedger.BankAccountId, ErrorCodes.BankDepositCashAccountInvalid)]
|
|
[InlineData(FakeGeneralLedger.CashAccountId, FakeGeneralLedger.CashAccountId, ErrorCodes.BankDepositBankAccountInvalid)]
|
|
[InlineData(99L, FakeGeneralLedger.BankAccountId, ErrorCodes.BankDepositCashAccountInvalid)]
|
|
public async Task Create_WrongAccountTypes_AreRejected(long from, long to, string code)
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000));
|
|
await db.SaveChangesAsync();
|
|
|
|
var request = Request(100);
|
|
request.FromGlCashAccountId = from;
|
|
request.ToGlBankAccountId = to;
|
|
var ex = await Assert.ThrowsAsync<DomainException>(() => DepositService(db, Start, new FakeGeneralLedger()).CreateAsync(request));
|
|
|
|
Assert.Equal(code, ex.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_CashAndBankAccountsSharingAnId_AreToldApartByType()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000));
|
|
await db.SaveChangesAsync();
|
|
// GL numbers cash and bank accounts in separate tables, so both can be account 1.
|
|
var gl = new FakeGeneralLedger
|
|
{
|
|
Accounts =
|
|
[
|
|
new("Cash", 1, "main", null, "Cash in Transit", "CASH-000001", 22, "1400-01-01", "LKR"),
|
|
new("Bank", 1, "Main - 01", "Commercial Bank", null, "2032434543", 2, "1000-01", "LKR")
|
|
]
|
|
};
|
|
var request = Request(400);
|
|
request.FromGlCashAccountId = 1;
|
|
request.ToGlBankAccountId = 1;
|
|
|
|
var dto = await DepositService(db, Start, gl).CreateAsync(request);
|
|
|
|
Assert.Equal(("main", "Main - 01"), (dto.FromCashAccountName, dto.ToBankAccountName));
|
|
Assert.Collection(Assert.Single(gl.Posted).Lines,
|
|
l => Assert.Equal(("1000-01", 400m), (l.AccountCode, l.DebitAmount)),
|
|
l => Assert.Equal(("1400-01-01", 400m), (l.AccountCode, l.CreditAmount)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_CannotUseAnotherBranchsCash()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000, warehouseId: Warehouse2));
|
|
await db.SaveChangesAsync();
|
|
|
|
var ex = await Assert.ThrowsAsync<DomainException>(() =>
|
|
DepositService(db, Start, new FakeGeneralLedger()).CreateAsync(Request(100, branchId: Branch1)));
|
|
|
|
Assert.Equal(ErrorCodes.BankDepositExceedsCashInHand, ex.Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-11, null)] // before tracking starts
|
|
[InlineData(1, null)] // business date in the future
|
|
[InlineData(-2, -3)] // deposited before the business day
|
|
[InlineData(-2, 2)] // deposit date beyond tomorrow
|
|
public async Task Create_InvalidDates_AreRejected(int businessOffset, int? depositOffset)
|
|
{
|
|
await using var db = CreateContext();
|
|
var request = Request(100);
|
|
request.BusinessDate = Today.AddDays(businessOffset);
|
|
request.DepositDate = depositOffset is { } d ? Today.AddDays(d) : null;
|
|
|
|
var ex = await Assert.ThrowsAsync<DomainException>(() => DepositService(db, Start, new FakeGeneralLedger()).CreateAsync(request));
|
|
|
|
Assert.Equal(ErrorCodes.BankDepositDateInvalid, ex.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_GlRejection_LeavesNoDeposit()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.SalesSlips.Add(CashSlip(Day, 1000));
|
|
await db.SaveChangesAsync();
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() =>
|
|
DepositService(db, Start, new FakeGeneralLedger { Reject = true }).CreateAsync(Request(500)));
|
|
|
|
Assert.Equal(0, await db.BankDeposits.CountAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task List_FiltersAndTotalsEveryMatchingRow()
|
|
{
|
|
await using var db = CreateContext();
|
|
db.BankDeposits.AddRange(
|
|
Deposit(Day, 100), Deposit(Day, 200), Deposit(Day.AddDays(-1), 400),
|
|
Deposit(Day, 800, branchId: Branch2), Deposit(Day, 1600, status: BankDepositStatus.Cancelled));
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await DepositService(db, Start, new FakeGeneralLedger()).ListAsync(
|
|
new() { PageSize = 1 }, new BankDepositListFilter(Day, Day, Branch1, null, BankDepositStatus.Posted));
|
|
|
|
Assert.Single(result.Items);
|
|
Assert.Equal(2, result.Pagination.TotalItems);
|
|
Assert.Equal(300m, result.TotalAmount);
|
|
}
|
|
|
|
private static CreateBankDepositRequest Request(decimal amount, int branchId = Branch1, DateOnly? depositDate = null) => new()
|
|
{
|
|
BranchId = branchId,
|
|
BusinessDate = Day,
|
|
DepositDate = depositDate,
|
|
Amount = amount,
|
|
FromGlCashAccountId = FakeGeneralLedger.CashAccountId,
|
|
ToGlBankAccountId = FakeGeneralLedger.BankAccountId,
|
|
DepositSlipNo = "SLIP-1"
|
|
};
|
|
}
|