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> 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( items.Select(b => ToBookResponse(b, includePages: false)).ToList(), total, page, pageSize); } public async Task 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 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 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 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); } }