feat: add cheque books and received cheques management
- Implemented ChequeBooksPage for viewing and filtering cheque books. - Added NewReceivedChequePage for recording received cheques. - Created ReceivedChequesPage for listing and managing received cheques. - Introduced API endpoints for bank accounts, cheque books, and received cheques. - Defined types for payments including bank accounts, cheque books, and received cheques.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
public class ChequeBookService(
|
||||
IChequeBookRepository repo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private const string DefaultCurrency = "LKR";
|
||||
|
||||
private static ChequePageResponse ToPageResponse(Models.ChequePage p, string chequeBookNo) =>
|
||||
new(p.ChequeNo, chequeBookNo, p.IssueStatus, p.PayeeType, p.PayeeId, p.PayeeName,
|
||||
p.IssueDate, p.Amount, p.CurrencyCode, p.VoucherId, p.ReferenceNo, p.Purpose,
|
||||
p.IsCrossCheque, p.IsAccountPayee, p.IsPostDated, p.Notes,
|
||||
p.PrintedBy, p.PrintedAt, p.ClearedDate, p.ClearedByBank, p.CancelReason);
|
||||
|
||||
private static ChequeBookResponse ToBookResponse(Models.ChequeBook b, bool includePages) =>
|
||||
new(b.ChequeBookNo, b.BranchId, b.FK_BankAccountId, b.StartChequeNo, b.EndChequeNo,
|
||||
b.TotalLeaves, b.ReceivedDate, b.Description, b.CreatedBy, b.Status,
|
||||
includePages ? b.Pages.OrderBy(p => p.ChequeNo).Select(p => ToPageResponse(p, b.ChequeBookNo)).ToList() : new());
|
||||
|
||||
public async Task<PagedResult<ChequeBookResponse>> ListAsync(
|
||||
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize)
|
||||
{
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize < 1 || pageSize > 200) pageSize = 50;
|
||||
|
||||
var (items, total) = await repo.ListAsync(bankAccountId, branchId, status, page, pageSize);
|
||||
return new PagedResult<ChequeBookResponse>(
|
||||
items.Select(b => ToBookResponse(b, includePages: false)).ToList(),
|
||||
total, page, pageSize);
|
||||
}
|
||||
|
||||
public async Task<ChequeBookResponse?> GetAsync(string chequeBookNo, bool expandPages)
|
||||
{
|
||||
var book = await repo.GetByNoAsync(chequeBookNo, includePages: expandPages);
|
||||
return book is null ? null : ToBookResponse(book, includePages: expandPages);
|
||||
}
|
||||
|
||||
public async Task<ChequeBookResponse> CreateAsync(CreateChequeBookRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.ChequeBookNo))
|
||||
throw new InvalidOperationException("ChequeBookNo is required");
|
||||
if (request.TotalLeaves <= 0)
|
||||
throw new InvalidOperationException("TotalLeaves must be greater than zero");
|
||||
if (!long.TryParse(request.StartChequeNo, out var start) ||
|
||||
!long.TryParse(request.EndChequeNo, out var end))
|
||||
throw new InvalidOperationException("StartChequeNo and EndChequeNo must be numeric");
|
||||
if (end < start)
|
||||
throw new InvalidOperationException("EndChequeNo must be greater than or equal to StartChequeNo");
|
||||
if (end - start + 1 != request.TotalLeaves)
|
||||
throw new InvalidOperationException("TotalLeaves does not match the Start/End cheque range");
|
||||
if (await repo.BookNoExistsAsync(request.ChequeBookNo))
|
||||
throw new InvalidOperationException("A cheque book with this number already exists");
|
||||
|
||||
var bankAccount = await bankAccountRepo.GetByIdAsync(request.BankAccountId);
|
||||
if (bankAccount is null || bankAccount.AccountType != BankAccountType.Bank)
|
||||
throw new InvalidOperationException("BankAccountId does not reference a valid bank account");
|
||||
|
||||
var book = new Models.ChequeBook
|
||||
{
|
||||
ChequeBookNo = request.ChequeBookNo,
|
||||
BranchId = request.BranchId,
|
||||
FK_BankAccountId = request.BankAccountId,
|
||||
StartChequeNo = request.StartChequeNo,
|
||||
EndChequeNo = request.EndChequeNo,
|
||||
TotalLeaves = request.TotalLeaves,
|
||||
ReceivedDate = request.ReceivedDate,
|
||||
Description = request.Description,
|
||||
CreatedBy = request.CreatedBy,
|
||||
Status = ChequeBookStatus.Active
|
||||
};
|
||||
|
||||
var width = request.StartChequeNo.Length;
|
||||
for (var n = start; n <= end; n++)
|
||||
{
|
||||
book.Pages.Add(new Models.ChequePage
|
||||
{
|
||||
ChequeNo = n.ToString().PadLeft(width, '0'),
|
||||
IssueStatus = ChequePageIssueStatus.Unused
|
||||
});
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(book);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToBookResponse(book, includePages: true);
|
||||
}
|
||||
|
||||
public async Task<ChequePageResponse?> IssueAsync(string chequeNo, IssueChequePageRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var page = await repo.GetPageByNoAsync(chequeNo);
|
||||
if (page is null) return null;
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Unused)
|
||||
throw new InvalidOperationException($"Cannot issue a cheque in status '{page.IssueStatus}'");
|
||||
if (page.ChequeBook is null || page.ChequeBook.Status != ChequeBookStatus.Active)
|
||||
throw new InvalidOperationException("The cheque book is not active");
|
||||
if (request.Amount <= 0)
|
||||
throw new InvalidOperationException("Amount must be greater than zero");
|
||||
if (string.IsNullOrWhiteSpace(request.PayeeName))
|
||||
throw new InvalidOperationException("PayeeName is required");
|
||||
|
||||
page.IssueStatus = ChequePageIssueStatus.Issued;
|
||||
page.PayeeType = request.PayeeType;
|
||||
page.PayeeId = request.PayeeId;
|
||||
page.PayeeName = request.PayeeName;
|
||||
page.IssueDate = request.IssueDate;
|
||||
page.Amount = request.Amount;
|
||||
page.CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode;
|
||||
page.VoucherId = request.VoucherId;
|
||||
page.ReferenceNo = request.ReferenceNo;
|
||||
page.Purpose = request.Purpose;
|
||||
page.IsCrossCheque = request.IsCrossCheque;
|
||||
page.IsAccountPayee = request.IsAccountPayee;
|
||||
page.IsPostDated = request.IsPostDated;
|
||||
page.Notes = request.Notes;
|
||||
page.PrintedBy = request.PrintedBy;
|
||||
page.PrintedAt = DateTime.Now;
|
||||
|
||||
repo.UpdatePage(page);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToPageResponse(page, page.ChequeBook!.ChequeBookNo);
|
||||
}
|
||||
|
||||
public async Task<ChequePageResponse?> UpdatePageStatusAsync(string chequeNo, UpdateChequePageStatusRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var page = await repo.GetPageByNoAsync(chequeNo);
|
||||
if (page is null) return null;
|
||||
|
||||
switch (request.Action)
|
||||
{
|
||||
case ChequePageStatusAction.Clear:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Issued)
|
||||
throw new InvalidOperationException("Only issued cheques can be cleared");
|
||||
if (request.ClearedDate is null)
|
||||
throw new InvalidOperationException("ClearedDate is required when clearing a cheque");
|
||||
page.IssueStatus = ChequePageIssueStatus.Cleared;
|
||||
page.ClearedDate = request.ClearedDate;
|
||||
page.ClearedByBank = true;
|
||||
break;
|
||||
|
||||
case ChequePageStatusAction.Bounce:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Issued)
|
||||
throw new InvalidOperationException("Only issued cheques can be bounced");
|
||||
page.IssueStatus = ChequePageIssueStatus.Bounced;
|
||||
break;
|
||||
|
||||
case ChequePageStatusAction.Cancel:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Issued)
|
||||
throw new InvalidOperationException("Only issued cheques can be cancelled");
|
||||
if (string.IsNullOrWhiteSpace(request.CancelReason))
|
||||
throw new InvalidOperationException("CancelReason is required when cancelling a cheque");
|
||||
page.IssueStatus = ChequePageIssueStatus.Cancelled;
|
||||
page.CancelReason = request.CancelReason;
|
||||
break;
|
||||
|
||||
case ChequePageStatusAction.Void:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Unused)
|
||||
throw new InvalidOperationException("Only unused cheques can be voided");
|
||||
page.IssueStatus = ChequePageIssueStatus.Void;
|
||||
break;
|
||||
}
|
||||
|
||||
repo.UpdatePage(page);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToPageResponse(page, page.ChequeBook!.ChequeBookNo);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user