feat: add cheque books and received cheques management
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 11m8s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 14m49s

- 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:
Kalana B. Thilakarathna
2026-10-19 16:09:05 +05:30
parent f3f2b71bd4
commit 87c807c16c
39 changed files with 10095 additions and 50 deletions
+59
View File
@@ -65,6 +65,10 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
public DbSet<RegionalTaxRule> RegionalTaxRules { get; set; }
public DbSet<RouteVisitLog> RouteVisitLogs { get; set; }
public DbSet<SysConfig> SysConfigs { get; set; }
public DbSet<BankAccount> BankAccounts { get; set; }
public DbSet<ChequeBook> ChequeBooks { get; set; }
public DbSet<ChequePage> ChequePages { get; set; }
public DbSet<ReceivedCheque> ReceivedCheques { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -594,6 +598,61 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyDefaultWarrantyPeriodDays, Value = "0", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null }
);
// --- Payment service: Bank Accounts & Cheques ---
modelBuilder.Entity<BankAccount>()
.Property(a => a.AccountName).HasMaxLength(200);
modelBuilder.Entity<BankAccount>()
.Property(a => a.BankName).HasMaxLength(200);
modelBuilder.Entity<BankAccount>()
.Property(a => a.CashAccountTypeName).HasMaxLength(100);
modelBuilder.Entity<BankAccount>()
.Property(a => a.AccountNumber).HasMaxLength(100);
modelBuilder.Entity<BankAccount>()
.Property(a => a.CurrencyCode).HasMaxLength(10);
modelBuilder.Entity<BankAccount>()
.HasIndex(a => new { a.AccountType, a.AccountName })
.IsUnique();
modelBuilder.Entity<ChequeBook>()
.Property(b => b.ChequeBookNo).HasMaxLength(100);
modelBuilder.Entity<ChequeBook>()
.Property(b => b.StartChequeNo).HasMaxLength(50);
modelBuilder.Entity<ChequeBook>()
.Property(b => b.EndChequeNo).HasMaxLength(50);
modelBuilder.Entity<ChequeBook>()
.HasIndex(b => b.ChequeBookNo).IsUnique();
modelBuilder.Entity<ChequeBook>()
.HasOne(b => b.BankAccount)
.WithMany()
.HasForeignKey(b => b.FK_BankAccountId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ChequePage>()
.Property(p => p.ChequeNo).HasMaxLength(50);
modelBuilder.Entity<ChequePage>()
.Property(p => p.CurrencyCode).HasMaxLength(10);
modelBuilder.Entity<ChequePage>()
.HasIndex(p => p.ChequeNo).IsUnique();
modelBuilder.Entity<ChequePage>()
.HasOne(p => p.ChequeBook)
.WithMany(b => b.Pages)
.HasForeignKey(p => p.FK_ChequeBookId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ReceivedCheque>()
.Property(c => c.ChequeNo).HasMaxLength(50);
modelBuilder.Entity<ReceivedCheque>()
.Property(c => c.ReceivedFromName).HasMaxLength(200);
modelBuilder.Entity<ReceivedCheque>()
.HasIndex(c => c.ChequeNo);
modelBuilder.Entity<ReceivedCheque>()
.HasOne(c => c.DepositBankAccount)
.WithMany()
.HasForeignKey(c => c.FK_DepositBankAccountId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
Seed.MasterDataSeed.Apply(modelBuilder);
Seed.OperationsDataSeed.Apply(modelBuilder);
@@ -0,0 +1,66 @@
using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.BankAccount;
[ApiController]
[Route("api")]
public class BankAccountController(BankAccountService service) : ControllerBase
{
[HttpGet("bank-accounts")]
public async Task<IActionResult> List([FromQuery] BankAccountType? accountType)
{
var result = await service.ListAsync(accountType);
return Ok(result);
}
[HttpPost("bank-accounts")]
public async Task<IActionResult> CreateBank(CreateBankAccountRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateBankAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPost("cash-accounts")]
public async Task<IActionResult> CreateCash(CreateCashAccountRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateCashAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("cash-accounts/{id:guid}")]
public async Task<IActionResult> UpdateCash(Guid id, UpdateCashAccountRequest request, CancellationToken ct)
{
try
{
var result = await service.UpdateCashAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPatch("cash-accounts/{id:guid}/status")]
public async Task<IActionResult> SetCashStatus(Guid id, SetCashAccountStatusRequest request, CancellationToken ct)
{
try
{
var result = await service.SetCashStatusAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpDelete("cash-accounts/{id:guid}")]
public async Task<IActionResult> DeleteCash(Guid id, CancellationToken ct)
{
try
{
var ok = await service.DeleteCashAsync(id, ct);
return ok ? NoContent() : NotFound();
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpGet("cash-account-types")]
public async Task<IActionResult> ListCashAccountTypes() => Ok(await service.ListCashAccountTypesAsync());
}
@@ -0,0 +1,33 @@
using Distribution.Models;
namespace Distribution.Features.BankAccount;
public record BankAccountResponse(
BankAccountType AccountType,
Guid AccountId,
string AccountName,
string? BankName,
string? CashAccountTypeName,
string? AccountNumber,
string CurrencyCode,
DateTime CreatedAt,
bool? IsActive,
bool? IsDefault);
public record CreateBankAccountRequest(
string AccountName,
string? BankName,
string? AccountNumber,
string? CurrencyCode);
public record CreateCashAccountRequest(
string AccountName,
string CashAccountTypeName,
string? AccountNumber,
string? CurrencyCode);
public record UpdateCashAccountRequest(string AccountName);
public record SetCashAccountStatusRequest(bool IsActive);
public record CashAccountTypeDto(string Name);
@@ -0,0 +1,40 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.BankAccount;
public class BankAccountRepository(AppDBContext context) : IBankAccountRepository
{
public async Task<Models.BankAccount?> GetByIdAsync(Guid id) =>
await context.BankAccounts.FirstOrDefaultAsync(a => a.Id == id);
public async Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType)
{
var query = context.BankAccounts.AsQueryable();
if (accountType is not null)
query = query.Where(a => a.AccountType == accountType);
return await query.OrderByDescending(a => a.CreatedAt).ToListAsync();
}
public async Task<List<string>> ListCashAccountTypeNamesAsync() =>
await context.BankAccounts
.Where(a => a.AccountType == BankAccountType.Cash && a.CashAccountTypeName != null)
.Select(a => a.CashAccountTypeName!)
.Distinct()
.OrderBy(n => n)
.ToListAsync();
public async Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null) =>
await context.BankAccounts.AnyAsync(a =>
a.AccountType == type &&
a.AccountName == accountName &&
(excludeId == null || a.Id != excludeId));
public async Task AddAsync(Models.BankAccount account) =>
await context.BankAccounts.AddAsync(account);
public void Update(Models.BankAccount account) => context.BankAccounts.Update(account);
public void Remove(Models.BankAccount account) => context.BankAccounts.Remove(account);
}
@@ -0,0 +1,116 @@
using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.BankAccount;
public class BankAccountService(IBankAccountRepository repo, IUnitOfWork uow)
{
private const string DefaultCurrency = "LKR";
private static BankAccountResponse ToResponse(Models.BankAccount a) =>
new(
a.AccountType,
a.Id,
a.AccountName,
a.AccountType == BankAccountType.Bank ? a.BankName : null,
a.AccountType == BankAccountType.Cash ? a.CashAccountTypeName : null,
a.AccountNumber,
a.CurrencyCode,
a.CreatedAt,
a.AccountType == BankAccountType.Cash ? a.IsActive : null,
a.AccountType == BankAccountType.Cash ? a.IsDefault : null);
public async Task<List<BankAccountResponse>> ListAsync(BankAccountType? accountType) =>
(await repo.ListAsync(accountType)).Select(ToResponse).ToList();
public async Task<List<CashAccountTypeDto>> ListCashAccountTypesAsync() =>
(await repo.ListCashAccountTypeNamesAsync()).Select(n => new CashAccountTypeDto(n)).ToList();
public async Task<BankAccountResponse> CreateBankAsync(CreateBankAccountRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.AccountName))
throw new InvalidOperationException("AccountName is required");
if (await repo.ExistsByNameAsync(BankAccountType.Bank, request.AccountName))
throw new InvalidOperationException("A bank account with this name already exists");
var account = new Models.BankAccount
{
AccountType = BankAccountType.Bank,
AccountName = request.AccountName,
BankName = request.BankName,
AccountNumber = request.AccountNumber,
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
IsActive = true,
IsDefault = false
};
await repo.AddAsync(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<BankAccountResponse> CreateCashAsync(CreateCashAccountRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.AccountName))
throw new InvalidOperationException("AccountName is required");
if (string.IsNullOrWhiteSpace(request.CashAccountTypeName))
throw new InvalidOperationException("CashAccountTypeName is required");
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName))
throw new InvalidOperationException("A cash account with this name already exists");
var account = new Models.BankAccount
{
AccountType = BankAccountType.Cash,
AccountName = request.AccountName,
CashAccountTypeName = request.CashAccountTypeName,
AccountNumber = request.AccountNumber,
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
IsActive = true,
IsDefault = false
};
await repo.AddAsync(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<BankAccountResponse?> UpdateCashAsync(Guid id, UpdateCashAccountRequest request, CancellationToken ct = default)
{
var account = await repo.GetByIdAsync(id);
if (account is null || account.AccountType != BankAccountType.Cash) return null;
if (string.IsNullOrWhiteSpace(request.AccountName))
throw new InvalidOperationException("AccountName is required");
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName, excludeId: id))
throw new InvalidOperationException("A cash account with this name already exists");
account.AccountName = request.AccountName;
repo.Update(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<BankAccountResponse?> SetCashStatusAsync(Guid id, SetCashAccountStatusRequest request, CancellationToken ct = default)
{
var account = await repo.GetByIdAsync(id);
if (account is null || account.AccountType != BankAccountType.Cash) return null;
if (account.IsDefault && !request.IsActive)
throw new InvalidOperationException("The default cash account cannot be deactivated");
account.IsActive = request.IsActive;
repo.Update(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<bool> DeleteCashAsync(Guid id, CancellationToken ct = default)
{
var account = await repo.GetByIdAsync(id);
if (account is null || account.AccountType != BankAccountType.Cash) return false;
if (account.IsDefault)
throw new InvalidOperationException("The default cash account cannot be deleted");
repo.Remove(account);
await uow.CommitAsync(ct);
return true;
}
}
@@ -0,0 +1,14 @@
using Distribution.Models;
namespace Distribution.Features.BankAccount;
public interface IBankAccountRepository
{
Task<Models.BankAccount?> GetByIdAsync(Guid id);
Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType);
Task<List<string>> ListCashAccountTypeNamesAsync();
Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null);
Task AddAsync(Models.BankAccount account);
void Update(Models.BankAccount account);
void Remove(Models.BankAccount account);
}
@@ -0,0 +1,55 @@
using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.ChequeBook;
[ApiController]
[Route("api")]
public class ChequeBookController(ChequeBookService service) : ControllerBase
{
[HttpGet("cheque-books")]
public async Task<IActionResult> List(
[FromQuery] Guid? bankAccountId,
[FromQuery] int? branchId,
[FromQuery] ChequeBookStatus? status,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
=> Ok(await service.ListAsync(bankAccountId, branchId, status, page, pageSize));
[HttpGet("cheque-books/{chequeBookNo}")]
public async Task<IActionResult> Get(string chequeBookNo, [FromQuery] string? expand)
{
var expandPages = string.Equals(expand, "pages", StringComparison.OrdinalIgnoreCase);
var result = await service.GetAsync(chequeBookNo, expandPages);
return result is null ? NotFound() : Ok(result);
}
[HttpPost("cheque-books")]
public async Task<IActionResult> Create(CreateChequeBookRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("cheque-pages/{chequeNo}/issue")]
public async Task<IActionResult> Issue(string chequeNo, IssueChequePageRequest request, CancellationToken ct)
{
try
{
var result = await service.IssueAsync(chequeNo, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("cheque-pages/{chequeNo}/status")]
public async Task<IActionResult> UpdateStatus(string chequeNo, UpdateChequePageStatusRequest request, CancellationToken ct)
{
try
{
var result = await service.UpdatePageStatusAsync(chequeNo, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
}
@@ -0,0 +1,74 @@
using Distribution.Models;
namespace Distribution.Features.ChequeBook;
public record ChequePageResponse(
string ChequeNo,
string ChequeBookNo,
ChequePageIssueStatus IssueStatus,
PayeeType? PayeeType,
int? PayeeId,
string? PayeeName,
DateTime? IssueDate,
decimal? Amount,
string? CurrencyCode,
int? VoucherId,
string? ReferenceNo,
string? Purpose,
bool? IsCrossCheque,
bool? IsAccountPayee,
bool? IsPostDated,
string? Notes,
string? PrintedBy,
DateTime? PrintedAt,
DateTime? ClearedDate,
bool? ClearedByBank,
string? CancelReason);
public record ChequeBookResponse(
string ChequeBookNo,
int BranchId,
Guid BankAccountId,
string StartChequeNo,
string EndChequeNo,
int TotalLeaves,
DateTime ReceivedDate,
string? Description,
string? CreatedBy,
ChequeBookStatus Status,
List<ChequePageResponse> Pages);
public record PagedResult<T>(IReadOnlyList<T> Items, int TotalCount, int? Page, int? PageSize);
public record CreateChequeBookRequest(
int BranchId,
Guid BankAccountId,
string ChequeBookNo,
string StartChequeNo,
string EndChequeNo,
int TotalLeaves,
DateTime ReceivedDate,
string? Description,
string? CreatedBy);
public record IssueChequePageRequest(
PayeeType PayeeType,
int? PayeeId,
string PayeeName,
DateTime IssueDate,
decimal Amount,
string? CurrencyCode,
int? VoucherId,
string? ReferenceNo,
string? Purpose,
bool? IsCrossCheque,
bool? IsAccountPayee,
bool? IsPostDated,
string? Notes,
string? PrintedBy);
public record UpdateChequePageStatusRequest(
ChequePageStatusAction Action,
DateTime? ClearedDate,
string? CancelReason,
string? PerformedBy);
@@ -0,0 +1,46 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.ChequeBook;
public class ChequeBookRepository(AppDBContext context) : IChequeBookRepository
{
public async Task<Models.ChequeBook?> GetByNoAsync(string chequeBookNo, bool includePages)
{
var query = context.ChequeBooks.AsQueryable();
if (includePages) query = query.Include(b => b.Pages);
return await query.FirstOrDefaultAsync(b => b.ChequeBookNo == chequeBookNo);
}
public async Task<(List<Models.ChequeBook> Items, int TotalCount)> ListAsync(
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize)
{
var query = context.ChequeBooks.AsQueryable();
if (bankAccountId is not null) query = query.Where(b => b.FK_BankAccountId == bankAccountId);
if (branchId is not null) query = query.Where(b => b.BranchId == branchId);
if (status is not null) query = query.Where(b => b.Status == status);
var total = await query.CountAsync();
var items = await query
.OrderByDescending(b => b.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, total);
}
public async Task<bool> BookNoExistsAsync(string chequeBookNo) =>
await context.ChequeBooks.AnyAsync(b => b.ChequeBookNo == chequeBookNo);
public async Task AddAsync(Models.ChequeBook book) => await context.ChequeBooks.AddAsync(book);
public void Update(Models.ChequeBook book) => context.ChequeBooks.Update(book);
public async Task<Models.ChequePage?> GetPageByNoAsync(string chequeNo) =>
await context.ChequePages
.Include(p => p.ChequeBook)
.FirstOrDefaultAsync(p => p.ChequeNo == chequeNo);
public void UpdatePage(Models.ChequePage page) => context.ChequePages.Update(page);
}
@@ -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);
}
}
@@ -0,0 +1,16 @@
using Distribution.Models;
namespace Distribution.Features.ChequeBook;
public interface IChequeBookRepository
{
Task<Models.ChequeBook?> GetByNoAsync(string chequeBookNo, bool includePages);
Task<(List<Models.ChequeBook> Items, int TotalCount)> ListAsync(
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize);
Task<bool> BookNoExistsAsync(string chequeBookNo);
Task AddAsync(Models.ChequeBook book);
void Update(Models.ChequeBook book);
Task<Models.ChequePage?> GetPageByNoAsync(string chequeNo);
void UpdatePage(Models.ChequePage page);
}
@@ -0,0 +1,13 @@
using Distribution.Models;
namespace Distribution.Features.ReceivedCheque;
public interface IReceivedChequeRepository
{
Task<Models.ReceivedCheque?> GetByIdAsync(Guid id);
Task<(List<Models.ReceivedCheque> Items, int TotalCount)> ListAsync(
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
int page, int pageSize);
Task AddAsync(Models.ReceivedCheque cheque);
void Update(Models.ReceivedCheque cheque);
}
@@ -0,0 +1,37 @@
using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.ReceivedCheque;
[ApiController]
[Route("api/received-cheques")]
public class ReceivedChequeController(ReceivedChequeService service) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> List(
[FromQuery] int? companyId,
[FromQuery] int? branchId,
[FromQuery] ReceivedChequeStatus? status,
[FromQuery] ReceivedFromType? receivedFromType,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
=> Ok(await service.ListAsync(companyId, branchId, status, receivedFromType, page, pageSize));
[HttpPost]
public async Task<IActionResult> Create(CreateReceivedChequeRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("{id:guid}/status")]
public async Task<IActionResult> UpdateStatus(Guid id, UpdateReceivedChequeStatusRequest request, CancellationToken ct)
{
try
{
var result = await service.UpdateStatusAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
}
@@ -0,0 +1,51 @@
using Distribution.Features.ChequeBook;
using Distribution.Models;
namespace Distribution.Features.ReceivedCheque;
public record ReceivedChequeResponse(
Guid ReceivedChequeId,
int CompanyId,
int? BranchId,
ReceivedFromType ReceivedFromType,
int? ReceivedFromId,
string ReceivedFromName,
string? DrawerBankName,
string? DrawerBankBranch,
string? AccountHolderName,
string ChequeNo,
DateTime ChequeDate,
decimal Amount,
DateTime ReceivedDate,
string? ReferenceType,
int? ReferenceId,
string? Notes,
string? CreatedBy,
ReceivedChequeStatus Status,
Guid? DepositBankAccountId,
DateTime? DepositDate);
public record CreateReceivedChequeRequest(
int CompanyId,
int? BranchId,
ReceivedFromType ReceivedFromType,
int? ReceivedFromId,
string ReceivedFromName,
string? DrawerBankName,
string? DrawerBankBranch,
string? AccountHolderName,
string ChequeNo,
DateTime ChequeDate,
decimal Amount,
DateTime ReceivedDate,
string? ReferenceType,
int? ReferenceId,
string? Notes,
string? CreatedBy);
public record UpdateReceivedChequeStatusRequest(
ReceivedChequeStatusAction Action,
Guid? DepositBankAccountId,
DateTime? DepositDate,
string? Notes,
string? PerformedBy);
@@ -0,0 +1,34 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.ReceivedCheque;
public class ReceivedChequeRepository(AppDBContext context) : IReceivedChequeRepository
{
public async Task<Models.ReceivedCheque?> GetByIdAsync(Guid id) =>
await context.ReceivedCheques.FirstOrDefaultAsync(c => c.Id == id);
public async Task<(List<Models.ReceivedCheque> Items, int TotalCount)> ListAsync(
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
int page, int pageSize)
{
var query = context.ReceivedCheques.AsQueryable();
if (companyId is not null) query = query.Where(c => c.CompanyId == companyId);
if (branchId is not null) query = query.Where(c => c.BranchId == branchId);
if (status is not null) query = query.Where(c => c.Status == status);
if (receivedFromType is not null) query = query.Where(c => c.ReceivedFromType == receivedFromType);
var total = await query.CountAsync();
var items = await query
.OrderByDescending(c => c.ReceivedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, total);
}
public async Task AddAsync(Models.ReceivedCheque cheque) => await context.ReceivedCheques.AddAsync(cheque);
public void Update(Models.ReceivedCheque cheque) => context.ReceivedCheques.Update(cheque);
}
@@ -0,0 +1,115 @@
using Distribution.Features.BankAccount;
using Distribution.Features.ChequeBook;
using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.ReceivedCheque;
public class ReceivedChequeService(
IReceivedChequeRepository repo,
IBankAccountRepository bankAccountRepo,
IUnitOfWork uow)
{
private static ReceivedChequeResponse ToResponse(Models.ReceivedCheque c) =>
new(c.Id, c.CompanyId, c.BranchId, c.ReceivedFromType, c.ReceivedFromId, c.ReceivedFromName,
c.DrawerBankName, c.DrawerBankBranch, c.AccountHolderName,
c.ChequeNo, c.ChequeDate, c.Amount, c.ReceivedDate,
c.ReferenceType, c.ReferenceId, c.Notes, c.CreatedBy,
c.Status, c.FK_DepositBankAccountId, c.DepositDate);
public async Task<PagedResult<ReceivedChequeResponse>> ListAsync(
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
int page, int pageSize)
{
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 200) pageSize = 50;
var (items, total) = await repo.ListAsync(companyId, branchId, status, receivedFromType, page, pageSize);
return new PagedResult<ReceivedChequeResponse>(
items.Select(ToResponse).ToList(), total, page, pageSize);
}
public async Task<ReceivedChequeResponse> CreateAsync(CreateReceivedChequeRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.ReceivedFromName))
throw new InvalidOperationException("ReceivedFromName is required");
if (string.IsNullOrWhiteSpace(request.ChequeNo))
throw new InvalidOperationException("ChequeNo is required");
if (request.Amount <= 0)
throw new InvalidOperationException("Amount must be greater than zero");
var cheque = new Models.ReceivedCheque
{
CompanyId = request.CompanyId,
BranchId = request.BranchId,
ReceivedFromType = request.ReceivedFromType,
ReceivedFromId = request.ReceivedFromId,
ReceivedFromName = request.ReceivedFromName,
DrawerBankName = request.DrawerBankName,
DrawerBankBranch = request.DrawerBankBranch,
AccountHolderName = request.AccountHolderName,
ChequeNo = request.ChequeNo,
ChequeDate = request.ChequeDate,
Amount = request.Amount,
ReceivedDate = request.ReceivedDate,
ReferenceType = request.ReferenceType,
ReferenceId = request.ReferenceId,
Notes = request.Notes,
CreatedBy = request.CreatedBy,
Status = ReceivedChequeStatus.Received
};
await repo.AddAsync(cheque);
await uow.CommitAsync(ct);
return ToResponse(cheque);
}
public async Task<ReceivedChequeResponse?> UpdateStatusAsync(Guid id, UpdateReceivedChequeStatusRequest request, CancellationToken ct = default)
{
var cheque = await repo.GetByIdAsync(id);
if (cheque is null) return null;
switch (request.Action)
{
case ReceivedChequeStatusAction.Deposit:
if (cheque.Status != ReceivedChequeStatus.Received)
throw new InvalidOperationException("Only received cheques can be deposited");
if (request.DepositBankAccountId is null)
throw new InvalidOperationException("DepositBankAccountId is required when depositing");
if (request.DepositDate is null)
throw new InvalidOperationException("DepositDate is required when depositing");
var bankAccount = await bankAccountRepo.GetByIdAsync(request.DepositBankAccountId.Value);
if (bankAccount is null || bankAccount.AccountType != BankAccountType.Bank)
throw new InvalidOperationException("DepositBankAccountId does not reference a valid bank account");
cheque.Status = ReceivedChequeStatus.Deposited;
cheque.FK_DepositBankAccountId = request.DepositBankAccountId;
cheque.DepositDate = request.DepositDate;
break;
case ReceivedChequeStatusAction.Clear:
if (cheque.Status != ReceivedChequeStatus.Deposited)
throw new InvalidOperationException("Only deposited cheques can be cleared");
cheque.Status = ReceivedChequeStatus.Cleared;
break;
case ReceivedChequeStatusAction.Return:
if (cheque.Status != ReceivedChequeStatus.Deposited)
throw new InvalidOperationException("Only deposited cheques can be returned");
cheque.Status = ReceivedChequeStatus.Returned;
break;
case ReceivedChequeStatusAction.Cancel:
if (cheque.Status != ReceivedChequeStatus.Received)
throw new InvalidOperationException("Only received cheques can be cancelled");
cheque.Status = ReceivedChequeStatus.Cancelled;
break;
}
if (!string.IsNullOrWhiteSpace(request.Notes))
cheque.Notes = request.Notes;
repo.Update(cheque);
await uow.CommitAsync(ct);
return ToResponse(cheque);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,926 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class AddPaymentServiceModule : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BankAccounts",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
AccountType = table.Column<int>(type: "int", nullable: false),
AccountName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
BankName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CashAccountTypeName = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
AccountNumber = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CurrencyCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
IsDefault = table.Column<bool>(type: "tinyint(1)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BankAccounts", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "ChequeBooks",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ChequeBookNo = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
BranchId = table.Column<int>(type: "int", nullable: false),
FK_BankAccountId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
StartChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
EndChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TotalLeaves = table.Column<int>(type: "int", nullable: false),
ReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
Description = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<int>(type: "int", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChequeBooks", x => x.Id);
table.ForeignKey(
name: "FK_ChequeBooks_BankAccounts_FK_BankAccountId",
column: x => x.FK_BankAccountId,
principalTable: "BankAccounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "ReceivedCheques",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CompanyId = table.Column<int>(type: "int", nullable: false),
BranchId = table.Column<int>(type: "int", nullable: true),
ReceivedFromType = table.Column<int>(type: "int", nullable: false),
ReceivedFromId = table.Column<int>(type: "int", nullable: true),
ReceivedFromName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
DrawerBankName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
DrawerBankBranch = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
AccountHolderName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ChequeDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
ReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
ReferenceType = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ReferenceId = table.Column<int>(type: "int", nullable: true),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<int>(type: "int", nullable: false),
FK_DepositBankAccountId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
DepositDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ReceivedCheques", x => x.Id);
table.ForeignKey(
name: "FK_ReceivedCheques_BankAccounts_FK_DepositBankAccountId",
column: x => x.FK_DepositBankAccountId,
principalTable: "BankAccounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "ChequePages",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FK_ChequeBookId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
IssueStatus = table.Column<int>(type: "int", nullable: false),
PayeeType = table.Column<int>(type: "int", nullable: true),
PayeeId = table.Column<int>(type: "int", nullable: true),
PayeeName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
IssueDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: true),
CurrencyCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
VoucherId = table.Column<int>(type: "int", nullable: true),
ReferenceNo = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Purpose = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
IsCrossCheque = table.Column<bool>(type: "tinyint(1)", nullable: true),
IsAccountPayee = table.Column<bool>(type: "tinyint(1)", nullable: true),
IsPostDated = table.Column<bool>(type: "tinyint(1)", nullable: true),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PrintedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PrintedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ClearedDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ClearedByBank = table.Column<bool>(type: "tinyint(1)", nullable: true),
CancelReason = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChequePages", x => x.Id);
table.ForeignKey(
name: "FK_ChequePages_ChequeBooks_FK_ChequeBookId",
column: x => x.FK_ChequeBookId,
principalTable: "ChequeBooks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8c43c584-0f65-496d-a77c-a7df46bb175d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("25576a9d-38cb-4ff5-b8ca-08df43d8a5a6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e47016c1-a014-476f-ba87-4b3d438de62a"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a24c0e9e-0194-4f96-823c-bdbdcd122d85"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e76ef6e2-67ba-462f-a954-b1dbc5a95d8c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bba4fdaa-fd0e-43dc-aedf-331212bd06cd"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bc66d450-af6b-426e-9d05-266ceba21fac"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("9ee2196c-1e4b-452d-aaf6-7c55e3d2be2b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("63508590-7a30-4640-9fc9-d3d54978f581"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("82802a3f-23bc-45db-a531-d7e880dd7970"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("65ccf5b6-754a-431a-81bf-d8e0587c956e"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6c713413-362b-475a-846b-9f3382cf7a35"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6dfe68ff-f5a3-41a8-981b-ac108a81408e"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("09c6b812-dc14-44a2-9362-429dfef3942d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bf29a1bb-234e-47a3-922f-f0fb3a1daba0"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("0d2f3497-4852-453e-a451-275e945b6c1d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("41c13dbc-4f51-4134-891c-b6955f728d65"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("29bc0291-7a91-4fc5-840b-9420b64b86fc"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8ae0c386-d206-4580-890f-c1673c1dfeb2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5db4bd09-9481-4608-9dec-3412ba7f3f69"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c1840bd7-88fd-496a-8d88-aef3d886e1ec"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("af97d659-c040-4fdc-beeb-62fc8334f95c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a6443517-3c2a-4407-a3bd-5497e053b0d6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("2f540d56-bff4-41fe-9181-4a423c1da51c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db18cfb8-a56c-46bc-baff-298e6204c455"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fe209c12-1189-485b-920e-ce6db3c132b9"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fedef395-394d-4237-b5aa-8254603ed304"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7cc47626-65b8-44b0-83fb-a5dff96c39f6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("cf04aea5-3f47-4717-95aa-8dab547f9cfb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c2906d62-9eea-4d08-b904-86a3f8b04a20"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7a546bf9-c2f3-4d23-a38e-a068da53dfeb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("19c916c3-7b17-42e0-b4b6-89b8cb6475f4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e6805dbe-a931-4e2a-b08f-83e8760d4968"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7d991f5c-16db-46c5-863c-93063c2e7382"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("513c50d0-5d7b-426d-beef-f14bdea626bc"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("638a6a60-25da-425c-bdec-683431c26d62"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b00bfae6-92bb-48cb-99d9-f2814baae47d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6562c9b4-0673-4ce0-8805-03734bc889ce"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6601e0fe-83dd-4307-be91-38dae998cc9c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c4848449-9678-4fa6-8a00-db4e504b57d6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("91c02c24-12a3-48d5-9d19-174c8e764152"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("0f00477f-5120-40ba-a0e6-0406e47d1c80"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db135a10-6335-478d-b2f8-3b5934cd142b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5c219dd9-9ff3-4541-a708-cc7e4bd4dd25"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("086e7b93-079d-49d0-90e9-95899b53be52"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("f0b3d172-cb32-4606-8272-00feaf4067a2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("75a882c0-b2e9-4efa-a64c-b5d530d42194"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("03540f63-d811-4436-869f-2f76995bbb19"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("73894d9f-a823-4b5b-b809-5cc0aa716a70"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("1ad4bfb8-cde6-4623-a21d-9a78af0026d6"));
migrationBuilder.CreateIndex(
name: "IX_BankAccounts_AccountType_AccountName",
table: "BankAccounts",
columns: new[] { "AccountType", "AccountName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ChequeBooks_ChequeBookNo",
table: "ChequeBooks",
column: "ChequeBookNo",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ChequeBooks_FK_BankAccountId",
table: "ChequeBooks",
column: "FK_BankAccountId");
migrationBuilder.CreateIndex(
name: "IX_ChequePages_ChequeNo",
table: "ChequePages",
column: "ChequeNo",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ChequePages_FK_ChequeBookId",
table: "ChequePages",
column: "FK_ChequeBookId");
migrationBuilder.CreateIndex(
name: "IX_ReceivedCheques_ChequeNo",
table: "ReceivedCheques",
column: "ChequeNo");
migrationBuilder.CreateIndex(
name: "IX_ReceivedCheques_FK_DepositBankAccountId",
table: "ReceivedCheques",
column: "FK_DepositBankAccountId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ChequePages");
migrationBuilder.DropTable(
name: "ReceivedCheques");
migrationBuilder.DropTable(
name: "ChequeBooks");
migrationBuilder.DropTable(
name: "BankAccounts");
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db785a23-5337-4fc8-b460-80c30b707d79"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b07b73f9-2c8b-4dff-af5c-42622b026321"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("2e867ba5-2739-4ce4-b8d7-25b14ed392c1"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("203d855d-f2d0-43d0-9d4f-9c4d0e8e7566"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("edd2a0ee-1ca9-47ff-9df6-7254929aef80"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("644b5ee5-f2c5-4187-a3d4-322cdd5c95d7"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("70fdf921-f3fd-4e66-bfd2-cb7bb156fa34"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e2de5341-4146-4c84-a584-8788774c14df"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ece07436-4a0b-435c-888b-a22c14612dc4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b72f8737-7a21-48c0-809a-bc0d3fdd7063"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fe1efed0-271f-45e3-a58b-244f1115246f"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("1b58e809-e4a0-4108-af6e-fd0dac7b3163"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("881e8e67-e2ee-47be-94f7-77da73a05365"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e5d0baa8-9def-4b81-8727-5886b2906eaf"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a46ec771-5e79-4595-bb40-91e8a954a792"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ef8b1808-3b14-43a8-ba75-30551e643cc5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("50142904-8d40-424b-aeb3-53e8cb110445"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("4bd67f53-7f84-492c-83f9-fd6488fa88b7"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("d9a0eb59-600e-47e9-8e07-8b7bffc93f8b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e5a9d4da-9497-4e7e-8fb9-796927cbb510"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ffa29833-505e-4f56-9a4d-58f5df844af2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("4e54994b-7ce1-4b99-aa98-de00c1d205fa"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("f45b7352-f42a-4bb3-9d1e-d7cbe2665c0c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("27744965-334b-4da2-8a2d-5d7433d11b9d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("cec1991b-ea93-45b0-b380-01cf0d36fc17"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("25675bb4-2ab5-437c-bee8-1b1ec1741736"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5fc66d40-1960-4028-af48-cf51a80bff04"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bd4e1740-a8b6-4a53-8633-a68c9858b3f5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("9de97081-77d7-43c3-90b9-af3bc9d4bbef"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6ae05e16-95e5-4d43-bfa0-56991393d76f"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("eb096aea-92c9-4556-ba4f-3084da223c7d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6c9dac72-200a-46eb-b49e-699b1ca775b5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8c8d0c74-40f6-4164-a6a7-6d2374ef162a"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("4a0d8cea-c33d-45a6-bd42-d8a51ce321c8"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a0ea0280-8e40-45bc-88e4-c3feb92e36d4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("61da4c55-4f19-4ce4-bfc9-3fa9073365a2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fbab8f2e-f224-42a0-be16-b7ea4e24eec5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bcf7f883-e6c8-4a97-8a27-6814b0ed9144"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("13c01d81-198b-45cb-9de9-f25130e16313"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("667c3022-ea0d-4d1c-8ae4-b64e2f104295"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("78607299-2f5c-4edb-9143-c4a83395f731"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e53313fd-c6d4-4d52-827f-14cfd55401eb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c2521113-60f4-41fa-8ded-6b6a6b91a584"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("14532efd-d9f9-4a46-a7c3-fbfbe85a6f13"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("438bbd52-ee61-4afc-9d59-7d0407da4f1d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("3e3d0cd9-f95e-4c1c-b829-cd02f1bad987"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("84daf87e-d3e1-4f4e-9a57-ec9996fff41d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ddcb8caf-106e-4072-b48e-a2542de51211"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("95d39d28-c5c7-4e82-87eb-40570db1c1f5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b8e7b327-33c9-4420-b4d1-0071c58a5c95"));
}
}
}
@@ -22,6 +22,54 @@ namespace Distribution.Migrations
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("Distribution.Models.BankAccount", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("AccountName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("AccountNumber")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("AccountType")
.HasColumnType("int");
b.Property<string>("BankName")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("CashAccountTypeName")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsDefault")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("AccountType", "AccountName")
.IsUnique();
b.ToTable("BankAccounts");
});
modelBuilder.Entity("Distribution.Models.Brand", b =>
{
b.Property<Guid>("Id")
@@ -107,6 +155,152 @@ namespace Distribution.Migrations
});
});
modelBuilder.Entity("Distribution.Models.ChequeBook", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("BranchId")
.HasColumnType("int");
b.Property<string>("ChequeBookNo")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<string>("EndChequeNo")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<Guid>("FK_BankAccountId")
.HasColumnType("char(36)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("ReceivedDate")
.HasColumnType("datetime(6)");
b.Property<string>("StartChequeNo")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("TotalLeaves")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ChequeBookNo")
.IsUnique();
b.HasIndex("FK_BankAccountId");
b.ToTable("ChequeBooks");
});
modelBuilder.Entity("Distribution.Models.ChequePage", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<decimal?>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<string>("CancelReason")
.HasColumnType("longtext");
b.Property<string>("ChequeNo")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<bool?>("ClearedByBank")
.HasColumnType("tinyint(1)");
b.Property<DateTime?>("ClearedDate")
.HasColumnType("datetime(6)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CurrencyCode")
.HasMaxLength(10)
.HasColumnType("varchar(10)");
b.Property<Guid>("FK_ChequeBookId")
.HasColumnType("char(36)");
b.Property<bool?>("IsAccountPayee")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<bool?>("IsCrossCheque")
.HasColumnType("tinyint(1)");
b.Property<bool?>("IsPostDated")
.HasColumnType("tinyint(1)");
b.Property<DateTime?>("IssueDate")
.HasColumnType("datetime(6)");
b.Property<int>("IssueStatus")
.HasColumnType("int");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<int?>("PayeeId")
.HasColumnType("int");
b.Property<string>("PayeeName")
.HasColumnType("longtext");
b.Property<int?>("PayeeType")
.HasColumnType("int");
b.Property<DateTime?>("PrintedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PrintedBy")
.HasColumnType("longtext");
b.Property<string>("Purpose")
.HasColumnType("longtext");
b.Property<string>("ReferenceNo")
.HasColumnType("longtext");
b.Property<int?>("VoucherId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ChequeNo")
.IsUnique();
b.HasIndex("FK_ChequeBookId");
b.ToTable("ChequePages");
});
modelBuilder.Entity("Distribution.Models.Claim", b =>
{
b.Property<Guid>("Id")
@@ -1914,6 +2108,88 @@ namespace Distribution.Migrations
});
});
modelBuilder.Entity("Distribution.Models.ReceivedCheque", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("AccountHolderName")
.HasColumnType("longtext");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int?>("BranchId")
.HasColumnType("int");
b.Property<DateTime>("ChequeDate")
.HasColumnType("datetime(6)");
b.Property<string>("ChequeNo")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<int>("CompanyId")
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("DepositDate")
.HasColumnType("datetime(6)");
b.Property<string>("DrawerBankBranch")
.HasColumnType("longtext");
b.Property<string>("DrawerBankName")
.HasColumnType("longtext");
b.Property<Guid?>("FK_DepositBankAccountId")
.HasColumnType("char(36)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<DateTime>("ReceivedDate")
.HasColumnType("datetime(6)");
b.Property<int?>("ReceivedFromId")
.HasColumnType("int");
b.Property<string>("ReceivedFromName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<int>("ReceivedFromType")
.HasColumnType("int");
b.Property<int?>("ReferenceId")
.HasColumnType("int");
b.Property<string>("ReferenceType")
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ChequeNo");
b.HasIndex("FK_DepositBankAccountId");
b.ToTable("ReceivedCheques");
});
modelBuilder.Entity("Distribution.Models.Region", b =>
{
b.Property<Guid>("Id")
@@ -2229,7 +2505,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000001"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("db785a23-5337-4fc8-b460-80c30b707d79"),
Id = new Guid("8c43c584-0f65-496d-a77c-a7df46bb175d"),
IsActive = true
},
new
@@ -2237,7 +2513,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000002"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("b07b73f9-2c8b-4dff-af5c-42622b026321"),
Id = new Guid("25576a9d-38cb-4ff5-b8ca-08df43d8a5a6"),
IsActive = true
},
new
@@ -2245,7 +2521,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("2e867ba5-2739-4ce4-b8d7-25b14ed392c1"),
Id = new Guid("e47016c1-a014-476f-ba87-4b3d438de62a"),
IsActive = true
},
new
@@ -2253,7 +2529,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000004"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("203d855d-f2d0-43d0-9d4f-9c4d0e8e7566"),
Id = new Guid("a24c0e9e-0194-4f96-823c-bdbdcd122d85"),
IsActive = true
},
new
@@ -2261,7 +2537,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000005"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("edd2a0ee-1ca9-47ff-9df6-7254929aef80"),
Id = new Guid("e76ef6e2-67ba-462f-a954-b1dbc5a95d8c"),
IsActive = true
},
new
@@ -2269,7 +2545,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000006"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("644b5ee5-f2c5-4187-a3d4-322cdd5c95d7"),
Id = new Guid("bba4fdaa-fd0e-43dc-aedf-331212bd06cd"),
IsActive = true
},
new
@@ -2277,7 +2553,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000007"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("70fdf921-f3fd-4e66-bfd2-cb7bb156fa34"),
Id = new Guid("bc66d450-af6b-426e-9d05-266ceba21fac"),
IsActive = true
},
new
@@ -2285,7 +2561,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000008"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("e2de5341-4146-4c84-a584-8788774c14df"),
Id = new Guid("9ee2196c-1e4b-452d-aaf6-7c55e3d2be2b"),
IsActive = true
},
new
@@ -2293,7 +2569,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000009"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("ece07436-4a0b-435c-888b-a22c14612dc4"),
Id = new Guid("63508590-7a30-4640-9fc9-d3d54978f581"),
IsActive = true
},
new
@@ -2301,7 +2577,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000010"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("b72f8737-7a21-48c0-809a-bc0d3fdd7063"),
Id = new Guid("82802a3f-23bc-45db-a531-d7e880dd7970"),
IsActive = true
},
new
@@ -2309,7 +2585,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000011"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("fe1efed0-271f-45e3-a58b-244f1115246f"),
Id = new Guid("65ccf5b6-754a-431a-81bf-d8e0587c956e"),
IsActive = true
},
new
@@ -2317,7 +2593,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000012"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("1b58e809-e4a0-4108-af6e-fd0dac7b3163"),
Id = new Guid("6c713413-362b-475a-846b-9f3382cf7a35"),
IsActive = true
},
new
@@ -2325,7 +2601,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000013"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("881e8e67-e2ee-47be-94f7-77da73a05365"),
Id = new Guid("6dfe68ff-f5a3-41a8-981b-ac108a81408e"),
IsActive = true
},
new
@@ -2333,7 +2609,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000014"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("e5d0baa8-9def-4b81-8727-5886b2906eaf"),
Id = new Guid("09c6b812-dc14-44a2-9362-429dfef3942d"),
IsActive = true
},
new
@@ -2341,7 +2617,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000015"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("a46ec771-5e79-4595-bb40-91e8a954a792"),
Id = new Guid("bf29a1bb-234e-47a3-922f-f0fb3a1daba0"),
IsActive = true
},
new
@@ -2349,7 +2625,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000016"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("ef8b1808-3b14-43a8-ba75-30551e643cc5"),
Id = new Guid("0d2f3497-4852-453e-a451-275e945b6c1d"),
IsActive = true
},
new
@@ -2357,7 +2633,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000017"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("50142904-8d40-424b-aeb3-53e8cb110445"),
Id = new Guid("41c13dbc-4f51-4134-891c-b6955f728d65"),
IsActive = true
},
new
@@ -2365,7 +2641,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000018"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("4bd67f53-7f84-492c-83f9-fd6488fa88b7"),
Id = new Guid("29bc0291-7a91-4fc5-840b-9420b64b86fc"),
IsActive = true
},
new
@@ -2373,7 +2649,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000019"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("d9a0eb59-600e-47e9-8e07-8b7bffc93f8b"),
Id = new Guid("8ae0c386-d206-4580-890f-c1673c1dfeb2"),
IsActive = true
},
new
@@ -2381,7 +2657,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000020"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("e5a9d4da-9497-4e7e-8fb9-796927cbb510"),
Id = new Guid("5db4bd09-9481-4608-9dec-3412ba7f3f69"),
IsActive = true
},
new
@@ -2389,7 +2665,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("ffa29833-505e-4f56-9a4d-58f5df844af2"),
Id = new Guid("c1840bd7-88fd-496a-8d88-aef3d886e1ec"),
IsActive = true
},
new
@@ -2397,7 +2673,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("4e54994b-7ce1-4b99-aa98-de00c1d205fa"),
Id = new Guid("af97d659-c040-4fdc-beeb-62fc8334f95c"),
IsActive = true
},
new
@@ -2405,7 +2681,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000023"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("f45b7352-f42a-4bb3-9d1e-d7cbe2665c0c"),
Id = new Guid("a6443517-3c2a-4407-a3bd-5497e053b0d6"),
IsActive = true
},
new
@@ -2413,7 +2689,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("27744965-334b-4da2-8a2d-5d7433d11b9d"),
Id = new Guid("2f540d56-bff4-41fe-9181-4a423c1da51c"),
IsActive = true
},
new
@@ -2421,7 +2697,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("cec1991b-ea93-45b0-b380-01cf0d36fc17"),
Id = new Guid("db18cfb8-a56c-46bc-baff-298e6204c455"),
IsActive = true
},
new
@@ -2429,7 +2705,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000026"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("25675bb4-2ab5-437c-bee8-1b1ec1741736"),
Id = new Guid("fe209c12-1189-485b-920e-ce6db3c132b9"),
IsActive = true
},
new
@@ -2437,7 +2713,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000027"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("5fc66d40-1960-4028-af48-cf51a80bff04"),
Id = new Guid("fedef395-394d-4237-b5aa-8254603ed304"),
IsActive = true
},
new
@@ -2445,7 +2721,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000028"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("bd4e1740-a8b6-4a53-8633-a68c9858b3f5"),
Id = new Guid("7cc47626-65b8-44b0-83fb-a5dff96c39f6"),
IsActive = true
},
new
@@ -2453,7 +2729,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000029"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("9de97081-77d7-43c3-90b9-af3bc9d4bbef"),
Id = new Guid("cf04aea5-3f47-4717-95aa-8dab547f9cfb"),
IsActive = true
},
new
@@ -2461,7 +2737,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000030"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("6ae05e16-95e5-4d43-bfa0-56991393d76f"),
Id = new Guid("c2906d62-9eea-4d08-b904-86a3f8b04a20"),
IsActive = true
},
new
@@ -2469,7 +2745,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("eb096aea-92c9-4556-ba4f-3084da223c7d"),
Id = new Guid("7a546bf9-c2f3-4d23-a38e-a068da53dfeb"),
IsActive = true
},
new
@@ -2477,7 +2753,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("6c9dac72-200a-46eb-b49e-699b1ca775b5"),
Id = new Guid("19c916c3-7b17-42e0-b4b6-89b8cb6475f4"),
IsActive = true
},
new
@@ -2485,7 +2761,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("8c8d0c74-40f6-4164-a6a7-6d2374ef162a"),
Id = new Guid("e6805dbe-a931-4e2a-b08f-83e8760d4968"),
IsActive = true
},
new
@@ -2493,7 +2769,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("4a0d8cea-c33d-45a6-bd42-d8a51ce321c8"),
Id = new Guid("7d991f5c-16db-46c5-863c-93063c2e7382"),
IsActive = true
},
new
@@ -2501,7 +2777,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("a0ea0280-8e40-45bc-88e4-c3feb92e36d4"),
Id = new Guid("513c50d0-5d7b-426d-beef-f14bdea626bc"),
IsActive = true
},
new
@@ -2509,7 +2785,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000036"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("61da4c55-4f19-4ce4-bfc9-3fa9073365a2"),
Id = new Guid("638a6a60-25da-425c-bdec-683431c26d62"),
IsActive = true
},
new
@@ -2517,7 +2793,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("fbab8f2e-f224-42a0-be16-b7ea4e24eec5"),
Id = new Guid("b00bfae6-92bb-48cb-99d9-f2814baae47d"),
IsActive = true
},
new
@@ -2525,7 +2801,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000038"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("bcf7f883-e6c8-4a97-8a27-6814b0ed9144"),
Id = new Guid("6562c9b4-0673-4ce0-8805-03734bc889ce"),
IsActive = true
},
new
@@ -2533,7 +2809,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000039"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("13c01d81-198b-45cb-9de9-f25130e16313"),
Id = new Guid("6601e0fe-83dd-4307-be91-38dae998cc9c"),
IsActive = true
},
new
@@ -2541,7 +2817,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000040"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("667c3022-ea0d-4d1c-8ae4-b64e2f104295"),
Id = new Guid("c4848449-9678-4fa6-8a00-db4e504b57d6"),
IsActive = true
},
new
@@ -2549,7 +2825,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000041"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("78607299-2f5c-4edb-9143-c4a83395f731"),
Id = new Guid("91c02c24-12a3-48d5-9d19-174c8e764152"),
IsActive = true
},
new
@@ -2557,7 +2833,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000042"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("e53313fd-c6d4-4d52-827f-14cfd55401eb"),
Id = new Guid("0f00477f-5120-40ba-a0e6-0406e47d1c80"),
IsActive = true
},
new
@@ -2565,7 +2841,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000043"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("c2521113-60f4-41fa-8ded-6b6a6b91a584"),
Id = new Guid("db135a10-6335-478d-b2f8-3b5934cd142b"),
IsActive = true
},
new
@@ -2573,7 +2849,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000044"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("14532efd-d9f9-4a46-a7c3-fbfbe85a6f13"),
Id = new Guid("5c219dd9-9ff3-4541-a708-cc7e4bd4dd25"),
IsActive = true
},
new
@@ -2581,7 +2857,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000045"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("438bbd52-ee61-4afc-9d59-7d0407da4f1d"),
Id = new Guid("086e7b93-079d-49d0-90e9-95899b53be52"),
IsActive = true
},
new
@@ -2589,7 +2865,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000046"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("3e3d0cd9-f95e-4c1c-b829-cd02f1bad987"),
Id = new Guid("f0b3d172-cb32-4606-8272-00feaf4067a2"),
IsActive = true
},
new
@@ -2597,7 +2873,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000047"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("84daf87e-d3e1-4f4e-9a57-ec9996fff41d"),
Id = new Guid("75a882c0-b2e9-4efa-a64c-b5d530d42194"),
IsActive = true
},
new
@@ -2605,7 +2881,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000048"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("ddcb8caf-106e-4072-b48e-a2542de51211"),
Id = new Guid("03540f63-d811-4436-869f-2f76995bbb19"),
IsActive = true
},
new
@@ -2613,7 +2889,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000049"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("95d39d28-c5c7-4e82-87eb-40570db1c1f5"),
Id = new Guid("73894d9f-a823-4b5b-b809-5cc0aa716a70"),
IsActive = true
},
new
@@ -2621,7 +2897,7 @@ namespace Distribution.Migrations
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"),
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000050"),
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
Id = new Guid("b8e7b327-33c9-4420-b4d1-0071c58a5c95"),
Id = new Guid("1ad4bfb8-cde6-4623-a21d-9a78af0026d6"),
IsActive = true
});
});
@@ -4273,6 +4549,28 @@ namespace Distribution.Migrations
});
});
modelBuilder.Entity("Distribution.Models.ChequeBook", b =>
{
b.HasOne("Distribution.Models.BankAccount", "BankAccount")
.WithMany()
.HasForeignKey("FK_BankAccountId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("BankAccount");
});
modelBuilder.Entity("Distribution.Models.ChequePage", b =>
{
b.HasOne("Distribution.Models.ChequeBook", "ChequeBook")
.WithMany("Pages")
.HasForeignKey("FK_ChequeBookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ChequeBook");
});
modelBuilder.Entity("Distribution.Models.Claim", b =>
{
b.HasOne("Distribution.Models.User", "AgencyUser")
@@ -4666,6 +4964,16 @@ namespace Distribution.Migrations
b.Navigation("PurchaseReturn");
});
modelBuilder.Entity("Distribution.Models.ReceivedCheque", b =>
{
b.HasOne("Distribution.Models.BankAccount", "DepositBankAccount")
.WithMany()
.HasForeignKey("FK_DepositBankAccountId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("DepositBankAccount");
});
modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b =>
{
b.HasOne("Distribution.Models.User", "AgencyUser")
@@ -5159,6 +5467,11 @@ namespace Distribution.Migrations
b.Navigation("Vehicle");
});
modelBuilder.Entity("Distribution.Models.ChequeBook", b =>
{
b.Navigation("Pages");
});
modelBuilder.Entity("Distribution.Models.GoodsReceipt", b =>
{
b.Navigation("Items");
+17
View File
@@ -0,0 +1,17 @@
namespace Distribution.Models
{
public class BankAccount : Template
{
public BankAccountType AccountType { get; set; }
public string AccountName { get; set; } = string.Empty;
public string? BankName { get; set; }
public string? CashAccountTypeName { get; set; }
public string? AccountNumber { get; set; }
public string CurrencyCode { get; set; } = "LKR";
public bool IsDefault { get; set; }
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class ChequeBook : Template
{
public string ChequeBookNo { get; set; } = string.Empty;
public int BranchId { get; set; }
public Guid FK_BankAccountId { get; set; }
[ForeignKey("FK_BankAccountId")]
public BankAccount? BankAccount { get; set; }
public string StartChequeNo { get; set; } = string.Empty;
public string EndChequeNo { get; set; } = string.Empty;
public int TotalLeaves { get; set; }
public DateTime ReceivedDate { get; set; }
public string? Description { get; set; }
public string? CreatedBy { get; set; }
public ChequeBookStatus Status { get; set; } = ChequeBookStatus.Active;
public List<ChequePage> Pages { get; set; } = new();
}
}
+38
View File
@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class ChequePage : Template
{
public string ChequeNo { get; set; } = string.Empty;
public Guid FK_ChequeBookId { get; set; }
[ForeignKey("FK_ChequeBookId")]
public ChequeBook? ChequeBook { get; set; }
public ChequePageIssueStatus IssueStatus { get; set; } = ChequePageIssueStatus.Unused;
public PayeeType? PayeeType { get; set; }
public int? PayeeId { get; set; }
public string? PayeeName { get; set; }
public DateTime? IssueDate { get; set; }
public decimal? Amount { get; set; }
public string? CurrencyCode { get; set; }
public int? VoucherId { get; set; }
public string? ReferenceNo { get; set; }
public string? Purpose { get; set; }
public bool? IsCrossCheque { get; set; }
public bool? IsAccountPayee { get; set; }
public bool? IsPostDated { get; set; }
public string? Notes { get; set; }
public string? PrintedBy { get; set; }
public DateTime? PrintedAt { get; set; }
public DateTime? ClearedDate { get; set; }
public bool? ClearedByBank { get; set; }
public string? CancelReason { get; set; }
}
}
+16
View File
@@ -49,4 +49,20 @@ namespace Distribution.Models
public enum TaxMode { Disabled, Optional, Mandatory }
public enum SerialStatus { InStock, Sold, Returned, Damaged }
public enum BankAccountType { Cash, Bank }
public enum ChequeBookStatus { Active, Completed, Cancelled }
public enum ChequePageIssueStatus { Unused, Issued, Cleared, Bounced, Cancelled, Void }
public enum ChequePageStatusAction { Clear, Bounce, Cancel, Void }
public enum PayeeType { Supplier, Customer, Employee, Other }
public enum ReceivedChequeStatus { Received, Deposited, Cleared, Returned, Cancelled }
public enum ReceivedChequeStatusAction { Deposit, Clear, Return, Cancel }
public enum ReceivedFromType { Customer, Supplier, Other }
}
+36
View File
@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class ReceivedCheque : Template
{
public int CompanyId { get; set; }
public int? BranchId { get; set; }
public ReceivedFromType ReceivedFromType { get; set; }
public int? ReceivedFromId { get; set; }
public string ReceivedFromName { get; set; } = string.Empty;
public string? DrawerBankName { get; set; }
public string? DrawerBankBranch { get; set; }
public string? AccountHolderName { get; set; }
public string ChequeNo { get; set; } = string.Empty;
public DateTime ChequeDate { get; set; }
public decimal Amount { get; set; }
public DateTime ReceivedDate { get; set; }
public string? ReferenceType { get; set; }
public int? ReferenceId { get; set; }
public string? Notes { get; set; }
public string? CreatedBy { get; set; }
public ReceivedChequeStatus Status { get; set; } = ReceivedChequeStatus.Received;
public Guid? FK_DepositBankAccountId { get; set; }
[ForeignKey("FK_DepositBankAccountId")]
public BankAccount? DepositBankAccount { get; set; }
public DateTime? DepositDate { get; set; }
}
}
+9
View File
@@ -6,6 +6,9 @@ using Distribution.Features.Brand;
using Distribution.Features.Category;
using Distribution.Features.DeliveryRide;
using Distribution.Features.Payment;
using Distribution.Features.BankAccount;
using Distribution.Features.ChequeBook;
using Distribution.Features.ReceivedCheque;
using Distribution.Features.DriverComplianceConfig;
using Distribution.Features.DriverProfile;
using Distribution.Features.Grn;
@@ -89,6 +92,9 @@ builder.Services.AddScoped<ISupplierClaimRepository, SupplierClaimRepository>();
builder.Services.AddScoped<ISysConfigRepository, SysConfigRepository>();
builder.Services.AddScoped<IDeliveryTaskRepository, DeliveryTaskRepository>();
builder.Services.AddScoped<IPaymentRepository, PaymentRepository>();
builder.Services.AddScoped<IBankAccountRepository, BankAccountRepository>();
builder.Services.AddScoped<IChequeBookRepository, ChequeBookRepository>();
builder.Services.AddScoped<IReceivedChequeRepository, ReceivedChequeRepository>();
builder.Services.AddScoped<RegionService>();
builder.Services.AddScoped<RouteService>();
@@ -124,6 +130,9 @@ builder.Services.AddScoped<ClaimService>();
builder.Services.AddScoped<SupplierClaimService>();
builder.Services.AddScoped<DeliveryTaskService>();
builder.Services.AddScoped<PaymentService>();
builder.Services.AddScoped<BankAccountService>();
builder.Services.AddScoped<ChequeBookService>();
builder.Services.AddScoped<ReceivedChequeService>();
builder.Services.AddScoped<SysConfigService>();
builder.Services.AddMemoryCache();
+185
View File
@@ -0,0 +1,185 @@
# Plan: Clone Accounts Module from ERP-core to Distribution
## Context
The ERP-core system has a fully implemented Accounts module (7 features) visible in the sidebar. The goal is to replicate all 7 features — with identical UI and independent backend — in the Distribution project (`ERP_Distribution`). Distribution will get its own C# controllers, services, EF entities, and migrations rather than sharing ERP-core's backend.
## Scope: All 7 Features
1. Cash / Bank Accounts (via GL service proxy)
2. Cheque Books (via GL service proxy)
3. Received Cheques (via GL service proxy)
4. Expenses (own ERP-core controllers)
5. Expense Setup (own ERP-core controllers)
6. Bank Deposits (own ERP-core controllers)
7. Opening Balances (own ERP-core controllers)
> **Important note:** Features 1–3 go through a separate General Ledger (GL) microservice proxied at `/api/v1/gl/*` in ERP-core. The Distribution backend must also proxy to this same GL service, OR the GL service must be updated to support the distribution context. Confirm with the team whether the GL service is shared infrastructure or ERP-specific before implementing features 1–3.
---
## Source Files (ERP-core)
### Frontend — `Frontend/erp-system/`
| Type | Path |
|------|------|
| Sidebar nav | `components/Layouts/AppSidebar.tsx` (lines 209–222) |
| Pages | `app/dashboard/accounts/**` (7 feature directories) |
| Shared components | `components/accounts/*.tsx` (8 files) |
| API clients | `lib/api/expenses.ts`, `bank-deposits.ts`, `general-ledger.ts`, `opening-balances.ts` |
| Types | `types/expenses.ts`, `bank-deposits.ts`, `general-ledger.ts`, `opening-balances.ts` |
| Route guard | `lib/nav-routes.ts` |
### Backend — `Backend/ERPCore/`
| Type | Path |
|------|------|
| Controllers | `Controllers/ExpensesController.cs`, `ExpenseSetupController.cs`, `BankDepositsController.cs`, `OpeningBalancesController.cs`, `GlAccountConfigController.cs` |
| Services | `Services/ExpenseService.cs`, `ExpensePaymentService.cs`, `ExpenseSetupService.cs`, `BankDepositService.cs`, `CashInHandService.cs`, `OpeningBalanceService.cs`, `GlAccountConfigService.cs` |
| Entities | `Domain/Entities/Expense.cs`, `ExpenseCategory.cs`, `BankDeposit.cs`, `GlAccountConfig.cs`, `OpeningBalanceRun.cs` |
| DTOs | `Dtos/Expenses/ExpenseDtos.cs`, `Dtos/Accounts/BankDepositDtos.cs`, `Dtos/OpeningBalances/` |
| Migrations | `Migrations/20261007*`, `20261008*`, `20261018*` |
---
## Target: Distribution Project — `C:\Users\lenovo\Desktop\NEWGITEA\ERP_Distribution`
---
## Implementation Phases
### Phase 1 — Explore Distribution Project Structure (Read-only prerequisite)
Before coding, read these files to confirm conventions:
- `ERP_Distribution/Distribution/Program.cs` — DI registration pattern
- `ERP_Distribution/Distribution/Controllers/` (any existing controller) — naming convention
- `ERP_Distribution/distribution_fe/components/Layouts/` — sidebar/nav structure
- `ERP_Distribution/distribution_fe/lib/api.ts` — API client pattern (may differ from ERP-core's per-feature files)
- `ERP_Distribution/distribution_fe/app/dashboard/` — existing route structure
### Phase 2 — Backend: Domain Entities & DTOs
In `ERP_Distribution/Distribution/`:
1. **Create entity files** (copy + namespace-adjust from ERP-core):
- `Domain/Entities/Expense.cs`
- `Domain/Entities/ExpenseCategory.cs`
- `Domain/Entities/BankDeposit.cs`
- `Domain/Entities/GlAccountConfig.cs`
- `Domain/Entities/OpeningBalanceRun.cs`
- Status enums: `ExpenseStatus.cs`, `BankDepositStatus.cs`
2. **Create DTO files**:
- `Dtos/Expenses/ExpenseDtos.cs`
- `Dtos/Accounts/BankDepositDtos.cs`
- `Dtos/OpeningBalances/OpeningBalanceDtos.cs`
- `Dtos/GlAccountConfig/GlAccountConfigDtos.cs`
3. **Register entities in DbContext** — add `DbSet<>` entries to the existing EF DbContext
4. **Create EF migration** — `add-migration AddAccountsModule` in Distribution project
### Phase 3 — Backend: Services & Controllers
In `ERP_Distribution/Distribution/`:
1. **Create service interfaces and implementations** (copy + namespace-adjust):
- `Services/IExpenseServices.cs` + `ExpenseService.cs`
- `Services/IExpenseServices.cs` + `ExpensePaymentService.cs`
- `Services/ExpenseSetupService.cs`
- `Services/IBankDepositServices.cs` + `BankDepositService.cs`
- `Services/CashInHandService.cs`
- `Services/IOpeningBalanceService.cs` + `OpeningBalanceService.cs`
- `Services/IGlAccountConfigService.cs` + `GlAccountConfigService.cs`
2. **Create controllers** (copy + namespace-adjust, update route prefix if needed):
- `Controllers/ExpensesController.cs`
- `Controllers/ExpenseSetupController.cs`
- `Controllers/BankDepositsController.cs`
- `Controllers/OpeningBalancesController.cs`
- `Controllers/GlAccountConfigController.cs`
- **GL Proxy controller** — a new controller that proxies `/api/v1/gl/*` to the shared GL microservice (same pattern as ERP-core)
3. **Register services in DI** — add to `Program.cs`:
```csharp
builder.Services.AddScoped<IExpenseServices, ExpenseService>();
builder.Services.AddScoped<IBankDepositServices, BankDepositService>();
// ... etc.
```
4. **Add permissions/policies** — mirror `NavPolicies` from ERP-core for accounts permission codes
### Phase 4 — Frontend: Types & API Clients
In `ERP_Distribution/distribution_fe/`:
1. **Copy type files** to `types/`:
- `types/expenses.ts`
- `types/bank-deposits.ts`
- `types/general-ledger.ts`
- `types/opening-balances.ts`
2. **Copy API client files** to `lib/api/` (update base URL to distribution backend):
- `lib/api/expenses.ts`
- `lib/api/bank-deposits.ts`
- `lib/api/general-ledger.ts`
- `lib/api/opening-balances.ts`
- Adjust any `NEXT_PUBLIC_API_URL` references to point to Distribution backend
### Phase 5 — Frontend: Components & Pages
In `ERP_Distribution/distribution_fe/`:
1. **Copy shared account components** to `components/accounts/`:
- `OpeningCashBankPanel.tsx`
- `OpeningStockPanel.tsx`
- `ExpenseForm.tsx`
- `ExpensePaymentDialog.tsx`
- `ChequePageDialog.tsx`
- `ReceivedChequeDialog.tsx`
- `expense-badges.tsx`
- `bank-deposit-badges.tsx`
2. **Create page directory structure** under `app/dashboard/accounts/`:
```
accounts/
├── page.tsx (hub page)
├── bank-accounts/page.tsx
├── bank-accounts/new/page.tsx
├── cheque-books/page.tsx
├── cheque-books/new/page.tsx
├── cheque-books/[chequeBookNo]/page.tsx
├── received-cheques/page.tsx
├── received-cheques/new/page.tsx
├── expenses/page.tsx
├── expenses/new/page.tsx
├── expenses/[id]/page.tsx
├── expense-setup/page.tsx
├── bank-deposits/page.tsx
├── bank-deposits/new/page.tsx
├── bank-deposits/[id]/page.tsx
└── opening-balances/page.tsx
```
3. Copy each page from ERP-core's equivalent path, adjusting import paths as needed
### Phase 6 — Frontend: Sidebar Navigation & Route Guard
1. **Add Accounts menu** to Distribution's sidebar/nav config (mirror ERP-core `AppSidebar.tsx` lines 209–222)
2. **Update route guard** — add accounts permission codes to `nav-routes.ts` (or equivalent) in the distribution frontend
3. **Add permission codes** to auth context/provider if Distribution uses role-based nav gating
---
## Key Decisions & Risks
| Item | Risk | Mitigation |
|------|------|------------|
| GL service shared vs. ERP-only | GL proxy may not accept Distribution's auth tokens or branch context | Confirm with team; may need a dedicated Distribution GL proxy config |
| DB schema differences | Distribution's EF context may have different conventions | Review existing migrations before adding new entities |
| API client base URL | Distribution frontend may use a different env var for the backend URL | Check `lib/api.ts` pattern before copying API clients |
| Permission system | Distribution may use different permission codes/claims | Match Distribution's existing RBAC pattern |
---
## Verification
1. **Backend**: Run `dotnet build` in `ERP_Distribution/Distribution` — zero errors
2. **Migration**: Run `dotnet ef database update` — schema applied cleanly
3. **API smoke test**: Hit `GET /api/v1/expenses`, `GET /api/v1/bank-deposits` via Swagger/Postman
4. **Frontend**: Run `npm run dev` in `distribution_fe` — Accounts menu appears in sidebar
5. **E2E flow**:
- Create an expense (Draft → Post → Pay)
- Record a bank deposit and verify cash-in-hand updates
- Open a cheque book and issue a page
- Enter opening balances
6. **Permissions**: Log in as a role without `accounts.*` codes — verify Accounts menu is hidden
@@ -0,0 +1,180 @@
'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { ArrowLeft } from 'lucide-react'
import { bankAccounts as bankAccountsApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import { BankAccountType, type CashAccountTypeDto } from '@/types/payments'
import { Button, buttonVariants } from '@/components/ui/button'
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { toast } from '@/components/ui/toast'
const OTHER_CASH_TYPE = '__other__'
export default function NewBankAccountPage() {
const router = useRouter()
const [accountType, setAccountType] = useState<BankAccountType>(BankAccountType.Bank)
const [cashAccountTypes, setCashAccountTypes] = useState<CashAccountTypeDto[] | null>(null)
const [cashAccountTypesError, setCashAccountTypesError] = useState<string | null>(null)
const [accountName, setAccountName] = useState('')
const [bankName, setBankName] = useState('')
const [cashAccountTypeChoice, setCashAccountTypeChoice] = useState('')
const [customCashAccountTypeName, setCustomCashAccountTypeName] = useState('')
const [accountNumber, setAccountNumber] = useState('')
const [currencyCode, setCurrencyCode] = useState('LKR')
const [errors, setErrors] = useState<Record<string, string>>({})
const [submitting, setSubmitting] = useState(false)
useEffect(() => {
if (accountType !== BankAccountType.Cash || cashAccountTypes !== null) return
bankAccountsApi.cashAccountTypes()
.then(setCashAccountTypes)
.catch(err => setCashAccountTypesError(getApiError(err)))
}, [accountType, cashAccountTypes])
const cashAccountTypeName =
cashAccountTypeChoice === OTHER_CASH_TYPE ? customCashAccountTypeName.trim() : cashAccountTypeChoice
async function handleSubmit() {
const next: Record<string, string> = {}
if (!accountName.trim()) next.accountName = 'Account name is required'
if (accountType === BankAccountType.Cash && !cashAccountTypeName) {
next.cashAccountTypeName = 'Select or enter a cash account type'
}
setErrors(next)
if (Object.keys(next).length > 0) return
setSubmitting(true)
try {
const created =
accountType === BankAccountType.Bank
? await bankAccountsApi.createBank({
accountName,
bankName: bankName || null,
accountNumber: accountNumber || null,
currencyCode: currencyCode || undefined,
})
: await bankAccountsApi.createCash({
accountName,
cashAccountTypeName,
accountNumber: accountNumber || null,
currencyCode: currencyCode || undefined,
})
toast.add({ title: `${accountType} account created`, description: created.accountName, type: 'success' })
router.push('/payments/bank-accounts')
} catch (err) {
toast.add({ title: `Could not create ${accountType.toLowerCase()} account`, description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Link href="/payments/bank-accounts" className={cn(buttonVariants({ variant: 'ghost', size: 'icon' }))}>
<ArrowLeft className="size-4" />
</Link>
<div>
<h1 className="text-2xl font-semibold text-foreground">New Cash / Bank Account</h1>
<p className="text-sm text-muted-foreground mt-0.5">Record a new cash or bank account for use in payments.</p>
</div>
</div>
<div className="rounded-xl border bg-card p-6">
<div className="mb-6 flex max-w-sm gap-2">
<Button
type="button"
variant={accountType === BankAccountType.Bank ? 'default' : 'outline'}
className="flex-1"
onClick={() => setAccountType(BankAccountType.Bank)}
>Bank</Button>
<Button
type="button"
variant={accountType === BankAccountType.Cash ? 'default' : 'outline'}
className="flex-1"
onClick={() => setAccountType(BankAccountType.Cash)}
>Cash</Button>
</div>
<div className="grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2 lg:grid-cols-3">
<Field data-invalid={!!errors.accountName}>
<FieldLabel htmlFor="ba-name">Account name</FieldLabel>
<Input
id="ba-name"
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
placeholder={accountType === BankAccountType.Bank ? 'Main Account' : 'Head Office Petty Cash'}
aria-invalid={!!errors.accountName}
/>
<FieldError errors={[errors.accountName ? { message: errors.accountName } : undefined]} />
</Field>
{accountType === BankAccountType.Bank ? (
<>
<Field>
<FieldLabel htmlFor="ba-bank">Bank name (optional)</FieldLabel>
<Input id="ba-bank" value={bankName} onChange={(e) => setBankName(e.target.value)} placeholder="Commercial Bank" />
</Field>
<Field>
<FieldLabel htmlFor="ba-acct-no">Account number (optional)</FieldLabel>
<Input id="ba-acct-no" value={accountNumber} onChange={(e) => setAccountNumber(e.target.value)} placeholder="8001234567" />
</Field>
</>
) : (
<>
<Field data-invalid={!!errors.cashAccountTypeName}>
<FieldLabel htmlFor="ba-cash-type">Cash account type</FieldLabel>
<Select<string> value={cashAccountTypeChoice} onValueChange={(v) => setCashAccountTypeChoice(v ?? '')}>
<SelectTrigger id="ba-cash-type" className="w-full" aria-invalid={!!errors.cashAccountTypeName}>
<SelectValue placeholder={cashAccountTypes === null ? 'Loading…' : 'Select a type'} />
</SelectTrigger>
<SelectContent>
{(cashAccountTypes ?? []).map(t => (
<SelectItem key={t.name} value={t.name} label={t.name}>{t.name}</SelectItem>
))}
<SelectItem value={OTHER_CASH_TYPE} label="Other, please specify">Other, please specify…</SelectItem>
</SelectContent>
</Select>
{cashAccountTypeChoice === OTHER_CASH_TYPE && (
<Input
value={customCashAccountTypeName}
onChange={(e) => setCustomCashAccountTypeName(e.target.value)}
placeholder="e.g. Site Cash"
className="mt-2"
/>
)}
{cashAccountTypesError && (
<p className="text-sm text-destructive">{cashAccountTypesError}</p>
)}
<FieldError errors={[errors.cashAccountTypeName ? { message: errors.cashAccountTypeName } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="ba-acct-no">Account number (optional)</FieldLabel>
<Input id="ba-acct-no" value={accountNumber} onChange={(e) => setAccountNumber(e.target.value)} />
</Field>
</>
)}
<Field>
<FieldLabel htmlFor="ba-currency">Currency</FieldLabel>
<Input id="ba-currency" value={currencyCode} onChange={(e) => setCurrencyCode(e.target.value)} maxLength={3} placeholder="LKR" />
</Field>
</div>
<div className="mt-6 flex justify-end gap-3">
<Link href="/payments/bank-accounts" className={cn(buttonVariants({ variant: 'outline' }))}>Cancel</Link>
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? 'Creating…' : 'Create'}</Button>
</div>
</div>
</div>
)
}
@@ -0,0 +1,276 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { CheckCircle2, Pencil, Plus, Search, Trash2, Wallet, XCircle } from 'lucide-react'
import { bankAccounts as bankAccountsApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import { BankAccountType, type BankAccountResponse } from '@/types/payments'
import { Badge } from '@/components/ui/badge'
import { Button, buttonVariants } from '@/components/ui/button'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Field, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Skeleton } from '@/components/ui/skeleton'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { toast } from '@/components/ui/toast'
type AccountTypeFilter = BankAccountType | 'All'
function formatDate(value: string): string {
return new Date(value).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
}
export default function BankAccountsPage() {
const [accounts, setAccounts] = useState<BankAccountResponse[] | null>(null)
const [error, setError] = useState<string | null>(null)
const [search, setSearch] = useState('')
const [accountType, setAccountType] = useState<AccountTypeFilter>('All')
const [editTarget, setEditTarget] = useState<BankAccountResponse | null>(null)
const [editName, setEditName] = useState('')
const [editError, setEditError] = useState<string | null>(null)
const [editSubmitting, setEditSubmitting] = useState(false)
const [statusPendingId, setStatusPendingId] = useState<string | null>(null)
const [deletingId, setDeletingId] = useState<string | null>(null)
const [confirmDelete, setConfirmDelete] = useState<BankAccountResponse | null>(null)
function load() {
const filter = accountType === 'All' ? undefined : accountType
bankAccountsApi
.list(filter)
.then(setAccounts)
.catch(err => setError(getApiError(err)))
}
useEffect(load, [accountType])
const filtered = useMemo(() => {
if (!accounts) return null
const q = search.trim().toLowerCase()
if (!q) return accounts
return accounts.filter(a =>
a.accountName.toLowerCase().includes(q) ||
(a.bankName ?? '').toLowerCase().includes(q) ||
(a.cashAccountTypeName ?? '').toLowerCase().includes(q) ||
(a.accountNumber ?? '').toLowerCase().includes(q) ||
a.currencyCode.toLowerCase().includes(q),
)
}, [accounts, search])
function openEdit(a: BankAccountResponse) {
setEditTarget(a)
setEditName(a.accountName)
setEditError(null)
}
async function handleRename() {
if (!editTarget) return
if (!editName.trim()) {
setEditError('Account name is required')
return
}
setEditSubmitting(true)
try {
await bankAccountsApi.updateCash(editTarget.accountId, editName.trim())
toast.add({ title: 'Cash account renamed', type: 'success' })
setEditTarget(null)
load()
} catch (err) {
setEditError(getApiError(err))
} finally {
setEditSubmitting(false)
}
}
async function toggleStatus(a: BankAccountResponse) {
setStatusPendingId(a.accountId)
try {
await bankAccountsApi.setCashStatus(a.accountId, !a.isActive)
toast.add({ title: a.isActive ? 'Cash account deactivated' : 'Cash account activated', type: 'success' })
load()
} catch (err) {
toast.add({ title: 'Could not update status', description: getApiError(err), type: 'error' })
} finally {
setStatusPendingId(null)
}
}
async function handleDelete(a: BankAccountResponse) {
setDeletingId(a.accountId)
try {
await bankAccountsApi.deleteCash(a.accountId)
toast.add({ title: 'Cash account deleted', type: 'success' })
load()
} catch (err) {
toast.add({ title: 'Could not delete cash account', description: getApiError(err), type: 'error' })
} finally {
setDeletingId(null)
setConfirmDelete(null)
}
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold text-foreground">Cash / Bank Accounts</h1>
<p className="text-sm text-muted-foreground mt-0.5">Cash and bank accounts used for payments, deposits, and cheques.</p>
</div>
<Link href="/payments/bank-accounts/new" className={cn(buttonVariants())}>
<Plus className="size-4" />
New Account
</Link>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<div className="relative flex-1">
<Search className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search name, bank/type, account no. or currency…"
className="pl-9"
aria-label="Search cash/bank accounts"
/>
</div>
<Select<AccountTypeFilter> value={accountType} onValueChange={(v) => setAccountType(v ?? 'All')}>
<SelectTrigger className="w-full sm:w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="All" label="All types">All types</SelectItem>
<SelectItem value={BankAccountType.Bank} label="Bank">Bank</SelectItem>
<SelectItem value={BankAccountType.Cash} label="Cash">Cash</SelectItem>
</SelectContent>
</Select>
</div>
{error && (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">{error}</div>
)}
{!error && filtered === null && (
<div className="flex flex-col gap-2">
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-12 w-full" />)}
</div>
)}
{!error && filtered !== null && filtered.length === 0 && (
<div className="flex flex-col items-center gap-3 rounded-xl border border-dashed py-16 text-center">
<Wallet className="size-10 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
{search ? 'No accounts match your search.' : 'No cash/bank accounts yet.'}
</p>
</div>
)}
{!error && filtered !== null && filtered.length > 0 && (
<Table>
<TableHeader>
<TableRow>
<TableHead>Type</TableHead>
<TableHead>Account name</TableHead>
<TableHead>Bank / Cash type</TableHead>
<TableHead>Account no.</TableHead>
<TableHead>Currency</TableHead>
<TableHead>Created</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filtered.map(a => (
<TableRow key={`${a.accountType}-${a.accountId}`}>
<TableCell>
<Badge variant={a.accountType === BankAccountType.Cash ? 'secondary' : 'default'}>
{a.accountType}
</Badge>
</TableCell>
<TableCell className="font-medium">{a.accountName}</TableCell>
<TableCell>{a.bankName ?? a.cashAccountTypeName ?? '—'}</TableCell>
<TableCell>{a.accountNumber ?? '—'}</TableCell>
<TableCell>{a.currencyCode}</TableCell>
<TableCell>{formatDate(a.createdAt)}</TableCell>
<TableCell>
{a.accountType === BankAccountType.Cash ? (
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" onClick={() => openEdit(a)} aria-label={`Edit ${a.accountName}`}>
<Pencil className="size-4" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
className={cn(a.isActive ? 'text-destructive hover:bg-destructive/10' : 'text-emerald-600 hover:bg-emerald-100')}
aria-label={a.isActive ? `Deactivate ${a.accountName}` : `Activate ${a.accountName}`}
title={a.isDefault && a.isActive ? "Default cash account can't be deactivated" : undefined}
disabled={statusPendingId === a.accountId || (a.isDefault === true && a.isActive === true)}
onClick={() => toggleStatus(a)}
>
{a.isActive ? <XCircle className="size-4" /> : <CheckCircle2 className="size-4" />}
</Button>
<Button
variant="ghost"
size="icon"
className="text-destructive hover:bg-destructive/10"
aria-label={`Delete ${a.accountName}`}
title={a.isDefault ? "Default cash account can't be deleted" : undefined}
disabled={deletingId === a.accountId || a.isDefault === true}
onClick={() => setConfirmDelete(a)}
>
<Trash2 className="size-4" />
</Button>
</div>
) : (
<span className="text-xs text-muted-foreground">—</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
<Dialog open={!!editTarget} onOpenChange={(v) => { if (!v) setEditTarget(null) }}>
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Rename cash account</DialogTitle>
<DialogDescription>
{editTarget?.isDefault ? 'This is the default cash account — only its name can be changed.' : "Update this cash account's display name."}
</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field data-invalid={!!editError}>
<FieldLabel htmlFor="cash-account-name">Account name</FieldLabel>
<Input id="cash-account-name" value={editName} onChange={(e) => setEditName(e.target.value)} aria-invalid={!!editError} />
<FieldError errors={[editError ? { message: editError } : undefined]} />
</Field>
</FieldGroup>
<div className="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
<Button variant="outline" onClick={() => setEditTarget(null)} disabled={editSubmitting}>Cancel</Button>
<Button onClick={handleRename} disabled={editSubmitting}>{editSubmitting ? 'Saving…' : 'Save'}</Button>
</div>
</DialogContent>
</Dialog>
<Dialog open={!!confirmDelete} onOpenChange={(v) => { if (!v) setConfirmDelete(null) }}>
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Delete {confirmDelete?.accountName}?</DialogTitle>
<DialogDescription>This permanently removes the cash account. Accounts referenced elsewhere cannot be deleted.</DialogDescription>
</DialogHeader>
<div className="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:justify-end">
<Button variant="outline" onClick={() => setConfirmDelete(null)} disabled={!!deletingId}>Cancel</Button>
<Button variant="destructive" onClick={() => confirmDelete && handleDelete(confirmDelete)} disabled={!!deletingId}>
{deletingId ? 'Deleting…' : 'Delete'}
</Button>
</div>
</DialogContent>
</Dialog>
</div>
)
}
@@ -0,0 +1,397 @@
'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { useParams } from 'next/navigation'
import { ArrowLeft, BookText } from 'lucide-react'
import { bankAccounts as bankAccountsApi, chequeBooks as chequeBooksApi, chequePages as chequePagesApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import {
BankAccountType,
ChequePageIssueStatus,
ChequePageStatusAction,
PayeeType,
type BankAccountResponse,
type ChequeBookResponse,
type ChequePageResponse,
} from '@/types/payments'
import { Badge } from '@/components/ui/badge'
import { Button, buttonVariants } from '@/components/ui/button'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Skeleton } from '@/components/ui/skeleton'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { toast } from '@/components/ui/toast'
const STATUS_BADGE_VARIANT: Record<ChequePageIssueStatus, 'default' | 'secondary' | 'destructive' | 'outline'> = {
[ChequePageIssueStatus.Unused]: 'outline',
[ChequePageIssueStatus.Issued]: 'default',
[ChequePageIssueStatus.Cleared]: 'secondary',
[ChequePageIssueStatus.Bounced]: 'destructive',
[ChequePageIssueStatus.Cancelled]: 'destructive',
[ChequePageIssueStatus.Void]: 'outline',
}
function formatDate(value: string | null): string {
if (!value) return '—'
return new Date(value).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
}
function formatAmount(value: number | null): string {
if (value === null) return '—'
return value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
export default function ChequeBookDetailPage() {
const params = useParams<{ chequeBookNo: string }>()
const chequeBookNo = decodeURIComponent(params.chequeBookNo)
const [book, setBook] = useState<ChequeBookResponse | null>(null)
const [bankAccount, setBankAccount] = useState<BankAccountResponse | null>(null)
const [error, setError] = useState<string | null>(null)
const [selectedPage, setSelectedPage] = useState<ChequePageResponse | null>(null)
useEffect(() => {
let cancelled = false
chequeBooksApi
.get(chequeBookNo, true)
.then(async res => {
if (cancelled) return
setBook(res)
try {
const banks = await bankAccountsApi.list(BankAccountType.Bank)
if (!cancelled) setBankAccount(banks.find(b => b.accountId === res.bankAccountId) ?? null)
} catch { /* ignore */ }
})
.catch(err => { if (!cancelled) setError(getApiError(err)) })
return () => { cancelled = true }
}, [chequeBookNo])
function handlePageUpdated(updated: ChequePageResponse) {
setBook(prev => prev ? { ...prev, pages: prev.pages.map(p => p.chequeNo === updated.chequeNo ? updated : p) } : prev)
setSelectedPage(updated)
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Link href="/payments/cheque-books" className={cn(buttonVariants({ variant: 'ghost', size: 'icon' }))}>
<ArrowLeft className="size-4" />
</Link>
<div>
<h1 className="text-2xl font-semibold text-foreground">Cheque Book {chequeBookNo}</h1>
<p className="text-sm text-muted-foreground mt-0.5">Every leaf in this book — click one to view or take an action.</p>
</div>
</div>
{error && (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">{error}</div>
)}
{!error && book === null && (
<div className="flex flex-col gap-2">
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-12 w-full" />)}
</div>
)}
{!error && book !== null && (
<>
<div className="grid grid-cols-2 gap-4 rounded-xl border bg-card p-5 sm:grid-cols-4">
<div>
<p className="text-xs text-muted-foreground">Bank account</p>
<p className="text-sm font-medium">{bankAccount ? bankAccount.accountName : `#${book.bankAccountId.slice(0, 8)}`}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Branch</p>
<p className="text-sm font-medium">{book.branchId}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Range</p>
<p className="text-sm font-medium">{book.startChequeNo} – {book.endChequeNo}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Received</p>
<p className="text-sm font-medium">{formatDate(book.receivedDate)}</p>
</div>
</div>
{book.pages.length === 0 ? (
<div className="flex flex-col items-center gap-3 rounded-xl border border-dashed py-16 text-center">
<BookText className="size-10 text-muted-foreground" />
<p className="text-sm text-muted-foreground">No pages found for this book.</p>
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Cheque no.</TableHead>
<TableHead>Status</TableHead>
<TableHead>Payee</TableHead>
<TableHead>Issue date</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{book.pages.map(p => (
<TableRow key={p.chequeNo} className="cursor-pointer" onClick={() => setSelectedPage(p)}>
<TableCell className="font-medium">{p.chequeNo}</TableCell>
<TableCell>
<Badge variant={STATUS_BADGE_VARIANT[p.issueStatus]}>{p.issueStatus}</Badge>
</TableCell>
<TableCell>{p.payeeName ?? '—'}</TableCell>
<TableCell>{formatDate(p.issueDate)}</TableCell>
<TableCell className="text-right tabular-nums">{formatAmount(p.amount)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</>
)}
<ChequePageDialog page={selectedPage} onClose={() => setSelectedPage(null)} onUpdated={handlePageUpdated} />
</div>
)
}
// Inline cheque page action dialog — Distribution doesn't ship a shared ChequePageDialog component.
function ChequePageDialog({
page,
onClose,
onUpdated,
}: {
page: ChequePageResponse | null
onClose: () => void
onUpdated: (p: ChequePageResponse) => void
}) {
const open = page !== null
const [mode, setMode] = useState<'view' | 'issue' | 'status'>('view')
const [submitting, setSubmitting] = useState(false)
// Issue form
const [payeeType, setPayeeType] = useState<PayeeType>(PayeeType.Supplier)
const [payeeName, setPayeeName] = useState('')
const [issueDate, setIssueDate] = useState('')
const [amount, setAmount] = useState('')
const [purpose, setPurpose] = useState('')
const [issueErrors, setIssueErrors] = useState<Record<string, string>>({})
// Status form
const [action, setAction] = useState<ChequePageStatusAction>(ChequePageStatusAction.Clear)
const [clearedDate, setClearedDate] = useState('')
const [cancelReason, setCancelReason] = useState('')
useEffect(() => {
if (!page) return
setMode('view')
setPayeeType(page.payeeType ?? PayeeType.Supplier)
setPayeeName(page.payeeName ?? '')
setIssueDate(page.issueDate ? page.issueDate.slice(0, 10) : '')
setAmount(page.amount != null ? String(page.amount) : '')
setPurpose(page.purpose ?? '')
setIssueErrors({})
setAction(ChequePageStatusAction.Clear)
setClearedDate('')
setCancelReason('')
}, [page])
if (!page) return null
async function handleIssue() {
if (!page) return
const next: Record<string, string> = {}
if (!payeeName.trim()) next.payeeName = 'Payee name is required'
if (!issueDate) next.issueDate = 'Issue date is required'
const amountNum = Number(amount)
if (!amount || Number.isNaN(amountNum) || amountNum <= 0) next.amount = 'Amount must be greater than zero'
setIssueErrors(next)
if (Object.keys(next).length > 0) return
setSubmitting(true)
try {
const updated = await chequePagesApi.issue(page.chequeNo, {
payeeType,
payeeName,
issueDate,
amount: amountNum,
purpose: purpose || undefined,
})
toast.add({ title: 'Cheque issued', description: `${updated.chequeNo} — ${updated.payeeName}`, type: 'success' })
onUpdated(updated)
setMode('view')
} catch (err) {
toast.add({ title: 'Could not issue cheque', description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
async function handleStatus() {
if (!page) return
if (action === ChequePageStatusAction.Clear && !clearedDate) {
toast.add({ title: 'Cleared date is required', type: 'error' })
return
}
if (action === ChequePageStatusAction.Cancel && !cancelReason.trim()) {
toast.add({ title: 'Cancel reason is required', type: 'error' })
return
}
setSubmitting(true)
try {
const updated = await chequePagesApi.updateStatus(page.chequeNo, {
action,
clearedDate: action === ChequePageStatusAction.Clear ? clearedDate : undefined,
cancelReason: action === ChequePageStatusAction.Cancel ? cancelReason : undefined,
})
toast.add({ title: `Cheque ${action.toLowerCase()}ed`, type: 'success' })
onUpdated(updated)
setMode('view')
} catch (err) {
toast.add({ title: 'Could not update status', description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
const canIssue = page.issueStatus === ChequePageIssueStatus.Unused
const canChangeStatus = page.issueStatus === ChequePageIssueStatus.Issued || page.issueStatus === ChequePageIssueStatus.Unused
const availableActions: ChequePageStatusAction[] =
page.issueStatus === ChequePageIssueStatus.Issued
? [ChequePageStatusAction.Clear, ChequePageStatusAction.Bounce, ChequePageStatusAction.Cancel]
: page.issueStatus === ChequePageIssueStatus.Unused
? [ChequePageStatusAction.Void]
: []
return (
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose() }}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Cheque {page.chequeNo}</DialogTitle>
<DialogDescription>
Status: <Badge variant={STATUS_BADGE_VARIANT[page.issueStatus]}>{page.issueStatus}</Badge>
</DialogDescription>
</DialogHeader>
{mode === 'view' && (
<div className="flex flex-col gap-3 text-sm">
<div className="grid grid-cols-2 gap-3">
<div>
<p className="text-xs text-muted-foreground">Payee</p>
<p>{page.payeeName ?? '—'}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Payee type</p>
<p>{page.payeeType ?? '—'}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Issue date</p>
<p>{formatDate(page.issueDate)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Amount</p>
<p className="tabular-nums">{formatAmount(page.amount)}</p>
</div>
{page.purpose && (
<div className="col-span-2">
<p className="text-xs text-muted-foreground">Purpose</p>
<p>{page.purpose}</p>
</div>
)}
{page.cancelReason && (
<div className="col-span-2">
<p className="text-xs text-muted-foreground">Cancel reason</p>
<p>{page.cancelReason}</p>
</div>
)}
</div>
<div className="flex gap-2 pt-2">
{canIssue && <Button onClick={() => setMode('issue')}>Issue cheque</Button>}
{canChangeStatus && availableActions.length > 0 && (
<Button variant="outline" onClick={() => { setAction(availableActions[0]); setMode('status') }}>
Change status
</Button>
)}
</div>
</div>
)}
{mode === 'issue' && (
<div className="flex flex-col gap-4">
<Field>
<FieldLabel htmlFor="pt">Payee type</FieldLabel>
<Select<PayeeType> value={payeeType} onValueChange={(v) => setPayeeType(v ?? PayeeType.Supplier)}>
<SelectTrigger id="pt" className="w-full"><SelectValue /></SelectTrigger>
<SelectContent>
{Object.values(PayeeType).map(t => (
<SelectItem key={t} value={t} label={t}>{t}</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field data-invalid={!!issueErrors.payeeName}>
<FieldLabel htmlFor="pn">Payee name</FieldLabel>
<Input id="pn" value={payeeName} onChange={(e) => setPayeeName(e.target.value)} aria-invalid={!!issueErrors.payeeName} />
<FieldError errors={[issueErrors.payeeName ? { message: issueErrors.payeeName } : undefined]} />
</Field>
<Field data-invalid={!!issueErrors.issueDate}>
<FieldLabel htmlFor="id">Issue date</FieldLabel>
<Input id="id" type="date" value={issueDate} onChange={(e) => setIssueDate(e.target.value)} aria-invalid={!!issueErrors.issueDate} />
<FieldError errors={[issueErrors.issueDate ? { message: issueErrors.issueDate } : undefined]} />
</Field>
<Field data-invalid={!!issueErrors.amount}>
<FieldLabel htmlFor="am">Amount</FieldLabel>
<Input id="am" type="number" value={amount} onChange={(e) => setAmount(e.target.value)} aria-invalid={!!issueErrors.amount} />
<FieldError errors={[issueErrors.amount ? { message: issueErrors.amount } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="pp">Purpose (optional)</FieldLabel>
<Input id="pp" value={purpose} onChange={(e) => setPurpose(e.target.value)} />
</Field>
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" onClick={() => setMode('view')} disabled={submitting}>Back</Button>
<Button onClick={handleIssue} disabled={submitting}>{submitting ? 'Issuing…' : 'Issue'}</Button>
</div>
</div>
)}
{mode === 'status' && (
<div className="flex flex-col gap-4">
<Field>
<FieldLabel htmlFor="ac">Action</FieldLabel>
<Select<ChequePageStatusAction> value={action} onValueChange={(v) => setAction(v ?? availableActions[0])}>
<SelectTrigger id="ac" className="w-full"><SelectValue /></SelectTrigger>
<SelectContent>
{availableActions.map(a => (
<SelectItem key={a} value={a} label={a}>{a}</SelectItem>
))}
</SelectContent>
</Select>
</Field>
{action === ChequePageStatusAction.Clear && (
<Field>
<FieldLabel htmlFor="cd">Cleared date</FieldLabel>
<Input id="cd" type="date" value={clearedDate} onChange={(e) => setClearedDate(e.target.value)} />
</Field>
)}
{action === ChequePageStatusAction.Cancel && (
<Field>
<FieldLabel htmlFor="cr">Cancel reason</FieldLabel>
<Input id="cr" value={cancelReason} onChange={(e) => setCancelReason(e.target.value)} />
</Field>
)}
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" onClick={() => setMode('view')} disabled={submitting}>Back</Button>
<Button onClick={handleStatus} disabled={submitting}>{submitting ? 'Saving…' : 'Confirm'}</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
)
}
@@ -0,0 +1,167 @@
'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { ArrowLeft } from 'lucide-react'
import { bankAccounts as bankAccountsApi, chequeBooks as chequeBooksApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import { BankAccountType, type BankAccountResponse } from '@/types/payments'
import { Button, buttonVariants } from '@/components/ui/button'
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { toast } from '@/components/ui/toast'
export default function NewChequeBookPage() {
const router = useRouter()
const [bankAccounts, setBankAccounts] = useState<BankAccountResponse[] | null>(null)
const [bankAccountsError, setBankAccountsError] = useState<string | null>(null)
const [branchId, setBranchId] = useState('')
const [bankAccountId, setBankAccountId] = useState('')
const [chequeBookNo, setChequeBookNo] = useState('')
const [startChequeNo, setStartChequeNo] = useState('')
const [endChequeNo, setEndChequeNo] = useState('')
const [totalLeaves, setTotalLeaves] = useState('')
const [receivedDate, setReceivedDate] = useState('')
const [description, setDescription] = useState('')
const [createdBy, setCreatedBy] = useState('')
const [errors, setErrors] = useState<Record<string, string>>({})
const [submitting, setSubmitting] = useState(false)
useEffect(() => {
bankAccountsApi.list(BankAccountType.Bank)
.then(setBankAccounts)
.catch(err => setBankAccountsError(getApiError(err)))
}, [])
async function handleSubmit() {
const next: Record<string, string> = {}
if (!bankAccountId) next.bankAccountId = 'Bank account is required'
if (!branchId) next.branchId = 'Branch is required'
if (!chequeBookNo.trim()) next.chequeBookNo = 'Cheque book number is required'
if (!startChequeNo.trim()) next.startChequeNo = 'Start cheque number is required'
if (!endChequeNo.trim()) next.endChequeNo = 'End cheque number is required'
const leavesNum = Number(totalLeaves)
if (!totalLeaves || Number.isNaN(leavesNum) || leavesNum <= 0) next.totalLeaves = 'Total leaves must be greater than zero'
if (!receivedDate) next.receivedDate = 'Received date is required'
setErrors(next)
if (Object.keys(next).length > 0) return
setSubmitting(true)
try {
const created = await chequeBooksApi.create({
branchId: Number(branchId),
bankAccountId,
chequeBookNo,
startChequeNo,
endChequeNo,
totalLeaves: Number(totalLeaves),
receivedDate,
description: description || undefined,
createdBy: createdBy || undefined,
})
toast.add({ title: 'Cheque book created', description: `${created.chequeBookNo} — ${created.totalLeaves} leaves`, type: 'success' })
router.push(`/payments/cheque-books/${encodeURIComponent(created.chequeBookNo)}`)
} catch (err) {
toast.add({ title: 'Could not create cheque book', description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Link href="/payments/cheque-books" className={cn(buttonVariants({ variant: 'ghost', size: 'icon' }))}>
<ArrowLeft className="size-4" />
</Link>
<div>
<h1 className="text-2xl font-semibold text-foreground">New Cheque Book</h1>
<p className="text-sm text-muted-foreground mt-0.5">
Every leaf from the start to end cheque number is generated automatically, all &quot;Unused&quot;.
</p>
</div>
</div>
{bankAccountsError && (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">{bankAccountsError}</div>
)}
<div className="rounded-xl border bg-card p-6">
<div className="grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2 lg:grid-cols-3">
<Field data-invalid={!!errors.bankAccountId}>
<FieldLabel htmlFor="cb-bank">Bank account</FieldLabel>
<Select<string> value={bankAccountId} onValueChange={(v) => setBankAccountId(v ?? '')}>
<SelectTrigger id="cb-bank" className="w-full" aria-invalid={!!errors.bankAccountId}>
<SelectValue placeholder={bankAccounts === null ? 'Loading…' : 'Select a bank account'} />
</SelectTrigger>
<SelectContent>
{(bankAccounts ?? []).map(a => (
<SelectItem key={a.accountId} value={a.accountId} label={a.accountName}>
{a.accountName}{a.bankName ? ` — ${a.bankName}` : ''}
</SelectItem>
))}
</SelectContent>
</Select>
<FieldError errors={[errors.bankAccountId ? { message: errors.bankAccountId } : undefined]} />
</Field>
<Field data-invalid={!!errors.branchId}>
<FieldLabel htmlFor="cb-branch">Branch</FieldLabel>
<Input id="cb-branch" type="number" value={branchId} onChange={(e) => setBranchId(e.target.value)} placeholder="1" aria-invalid={!!errors.branchId} />
<FieldError errors={[errors.branchId ? { message: errors.branchId } : undefined]} />
</Field>
<Field data-invalid={!!errors.chequeBookNo}>
<FieldLabel htmlFor="cb-no">Cheque book number</FieldLabel>
<Input id="cb-no" value={chequeBookNo} onChange={(e) => setChequeBookNo(e.target.value)} placeholder="CB-0001" aria-invalid={!!errors.chequeBookNo} />
<FieldError errors={[errors.chequeBookNo ? { message: errors.chequeBookNo } : undefined]} />
</Field>
<Field data-invalid={!!errors.startChequeNo}>
<FieldLabel htmlFor="cb-start">Start cheque no.</FieldLabel>
<Input id="cb-start" value={startChequeNo} onChange={(e) => setStartChequeNo(e.target.value)} placeholder="000001" aria-invalid={!!errors.startChequeNo} />
<FieldError errors={[errors.startChequeNo ? { message: errors.startChequeNo } : undefined]} />
</Field>
<Field data-invalid={!!errors.endChequeNo}>
<FieldLabel htmlFor="cb-end">End cheque no.</FieldLabel>
<Input id="cb-end" value={endChequeNo} onChange={(e) => setEndChequeNo(e.target.value)} placeholder="000025" aria-invalid={!!errors.endChequeNo} />
<FieldError errors={[errors.endChequeNo ? { message: errors.endChequeNo } : undefined]} />
</Field>
<Field data-invalid={!!errors.totalLeaves}>
<FieldLabel htmlFor="cb-leaves">Total leaves</FieldLabel>
<Input id="cb-leaves" type="number" value={totalLeaves} onChange={(e) => setTotalLeaves(e.target.value)} placeholder="25" aria-invalid={!!errors.totalLeaves} />
<p className="text-xs text-muted-foreground">Must equal end − start + 1.</p>
<FieldError errors={[errors.totalLeaves ? { message: errors.totalLeaves } : undefined]} />
</Field>
<Field data-invalid={!!errors.receivedDate}>
<FieldLabel htmlFor="cb-received">Received date</FieldLabel>
<Input id="cb-received" type="date" value={receivedDate} onChange={(e) => setReceivedDate(e.target.value)} aria-invalid={!!errors.receivedDate} />
<FieldError errors={[errors.receivedDate ? { message: errors.receivedDate } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="cb-desc">Description (optional)</FieldLabel>
<Input id="cb-desc" value={description} onChange={(e) => setDescription(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="cb-by">Created by (optional)</FieldLabel>
<Input id="cb-by" value={createdBy} onChange={(e) => setCreatedBy(e.target.value)} />
</Field>
</div>
<div className="mt-6 flex justify-end gap-3">
<Link href="/payments/cheque-books" className={cn(buttonVariants({ variant: 'outline' }))}>Cancel</Link>
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? 'Creating…' : 'Create'}</Button>
</div>
</div>
</div>
)
}
@@ -0,0 +1,143 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { BookText, Plus } from 'lucide-react'
import { bankAccounts as bankAccountsApi, chequeBooks as chequeBooksApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import {
BankAccountType,
ChequeBookStatus,
type BankAccountResponse,
type ChequeBookResponse,
} from '@/types/payments'
import { Badge } from '@/components/ui/badge'
import { buttonVariants } from '@/components/ui/button'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Skeleton } from '@/components/ui/skeleton'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
type StatusFilter = ChequeBookStatus | 'All'
const STATUS_BADGE_VARIANT: Record<ChequeBookStatus, 'default' | 'secondary' | 'destructive'> = {
[ChequeBookStatus.Active]: 'default',
[ChequeBookStatus.Completed]: 'secondary',
[ChequeBookStatus.Cancelled]: 'destructive',
}
function formatDate(value: string): string {
return new Date(value).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
}
export default function ChequeBooksPage() {
const router = useRouter()
const [books, setBooks] = useState<ChequeBookResponse[] | null>(null)
const [bankAccountMap, setBankAccountMap] = useState<Map<string, BankAccountResponse>>(new Map())
const [error, setError] = useState<string | null>(null)
const [status, setStatus] = useState<StatusFilter>('All')
useEffect(() => {
bankAccountsApi
.list(BankAccountType.Bank)
.then(list => setBankAccountMap(new Map(list.map(a => [a.accountId, a]))))
.catch(() => setBankAccountMap(new Map()))
}, [])
useEffect(() => {
let cancelled = false
chequeBooksApi
.list({ status: status === 'All' ? undefined : status, pageSize: 100 })
.then(res => { if (!cancelled) setBooks(res.items) })
.catch(err => { if (!cancelled) setError(getApiError(err)) })
return () => { cancelled = true }
}, [status])
const sortedBooks = useMemo(() => books, [books])
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold text-foreground">Cheque Books</h1>
<p className="text-sm text-muted-foreground mt-0.5">Cheque books issued from the company&apos;s own supply.</p>
</div>
<Link href="/payments/cheque-books/new" className={cn(buttonVariants())}>
<Plus className="size-4" />
New Cheque Book
</Link>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<Select<StatusFilter> value={status} onValueChange={(v) => setStatus(v ?? 'All')}>
<SelectTrigger className="w-full sm:w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="All" label="All statuses">All statuses</SelectItem>
<SelectItem value={ChequeBookStatus.Active} label="Active">Active</SelectItem>
<SelectItem value={ChequeBookStatus.Completed} label="Completed">Completed</SelectItem>
<SelectItem value={ChequeBookStatus.Cancelled} label="Cancelled">Cancelled</SelectItem>
</SelectContent>
</Select>
</div>
{error && (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">{error}</div>
)}
{!error && sortedBooks === null && (
<div className="flex flex-col gap-2">
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-12 w-full" />)}
</div>
)}
{!error && sortedBooks !== null && sortedBooks.length === 0 && (
<div className="flex flex-col items-center gap-3 rounded-xl border border-dashed py-16 text-center">
<BookText className="size-10 text-muted-foreground" />
<p className="text-sm text-muted-foreground">No cheque books yet.</p>
</div>
)}
{!error && sortedBooks !== null && sortedBooks.length > 0 && (
<Table>
<TableHeader>
<TableRow>
<TableHead>Cheque book no.</TableHead>
<TableHead>Bank account</TableHead>
<TableHead>Branch</TableHead>
<TableHead>Range</TableHead>
<TableHead>Leaves</TableHead>
<TableHead>Received</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedBooks.map(b => {
const bank = bankAccountMap.get(b.bankAccountId)
return (
<TableRow
key={b.chequeBookNo}
className="cursor-pointer"
onClick={() => router.push(`/payments/cheque-books/${encodeURIComponent(b.chequeBookNo)}`)}
>
<TableCell className="font-medium">{b.chequeBookNo}</TableCell>
<TableCell>{bank ? bank.accountName : `#${b.bankAccountId.slice(0, 8)}`}</TableCell>
<TableCell>{b.branchId || '—'}</TableCell>
<TableCell>{b.startChequeNo} – {b.endChequeNo}</TableCell>
<TableCell>{b.totalLeaves}</TableCell>
<TableCell>{formatDate(b.receivedDate)}</TableCell>
<TableCell>
<Badge variant={STATUS_BADGE_VARIANT[b.status]}>{b.status}</Badge>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
)}
</div>
)
}
@@ -0,0 +1,189 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { ArrowLeft } from 'lucide-react'
import { receivedCheques as receivedChequesApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import { ReceivedFromType } from '@/types/payments'
import { Button, buttonVariants } from '@/components/ui/button'
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { toast } from '@/components/ui/toast'
export default function NewReceivedChequePage() {
const router = useRouter()
const [companyId, setCompanyId] = useState('')
const [branchId, setBranchId] = useState('')
const [receivedFromType, setReceivedFromType] = useState<ReceivedFromType>(ReceivedFromType.Customer)
const [receivedFromId, setReceivedFromId] = useState('')
const [receivedFromName, setReceivedFromName] = useState('')
const [drawerBankName, setDrawerBankName] = useState('')
const [drawerBankBranch, setDrawerBankBranch] = useState('')
const [accountHolderName, setAccountHolderName] = useState('')
const [chequeNo, setChequeNo] = useState('')
const [chequeDate, setChequeDate] = useState('')
const [amount, setAmount] = useState('')
const [receivedDate, setReceivedDate] = useState('')
const [referenceType, setReferenceType] = useState('')
const [referenceId, setReferenceId] = useState('')
const [notes, setNotes] = useState('')
const [createdBy, setCreatedBy] = useState('')
const [errors, setErrors] = useState<Record<string, string>>({})
const [submitting, setSubmitting] = useState(false)
async function handleSubmit() {
const next: Record<string, string> = {}
const companyNum = Number(companyId)
if (!companyId || Number.isNaN(companyNum) || companyNum <= 0) next.companyId = 'Company ID is required'
if (!receivedFromName.trim()) next.receivedFromName = 'Received from name is required'
if (!chequeNo.trim()) next.chequeNo = 'Cheque number is required'
if (!chequeDate) next.chequeDate = 'Cheque date is required'
const amountNum = Number(amount)
if (!amount || Number.isNaN(amountNum) || amountNum <= 0) next.amount = 'Amount must be greater than zero'
if (!receivedDate) next.receivedDate = 'Received date is required'
setErrors(next)
if (Object.keys(next).length > 0) return
setSubmitting(true)
try {
const created = await receivedChequesApi.create({
companyId: companyNum,
branchId: branchId ? Number(branchId) : undefined,
receivedFromType,
receivedFromId: receivedFromId ? Number(receivedFromId) : undefined,
receivedFromName,
drawerBankName: drawerBankName || undefined,
drawerBankBranch: drawerBankBranch || undefined,
accountHolderName: accountHolderName || undefined,
chequeNo,
chequeDate,
amount: amountNum,
receivedDate,
referenceType: referenceType || undefined,
referenceId: referenceId ? Number(referenceId) : undefined,
notes: notes || undefined,
createdBy: createdBy || undefined,
})
toast.add({ title: 'Received cheque recorded', description: `${created.chequeNo} — ${created.receivedFromName}`, type: 'success' })
router.push('/payments/received-cheques')
} catch (err) {
toast.add({ title: 'Could not record received cheque', description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Link href="/payments/received-cheques" className={cn(buttonVariants({ variant: 'ghost', size: 'icon' }))}>
<ArrowLeft className="size-4" />
</Link>
<div>
<h1 className="text-2xl font-semibold text-foreground">New Received Cheque</h1>
<p className="text-sm text-muted-foreground mt-0.5">Record a cheque received from a customer, supplier, or other party.</p>
</div>
</div>
<div className="rounded-xl border bg-card p-6">
<div className="grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2 lg:grid-cols-3">
<Field data-invalid={!!errors.companyId}>
<FieldLabel htmlFor="rc-company">Company ID</FieldLabel>
<Input id="rc-company" type="number" value={companyId} onChange={(e) => setCompanyId(e.target.value)} placeholder="1" aria-invalid={!!errors.companyId} />
<FieldError errors={[errors.companyId ? { message: errors.companyId } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="rc-branch">Branch (optional)</FieldLabel>
<Input id="rc-branch" type="number" value={branchId} onChange={(e) => setBranchId(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-from-type">Received from type</FieldLabel>
<Select<ReceivedFromType> value={receivedFromType} onValueChange={(v) => setReceivedFromType(v ?? ReceivedFromType.Customer)}>
<SelectTrigger id="rc-from-type" className="w-full"><SelectValue /></SelectTrigger>
<SelectContent>
{Object.values(ReceivedFromType).map(t => (
<SelectItem key={t} value={t} label={t}>{t}</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field data-invalid={!!errors.receivedFromName}>
<FieldLabel htmlFor="rc-from-name">Received from name</FieldLabel>
<Input id="rc-from-name" value={receivedFromName} onChange={(e) => setReceivedFromName(e.target.value)} aria-invalid={!!errors.receivedFromName} />
<FieldError errors={[errors.receivedFromName ? { message: errors.receivedFromName } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="rc-from-id">Received from ID (optional)</FieldLabel>
<Input id="rc-from-id" type="number" value={receivedFromId} onChange={(e) => setReceivedFromId(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-drawer-bank">Drawer bank (optional)</FieldLabel>
<Input id="rc-drawer-bank" value={drawerBankName} onChange={(e) => setDrawerBankName(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-drawer-branch">Drawer branch (optional)</FieldLabel>
<Input id="rc-drawer-branch" value={drawerBankBranch} onChange={(e) => setDrawerBankBranch(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-holder">Account holder name (optional)</FieldLabel>
<Input id="rc-holder" value={accountHolderName} onChange={(e) => setAccountHolderName(e.target.value)} />
</Field>
<Field data-invalid={!!errors.chequeNo}>
<FieldLabel htmlFor="rc-cheque-no">Cheque number</FieldLabel>
<Input id="rc-cheque-no" value={chequeNo} onChange={(e) => setChequeNo(e.target.value)} aria-invalid={!!errors.chequeNo} />
<FieldError errors={[errors.chequeNo ? { message: errors.chequeNo } : undefined]} />
</Field>
<Field data-invalid={!!errors.chequeDate}>
<FieldLabel htmlFor="rc-cheque-date">Cheque date</FieldLabel>
<Input id="rc-cheque-date" type="date" value={chequeDate} onChange={(e) => setChequeDate(e.target.value)} aria-invalid={!!errors.chequeDate} />
<FieldError errors={[errors.chequeDate ? { message: errors.chequeDate } : undefined]} />
</Field>
<Field data-invalid={!!errors.amount}>
<FieldLabel htmlFor="rc-amount">Amount</FieldLabel>
<Input id="rc-amount" type="number" value={amount} onChange={(e) => setAmount(e.target.value)} aria-invalid={!!errors.amount} />
<FieldError errors={[errors.amount ? { message: errors.amount } : undefined]} />
</Field>
<Field data-invalid={!!errors.receivedDate}>
<FieldLabel htmlFor="rc-received-date">Received date</FieldLabel>
<Input id="rc-received-date" type="date" value={receivedDate} onChange={(e) => setReceivedDate(e.target.value)} aria-invalid={!!errors.receivedDate} />
<FieldError errors={[errors.receivedDate ? { message: errors.receivedDate } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="rc-ref-type">Reference type (optional)</FieldLabel>
<Input id="rc-ref-type" value={referenceType} onChange={(e) => setReferenceType(e.target.value)} placeholder="Invoice" />
</Field>
<Field>
<FieldLabel htmlFor="rc-ref-id">Reference ID (optional)</FieldLabel>
<Input id="rc-ref-id" type="number" value={referenceId} onChange={(e) => setReferenceId(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-notes">Notes (optional)</FieldLabel>
<Input id="rc-notes" value={notes} onChange={(e) => setNotes(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-created-by">Created by (optional)</FieldLabel>
<Input id="rc-created-by" value={createdBy} onChange={(e) => setCreatedBy(e.target.value)} />
</Field>
</div>
<div className="mt-6 flex justify-end gap-3">
<Link href="/payments/received-cheques" className={cn(buttonVariants({ variant: 'outline' }))}>Cancel</Link>
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? 'Recording…' : 'Record'}</Button>
</div>
</div>
</div>
)
}
@@ -0,0 +1,287 @@
'use client'
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { Inbox, Plus } from 'lucide-react'
import { bankAccounts as bankAccountsApi, receivedCheques as receivedChequesApi, getApiError } from '@/lib/api/index'
import { cn } from '@/lib/utils'
import {
BankAccountType,
ReceivedChequeStatus,
ReceivedChequeStatusAction,
type BankAccountResponse,
type ReceivedChequeResponse,
} from '@/types/payments'
import { Badge } from '@/components/ui/badge'
import { Button, buttonVariants } from '@/components/ui/button'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Field, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Skeleton } from '@/components/ui/skeleton'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { toast } from '@/components/ui/toast'
type StatusFilter = ReceivedChequeStatus | 'All'
const STATUS_BADGE_VARIANT: Record<ReceivedChequeStatus, 'default' | 'secondary' | 'destructive' | 'outline'> = {
[ReceivedChequeStatus.Received]: 'outline',
[ReceivedChequeStatus.Deposited]: 'default',
[ReceivedChequeStatus.Cleared]: 'secondary',
[ReceivedChequeStatus.Returned]: 'destructive',
[ReceivedChequeStatus.Cancelled]: 'destructive',
}
function formatDate(value: string | null): string {
if (!value) return '—'
return new Date(value).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
}
function formatAmount(value: number): string {
return value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
export default function ReceivedChequesPage() {
const [cheques, setCheques] = useState<ReceivedChequeResponse[] | null>(null)
const [error, setError] = useState<string | null>(null)
const [status, setStatus] = useState<StatusFilter>('All')
const [selected, setSelected] = useState<ReceivedChequeResponse | null>(null)
function load() {
receivedChequesApi
.list({ status: status === 'All' ? undefined : status, pageSize: 100 })
.then(res => setCheques(res.items))
.catch(err => setError(getApiError(err)))
}
useEffect(load, [status])
function handleUpdated(updated: ReceivedChequeResponse) {
setCheques(prev => prev ? prev.map(c => c.receivedChequeId === updated.receivedChequeId ? updated : c) : prev)
setSelected(updated)
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold text-foreground">Received Cheques</h1>
<p className="text-sm text-muted-foreground mt-0.5">Cheques received from customers, suppliers, or others.</p>
</div>
<Link href="/payments/received-cheques/new" className={cn(buttonVariants())}>
<Plus className="size-4" />
New Received Cheque
</Link>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<Select<StatusFilter> value={status} onValueChange={(v) => setStatus(v ?? 'All')}>
<SelectTrigger className="w-full sm:w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="All" label="All statuses">All statuses</SelectItem>
{Object.values(ReceivedChequeStatus).map(s => (
<SelectItem key={s} value={s} label={s}>{s}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{error && (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">{error}</div>
)}
{!error && cheques === null && (
<div className="flex flex-col gap-2">
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-12 w-full" />)}
</div>
)}
{!error && cheques !== null && cheques.length === 0 && (
<div className="flex flex-col items-center gap-3 rounded-xl border border-dashed py-16 text-center">
<Inbox className="size-10 text-muted-foreground" />
<p className="text-sm text-muted-foreground">No received cheques yet.</p>
</div>
)}
{!error && cheques !== null && cheques.length > 0 && (
<Table>
<TableHeader>
<TableRow>
<TableHead>Cheque no.</TableHead>
<TableHead>Received from</TableHead>
<TableHead>Type</TableHead>
<TableHead className="text-right">Amount</TableHead>
<TableHead>Received</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{cheques.map(c => (
<TableRow key={c.receivedChequeId} className="cursor-pointer" onClick={() => setSelected(c)}>
<TableCell className="font-medium">{c.chequeNo}</TableCell>
<TableCell>{c.receivedFromName}</TableCell>
<TableCell>{c.receivedFromType}</TableCell>
<TableCell className="text-right tabular-nums">{formatAmount(c.amount)}</TableCell>
<TableCell>{formatDate(c.receivedDate)}</TableCell>
<TableCell>
<Badge variant={STATUS_BADGE_VARIANT[c.status]}>{c.status}</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
<ReceivedChequeDialog cheque={selected} onClose={() => setSelected(null)} onUpdated={handleUpdated} />
</div>
)
}
function ReceivedChequeDialog({
cheque,
onClose,
onUpdated,
}: {
cheque: ReceivedChequeResponse | null
onClose: () => void
onUpdated: (c: ReceivedChequeResponse) => void
}) {
const open = cheque !== null
const [mode, setMode] = useState<'view' | 'action'>('view')
const [action, setAction] = useState<ReceivedChequeStatusAction>(ReceivedChequeStatusAction.Deposit)
const [depositBankAccountId, setDepositBankAccountId] = useState('')
const [depositDate, setDepositDate] = useState('')
const [notes, setNotes] = useState('')
const [submitting, setSubmitting] = useState(false)
const [bankOptions, setBankOptions] = useState<BankAccountResponse[]>([])
useEffect(() => {
if (!cheque) return
setMode('view')
setAction(ReceivedChequeStatusAction.Deposit)
setDepositBankAccountId('')
setDepositDate('')
setNotes('')
}, [cheque])
useEffect(() => {
if (mode !== 'action' || action !== ReceivedChequeStatusAction.Deposit || bankOptions.length > 0) return
bankAccountsApi.list(BankAccountType.Bank).then(setBankOptions).catch(() => setBankOptions([]))
}, [mode, action, bankOptions.length])
if (!cheque) return null
const availableActions: ReceivedChequeStatusAction[] =
cheque.status === ReceivedChequeStatus.Received
? [ReceivedChequeStatusAction.Deposit, ReceivedChequeStatusAction.Cancel]
: cheque.status === ReceivedChequeStatus.Deposited
? [ReceivedChequeStatusAction.Clear, ReceivedChequeStatusAction.Return]
: []
async function handleSubmit() {
if (!cheque) return
if (action === ReceivedChequeStatusAction.Deposit && (!depositBankAccountId || !depositDate)) {
toast.add({ title: 'Deposit bank account and date are required', type: 'error' })
return
}
setSubmitting(true)
try {
const updated = await receivedChequesApi.updateStatus(cheque.receivedChequeId, {
action,
depositBankAccountId: action === ReceivedChequeStatusAction.Deposit ? depositBankAccountId : undefined,
depositDate: action === ReceivedChequeStatusAction.Deposit ? depositDate : undefined,
notes: notes || undefined,
})
toast.add({ title: `Cheque ${action.toLowerCase()}ed`, type: 'success' })
onUpdated(updated)
setMode('view')
} catch (err) {
toast.add({ title: 'Could not update status', description: getApiError(err), type: 'error' })
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose() }}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Cheque {cheque.chequeNo}</DialogTitle>
<DialogDescription>
Status: <Badge variant={STATUS_BADGE_VARIANT[cheque.status]}>{cheque.status}</Badge>
</DialogDescription>
</DialogHeader>
{mode === 'view' && (
<div className="flex flex-col gap-3 text-sm">
<div className="grid grid-cols-2 gap-3">
<div><p className="text-xs text-muted-foreground">Received from</p><p>{cheque.receivedFromName}</p></div>
<div><p className="text-xs text-muted-foreground">Type</p><p>{cheque.receivedFromType}</p></div>
<div><p className="text-xs text-muted-foreground">Cheque date</p><p>{formatDate(cheque.chequeDate)}</p></div>
<div><p className="text-xs text-muted-foreground">Amount</p><p className="tabular-nums">{formatAmount(cheque.amount)}</p></div>
<div><p className="text-xs text-muted-foreground">Drawer bank</p><p>{cheque.drawerBankName ?? '—'}</p></div>
<div><p className="text-xs text-muted-foreground">Deposit date</p><p>{formatDate(cheque.depositDate)}</p></div>
{cheque.notes && (
<div className="col-span-2"><p className="text-xs text-muted-foreground">Notes</p><p>{cheque.notes}</p></div>
)}
</div>
{availableActions.length > 0 && (
<div className="pt-2">
<Button onClick={() => { setAction(availableActions[0]); setMode('action') }}>Change status</Button>
</div>
)}
</div>
)}
{mode === 'action' && (
<div className="flex flex-col gap-4">
<Field>
<FieldLabel htmlFor="rc-action">Action</FieldLabel>
<Select<ReceivedChequeStatusAction> value={action} onValueChange={(v) => setAction(v ?? availableActions[0])}>
<SelectTrigger id="rc-action" className="w-full"><SelectValue /></SelectTrigger>
<SelectContent>
{availableActions.map(a => (
<SelectItem key={a} value={a} label={a}>{a}</SelectItem>
))}
</SelectContent>
</Select>
</Field>
{action === ReceivedChequeStatusAction.Deposit && (
<>
<Field>
<FieldLabel htmlFor="rc-deposit-bank">Deposit bank account</FieldLabel>
<Select<string> value={depositBankAccountId} onValueChange={(v) => setDepositBankAccountId(v ?? '')}>
<SelectTrigger id="rc-deposit-bank" className="w-full">
<SelectValue placeholder="Select a bank account" />
</SelectTrigger>
<SelectContent>
{bankOptions.map(b => (
<SelectItem key={b.accountId} value={b.accountId} label={b.accountName}>{b.accountName}</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="rc-deposit-date">Deposit date</FieldLabel>
<Input id="rc-deposit-date" type="date" value={depositDate} onChange={(e) => setDepositDate(e.target.value)} />
</Field>
</>
)}
<Field>
<FieldLabel htmlFor="rc-notes">Notes (optional)</FieldLabel>
<Input id="rc-notes" value={notes} onChange={(e) => setNotes(e.target.value)} />
</Field>
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" onClick={() => setMode('view')} disabled={submitting}>Back</Button>
<Button onClick={handleSubmit} disabled={submitting}>{submitting ? 'Saving…' : 'Confirm'}</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
)
}
+9
View File
@@ -127,6 +127,15 @@ export const NAV_GROUPS: NavGroup[] = [
{ title: 'Suppliers', href: '/suppliers', icon: UserGroupIcon, description: 'Supplier accounts used for raising purchase orders', permission: 'suppliers.view' },
],
},
{
title: 'Accounts',
icon: BankIcon,
items: [
{ title: 'Bank Accounts', href: '/payments/bank-accounts', icon: BankIcon, description: 'Cash and bank accounts used for payments and deposits' },
{ title: 'Cheque Books', href: '/payments/cheque-books', icon: ClipboardListIcon, description: 'Cheque books issued from the company’s own supply' },
{ title: 'Received Cheques', href: '/payments/received-cheques', icon: DollarSquareIcon, description: 'Cheques received from customers, suppliers, or others' },
],
},
{
title: 'Returns & Claims',
icon: ClipboardListIcon,
+34
View File
@@ -0,0 +1,34 @@
import { api } from '@/lib/api'
import type {
BankAccountResponse,
BankAccountType,
CashAccountTypeDto,
CreateBankAccountRequest,
CreateCashAccountRequest,
} from '@/types/payments'
export const bankAccounts = {
list: (accountType?: BankAccountType) =>
api
.get<BankAccountResponse[]>('/bank-accounts', {
params: accountType ? { accountType } : undefined,
})
.then(r => r.data),
createBank: (body: CreateBankAccountRequest) =>
api.post<BankAccountResponse>('/bank-accounts', body).then(r => r.data),
createCash: (body: CreateCashAccountRequest) =>
api.post<BankAccountResponse>('/cash-accounts', body).then(r => r.data),
updateCash: (id: string, accountName: string) =>
api.put<BankAccountResponse>(`/cash-accounts/${id}`, { accountName }).then(r => r.data),
setCashStatus: (id: string, isActive: boolean) =>
api.patch<BankAccountResponse>(`/cash-accounts/${id}/status`, { isActive }).then(r => r.data),
deleteCash: (id: string) => api.delete(`/cash-accounts/${id}`),
cashAccountTypes: () =>
api.get<CashAccountTypeDto[]>('/cash-account-types').then(r => r.data),
}
+51
View File
@@ -0,0 +1,51 @@
import { api } from '@/lib/api'
import type {
ChequeBookResponse,
ChequeBookStatus,
ChequePageResponse,
CreateChequeBookRequest,
IssueChequePageRequest,
PagedResult,
UpdateChequePageStatusRequest,
} from '@/types/payments'
export const chequeBooks = {
list: (params?: {
bankAccountId?: string
branchId?: number
status?: ChequeBookStatus
page?: number
pageSize?: number
}) =>
api
.get<PagedResult<ChequeBookResponse>>('/cheque-books', { params })
.then(r => r.data),
get: (chequeBookNo: string, expandPages = false) =>
api
.get<ChequeBookResponse>(`/cheque-books/${encodeURIComponent(chequeBookNo)}`, {
params: expandPages ? { expand: 'pages' } : undefined,
})
.then(r => r.data),
create: (body: CreateChequeBookRequest) =>
api.post<ChequeBookResponse>('/cheque-books', body).then(r => r.data),
}
export const chequePages = {
issue: (chequeNo: string, body: IssueChequePageRequest) =>
api
.put<ChequePageResponse>(
`/cheque-pages/${encodeURIComponent(chequeNo)}/issue`,
body,
)
.then(r => r.data),
updateStatus: (chequeNo: string, body: UpdateChequePageStatusRequest) =>
api
.put<ChequePageResponse>(
`/cheque-pages/${encodeURIComponent(chequeNo)}/status`,
body,
)
.then(r => r.data),
}
+3
View File
@@ -12,6 +12,9 @@ export { productReports } from './reports'
export { shopPos, invoices, payments } from './sales'
export { saleReturns, claims, supplierClaims } from './returns-claims'
export { sysConfig, ITEM_TRACKING_KEYS, GRN_TAX_KEYS, grnTaxSettings, DEFAULT_GRN_TAX_SETTINGS } from './sys-config'
export { bankAccounts } from './bank-accounts'
export { chequeBooks, chequePages } from './cheques'
export { receivedCheques } from './received-cheques'
export { getPaginatedItems } from './paginated'
export type { PageParams } from './master'
@@ -0,0 +1,29 @@
import { api } from '@/lib/api'
import type {
CreateReceivedChequeRequest,
PagedResult,
ReceivedChequeResponse,
ReceivedChequeStatus,
ReceivedFromType,
UpdateReceivedChequeStatusRequest,
} from '@/types/payments'
export const receivedCheques = {
list: (params?: {
companyId?: number
branchId?: number
status?: ReceivedChequeStatus
receivedFromType?: ReceivedFromType
page?: number
pageSize?: number
}) =>
api
.get<PagedResult<ReceivedChequeResponse>>('/received-cheques', { params })
.then(r => r.data),
create: (body: CreateReceivedChequeRequest) =>
api.post<ReceivedChequeResponse>('/received-cheques', body).then(r => r.data),
updateStatus: (id: string, body: UpdateReceivedChequeStatusRequest) =>
api.put<ReceivedChequeResponse>(`/received-cheques/${id}/status`, body).then(r => r.data),
}
+223
View File
@@ -0,0 +1,223 @@
// Mirrors Distribution backend DTOs in Features/{BankAccount,ChequeBook,ReceivedCheque}.
// Entities use Guid Ids on the backend — represented as strings here.
export enum BankAccountType {
Cash = 'Cash',
Bank = 'Bank',
}
export interface BankAccountResponse {
accountType: BankAccountType
accountId: string
accountName: string
bankName: string | null
cashAccountTypeName: string | null
accountNumber: string | null
currencyCode: string
createdAt: string
isActive: boolean | null
isDefault: boolean | null
}
export interface CreateBankAccountRequest {
accountName: string
bankName?: string | null
accountNumber?: string | null
currencyCode?: string
}
export interface CreateCashAccountRequest {
accountName: string
cashAccountTypeName: string
accountNumber?: string | null
currencyCode?: string
}
export interface CashAccountTypeDto {
name: string
}
// Cheque Books / Pages
export enum ChequeBookStatus {
Active = 'Active',
Completed = 'Completed',
Cancelled = 'Cancelled',
}
export enum ChequePageIssueStatus {
Unused = 'Unused',
Issued = 'Issued',
Cleared = 'Cleared',
Bounced = 'Bounced',
Cancelled = 'Cancelled',
Void = 'Void',
}
export enum ChequePageStatusAction {
Clear = 'Clear',
Bounce = 'Bounce',
Cancel = 'Cancel',
Void = 'Void',
}
export enum PayeeType {
Supplier = 'Supplier',
Customer = 'Customer',
Employee = 'Employee',
Other = 'Other',
}
export interface ChequePageResponse {
chequeNo: string
chequeBookNo: string
issueStatus: ChequePageIssueStatus
payeeType: PayeeType | null
payeeId: number | null
payeeName: string | null
issueDate: string | null
amount: number | null
currencyCode: string | null
voucherId: number | null
referenceNo: string | null
purpose: string | null
isCrossCheque: boolean | null
isAccountPayee: boolean | null
isPostDated: boolean | null
notes: string | null
printedBy: string | null
printedAt: string | null
clearedDate: string | null
clearedByBank: boolean | null
cancelReason: string | null
}
export interface ChequeBookResponse {
chequeBookNo: string
branchId: number
bankAccountId: string
startChequeNo: string
endChequeNo: string
totalLeaves: number
receivedDate: string
description: string | null
createdBy: string | null
status: ChequeBookStatus
pages: ChequePageResponse[]
}
export interface PagedResult<T> {
items: T[]
totalCount: number
page: number | null
pageSize: number | null
}
export interface CreateChequeBookRequest {
branchId: number
bankAccountId: string
chequeBookNo: string
startChequeNo: string
endChequeNo: string
totalLeaves: number
receivedDate: string
description?: string
createdBy?: string
}
export interface IssueChequePageRequest {
payeeType: PayeeType
payeeId?: number
payeeName: string
issueDate: string
amount: number
currencyCode?: string
voucherId?: number
referenceNo?: string
purpose?: string
isCrossCheque?: boolean
isAccountPayee?: boolean
isPostDated?: boolean
notes?: string
printedBy?: string
}
export interface UpdateChequePageStatusRequest {
action: ChequePageStatusAction
clearedDate?: string
cancelReason?: string
performedBy?: string
}
// Received Cheques
export enum ReceivedChequeStatus {
Received = 'Received',
Deposited = 'Deposited',
Cleared = 'Cleared',
Returned = 'Returned',
Cancelled = 'Cancelled',
}
export enum ReceivedChequeStatusAction {
Deposit = 'Deposit',
Clear = 'Clear',
Return = 'Return',
Cancel = 'Cancel',
}
export enum ReceivedFromType {
Customer = 'Customer',
Supplier = 'Supplier',
Other = 'Other',
}
export interface ReceivedChequeResponse {
receivedChequeId: string
companyId: number
branchId: number | null
receivedFromType: ReceivedFromType
receivedFromId: number | null
receivedFromName: string
drawerBankName: string | null
drawerBankBranch: string | null
accountHolderName: string | null
chequeNo: string
chequeDate: string
amount: number
receivedDate: string
referenceType: string | null
referenceId: number | null
notes: string | null
createdBy: string | null
status: ReceivedChequeStatus
depositBankAccountId: string | null
depositDate: string | null
}
export interface CreateReceivedChequeRequest {
companyId: number
branchId?: number
receivedFromType: ReceivedFromType
receivedFromId?: number
receivedFromName: string
drawerBankName?: string
drawerBankBranch?: string
accountHolderName?: string
chequeNo: string
chequeDate: string
amount: number
receivedDate: string
referenceType?: string
referenceId?: number
notes?: string
createdBy?: string
}
export interface UpdateReceivedChequeStatusRequest {
action: ReceivedChequeStatusAction
depositBankAccountId?: string
depositDate?: string
notes?: string
performedBy?: string
}