diff --git a/Distribution/Data/AppDBContext.cs b/Distribution/Data/AppDBContext.cs index c756b6c..c951478 100644 --- a/Distribution/Data/AppDBContext.cs +++ b/Distribution/Data/AppDBContext.cs @@ -76,6 +76,11 @@ public class AppDBContext(DbContextOptions options) : DbContext(op public DbSet SupplierPaymentGRNs { get; set; } public DbSet Notifications { get; set; } public DbSet NotificationRecipients { get; set; } + public DbSet ExpenseCategories { get; set; } + public DbSet ExpenseVendors { get; set; } + public DbSet Expenses { get; set; } + public DbSet ExpenseAttachments { get; set; } + public DbSet RecurringExpenseTemplates { get; set; } public DbSet StoreCreditMovements { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -813,7 +818,88 @@ public class AppDBContext(DbContextOptions options) : DbContext(op modelBuilder.Entity() .HasIndex(m => new { m.FK_StoreId, m.OccurredAt }); + // --- Expense --- + + modelBuilder.Entity() + .HasOne(c => c.Parent) + .WithMany() + .HasForeignKey(c => c.FK_ParentCategoryId) + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(false); + + modelBuilder.Entity() + .Property(c => c.Name).HasMaxLength(100); + modelBuilder.Entity() + .Property(c => c.Code).HasMaxLength(50); + modelBuilder.Entity() + .HasIndex(c => new { c.FK_ParentCategoryId, c.Name }).IsUnique(); + + modelBuilder.Entity() + .Property(v => v.Name).HasMaxLength(200); + modelBuilder.Entity() + .HasIndex(v => v.Name).IsUnique(); + + modelBuilder.Entity() + .HasOne(e => e.Category).WithMany() + .HasForeignKey(e => e.FK_CategoryId) + .OnDelete(DeleteBehavior.Restrict); + modelBuilder.Entity() + .HasOne(e => e.Warehouse).WithMany() + .HasForeignKey(e => e.FK_WarehouseId) + .OnDelete(DeleteBehavior.Restrict).IsRequired(false); + modelBuilder.Entity() + .HasOne(e => e.Vendor).WithMany() + .HasForeignKey(e => e.FK_VendorId) + .OnDelete(DeleteBehavior.Restrict).IsRequired(false); + modelBuilder.Entity() + .HasOne(e => e.BankAccount).WithMany() + .HasForeignKey(e => e.FK_BankAccountId) + .OnDelete(DeleteBehavior.Restrict).IsRequired(false); + modelBuilder.Entity() + .HasOne(e => e.ChequePage).WithMany() + .HasForeignKey(e => e.FK_ChequePageId) + .OnDelete(DeleteBehavior.Restrict).IsRequired(false); + modelBuilder.Entity() + .HasOne(e => e.RecurringTemplate).WithMany() + .HasForeignKey(e => e.FK_RecurringTemplateId) + .OnDelete(DeleteBehavior.SetNull).IsRequired(false); + modelBuilder.Entity() + .Property(e => e.Payee).HasMaxLength(200); + modelBuilder.Entity() + .Property(e => e.BillRef).HasMaxLength(100); + modelBuilder.Entity() + .HasIndex(e => new { e.Status, e.BillDate }); + + modelBuilder.Entity() + .HasOne(a => a.Expense).WithMany(e => e.Attachments) + .HasForeignKey(a => a.FK_ExpenseId) + .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .Property(a => a.OriginalFileName).HasMaxLength(260); + modelBuilder.Entity() + .Property(a => a.StoredPath).HasMaxLength(500); + modelBuilder.Entity() + .Property(a => a.ContentType).HasMaxLength(100); + + modelBuilder.Entity() + .HasOne(t => t.Category).WithMany() + .HasForeignKey(t => t.FK_CategoryId) + .OnDelete(DeleteBehavior.Restrict); + modelBuilder.Entity() + .HasOne(t => t.Warehouse).WithMany() + .HasForeignKey(t => t.FK_WarehouseId) + .OnDelete(DeleteBehavior.Restrict).IsRequired(false); + modelBuilder.Entity() + .HasOne(t => t.Vendor).WithMany() + .HasForeignKey(t => t.FK_VendorId) + .OnDelete(DeleteBehavior.Restrict).IsRequired(false); + modelBuilder.Entity() + .Property(t => t.Name).HasMaxLength(200); + modelBuilder.Entity() + .HasIndex(t => new { t.IsPaused, t.NextRunDate }); + Seed.RoleAndPermissionSeed.Apply(modelBuilder); + Seed.ExpenseSeed.Apply(modelBuilder); Seed.MasterDataSeed.Apply(modelBuilder); Seed.OperationsDataSeed.Apply(modelBuilder); Seed.ProcurementSeed.Apply(modelBuilder); diff --git a/Distribution/Data/Seed/ExpenseSeed.cs b/Distribution/Data/Seed/ExpenseSeed.cs new file mode 100644 index 0000000..7aeac95 --- /dev/null +++ b/Distribution/Data/Seed/ExpenseSeed.cs @@ -0,0 +1,61 @@ +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Data.Seed; + +public static class ExpenseSeed +{ + private static readonly DateTime SeedDate = new(2026, 10, 21); + + // Top-level categories + private static readonly Guid CatSalaries = new("30000000-0000-0000-0000-000000000001"); + private static readonly Guid CatUtilities = new("30000000-0000-0000-0000-000000000002"); + private static readonly Guid CatRent = new("30000000-0000-0000-0000-000000000003"); + private static readonly Guid CatVehicle = new("30000000-0000-0000-0000-000000000004"); + private static readonly Guid CatOffice = new("30000000-0000-0000-0000-000000000005"); + private static readonly Guid CatBankFees = new("30000000-0000-0000-0000-000000000006"); + private static readonly Guid CatMisc = new("30000000-0000-0000-0000-000000000007"); + + public static void Apply(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasData( + // Top-level + new ExpenseCategory { Id = CatSalaries, Name = "Salaries", Code = "SAL", CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = CatUtilities, Name = "Utilities", Code = "UTL", CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = CatRent, Name = "Rent", Code = "RNT", CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = CatVehicle, Name = "Vehicle", Code = "VEH", CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = CatOffice, Name = "Office", Code = "OFF", CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = CatBankFees, Name = "Bank Fees", Code = "BNK", CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = CatMisc, Name = "Miscellaneous", Code = "MSC", CreatedAt = SeedDate, IsActive = true }, + + // Titles under Salaries + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000101"), Name = "Basic Salary", FK_ParentCategoryId = CatSalaries, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000102"), Name = "Bonus", FK_ParentCategoryId = CatSalaries, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000103"), Name = "Overtime", FK_ParentCategoryId = CatSalaries, CreatedAt = SeedDate, IsActive = true }, + + // Titles under Utilities + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000201"), Name = "Electricity", FK_ParentCategoryId = CatUtilities, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000202"), Name = "Water", FK_ParentCategoryId = CatUtilities, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000203"), Name = "Internet", FK_ParentCategoryId = CatUtilities, CreatedAt = SeedDate, IsActive = true }, + + // Titles under Rent + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000301"), Name = "Warehouse Rent", FK_ParentCategoryId = CatRent, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000302"), Name = "Office Rent", FK_ParentCategoryId = CatRent, CreatedAt = SeedDate, IsActive = true }, + + // Titles under Vehicle + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000401"), Name = "Fuel", FK_ParentCategoryId = CatVehicle, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000402"), Name = "Maintenance", FK_ParentCategoryId = CatVehicle, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000403"), Name = "Insurance", FK_ParentCategoryId = CatVehicle, CreatedAt = SeedDate, IsActive = true }, + + // Titles under Office + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000501"), Name = "Stationery", FK_ParentCategoryId = CatOffice, CreatedAt = SeedDate, IsActive = true }, + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000502"), Name = "Cleaning", FK_ParentCategoryId = CatOffice, CreatedAt = SeedDate, IsActive = true }, + + // Titles under Bank Fees + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000601"), Name = "Service Charges", FK_ParentCategoryId = CatBankFees, CreatedAt = SeedDate, IsActive = true }, + + // Titles under Misc + new ExpenseCategory { Id = new("30000000-0000-0000-0000-000000000701"), Name = "Other", FK_ParentCategoryId = CatMisc, CreatedAt = SeedDate, IsActive = true } + ); + } +} diff --git a/Distribution/Data/Seed/RoleAndPermissionSeed.cs b/Distribution/Data/Seed/RoleAndPermissionSeed.cs index 1f72010..9fda74b 100644 --- a/Distribution/Data/Seed/RoleAndPermissionSeed.cs +++ b/Distribution/Data/Seed/RoleAndPermissionSeed.cs @@ -68,6 +68,13 @@ public static class RoleAndPermissionSeed private static readonly Guid PermSupplierClaimManage= new("20000000-0000-0000-0000-000000000048"); private static readonly Guid PermSysConfigView = new("20000000-0000-0000-0000-000000000049"); private static readonly Guid PermSysConfigManage = new("20000000-0000-0000-0000-000000000050"); + private static readonly Guid PermExpenseView = new("20000000-0000-0000-0000-000000000051"); + private static readonly Guid PermExpenseCreate = new("20000000-0000-0000-0000-000000000052"); + private static readonly Guid PermExpensePost = new("20000000-0000-0000-0000-000000000053"); + private static readonly Guid PermExpensePay = new("20000000-0000-0000-0000-000000000054"); + private static readonly Guid PermExpenseVoid = new("20000000-0000-0000-0000-000000000055"); + private static readonly Guid PermExpenseManageCats = new("20000000-0000-0000-0000-000000000056"); + private static readonly Guid PermExpenseManageRec = new("20000000-0000-0000-0000-000000000057"); public static void Apply(ModelBuilder modelBuilder) { @@ -134,7 +141,14 @@ public static class RoleAndPermissionSeed new Permission { Id = PermSupplierClaimView, Code = "supplier-claim.view", Description = "View supplier claims", CreatedAt = SeedDate, IsActive = true }, new Permission { Id = PermSupplierClaimManage,Code = "supplier-claim.manage", Description = "Create and resolve supplier claims", CreatedAt = SeedDate, IsActive = true }, new Permission { Id = PermSysConfigView, Code = "sys-config.view", Description = "View system configuration settings", CreatedAt = SeedDate, IsActive = true }, - new Permission { Id = PermSysConfigManage, Code = "sys-config.manage", Description = "Update system configuration settings", CreatedAt = SeedDate, IsActive = true } + new Permission { Id = PermSysConfigManage, Code = "sys-config.manage", Description = "Update system configuration settings", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpenseView, Code = "expense.view", Description = "View expenses, categories, vendors and recurring templates", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpenseCreate, Code = "expense.create", Description = "Create and edit draft expenses", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpensePost, Code = "expense.post", Description = "Post a draft expense (approve it)", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpensePay, Code = "expense.pay", Description = "Pay a posted expense (debit a bank account or issue a cheque)", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpenseVoid, Code = "expense.void", Description = "Void an expense", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpenseManageCats, Code = "expense.manage_categories", Description = "Manage expense categories, titles and vendors", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermExpenseManageRec, Code = "expense.manage_recurring", Description = "Manage recurring expense templates", CreatedAt = SeedDate, IsActive = true } ); // SuperAdmin gets every permission @@ -191,7 +205,14 @@ public static class RoleAndPermissionSeed new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSupplierClaimView, CreatedAt = SeedDate, IsActive = true }, new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSupplierClaimManage,CreatedAt = SeedDate, IsActive = true }, new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigView, CreatedAt = SeedDate, IsActive = true }, - new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigManage, CreatedAt = SeedDate, IsActive = true } + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigManage, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseView, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseCreate, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpensePost, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpensePay, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseVoid, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseManageCats, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermExpenseManageRec, CreatedAt = SeedDate, IsActive = true } ); } } diff --git a/Distribution/Features/Expense/ExpenseAttachmentController.cs b/Distribution/Features/Expense/ExpenseAttachmentController.cs new file mode 100644 index 0000000..c21d186 --- /dev/null +++ b/Distribution/Features/Expense/ExpenseAttachmentController.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.Expense; + +[ApiController] +public class ExpenseAttachmentController(ExpenseAttachmentService service) : ControllerBase +{ + [HttpPost("api/expenses/{expenseId:guid}/attachments")] + [Authorize("expense.create")] + [RequestSizeLimit(10 * 1024 * 1024)] + public async Task Upload(Guid expenseId, IFormFile file, CancellationToken ct) + { + try + { + var result = await service.UploadAsync(expenseId, file, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpGet("api/expense-attachments/{id:guid}/download")] + [Authorize("expense.view")] + public async Task Download(Guid id, CancellationToken ct) + { + var result = await service.DownloadAsync(id, ct); + if (result is null) return NotFound(); + return File(result.Value.Stream, result.Value.ContentType, result.Value.FileName); + } + + [HttpDelete("api/expense-attachments/{id:guid}")] + [Authorize("expense.create")] + public async Task Delete(Guid id, CancellationToken ct) => + await service.DeleteAsync(id, ct) ? NoContent() : NotFound(); +} diff --git a/Distribution/Features/Expense/ExpenseAttachmentService.cs b/Distribution/Features/Expense/ExpenseAttachmentService.cs new file mode 100644 index 0000000..2b7f16a --- /dev/null +++ b/Distribution/Features/Expense/ExpenseAttachmentService.cs @@ -0,0 +1,108 @@ +using Distribution.Data; +using Distribution.Infra.Authorization; +using Distribution.Infra.UoW; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.Expense; + +public class ExpenseAttachmentService( + AppDBContext context, + IConfiguration config, + IUnitOfWork uow, + ICurrentUserService currentUser) +{ + private const long MaxBytes = 10 * 1024 * 1024; + private static readonly string[] AllowedExtensions = [".pdf", ".jpg", ".jpeg", ".png", ".webp"]; + private static readonly string[] AllowedContentTypes = + ["application/pdf", "image/jpeg", "image/jpg", "image/png", "image/webp"]; + + private string AttachmentsRoot => + config["Expense:AttachmentsRoot"] + ?? throw new InvalidOperationException("Expense:AttachmentsRoot is not configured"); + + public async Task UploadAsync(Guid expenseId, IFormFile file, CancellationToken ct) + { + var expense = await context.Expenses.FirstOrDefaultAsync(e => e.Id == expenseId && e.IsActive, ct); + if (expense is null) return null; + + if (file is null || file.Length == 0) + throw new InvalidOperationException("File is empty"); + if (file.Length > MaxBytes) + throw new InvalidOperationException($"File exceeds the {MaxBytes / (1024 * 1024)}MB limit"); + + var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (!AllowedExtensions.Contains(ext)) + throw new InvalidOperationException($"File type '{ext}' is not allowed"); + if (!AllowedContentTypes.Contains(file.ContentType.ToLowerInvariant())) + throw new InvalidOperationException($"Content type '{file.ContentType}' is not allowed"); + + var userId = currentUser.UserId + ?? throw new InvalidOperationException("Authenticated user is required"); + + var now = DateTime.Now; + var relativeDir = Path.Combine(now.Year.ToString("D4"), now.Month.ToString("D2")); + var absoluteDir = Path.Combine(AttachmentsRoot, relativeDir); + Directory.CreateDirectory(absoluteDir); + + var storedName = $"{Guid.NewGuid()}{ext}"; + var absolutePath = Path.Combine(absoluteDir, storedName); + + await using (var stream = File.Create(absolutePath)) + { + await file.CopyToAsync(stream, ct); + } + + var attachment = new Models.ExpenseAttachment + { + FK_ExpenseId = expenseId, + OriginalFileName = file.FileName, + StoredPath = absolutePath, + ContentType = file.ContentType, + SizeBytes = file.Length, + UploadedByUserId = userId + }; + + await uow.BeginAsync(ct); + try + { + await context.ExpenseAttachments.AddAsync(attachment, ct); + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + try { File.Delete(absolutePath); } catch { } + throw; + } + + return new ExpenseAttachmentResponse(attachment.Id, attachment.OriginalFileName, + attachment.ContentType, attachment.SizeBytes, attachment.CreatedAt); + } + + public async Task<(Stream Stream, string ContentType, string FileName)?> DownloadAsync(Guid attachmentId, CancellationToken ct) + { + var a = await context.ExpenseAttachments.FirstOrDefaultAsync(x => x.Id == attachmentId && x.IsActive, ct); + if (a is null) return null; + if (!File.Exists(a.StoredPath)) return null; + + var stream = File.OpenRead(a.StoredPath); + return (stream, a.ContentType, a.OriginalFileName); + } + + public async Task DeleteAsync(Guid attachmentId, CancellationToken ct) + { + var a = await context.ExpenseAttachments.FirstOrDefaultAsync(x => x.Id == attachmentId && x.IsActive, ct); + if (a is null) return false; + + a.IsActive = false; + + await uow.BeginAsync(ct); + try { context.ExpenseAttachments.Update(a); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + // Best-effort file removal — keep DB soft-delete even if the file is gone. + try { File.Delete(a.StoredPath); } catch { } + + return true; + } +} diff --git a/Distribution/Features/Expense/ExpenseController.cs b/Distribution/Features/Expense/ExpenseController.cs new file mode 100644 index 0000000..907d2ee --- /dev/null +++ b/Distribution/Features/Expense/ExpenseController.cs @@ -0,0 +1,109 @@ +using Distribution.Infra.Authorization; +using Distribution.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.Expense; + +[ApiController] +[Route("api/expenses")] +public class ExpenseController( + ExpenseService service, + IPermissionService permissions, + ICurrentUserService currentUser) : ControllerBase +{ + private async Task HasAsync(string code) + { + if (currentUser.RoleId is not { } roleId) return false; + return await permissions.HasPermissionAsync(roleId, code); + } + + [HttpGet] + [Authorize("expense.view")] + public async Task List( + [FromQuery] ExpenseStatus? status, + [FromQuery] Guid? categoryId, + [FromQuery] Guid? warehouseId, + [FromQuery] DateTime? from, + [FromQuery] DateTime? to, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20) => + Ok(await service.ListAsync(status, categoryId, warehouseId, from, to, page, pageSize)); + + [HttpGet("{id:guid}")] + [Authorize("expense.view")] + public async Task Get(Guid id) + { + var result = await service.GetAsync(id); + return result is null ? NotFound() : Ok(result); + } + + /// + /// Create endpoint takes a required ?action= query to drive the 4 form buttons: + /// draft → Save draft; post → Save & post (requires expense.post); pay → Save & pay + /// (requires expense.post + expense.pay). Draft is the default. + /// + [HttpPost] + [Authorize("expense.create")] + public async Task Create( + CreateExpenseRequest request, + [FromQuery] ExpenseAction action = ExpenseAction.Draft, + CancellationToken ct = default) + { + if (action is ExpenseAction.Post or ExpenseAction.Pay && !await HasAsync("expense.post")) + return Forbid(); + if (action == ExpenseAction.Pay && !await HasAsync("expense.pay")) + return Forbid(); + + try { return Ok(await service.CreateAsync(request, action, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPut("{id:guid}")] + [Authorize("expense.create")] + public async Task UpdateDraft(Guid id, UpdateExpenseDraftRequest request, CancellationToken ct) + { + try + { + var result = await service.UpdateDraftAsync(id, request, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("{id:guid}/post")] + [Authorize("expense.post")] + public async Task Post(Guid id, CancellationToken ct) + { + try + { + var result = await service.PostAsync(id, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("{id:guid}/pay")] + [Authorize("expense.pay")] + public async Task Pay(Guid id, ExpensePaymentRequest request, CancellationToken ct) + { + try + { + var result = await service.PayAsync(id, request, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("{id:guid}/void")] + [Authorize("expense.void")] + public async Task Void(Guid id, VoidExpenseRequest request, CancellationToken ct) + { + try + { + var result = await service.VoidAsync(id, request?.Reason, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } +} diff --git a/Distribution/Features/Expense/ExpenseDTOs.cs b/Distribution/Features/Expense/ExpenseDTOs.cs new file mode 100644 index 0000000..f3dea53 --- /dev/null +++ b/Distribution/Features/Expense/ExpenseDTOs.cs @@ -0,0 +1,77 @@ +using Distribution.Models; + +namespace Distribution.Features.Expense; + +public record ExpensePaymentRequest( + PaymentMethod Method, + Guid? BankAccountId, + Guid? ChequePageId); + +public record CreateExpenseRequest( + Guid CategoryId, + Guid? WarehouseId, + decimal Amount, + DateTime BillDate, + DateTime? DueDate, + DateTime? PeriodCovered, + Guid? VendorId, + string? Payee, + string? BillRef, + string? Description, + /// Required when action = Pay. Ignored for Draft / Post. + ExpensePaymentRequest? Payment); + +public record UpdateExpenseDraftRequest( + Guid CategoryId, + Guid? WarehouseId, + decimal Amount, + DateTime BillDate, + DateTime? DueDate, + DateTime? PeriodCovered, + Guid? VendorId, + string? Payee, + string? BillRef, + string? Description); + +public record VoidExpenseRequest(string? Reason); + +public record ExpenseAttachmentResponse( + Guid Id, + string OriginalFileName, + string ContentType, + long SizeBytes, + DateTime CreatedAt); + +public record ExpenseResponse( + Guid Id, + Guid CategoryId, + string CategoryName, + Guid? ParentCategoryId, + string? ParentCategoryName, + Guid? WarehouseId, + string? WarehouseName, + decimal Amount, + DateTime BillDate, + DateTime? DueDate, + DateTime? PeriodCovered, + Guid? VendorId, + string? VendorName, + string? Payee, + string? BillRef, + string? Description, + ExpenseStatus Status, + PaymentMethod? Method, + Guid? BankAccountId, + Guid? ChequePageId, + string? ChequeNo, + DateTime? PaidAt, + Guid RecordedByUserId, + Guid? PostedByUserId, + Guid? PaidByUserId, + Guid? VoidedByUserId, + string? VoidReason, + Guid? RecurringTemplateId, + List Attachments, + DateTime CreatedAt); + +public record PagedExpensesResponse(List Items, int Page, int PageSize, int Total); diff --git a/Distribution/Features/Expense/ExpenseRepository.cs b/Distribution/Features/Expense/ExpenseRepository.cs new file mode 100644 index 0000000..2422f6e --- /dev/null +++ b/Distribution/Features/Expense/ExpenseRepository.cs @@ -0,0 +1,50 @@ +using Distribution.Data; +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.Expense; + +public class ExpenseRepository(AppDBContext context) : IExpenseRepository +{ + private IQueryable Base() => + context.Expenses + .Include(e => e.Category!).ThenInclude(c => c.Parent) + .Include(e => e.Warehouse) + .Include(e => e.Vendor) + .Include(e => e.ChequePage) + .Include(e => e.Attachments.Where(a => a.IsActive)); + + public async Task GetByIdAsync(Guid id) => + await Base().FirstOrDefaultAsync(e => e.Id == id && e.IsActive); + + private IQueryable Filtered( + ExpenseStatus? status, Guid? categoryId, Guid? warehouseId, DateTime? from, DateTime? to) => + Base() + .Where(e => e.IsActive) + .Where(e => !status.HasValue || e.Status == status) + .Where(e => !categoryId.HasValue + || e.FK_CategoryId == categoryId + || e.Category!.FK_ParentCategoryId == categoryId) + .Where(e => !warehouseId.HasValue || e.FK_WarehouseId == warehouseId) + .Where(e => !from.HasValue || e.BillDate >= from) + .Where(e => !to.HasValue || e.BillDate <= to); + + public async Task> ListAsync( + ExpenseStatus? status, Guid? categoryId, Guid? warehouseId, DateTime? from, DateTime? to, + int page, int pageSize) => + await Filtered(status, categoryId, warehouseId, from, to) + .OrderByDescending(e => e.BillDate) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + public async Task CountAsync( + ExpenseStatus? status, Guid? categoryId, Guid? warehouseId, DateTime? from, DateTime? to) => + await Filtered(status, categoryId, warehouseId, from, to).CountAsync(); + + public async Task AddAsync(Models.Expense entity) => + await context.Expenses.AddAsync(entity); + + public void Update(Models.Expense entity) => + context.Expenses.Update(entity); +} diff --git a/Distribution/Features/Expense/ExpenseService.cs b/Distribution/Features/Expense/ExpenseService.cs new file mode 100644 index 0000000..e22feb4 --- /dev/null +++ b/Distribution/Features/Expense/ExpenseService.cs @@ -0,0 +1,269 @@ +using Distribution.Data; +using Distribution.Features.BankAccount; +using Distribution.Features.ChequeBook; +using Distribution.Infra.Authorization; +using Distribution.Infra.UoW; +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.Expense; + +public class ExpenseService( + AppDBContext context, + IExpenseRepository repo, + IBankAccountRepository bankAccountRepo, + IChequeBookRepository chequeBookRepo, + IUnitOfWork uow, + ICurrentUserService currentUser) +{ + private static ExpenseAttachmentResponse MapAttachment(Models.ExpenseAttachment a) => + new(a.Id, a.OriginalFileName, a.ContentType, a.SizeBytes, a.CreatedAt); + + private static ExpenseResponse ToResponse(Models.Expense e) => + new(e.Id, + e.FK_CategoryId, e.Category?.Name ?? string.Empty, + e.Category?.FK_ParentCategoryId, e.Category?.Parent?.Name, + e.FK_WarehouseId, e.Warehouse?.Name, + e.Amount, e.BillDate, e.DueDate, e.PeriodCovered, + e.FK_VendorId, e.Vendor?.Name, + e.Payee, e.BillRef, e.Description, + e.Status, e.Method, e.FK_BankAccountId, e.FK_ChequePageId, e.ChequePage?.ChequeNo, + e.PaidAt, + e.RecordedByUserId, e.PostedByUserId, e.PaidByUserId, e.VoidedByUserId, e.VoidReason, + e.FK_RecurringTemplateId, + e.Attachments.Where(a => a.IsActive).Select(MapAttachment).ToList(), + e.CreatedAt); + + public async Task ListAsync( + ExpenseStatus? status, Guid? categoryId, Guid? warehouseId, DateTime? from, DateTime? to, + int page, int pageSize) + { + if (page < 1) page = 1; + if (pageSize < 1 || pageSize > 200) pageSize = 20; + + var items = await repo.ListAsync(status, categoryId, warehouseId, from, to, page, pageSize); + var total = await repo.CountAsync(status, categoryId, warehouseId, from, to); + return new PagedExpensesResponse(items.Select(ToResponse).ToList(), page, pageSize, total); + } + + public async Task GetAsync(Guid id) + { + var e = await repo.GetByIdAsync(id); + return e is null ? null : ToResponse(e); + } + + public async Task CreateAsync(CreateExpenseRequest request, ExpenseAction action, CancellationToken ct = default) + { + ValidateBase(request); + + var category = await context.ExpenseCategories.Include(c => c.Parent) + .FirstOrDefaultAsync(c => c.Id == request.CategoryId && c.IsActive, ct) + ?? throw new InvalidOperationException("Category not found"); + if (category.FK_ParentCategoryId is null) + throw new InvalidOperationException("Pick a specific title under a category, not the category itself"); + + var userId = currentUser.UserId + ?? throw new InvalidOperationException("Authenticated user is required"); + + var entity = new Models.Expense + { + FK_CategoryId = request.CategoryId, + FK_WarehouseId = request.WarehouseId, + Amount = request.Amount, + BillDate = request.BillDate.Date, + DueDate = request.DueDate?.Date, + PeriodCovered = request.PeriodCovered?.Date, + FK_VendorId = request.VendorId, + Payee = request.Payee, + BillRef = request.BillRef, + Description = request.Description, + RecordedByUserId = userId, + Status = ExpenseStatus.Draft + }; + + await uow.BeginAsync(ct); + try + { + await repo.AddAsync(entity); + + if (action is ExpenseAction.Post or ExpenseAction.Pay) + { + entity.Status = ExpenseStatus.Posted; + entity.PostedByUserId = userId; + entity.PostedAt = DateTime.Now; + } + + if (action == ExpenseAction.Pay) + { + if (request.Payment is null) + throw new InvalidOperationException("Payment details are required when action = Pay"); + await ApplyPaymentAsync(entity, request.Payment, userId, ct); + } + + await uow.CommitAsync(ct); + } + catch { await uow.RollbackAsync(ct); throw; } + + return ToResponse((await repo.GetByIdAsync(entity.Id))!); + } + + public async Task UpdateDraftAsync(Guid id, UpdateExpenseDraftRequest request, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + if (entity.Status != ExpenseStatus.Draft) + throw new InvalidOperationException("Only Draft expenses can be edited"); + + if (request.Amount <= 0) throw new InvalidOperationException("Amount must be greater than zero"); + + entity.FK_CategoryId = request.CategoryId; + entity.FK_WarehouseId = request.WarehouseId; + entity.Amount = request.Amount; + entity.BillDate = request.BillDate.Date; + entity.DueDate = request.DueDate?.Date; + entity.PeriodCovered = request.PeriodCovered?.Date; + entity.FK_VendorId = request.VendorId; + entity.Payee = request.Payee; + entity.BillRef = request.BillRef; + entity.Description = request.Description; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return ToResponse((await repo.GetByIdAsync(id))!); + } + + public async Task PostAsync(Guid id, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + if (entity.Status != ExpenseStatus.Draft) + throw new InvalidOperationException("Only Draft expenses can be posted"); + + var userId = currentUser.UserId + ?? throw new InvalidOperationException("Authenticated user is required"); + + entity.Status = ExpenseStatus.Posted; + entity.PostedByUserId = userId; + entity.PostedAt = DateTime.Now; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return ToResponse((await repo.GetByIdAsync(id))!); + } + + public async Task PayAsync(Guid id, ExpensePaymentRequest payment, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + if (entity.Status != ExpenseStatus.Posted) + throw new InvalidOperationException("Only Posted expenses can be paid"); + + var userId = currentUser.UserId + ?? throw new InvalidOperationException("Authenticated user is required"); + + await uow.BeginAsync(ct); + try + { + await ApplyPaymentAsync(entity, payment, userId, ct); + await uow.CommitAsync(ct); + } + catch { await uow.RollbackAsync(ct); throw; } + + return ToResponse((await repo.GetByIdAsync(id))!); + } + + public async Task VoidAsync(Guid id, string? reason, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + if (entity.Status == ExpenseStatus.Void) + throw new InvalidOperationException("Already voided"); + if (entity.Status == ExpenseStatus.Paid) + throw new InvalidOperationException("Paid expenses cannot be voided — issue a reversing entry instead"); + + var userId = currentUser.UserId + ?? throw new InvalidOperationException("Authenticated user is required"); + + entity.Status = ExpenseStatus.Void; + entity.VoidedByUserId = userId; + entity.VoidedAt = DateTime.Now; + entity.VoidReason = reason; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return ToResponse((await repo.GetByIdAsync(id))!); + } + + // ─── helpers ────────────────────────────────────────────────────────────── + + private static void ValidateBase(CreateExpenseRequest request) + { + if (request.Amount <= 0) throw new InvalidOperationException("Amount must be greater than zero"); + if (request.DueDate.HasValue && request.DueDate.Value.Date < request.BillDate.Date) + throw new InvalidOperationException("Due date cannot be earlier than bill date"); + } + + /// + /// Settles payment: Cash → debit a BankAccount (incl. petty cash which is just a Cash account); + /// Cheque → consume an Unused ChequePage from an Active cheque book. + /// + private async Task ApplyPaymentAsync(Models.Expense entity, ExpensePaymentRequest payment, Guid userId, CancellationToken ct) + { + if (payment.Method == PaymentMethod.Credit) + throw new InvalidOperationException("Credit is not a valid method for expense payments"); + + if (payment.Method == PaymentMethod.Cash) + { + if (payment.BankAccountId is null) + throw new InvalidOperationException("BankAccountId is required when Method is Cash"); + + var account = await bankAccountRepo.GetByIdAsync(payment.BankAccountId.Value) + ?? throw new InvalidOperationException("BankAccountId does not reference a valid account"); + if (account.IsActive is false) + throw new InvalidOperationException("The selected account is inactive"); + + entity.FK_BankAccountId = account.Id; + } + else // Cheque + { + if (payment.ChequePageId is null) + throw new InvalidOperationException("ChequePageId is required when Method is Cheque"); + + var page = await context.ChequePages + .Include(p => p.ChequeBook) + .FirstOrDefaultAsync(p => p.Id == payment.ChequePageId.Value, ct) + ?? throw new InvalidOperationException("ChequePageId does not reference a valid cheque page"); + 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"); + + page.IssueStatus = ChequePageIssueStatus.Issued; + page.PayeeType = PayeeType.Other; + page.PayeeName = entity.Payee ?? entity.Vendor?.Name ?? "Expense"; + page.IssueDate = DateTime.Now; + page.Amount = entity.Amount; + page.CurrencyCode = "LKR"; + page.PrintedAt = DateTime.Now; + + chequeBookRepo.UpdatePage(page); + entity.FK_ChequePageId = page.Id; + entity.ChequePage = page; + } + + entity.Method = payment.Method; + entity.Status = ExpenseStatus.Paid; + entity.PaidByUserId = userId; + entity.PaidAt = DateTime.Now; + + // TODO ledger: when accounting is wired in, post a journal entry here — + // Debit Expense (category) + // Credit Cash/Bank (BankAccount) OR Cheques-outstanding (for Cheque payments) + } +} diff --git a/Distribution/Features/Expense/IExpenseRepository.cs b/Distribution/Features/Expense/IExpenseRepository.cs new file mode 100644 index 0000000..c42e8da --- /dev/null +++ b/Distribution/Features/Expense/IExpenseRepository.cs @@ -0,0 +1,24 @@ +using Distribution.Models; + +namespace Distribution.Features.Expense; + +public interface IExpenseRepository +{ + Task GetByIdAsync(Guid id); + Task> ListAsync( + ExpenseStatus? status, + Guid? categoryId, + Guid? warehouseId, + DateTime? from, + DateTime? to, + int page, + int pageSize); + Task CountAsync( + ExpenseStatus? status, + Guid? categoryId, + Guid? warehouseId, + DateTime? from, + DateTime? to); + Task AddAsync(Models.Expense entity); + void Update(Models.Expense entity); +} diff --git a/Distribution/Features/ExpenseCategory/ExpenseCategoryController.cs b/Distribution/Features/ExpenseCategory/ExpenseCategoryController.cs new file mode 100644 index 0000000..d6ab4ca --- /dev/null +++ b/Distribution/Features/ExpenseCategory/ExpenseCategoryController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.ExpenseCategory; + +[ApiController] +[Route("api/expense-categories")] +public class ExpenseCategoryController(ExpenseCategoryService service) : ControllerBase +{ + [HttpGet] + [Authorize("expense.view")] + public async Task Tree() => Ok(await service.GetTreeAsync()); + + [HttpPost] + [Authorize("expense.manage_categories")] + public async Task Create(CreateExpenseCategoryRequest request, CancellationToken ct) + { + try { return Ok(await service.CreateAsync(request, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPut("{id:guid}")] + [Authorize("expense.manage_categories")] + public async Task Update(Guid id, UpdateExpenseCategoryRequest request, CancellationToken ct) + { + var result = await service.UpdateAsync(id, request, ct); + return result is null ? NotFound() : Ok(result); + } +} diff --git a/Distribution/Features/ExpenseCategory/ExpenseCategoryDTOs.cs b/Distribution/Features/ExpenseCategory/ExpenseCategoryDTOs.cs new file mode 100644 index 0000000..8537c1d --- /dev/null +++ b/Distribution/Features/ExpenseCategory/ExpenseCategoryDTOs.cs @@ -0,0 +1,19 @@ +namespace Distribution.Features.ExpenseCategory; + +public record ExpenseCategoryResponse( + Guid Id, + string Name, + string? Code, + Guid? ParentCategoryId, + bool IsActive, + List Children); + +public record CreateExpenseCategoryRequest( + string Name, + string? Code, + Guid? ParentCategoryId); + +public record UpdateExpenseCategoryRequest( + string Name, + string? Code, + bool IsActive); diff --git a/Distribution/Features/ExpenseCategory/ExpenseCategoryRepository.cs b/Distribution/Features/ExpenseCategory/ExpenseCategoryRepository.cs new file mode 100644 index 0000000..21cb0a6 --- /dev/null +++ b/Distribution/Features/ExpenseCategory/ExpenseCategoryRepository.cs @@ -0,0 +1,19 @@ +using Distribution.Data; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.ExpenseCategory; + +public class ExpenseCategoryRepository(AppDBContext context) : IExpenseCategoryRepository +{ + public async Task GetByIdAsync(Guid id) => + await context.ExpenseCategories.FirstOrDefaultAsync(c => c.Id == id); + + public async Task> GetAllAsync() => + await context.ExpenseCategories.OrderBy(c => c.FK_ParentCategoryId).ThenBy(c => c.Name).ToListAsync(); + + public async Task AddAsync(Models.ExpenseCategory entity) => + await context.ExpenseCategories.AddAsync(entity); + + public void Update(Models.ExpenseCategory entity) => + context.ExpenseCategories.Update(entity); +} diff --git a/Distribution/Features/ExpenseCategory/ExpenseCategoryService.cs b/Distribution/Features/ExpenseCategory/ExpenseCategoryService.cs new file mode 100644 index 0000000..7cc9021 --- /dev/null +++ b/Distribution/Features/ExpenseCategory/ExpenseCategoryService.cs @@ -0,0 +1,64 @@ +using Distribution.Infra.UoW; + +namespace Distribution.Features.ExpenseCategory; + +public class ExpenseCategoryService(IExpenseCategoryRepository repo, IUnitOfWork uow) +{ + public async Task> GetTreeAsync() + { + var all = await repo.GetAllAsync(); + var byParent = all.ToLookup(c => c.FK_ParentCategoryId); + + return all + .Where(c => c.FK_ParentCategoryId is null) + .Select(c => Map(c, byParent)) + .ToList(); + } + + private static ExpenseCategoryResponse Map(Models.ExpenseCategory c, ILookup byParent) => + new(c.Id, c.Name, c.Code, c.FK_ParentCategoryId, c.IsActive, + byParent[c.Id].Select(child => Map(child, byParent)).ToList()); + + public async Task CreateAsync(CreateExpenseCategoryRequest request, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(request.Name)) + throw new InvalidOperationException("Name is required"); + + if (request.ParentCategoryId is { } parentId) + { + var parent = await repo.GetByIdAsync(parentId) + ?? throw new InvalidOperationException("Parent category not found"); + if (parent.FK_ParentCategoryId is not null) + throw new InvalidOperationException("Only two levels are allowed — cannot nest under a title"); + } + + var entity = new Models.ExpenseCategory + { + Name = request.Name.Trim(), + Code = string.IsNullOrWhiteSpace(request.Code) ? null : request.Code.Trim(), + FK_ParentCategoryId = request.ParentCategoryId + }; + + await uow.BeginAsync(ct); + try { await repo.AddAsync(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return new ExpenseCategoryResponse(entity.Id, entity.Name, entity.Code, entity.FK_ParentCategoryId, entity.IsActive, []); + } + + public async Task UpdateAsync(Guid id, UpdateExpenseCategoryRequest request, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + + entity.Name = request.Name.Trim(); + entity.Code = string.IsNullOrWhiteSpace(request.Code) ? null : request.Code.Trim(); + entity.IsActive = request.IsActive; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return new ExpenseCategoryResponse(entity.Id, entity.Name, entity.Code, entity.FK_ParentCategoryId, entity.IsActive, []); + } +} diff --git a/Distribution/Features/ExpenseCategory/IExpenseCategoryRepository.cs b/Distribution/Features/ExpenseCategory/IExpenseCategoryRepository.cs new file mode 100644 index 0000000..10fc0f7 --- /dev/null +++ b/Distribution/Features/ExpenseCategory/IExpenseCategoryRepository.cs @@ -0,0 +1,11 @@ +using Distribution.Models; + +namespace Distribution.Features.ExpenseCategory; + +public interface IExpenseCategoryRepository +{ + Task GetByIdAsync(Guid id); + Task> GetAllAsync(); + Task AddAsync(Models.ExpenseCategory entity); + void Update(Models.ExpenseCategory entity); +} diff --git a/Distribution/Features/ExpenseVendor/ExpenseVendorController.cs b/Distribution/Features/ExpenseVendor/ExpenseVendorController.cs new file mode 100644 index 0000000..78d8d73 --- /dev/null +++ b/Distribution/Features/ExpenseVendor/ExpenseVendorController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.ExpenseVendor; + +[ApiController] +[Route("api/expense-vendors")] +public class ExpenseVendorController(ExpenseVendorService service) : ControllerBase +{ + [HttpGet] + [Authorize("expense.view")] + public async Task List( + [FromQuery] string? search, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 50) => + Ok(await service.ListAsync(search, page, pageSize)); + + [HttpPost] + [Authorize("expense.manage_categories")] + public async Task Create(CreateExpenseVendorRequest request, CancellationToken ct) + { + try { return Ok(await service.CreateAsync(request, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPut("{id:guid}")] + [Authorize("expense.manage_categories")] + public async Task Update(Guid id, UpdateExpenseVendorRequest request, CancellationToken ct) + { + try + { + var result = await service.UpdateAsync(id, request, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } +} diff --git a/Distribution/Features/ExpenseVendor/ExpenseVendorDTOs.cs b/Distribution/Features/ExpenseVendor/ExpenseVendorDTOs.cs new file mode 100644 index 0000000..8ba95e1 --- /dev/null +++ b/Distribution/Features/ExpenseVendor/ExpenseVendorDTOs.cs @@ -0,0 +1,7 @@ +namespace Distribution.Features.ExpenseVendor; + +public record ExpenseVendorResponse(Guid Id, string Name, string? ContactInfo, bool IsActive); + +public record CreateExpenseVendorRequest(string Name, string? ContactInfo); + +public record UpdateExpenseVendorRequest(string Name, string? ContactInfo, bool IsActive); diff --git a/Distribution/Features/ExpenseVendor/ExpenseVendorRepository.cs b/Distribution/Features/ExpenseVendor/ExpenseVendorRepository.cs new file mode 100644 index 0000000..3253709 --- /dev/null +++ b/Distribution/Features/ExpenseVendor/ExpenseVendorRepository.cs @@ -0,0 +1,31 @@ +using Distribution.Data; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.ExpenseVendor; + +public class ExpenseVendorRepository(AppDBContext context) : IExpenseVendorRepository +{ + public async Task GetByIdAsync(Guid id) => + await context.ExpenseVendors.FirstOrDefaultAsync(v => v.Id == id); + + public async Task> ListAsync(string? search, int page, int pageSize) => + await context.ExpenseVendors + .Where(v => v.IsActive && (search == null || v.Name.Contains(search))) + .OrderBy(v => v.Name) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + public async Task CountAsync(string? search) => + await context.ExpenseVendors + .CountAsync(v => v.IsActive && (search == null || v.Name.Contains(search))); + + public async Task NameExistsAsync(string name, Guid? excludeId) => + await context.ExpenseVendors.AnyAsync(v => v.Name == name && (excludeId == null || v.Id != excludeId)); + + public async Task AddAsync(Models.ExpenseVendor entity) => + await context.ExpenseVendors.AddAsync(entity); + + public void Update(Models.ExpenseVendor entity) => + context.ExpenseVendors.Update(entity); +} diff --git a/Distribution/Features/ExpenseVendor/ExpenseVendorService.cs b/Distribution/Features/ExpenseVendor/ExpenseVendorService.cs new file mode 100644 index 0000000..1725ac2 --- /dev/null +++ b/Distribution/Features/ExpenseVendor/ExpenseVendorService.cs @@ -0,0 +1,59 @@ +using Distribution.Infra.UoW; + +namespace Distribution.Features.ExpenseVendor; + +public record PagedExpenseVendorsResponse(List Items, int Page, int PageSize, int Total); + +public class ExpenseVendorService(IExpenseVendorRepository repo, IUnitOfWork uow) +{ + private static ExpenseVendorResponse Map(Models.ExpenseVendor v) => + new(v.Id, v.Name, v.ContactInfo, v.IsActive); + + public async Task ListAsync(string? search, int page, int pageSize) + { + if (page < 1) page = 1; + if (pageSize < 1 || pageSize > 200) pageSize = 50; + + search = string.IsNullOrWhiteSpace(search) ? null : search.Trim(); + var items = await repo.ListAsync(search, page, pageSize); + var total = await repo.CountAsync(search); + return new PagedExpenseVendorsResponse(items.Select(Map).ToList(), page, pageSize, total); + } + + public async Task CreateAsync(CreateExpenseVendorRequest request, CancellationToken ct = default) + { + var name = (request.Name ?? "").Trim(); + if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Name is required"); + if (await repo.NameExistsAsync(name, null)) + throw new InvalidOperationException("A vendor with this name already exists"); + + var entity = new Models.ExpenseVendor { Name = name, ContactInfo = request.ContactInfo }; + + await uow.BeginAsync(ct); + try { await repo.AddAsync(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return Map(entity); + } + + public async Task UpdateAsync(Guid id, UpdateExpenseVendorRequest request, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + + var name = (request.Name ?? "").Trim(); + if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Name is required"); + if (await repo.NameExistsAsync(name, id)) + throw new InvalidOperationException("A vendor with this name already exists"); + + entity.Name = name; + entity.ContactInfo = request.ContactInfo; + entity.IsActive = request.IsActive; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return Map(entity); + } +} diff --git a/Distribution/Features/ExpenseVendor/IExpenseVendorRepository.cs b/Distribution/Features/ExpenseVendor/IExpenseVendorRepository.cs new file mode 100644 index 0000000..7e46eac --- /dev/null +++ b/Distribution/Features/ExpenseVendor/IExpenseVendorRepository.cs @@ -0,0 +1,11 @@ +namespace Distribution.Features.ExpenseVendor; + +public interface IExpenseVendorRepository +{ + Task GetByIdAsync(Guid id); + Task> ListAsync(string? search, int page, int pageSize); + Task CountAsync(string? search); + Task NameExistsAsync(string name, Guid? excludeId); + Task AddAsync(Models.ExpenseVendor entity); + void Update(Models.ExpenseVendor entity); +} diff --git a/Distribution/Features/RecurringExpense/IRecurringExpenseRepository.cs b/Distribution/Features/RecurringExpense/IRecurringExpenseRepository.cs new file mode 100644 index 0000000..f358e7d --- /dev/null +++ b/Distribution/Features/RecurringExpense/IRecurringExpenseRepository.cs @@ -0,0 +1,11 @@ +namespace Distribution.Features.RecurringExpense; + +public interface IRecurringExpenseRepository +{ + Task GetByIdAsync(Guid id); + Task> ListAsync(int page, int pageSize); + Task CountAsync(); + Task> ListDueAsync(DateTime asOf); + Task AddAsync(Models.RecurringExpenseTemplate entity); + void Update(Models.RecurringExpenseTemplate entity); +} diff --git a/Distribution/Features/RecurringExpense/RecurringExpenseController.cs b/Distribution/Features/RecurringExpense/RecurringExpenseController.cs new file mode 100644 index 0000000..6799f20 --- /dev/null +++ b/Distribution/Features/RecurringExpense/RecurringExpenseController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.RecurringExpense; + +public record ToggleRequest(bool Paused); + +[ApiController] +[Route("api/recurring-expenses")] +public class RecurringExpenseController(RecurringExpenseService service) : ControllerBase +{ + [HttpGet] + [Authorize("expense.view")] + public async Task List([FromQuery] int page = 1, [FromQuery] int pageSize = 20) => + Ok(await service.ListAsync(page, pageSize)); + + [HttpPost] + [Authorize("expense.manage_recurring")] + public async Task Create(CreateRecurringExpenseRequest request, CancellationToken ct) + { + try { return Ok(await service.CreateAsync(request, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPut("{id:guid}")] + [Authorize("expense.manage_recurring")] + public async Task Update(Guid id, UpdateRecurringExpenseRequest request, CancellationToken ct) + { + try + { + var result = await service.UpdateAsync(id, request, ct); + return result is null ? NotFound() : Ok(result); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("{id:guid}/toggle")] + [Authorize("expense.manage_recurring")] + public async Task Toggle(Guid id, ToggleRequest body, CancellationToken ct) + { + var result = await service.ToggleAsync(id, body.Paused, ct); + return result is null ? NotFound() : Ok(result); + } + + [HttpPost("run-due")] + [Authorize("expense.manage_recurring")] + public async Task RunDue(CancellationToken ct) + { + try { return Ok(await service.RunDueAsync(ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } +} diff --git a/Distribution/Features/RecurringExpense/RecurringExpenseDTOs.cs b/Distribution/Features/RecurringExpense/RecurringExpenseDTOs.cs new file mode 100644 index 0000000..de5b273 --- /dev/null +++ b/Distribution/Features/RecurringExpense/RecurringExpenseDTOs.cs @@ -0,0 +1,60 @@ +using Distribution.Models; + +namespace Distribution.Features.RecurringExpense; + +public record CreateRecurringExpenseRequest( + string Name, + Guid CategoryId, + Guid? WarehouseId, + Guid? VendorId, + decimal Amount, + string? Payee, + string? Description, + RecurringFrequency Frequency, + int? DayOfMonth, + int? DayOfWeek, + DateTime StartDate, + DateTime? EndDate, + bool AutoPost); + +public record UpdateRecurringExpenseRequest( + string Name, + Guid CategoryId, + Guid? WarehouseId, + Guid? VendorId, + decimal Amount, + string? Payee, + string? Description, + RecurringFrequency Frequency, + int? DayOfMonth, + int? DayOfWeek, + DateTime StartDate, + DateTime? EndDate, + bool AutoPost); + +public record RecurringExpenseResponse( + Guid Id, + string Name, + Guid CategoryId, + string CategoryName, + Guid? WarehouseId, + string? WarehouseName, + Guid? VendorId, + string? VendorName, + decimal Amount, + string? Payee, + string? Description, + RecurringFrequency Frequency, + int? DayOfMonth, + int? DayOfWeek, + DateTime StartDate, + DateTime? EndDate, + DateTime NextRunDate, + bool AutoPost, + bool IsPaused, + bool IsActive, + DateTime CreatedAt); + +public record PagedRecurringExpensesResponse(List Items, int Page, int PageSize, int Total); + +public record RunDueResponse(int Generated, List ExpenseIds); diff --git a/Distribution/Features/RecurringExpense/RecurringExpenseRepository.cs b/Distribution/Features/RecurringExpense/RecurringExpenseRepository.cs new file mode 100644 index 0000000..48bca3e --- /dev/null +++ b/Distribution/Features/RecurringExpense/RecurringExpenseRepository.cs @@ -0,0 +1,39 @@ +using Distribution.Data; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.RecurringExpense; + +public class RecurringExpenseRepository(AppDBContext context) : IRecurringExpenseRepository +{ + private IQueryable Base() => + context.RecurringExpenseTemplates + .Include(t => t.Category) + .Include(t => t.Warehouse) + .Include(t => t.Vendor); + + public async Task GetByIdAsync(Guid id) => + await Base().FirstOrDefaultAsync(t => t.Id == id); + + public async Task> ListAsync(int page, int pageSize) => + await Base() + .Where(t => t.IsActive) + .OrderBy(t => t.IsPaused).ThenBy(t => t.NextRunDate) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + public async Task CountAsync() => + await context.RecurringExpenseTemplates.CountAsync(t => t.IsActive); + + public async Task> ListDueAsync(DateTime asOf) => + await Base() + .Where(t => t.IsActive && !t.IsPaused && t.NextRunDate <= asOf + && (t.EndDate == null || t.EndDate >= asOf)) + .ToListAsync(); + + public async Task AddAsync(Models.RecurringExpenseTemplate entity) => + await context.RecurringExpenseTemplates.AddAsync(entity); + + public void Update(Models.RecurringExpenseTemplate entity) => + context.RecurringExpenseTemplates.Update(entity); +} diff --git a/Distribution/Features/RecurringExpense/RecurringExpenseService.cs b/Distribution/Features/RecurringExpense/RecurringExpenseService.cs new file mode 100644 index 0000000..6ac784c --- /dev/null +++ b/Distribution/Features/RecurringExpense/RecurringExpenseService.cs @@ -0,0 +1,183 @@ +using Distribution.Data; +using Distribution.Infra.Authorization; +using Distribution.Infra.UoW; +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.RecurringExpense; + +public class RecurringExpenseService( + AppDBContext context, + IRecurringExpenseRepository repo, + IUnitOfWork uow, + ICurrentUserService currentUser) +{ + private static RecurringExpenseResponse Map(Models.RecurringExpenseTemplate t) => + new(t.Id, t.Name, + t.FK_CategoryId, t.Category?.Name ?? string.Empty, + t.FK_WarehouseId, t.Warehouse?.Name, + t.FK_VendorId, t.Vendor?.Name, + t.Amount, t.Payee, t.Description, + t.Frequency, t.DayOfMonth, t.DayOfWeek, + t.StartDate, t.EndDate, t.NextRunDate, t.AutoPost, t.IsPaused, t.IsActive, t.CreatedAt); + + public async Task ListAsync(int page, int pageSize) + { + if (page < 1) page = 1; + if (pageSize < 1 || pageSize > 200) pageSize = 20; + var items = await repo.ListAsync(page, pageSize); + var total = await repo.CountAsync(); + return new PagedRecurringExpensesResponse(items.Select(Map).ToList(), page, pageSize, total); + } + + public async Task CreateAsync(CreateRecurringExpenseRequest request, CancellationToken ct = default) + { + ValidateSchedule(request.Frequency, request.DayOfMonth, request.DayOfWeek); + if (request.Amount <= 0) throw new InvalidOperationException("Amount must be greater than zero"); + + var category = await context.ExpenseCategories.FirstOrDefaultAsync(c => c.Id == request.CategoryId && c.IsActive, ct) + ?? throw new InvalidOperationException("Category not found"); + if (category.FK_ParentCategoryId is null) + throw new InvalidOperationException("Pick a specific title under a category"); + + var entity = new Models.RecurringExpenseTemplate + { + Name = request.Name.Trim(), + FK_CategoryId = request.CategoryId, + FK_WarehouseId = request.WarehouseId, + FK_VendorId = request.VendorId, + Amount = request.Amount, + Payee = request.Payee, + Description = request.Description, + Frequency = request.Frequency, + DayOfMonth = request.DayOfMonth, + DayOfWeek = request.DayOfWeek, + StartDate = request.StartDate.Date, + EndDate = request.EndDate?.Date, + NextRunDate = request.StartDate.Date, + AutoPost = request.AutoPost + }; + + await uow.BeginAsync(ct); + try { await repo.AddAsync(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return Map((await repo.GetByIdAsync(entity.Id))!); + } + + public async Task UpdateAsync(Guid id, UpdateRecurringExpenseRequest request, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + + ValidateSchedule(request.Frequency, request.DayOfMonth, request.DayOfWeek); + if (request.Amount <= 0) throw new InvalidOperationException("Amount must be greater than zero"); + + entity.Name = request.Name.Trim(); + entity.FK_CategoryId = request.CategoryId; + entity.FK_WarehouseId = request.WarehouseId; + entity.FK_VendorId = request.VendorId; + entity.Amount = request.Amount; + entity.Payee = request.Payee; + entity.Description = request.Description; + entity.Frequency = request.Frequency; + entity.DayOfMonth = request.DayOfMonth; + entity.DayOfWeek = request.DayOfWeek; + entity.StartDate = request.StartDate.Date; + entity.EndDate = request.EndDate?.Date; + entity.AutoPost = request.AutoPost; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return Map((await repo.GetByIdAsync(id))!); + } + + public async Task ToggleAsync(Guid id, bool paused, CancellationToken ct = default) + { + var entity = await repo.GetByIdAsync(id); + if (entity is null) return null; + entity.IsPaused = paused; + + await uow.BeginAsync(ct); + try { repo.Update(entity); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + + return Map(entity); + } + + /// + /// Materializes every active, non-paused template whose NextRunDate ≤ today + /// into an Expense row, then advances NextRunDate by one period. Idempotent + /// per call — advances only after a successful insert. + /// + public async Task RunDueAsync(CancellationToken ct = default) + { + var today = DateTime.Today; + var due = await repo.ListDueAsync(today); + if (due.Count == 0) return new RunDueResponse(0, []); + + var userId = currentUser.UserId + ?? throw new InvalidOperationException("Authenticated user is required"); + + var createdIds = new List(); + + await uow.BeginAsync(ct); + try + { + foreach (var t in due) + { + var expense = new Models.Expense + { + FK_CategoryId = t.FK_CategoryId, + FK_WarehouseId = t.FK_WarehouseId, + FK_VendorId = t.FK_VendorId, + Amount = t.Amount, + BillDate = t.NextRunDate, + PeriodCovered = new DateTime(t.NextRunDate.Year, t.NextRunDate.Month, 1), + Payee = t.Payee, + Description = t.Description, + Status = t.AutoPost ? ExpenseStatus.Posted : ExpenseStatus.Draft, + RecordedByUserId = userId, + PostedByUserId = t.AutoPost ? userId : null, + PostedAt = t.AutoPost ? DateTime.Now : null, + FK_RecurringTemplateId = t.Id + }; + await context.Expenses.AddAsync(expense, ct); + createdIds.Add(expense.Id); + + t.NextRunDate = AdvanceNext(t.NextRunDate, t.Frequency); + repo.Update(t); + } + + await uow.CommitAsync(ct); + } + catch { await uow.RollbackAsync(ct); throw; } + + return new RunDueResponse(createdIds.Count, createdIds); + } + + private static DateTime AdvanceNext(DateTime from, RecurringFrequency freq) => freq switch + { + RecurringFrequency.Weekly => from.AddDays(7), + RecurringFrequency.Monthly => from.AddMonths(1), + RecurringFrequency.Quarterly => from.AddMonths(3), + RecurringFrequency.Yearly => from.AddYears(1), + _ => throw new InvalidOperationException($"Unknown frequency {freq}") + }; + + private static void ValidateSchedule(RecurringFrequency freq, int? dayOfMonth, int? dayOfWeek) + { + if (freq == RecurringFrequency.Weekly) + { + if (dayOfWeek is null or < 0 or > 6) + throw new InvalidOperationException("DayOfWeek (0-6) is required for Weekly"); + } + else + { + if (dayOfMonth is null or < 1 or > 31) + throw new InvalidOperationException("DayOfMonth (1-31) is required for Monthly/Quarterly/Yearly"); + } + } +} diff --git a/Distribution/Migrations/20261021060022_AddExpenseModule.Designer.cs b/Distribution/Migrations/20261021060022_AddExpenseModule.Designer.cs new file mode 100644 index 0000000..82f7e3c --- /dev/null +++ b/Distribution/Migrations/20261021060022_AddExpenseModule.Designer.cs @@ -0,0 +1,6448 @@ +// +using System; +using Distribution.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Distribution.Migrations +{ + [DbContext(typeof(AppDBContext))] + [Migration("20261021060022_AddExpenseModule")] + partial class AddExpenseModule + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Distribution.Models.BankAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("AccountType") + .HasColumnType("int"); + + b.Property("BankName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CashAccountTypeName") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("AccountType", "AccountName") + .IsUnique(); + + b.ToTable("BankAccounts"); + + b.HasData( + new + { + Id = new Guid("11111111-1111-1111-1111-000000000001"), + AccountName = "Petty Cash", + AccountType = 0, + CashAccountTypeName = "Petty Cash", + CreatedAt = new DateTime(2026, 10, 19, 0, 0, 0, 0, DateTimeKind.Unspecified), + CurrencyCode = "LKR", + IsActive = true, + IsDefault = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Brands"); + + b.HasData( + new + { + Id = new Guid("21000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Coca-Cola" + }, + new + { + Id = new Guid("21000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Lays" + }); + }); + + modelBuilder.Entity("Distribution.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomerReturn") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Beverages" + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snacks" + }); + }); + + modelBuilder.Entity("Distribution.Models.ChequeBook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("ChequeBookNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("EndChequeNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("FK_BankAccountId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedDate") + .HasColumnType("datetime(6)"); + + b.Property("StartChequeNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("ChequeNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ClearedByBank") + .HasColumnType("tinyint(1)"); + + b.Property("ClearedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrencyCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("FK_ChequeBookId") + .HasColumnType("char(36)"); + + b.Property("IsAccountPayee") + .HasColumnType("tinyint(1)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCrossCheque") + .HasColumnType("tinyint(1)"); + + b.Property("IsPostDated") + .HasColumnType("tinyint(1)"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("IssueStatus") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("PayeeId") + .HasColumnType("int"); + + b.Property("PayeeName") + .HasColumnType("longtext"); + + b.Property("PayeeType") + .HasColumnType("int"); + + b.Property("PrintedAt") + .HasColumnType("datetime(6)"); + + b.Property("PrintedBy") + .HasColumnType("longtext"); + + b.Property("Purpose") + .HasColumnType("longtext"); + + b.Property("ReferenceNo") + .HasColumnType("longtext"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("SettledAt") + .HasColumnType("datetime(6)"); + + b.Property("SettledByUserId") + .HasColumnType("char(36)"); + + b.Property("SettlementAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("SettledByUserId"); + + b.ToTable("Claims"); + + b.HasData( + new + { + Id = new Guid("53000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 27, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "Distributor margin claim for Q3 promotional shortfall", + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + IsActive = true, + SettledAt = new DateTime(2026, 8, 28, 0, 0, 0, 0, DateTimeKind.Unspecified), + SettledByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + SettlementAmount = 15000m, + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssuedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("CreditNotes"); + + b.HasData( + new + { + Id = new Guid("54000000-0000-0000-0000-000000000001"), + Amount = 300m, + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + IssuedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelTypeCode") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_DriverProfileId") + .HasColumnType("char(36)"); + + b.Property("FK_VehicleId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Meterend") + .HasColumnType("int"); + + b.Property("Meterstart") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_DriverProfileId"); + + b.HasIndex("FK_VehicleId"); + + b.ToTable("DeliveryRuns"); + + b.HasData( + new + { + Id = new Guid("31000000-0000-0000-0000-000000000001"), + ClosedAt = new DateTime(2026, 8, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000001"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000001"), + IsActive = true, + Meterend = 10550, + Meterstart = 10500, + StartedAt = new DateTime(2026, 8, 25, 8, 0, 0, 0, DateTimeKind.Unspecified), + Status = 2 + }, + new + { + Id = new Guid("31000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 8, 45, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000002"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000002"), + IsActive = true, + Meterend = 0, + Meterstart = 20300, + StartedAt = new DateTime(2026, 8, 30, 9, 0, 0, 0, DateTimeKind.Unspecified), + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("DeliveryTasks"); + + b.HasData( + new + { + Id = new Guid("37000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 30, 10, 5, 0, 0, DateTimeKind.Unspecified), + CustomerConfirmed = false, + FK_RideId = new Guid("31000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Sequence = 1, + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverComplianceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("EnforceLicenseExpiry") + .HasColumnType("tinyint(1)"); + + b.Property("EnforceMedicalClearance") + .HasColumnType("tinyint(1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("DriverComplianceConfigs"); + + b.HasData( + new + { + Id = new Guid("64000000-0000-0000-0000-000000000001"), + EnforceLicenseExpiry = true, + EnforceMedicalClearance = true, + UpdatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + UpdatedBy = new Guid("29000000-0000-0000-0000-000000000001") + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LicenseExpiry") + .HasColumnType("datetime(6)"); + + b.Property("LicenseNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MedicalClearanceExpiry") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_UserId") + .IsUnique(); + + b.ToTable("DriverProfiles"); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000004"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 6, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B1234567", + MedicalClearanceExpiry = new DateTime(2027, 1, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000005"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 9, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B7654321", + MedicalClearanceExpiry = new DateTime(2027, 3, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("BillDate") + .HasColumnType("datetime(6)"); + + b.Property("BillRef") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DueDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_BankAccountId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_ChequePageId") + .HasColumnType("char(36)"); + + b.Property("FK_RecurringTemplateId") + .HasColumnType("char(36)"); + + b.Property("FK_VendorId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("PaidAt") + .HasColumnType("datetime(6)"); + + b.Property("PaidByUserId") + .HasColumnType("char(36)"); + + b.Property("Payee") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PeriodCovered") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedByUserId") + .HasColumnType("char(36)"); + + b.Property("RecordedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("VoidReason") + .HasColumnType("longtext"); + + b.Property("VoidedAt") + .HasColumnType("datetime(6)"); + + b.Property("VoidedByUserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_BankAccountId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_ChequePageId"); + + b.HasIndex("FK_RecurringTemplateId"); + + b.HasIndex("FK_VendorId"); + + b.HasIndex("FK_WarehouseId"); + + b.HasIndex("Status", "BillDate"); + + b.ToTable("Expenses"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ExpenseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StoredPath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UploadedByUserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ExpenseId"); + + b.ToTable("ExpenseAttachments"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ParentCategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ParentCategoryId", "Name") + .IsUnique(); + + b.ToTable("ExpenseCategories"); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + Code = "SAL", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Salaries" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + Code = "UTL", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Utilities" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000003"), + Code = "RNT", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Rent" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000004"), + Code = "VEH", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Vehicle" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000005"), + Code = "OFF", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Office" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000006"), + Code = "BNK", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Bank Fees" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000007"), + Code = "MSC", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Miscellaneous" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000101"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Basic Salary" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000102"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Bonus" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000103"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Overtime" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000201"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000002"), + IsActive = true, + Name = "Electricity" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000202"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000002"), + IsActive = true, + Name = "Water" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000203"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000002"), + IsActive = true, + Name = "Internet" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000301"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000003"), + IsActive = true, + Name = "Warehouse Rent" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000302"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000003"), + IsActive = true, + Name = "Office Rent" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000401"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000004"), + IsActive = true, + Name = "Fuel" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000402"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000004"), + IsActive = true, + Name = "Maintenance" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000403"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000004"), + IsActive = true, + Name = "Insurance" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000501"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000005"), + IsActive = true, + Name = "Stationery" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000502"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000005"), + IsActive = true, + Name = "Cleaning" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000601"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000006"), + IsActive = true, + Name = "Service Charges" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000701"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000007"), + IsActive = true, + Name = "Other" + }); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseVendor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ExpenseVendors"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("FK_ReceivedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NbtPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("FK_SupplierId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("GoodsReceipts"); + + b.HasData( + new + { + Id = new Guid("43000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + NbtPercent = 0m, + ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcceptedQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQuantity") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NbtPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RejectedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderItemId"); + + b.ToTable("GoodsReceiptItems"); + + b.HasData( + new + { + Id = new Guid("44000000-0000-0000-0000-000000000001"), + AcceptedQuantity = 200, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"), + FreeQuantity = 0, + IsActive = true, + NbtPercent = 0m, + RejectedQuantity = 0, + UnitCost = 0m, + VatPercent = 0m + }, + new + { + Id = new Guid("44000000-0000-0000-0000-000000000002"), + AcceptedQuantity = 98, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"), + FreeQuantity = 0, + IsActive = true, + NbtPercent = 0m, + RejectedQuantity = 2, + UnitCost = 0m, + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemBatchId"); + + b.ToTable("GoodsReceiptItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("GoodsReceiptItemSerials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FinalizedAt") + .HasColumnType("datetime(6)"); + + b.Property("FinalizedBy") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PrintedAt") + .HasColumnType("datetime(6)"); + + b.Property("PrintedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("Invoices"); + + b.HasData( + new + { + Id = new Guid("34500000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FinalizedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FinalizedBy = new Guid("29000000-0000-0000-0000-000000000001"), + IsActive = true, + PaymentStatus = 2, + PrintedAt = new DateTime(2026, 8, 25, 8, 45, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("34500000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + PaymentStatus = 1, + PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 1, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomWarrantyPeriod") + .HasColumnType("tinyint(1)"); + + b.Property("CostPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalErpItemId") + .HasColumnType("int"); + + b.Property("FK_BaseUomId") + .HasColumnType("char(36)"); + + b.Property("FK_BrandId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_SubCategoryId") + .HasColumnType("char(36)"); + + b.Property("HasWarranty") + .HasColumnType("tinyint(1)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ReorderLevel") + .HasColumnType("decimal(65,30)"); + + b.Property("SalePrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Sku") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SyncSource") + .HasColumnType("int"); + + b.Property("TracksBatches") + .HasColumnType("tinyint(1)"); + + b.Property("TracksExpiryDate") + .HasColumnType("tinyint(1)"); + + b.Property("TracksSerialNumbers") + .HasColumnType("tinyint(1)"); + + b.Property("WarrantyPeriodDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_BaseUomId"); + + b.HasIndex("FK_BrandId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_SubCategoryId"); + + b.ToTable("Items"); + + b.HasData( + new + { + Id = new Guid("23000000-0000-0000-0000-000000000001"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 500ml bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 500ml", + SalePrice = 60m, + Sku = "BEV-COKE-500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000002"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 1.5L bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 1.5L", + SalePrice = 120m, + Sku = "BEV-COKE-1500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000003"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Classic Salted 52g pack", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Classic 52g", + SalePrice = 50m, + Sku = "SNK-LAYS-52", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000004"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Family Pack 150g", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Family Pack 150g", + SalePrice = 150m, + Sku = "SNK-LAYS-FAM", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BatchNumber") + .HasColumnType("varchar(255)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ManufactureDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "BatchNumber"); + + b.ToTable("ItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentLocationId") + .HasColumnType("char(36)"); + + b.Property("CurrentLocationType") + .HasColumnType("int"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WarrantyExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("WarrantyPeriodDaysOverride") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "SerialNumber") + .IsUnique(); + + b.ToTable("ItemSerialNumbers"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PopulatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PopulatedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.ToTable("LoadingSheets"); + + b.HasData( + new + { + Id = new Guid("35000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 7, 45, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_RideId = new Guid("31000000-0000-0000-0000-000000000001"), + IsActive = true, + PopulatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + PopulatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOItemId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("QuantityDispatched") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_LoadingSheetId"); + + b.HasIndex("FK_ShopPOItemId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("LoadingSheetItems"); + + b.HasData( + new + { + Id = new Guid("36000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 50m + }, + new + { + Id = new Guid("36000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 30m + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.HasKey("FK_LoadingSheetId", "FK_ShopPOId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("LoadingSheetOrders"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("ChequeDate") + .HasColumnType("datetime(6)"); + + b.Property("ChequeNumber") + .HasColumnType("longtext"); + + b.Property("CollectedAt") + .HasColumnType("datetime(6)"); + + b.Property("CollectedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntryMode") + .HasColumnType("int"); + + b.Property("FK_BankAccountId") + .HasColumnType("char(36)"); + + b.Property("FK_InvoiceId") + .HasColumnType("char(36)"); + + b.Property("FK_ReceivedChequeId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("RecordedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_BankAccountId"); + + b.HasIndex("FK_InvoiceId"); + + b.HasIndex("FK_ReceivedChequeId"); + + b.ToTable("Payments"); + + b.HasData( + new + { + Id = new Guid("47000000-0000-0000-0000-000000000001"), + Amount = 3000m, + CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000002"), + Amount = 1000m, + CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000003"), + Amount = 800m, + ChequeDate = new DateTime(2026, 8, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + ChequeNumber = "000456", + CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0, 0, DateTimeKind.Unspecified), + EntryMode = 1, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 1, + RecordedBy = new Guid("29000000-0000-0000-0000-000000000001") + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000004"), + Amount = 300m, + CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Permissions"); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + Code = "orders.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View shop POs, invoices, payments and returns", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + Code = "rides.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage delivery rides and delivery tasks", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000003"), + Code = "loading.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage loading sheets", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000004"), + Code = "stores.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View store accounts, credit limits and balances", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000005"), + Code = "sales_visits.checkin", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Check in and out of scheduled store visits", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000006"), + Code = "items.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage catalogue products", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000007"), + Code = "categories.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage product categories and brands", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000008"), + Code = "warehouses.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View stock balances, movements, warehouses and reports", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000009"), + Code = "purchase_orders.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage purchase orders, GRNs and supplier claims", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000010"), + Code = "pricelists.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage regional price lists", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000011"), + Code = "schemes.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage pricing schemes and promotions", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000012"), + Code = "suppliers.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage supplier accounts", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000013"), + Code = "driver_profiles.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage driver profiles and licensing", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000014"), + Code = "compliance.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Manage driver compliance rules", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000015"), + Code = "users.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage staff user accounts", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000016"), + Code = "roles.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage roles and their permission sets", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000017"), + Code = "regions.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage regions and region configuration", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000018"), + Code = "routes.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage delivery routes", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000019"), + Code = "vehicles.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View and manage fleet vehicles", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000020"), + Code = "claim.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View agency claims and settlement status", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000021"), + Code = "claim.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and settle agency claims", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000022"), + Code = "delivery-task.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View delivery tasks within a ride", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000023"), + Code = "delivery-task.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Confirm and fail delivery tasks", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000024"), + Code = "driver.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View driver profiles and license details", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000025"), + Code = "driver.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and update driver profiles", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000026"), + Code = "grn.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View goods receipt notes", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000027"), + Code = "grn.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and confirm goods receipts", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000028"), + Code = "loading-sheet.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View loading sheets and their items", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000029"), + Code = "loading-sheet.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create, edit, and confirm loading sheets", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000030"), + Code = "price-list.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View regional price lists", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000031"), + Code = "price-list.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and manage item price lists", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000032"), + Code = "purchase-order.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View purchase orders and line items", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000033"), + Code = "purchase-order.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create, confirm, and close purchase orders", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000034"), + Code = "purchase-return.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and manage purchase returns to suppliers", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000035"), + Code = "region.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View regions and their configuration", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000036"), + Code = "region.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and update regions, targets, and budgets", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000037"), + Code = "return.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View order returns", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000038"), + Code = "return.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and manage order returns", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000039"), + Code = "route.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View delivery routes", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000040"), + Code = "route.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and manage delivery routes", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000041"), + Code = "scheme.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View pricing schemes and promotions", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000042"), + Code = "scheme.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and configure pricing schemes", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000043"), + Code = "stock.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View stock balances and movements", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000044"), + Code = "stock.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Transfer stock and record damage or adjustments", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000045"), + Code = "store.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View store accounts and balances", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000046"), + Code = "store.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and update store accounts", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000047"), + Code = "supplier-claim.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View supplier claims", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000048"), + Code = "supplier-claim.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and resolve supplier claims", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000049"), + Code = "sys-config.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View system configuration settings", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000050"), + Code = "sys-config.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Update system configuration settings", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000051"), + Code = "expense.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View expenses, categories, vendors and recurring templates", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000052"), + Code = "expense.create", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and edit draft expenses", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000053"), + Code = "expense.post", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Post a draft expense (approve it)", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000054"), + Code = "expense.pay", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Pay a posted expense (debit a bank account or issue a cheque)", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000055"), + Code = "expense.void", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Void an expense", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000056"), + Code = "expense.manage_categories", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Manage expense categories, titles and vendors", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000057"), + Code = "expense.manage_recurring", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Manage recurring expense templates", + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("PriceLists"); + + b.HasData( + new + { + Id = new Guid("39000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveFrom = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Western Province Price List" + }); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgencyMarginPercent") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PriceListId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("MinPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Price") + .HasColumnType("decimal(65,30)"); + + b.Property("RetailPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PriceListId", "FK_ItemId") + .IsUnique(); + + b.ToTable("PriceListItems"); + + b.HasData( + new + { + Id = new Guid("40000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 55m, + Price = 60m + }, + new + { + Id = new Guid("40000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 45m, + Price = 50m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_SupplierId"); + + b.ToTable("PurchaseOrders"); + + b.HasData( + new + { + Id = new Guid("41000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000001"), + IsActive = true, + Status = 2 + }, + new + { + Id = new Guid("41000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000002"), + IsActive = true, + Status = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelledQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCancelled") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("ReceivedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseOrderItems"); + + b.HasData( + new + { + Id = new Guid("42000000-0000-0000-0000-000000000001"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 200, + ReceivedQuantity = 200, + UnitCost = 40m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000002"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 100, + ReceivedQuantity = 98, + UnitCost = 80m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000003"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + IsCancelled = false, + Quantity = 150, + ReceivedQuantity = 150, + UnitCost = 35m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseReturns"); + + b.HasData( + new + { + Id = new Guid("45000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + Reason = "Damaged in transit" + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseReturnId"); + + b.ToTable("PurchaseReturnItems"); + + b.HasData( + new + { + Id = new Guid("46000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseReturnId = new Guid("45000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 10 + }); + }); + + modelBuilder.Entity("Distribution.Models.ReceivedCheque", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccountHolderName") + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("ChequeDate") + .HasColumnType("datetime(6)"); + + b.Property("ChequeNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CompanyId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("DepositDate") + .HasColumnType("datetime(6)"); + + b.Property("DrawerBankBranch") + .HasColumnType("longtext"); + + b.Property("DrawerBankName") + .HasColumnType("longtext"); + + b.Property("FK_DepositBankAccountId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("ReceivedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReceivedFromId") + .HasColumnType("int"); + + b.Property("ReceivedFromName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ReceivedFromType") + .HasColumnType("int"); + + b.Property("ReferenceId") + .HasColumnType("int"); + + b.Property("ReferenceType") + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ChequeNo"); + + b.HasIndex("FK_DepositBankAccountId"); + + b.ToTable("ReceivedCheques"); + }); + + modelBuilder.Entity("Distribution.Models.RecurringExpenseTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("AutoPost") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfMonth") + .HasColumnType("int"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_VendorId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("Frequency") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsPaused") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("Payee") + .HasColumnType("longtext"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_VendorId"); + + b.HasIndex("FK_WarehouseId"); + + b.HasIndex("IsPaused", "NextRunDate"); + + b.ToTable("RecurringExpenseTemplates"); + }); + + modelBuilder.Entity("Distribution.Models.Region", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Regions"); + + b.HasData( + new + { + Id = new Guid("24000000-0000-0000-0000-000000000001"), + Code = "REG-0001", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Western Province" + }, + new + { + Id = new Guid("24000000-0000-0000-0000-000000000002"), + Code = "REG-0002", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Central Province" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("FK_RegionId", "FK_AgencyUserId") + .IsUnique(); + + b.ToTable("RegionAgencyMaps"); + + b.HasData( + new + { + Id = new Guid("57000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BudgetAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalBudgets"); + + b.HasData( + new + { + Id = new Guid("58000000-0000-0000-0000-000000000001"), + BudgetAmount = 200000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TargetValue") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalTargets"); + + b.HasData( + new + { + Id = new Guid("59000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08", + TargetValue = 500000m + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RuleConfig") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId") + .IsUnique(); + + b.ToTable("RegionalTaxRules"); + + b.HasData( + new + { + Id = new Guid("60000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + RuleConfig = "{\"rate\": 0.15}" + }); + }); + + modelBuilder.Entity("Distribution.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 10, + Name = "SalesRep" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 20, + Name = "Supervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 30, + Name = "AreaManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 40, + Name = "AreaSupervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 50, + Name = "RegionalManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 60, + Name = "RegionalAgency" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000007"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 100, + Name = "SuperAdmin" + }); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FK_PermissionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_RoleId", "FK_PermissionId"); + + b.HasIndex("FK_PermissionId"); + + b.ToTable("RolePermissions"); + + b.HasData( + new + { + 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("fa88ee94-4387-45a8-8e0b-67a3eb0d9537"), + IsActive = true + }, + new + { + 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("007f96a6-dbb2-4745-be0f-7566ebab4f72"), + IsActive = true + }, + new + { + 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("855bb33c-4507-435f-86e7-b86128d61099"), + IsActive = true + }, + new + { + 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("86b89230-02bb-42d8-90d4-f93a75d729f8"), + IsActive = true + }, + new + { + 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("2dcd53da-35e2-44c5-9fd9-84e5eeecb099"), + IsActive = true + }, + new + { + 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("d32844a6-5e5d-4ac8-a56c-4bad70be7ceb"), + IsActive = true + }, + new + { + 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("1a49ce67-81e2-4c6f-8e16-e07824964fc8"), + IsActive = true + }, + new + { + 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("f4c6729b-5347-4be2-8686-db7347958f35"), + IsActive = true + }, + new + { + 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("d35f14b7-34ca-4637-8a22-9ca8e3ac52ad"), + IsActive = true + }, + new + { + 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("fcbb69ec-6023-4fb3-8d41-30658666015d"), + IsActive = true + }, + new + { + 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("88c5c40c-d211-40d9-8bf5-df11e9995738"), + IsActive = true + }, + new + { + 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("0d5c388f-08f1-4ee5-adcd-5e0d8e061128"), + IsActive = true + }, + new + { + 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("7e726a5d-f487-4e29-8730-75fd64dc0100"), + IsActive = true + }, + new + { + 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("9f6e9256-cb78-4a44-863e-6d31e69ff8d5"), + IsActive = true + }, + new + { + 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("8fbdda8b-9eb3-490c-8c21-7a8f51c82404"), + IsActive = true + }, + new + { + 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("56f26354-9548-42c2-8b58-30c936db1bcd"), + IsActive = true + }, + new + { + 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("69d9de8f-107b-42d0-b345-e215b5cd5ccc"), + IsActive = true + }, + new + { + 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("21fa8dd9-596d-4c2c-86de-0a27e4e8cfd4"), + IsActive = true + }, + new + { + 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("750380a2-57da-4b98-b26f-2d893055e83f"), + IsActive = true + }, + new + { + 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("9a628e80-953f-4b55-8f46-aafb51a2f03a"), + IsActive = true + }, + new + { + 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("82e0dcfc-9df0-4135-b1bc-29bafca0e203"), + IsActive = true + }, + new + { + 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("40b78431-d5fc-4ff3-b12c-cfd9560b24e4"), + IsActive = true + }, + new + { + 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("9b3dfd17-cedd-4d7c-940c-5123f3a41b04"), + IsActive = true + }, + new + { + 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("7230884d-f645-4aac-974e-9b4f8b46b9ff"), + IsActive = true + }, + new + { + 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("9e688eea-f12f-4e19-aefb-1e19e781dee5"), + IsActive = true + }, + new + { + 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("9d6babbb-3775-4780-ab60-740d6d51f228"), + IsActive = true + }, + new + { + 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("7386b804-bc20-4c7f-9a83-7de92a554f3d"), + IsActive = true + }, + new + { + 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("28228445-b041-46a1-b44f-c6b782bdb306"), + IsActive = true + }, + new + { + 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("e8f3a603-3493-41ef-b1fa-a0747c93903e"), + IsActive = true + }, + new + { + 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("8157e3d4-f167-47ce-a94c-a13de1ecbbea"), + IsActive = true + }, + new + { + 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("9c587621-e2a4-48b6-863e-7b95ba0d4615"), + IsActive = true + }, + new + { + 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("b5c889df-6dee-49fc-809f-396711d8eebb"), + IsActive = true + }, + new + { + 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("3a892eaf-8fd4-4db0-ada0-619af8e6da11"), + IsActive = true + }, + new + { + 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("c1504432-86de-4bdc-bf61-47bbb5e40f19"), + IsActive = true + }, + new + { + 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("ea52442b-2106-4ef0-9d99-73ef24620f8d"), + IsActive = true + }, + new + { + 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("e5d0003f-4d2e-4d49-9085-7141477d7536"), + IsActive = true + }, + new + { + 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("795c1b6a-d2f7-4be1-9ba4-cb9c9015bac1"), + IsActive = true + }, + new + { + 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("2c9af086-aec2-43f8-8bd6-bcd03e2acb31"), + IsActive = true + }, + new + { + 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("f2b6da6b-c0ca-4674-878b-13609840cf9a"), + IsActive = true + }, + new + { + 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("7580af1a-9570-4817-b2da-d0fdd25e6402"), + IsActive = true + }, + new + { + 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("1f6e4e53-7821-4c2a-a749-5be09b39e26d"), + IsActive = true + }, + new + { + 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("a41c5218-e402-47d1-a0a6-67c0fa0228a8"), + IsActive = true + }, + new + { + 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("98ef584e-58fb-462b-9cdf-439228aa3be4"), + IsActive = true + }, + new + { + 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("a4a06976-e48c-4cb4-9933-e9c222f6c80d"), + IsActive = true + }, + new + { + 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("068494b3-4cc7-4b70-b771-a73035f26ca4"), + IsActive = true + }, + new + { + 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("0e54ed62-3450-49af-9c57-b247e5b9a813"), + IsActive = true + }, + new + { + 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("80f35776-d931-435c-a02c-eed567593b5b"), + IsActive = true + }, + new + { + 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("69e6f0fb-75ef-41ee-a229-40ceef5bbf3e"), + IsActive = true + }, + new + { + 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("be136f8c-f847-4881-a723-60cf2cd874e4"), + IsActive = true + }, + new + { + 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("b3abf6c8-5317-4688-a5c4-426e843fb93c"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000051"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("756c4a39-7754-4b97-b967-29e7745084f6"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000052"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("0dee3b9c-f54e-4373-a33c-f774ab005787"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000053"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("0132bd0a-4baa-47fb-b2e2-e056dd662b0f"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000054"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("6cfbcfa8-c46e-4d57-8481-b6c695a6d2d8"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000055"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("04bdabea-6f18-4090-b21a-98afb14cc1b9"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000056"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("ae57197c-20f8-40be-9f3b-6a614c02403c"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000057"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("b27b94e1-1779-40ad-b212-53298735b0f2"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndPoint") + .HasColumnType("longtext"); + + b.Property("FK_OwnerUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Length") + .HasColumnType("decimal(65,30)"); + + b.Property("RouteName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartPoint") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_OwnerUserId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("Routes"); + + b.HasData( + new + { + Id = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Dehiwala", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 18.5m, + RouteName = "Colombo City Route", + StartPoint = "Colombo Fort" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Negombo", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 32.0m, + RouteName = "Negombo Coastal Route", + StartPoint = "Wattala" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Peradeniya", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 12.0m, + RouteName = "Kandy Town Route", + StartPoint = "Kandy Lake" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Matale", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 26.0m, + RouteName = "Matale Route", + StartPoint = "Kandy" + }); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CheckInAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInLatitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckInLongitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckOutAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeviationFlagged") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.HasIndex("FK_StoreId"); + + b.HasIndex("FK_UserId"); + + b.ToTable("RouteVisitLogs"); + + b.HasData( + new + { + Id = new Guid("66000000-0000-0000-0000-000000000001"), + CheckInAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9344m, + CheckInLongitude = 79.8428m, + CheckOutAt = new DateTime(2026, 8, 25, 9, 25, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = false + }, + new + { + Id = new Guid("66000000-0000-0000-0000-000000000002"), + CheckInAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9000m, + CheckInLongitude = 79.8654m, + CreatedAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000002"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = true + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Mode") + .HasColumnType("int"); + + b.Property("TotalCreditAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SaleReturns"); + + b.HasData( + new + { + Id = new Guid("51000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Mode = 0, + TotalCreditAmount = 300m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("SaleReturnItems"); + + b.HasData( + new + { + Id = new Guid("52000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 5m, + ReasonCode = 1, + UnitPrice = 60m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("SaleRoutes"); + + b.HasData( + new + { + Id = new Guid("69000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }, + new + { + Id = new Guid("69000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgentLimitExempt") + .HasColumnType("tinyint(1)"); + + b.Property("BaselineExpectedSales") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeFrom") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeTo") + .HasColumnType("datetime(6)"); + + b.Property("EligibleSegment") + .HasColumnType("longtext"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("FK_TriggerItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegionId") + .HasColumnType("char(36)"); + + b.Property("RuleConfig") + .HasColumnType("longtext"); + + b.Property("RuleType") + .HasColumnType("int"); + + b.Property("TriggerQty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_TriggerItemId"); + + b.HasIndex("RegionId"); + + b.ToTable("Schemes"); + + b.HasData( + new + { + Id = new Guid("48000000-0000-0000-0000-000000000001"), + AgentLimitExempt = false, + BaselineExpectedSales = 500000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + FK_TriggerItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FreeQty = 1m, + IsActive = true, + Name = "Buy 10 Coke Get 1 Free", + RuleType = 0, + TriggerQty = 10 + }, + new + { + Id = new Guid("48000000-0000-0000-0000-000000000002"), + AgentLimitExempt = false, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000004"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + FreeQty = 1m, + IsActive = true, + Name = "Snack Combo Bundle", + RuleType = 3 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("Qty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SchemeId", "FK_ItemId") + .IsUnique(); + + b.ToTable("SchemeBundleComponents"); + + b.HasData( + new + { + Id = new Guid("49000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000002"), + Qty = 5 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FieldInvoiceId") + .HasColumnType("char(36)"); + + b.Property("FreeItemCostValue") + .HasColumnType("decimal(65,30)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("RedeemedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_SchemeId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SchemeRedemptions"); + + b.HasData( + new + { + Id = new Guid("50000000-0000-0000-0000-000000000001"), + DiscountAmount = 0m, + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FreeItemCostValue = 40m, + FreeQty = 1m, + RedeemedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FulfilledAt") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .HasColumnType("char(36)") + .HasColumnName("InvoiceId"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedById") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("ShopPOs"); + + b.HasData( + new + { + Id = new Guid("33000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FulfilledAt = new DateTime(2026, 8, 25, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 0, + TotalAmount = 1200m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000003"), + ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 2, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityFulfilled") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("ShopPOItems"); + + b.HasData( + new + { + Id = new Guid("34000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 30, + QuantityFulfilled = 30, + UnitPrice = 60m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 24, + QuantityFulfilled = 24, + UnitPrice = 50m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Quantity = 10, + QuantityFulfilled = 0, + UnitPrice = 120m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + Quantity = 20, + QuantityFulfilled = 20, + UnitPrice = 120m + }); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("LocationId") + .HasColumnType("char(36)"); + + b.Property("LocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.HasIndex("FK_ItemId", "LocationId", "FK_ItemBatchId", "FK_ItemSerialNumberId") + .IsUnique(); + + b.ToTable("StockBalances"); + + b.HasData( + new + { + Id = new Guid("61000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 150m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 90m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000003"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("28000000-0000-0000-0000-000000000001"), + LocationType = 1, + Quantity = 20m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("FromLocationId") + .HasColumnType("char(36)"); + + b.Property("FromLocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ReferenceId") + .HasColumnType("char(36)"); + + b.Property("ToLocationId") + .HasColumnType("char(36)"); + + b.Property("ToLocationType") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("StockMovements"); + + b.HasData( + new + { + Id = new Guid("62000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + FromLocationType = 2, + Quantity = 200m, + ReferenceId = new Guid("43000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + ToLocationType = 0, + Type = 6 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + FromLocationType = 0, + Quantity = 50m, + ReferenceId = new Guid("35000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + ToLocationType = 1, + Type = 5 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + FromLocationType = 1, + Quantity = 30m, + ReferenceId = new Guid("32000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + ToLocationType = 2, + Type = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactNumber") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Latitude") + .HasColumnType("decimal(65,30)"); + + b.Property("Longitude") + .HasColumnType("decimal(65,30)"); + + b.Property("OneTimeAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("OutstandingBalance") + .HasColumnType("decimal(65,30)"); + + b.Property("OwnerName") + .HasColumnType("longtext"); + + b.Property("Segment") + .HasColumnType("longtext"); + + b.Property("StoreAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoreName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("Stores"); + + b.HasData( + new + { + Id = new Guid("26000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 50000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.9344m, + Longitude = 79.8428m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Saman Kumara", + Segment = "Gold", + StoreAddress = "12 York Street, Colombo Fort", + StoreName = "Fort Mini Mart" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 30000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.8508m, + Longitude = 79.8654m, + OneTimeAmount = 0m, + OutstandingBalance = 5000m, + OwnerName = "Nadeesha Silva", + Segment = "Silver", + StoreAddress = "45 Galle Road, Dehiwala", + StoreName = "Dehiwala Grocery" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 40000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 6.9890m, + Longitude = 79.8912m, + OneTimeAmount = 0m, + OutstandingBalance = 1200m, + OwnerName = "Ruwan Gunasekara", + Segment = "Gold", + StoreAddress = "88 Negombo Road, Wattala", + StoreName = "Wattala Supermarket" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 25000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 7.2094m, + Longitude = 79.8380m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Chamari Peiris", + StoreAddress = "5 Beach Road, Negombo", + StoreName = "Negombo Beach Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 35000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000003"), + IsActive = true, + Latitude = 7.2906m, + Longitude = 80.6337m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Dinesh Wickramasinghe", + Segment = "Key Account", + StoreAddress = "3 Lake Road, Kandy", + StoreName = "Kandy Lake View Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 20000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000004"), + IsActive = true, + Latitude = 7.4675m, + Longitude = 80.6234m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Ishara Bandara", + StoreAddress = "20 Main Street, Matale", + StoreName = "Matale Central Store" + }); + }); + + modelBuilder.Entity("Distribution.Models.StoreCreditMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .HasColumnType("int"); + + b.Property("FK_InvoiceId") + .HasColumnType("char(36)"); + + b.Property("FK_PaymentId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OccurredAt") + .HasColumnType("datetime(6)"); + + b.Property("RecordedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_InvoiceId"); + + b.HasIndex("FK_PaymentId"); + + b.HasIndex("FK_StoreId", "OccurredAt"); + + b.ToTable("StoreCreditMovements"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.ToTable("SubCategories"); + }); + + modelBuilder.Entity("Distribution.Models.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PerformanceScore") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.ToTable("Suppliers"); + + b.HasData( + new + { + Id = new Guid("38000000-0000-0000-0000-000000000001"), + ContactInfo = "+94 11 234 5678", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Ceylon Beverages Ltd", + PerformanceScore = 4.2m + }, + new + { + Id = new Guid("38000000-0000-0000-0000-000000000002"), + ContactInfo = "+94 11 876 5432", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snack Foods (Pvt) Ltd" + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EvidenceUrl") + .HasColumnType("longtext"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedByUserId") + .HasColumnType("char(36)"); + + b.Property("ResolutionAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("ResolutionNotes") + .HasColumnType("longtext"); + + b.Property("ResolutionType") + .HasColumnType("int"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("RaisedByUserId"); + + b.HasIndex("ResolvedByUserId"); + + b.ToTable("SupplierClaims"); + + b.HasData( + new + { + Id = new Guid("55000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "2 units of Coke 1.5L received damaged", + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + ResolutionAmount = 160m, + ResolutionNotes = "Credited against next invoice", + ResolutionType = 1, + ResolvedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + ResolvedByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierClaimId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssueType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SupplierClaimId"); + + b.ToTable("SupplierClaimItems"); + + b.HasData( + new + { + Id = new Guid("56000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_SupplierClaimId = new Guid("55000000-0000-0000-0000-000000000001"), + IsActive = true, + IssueType = 0, + Quantity = 2m + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_BankAccountId") + .HasColumnType("char(36)"); + + b.Property("FK_ChequePageId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("PaidAt") + .HasColumnType("datetime(6)"); + + b.Property("PaidByUserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_BankAccountId"); + + b.HasIndex("FK_ChequePageId"); + + b.HasIndex("FK_SupplierId"); + + b.ToTable("SupplierPayments"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierPaymentGRN", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllocatedAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierPaymentId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_SupplierPaymentId"); + + b.ToTable("SupplierPaymentGRNs"); + }); + + modelBuilder.Entity("Distribution.Models.SysConfig", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Key"); + + b.ToTable("SysConfigs"); + + b.HasData( + new + { + Key = "InvoiceCreationMode", + UpdatedAt = new DateTime(2026, 9, 17, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "ModeB" + }, + new + { + Key = "Feature.SerialNumberTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.BatchTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.ExpiryDateTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.WarrantyTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.CustomWarrantyOverride", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.DefaultWarrantyPeriodDays", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "0" + }); + }); + + modelBuilder.Entity("Distribution.Models.Uom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Uoms"); + + b.HasData( + new + { + Id = new Guid("22000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Piece" + }, + new + { + Id = new Guid("67000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Case" + }); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_FromUomId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ToUomId") + .HasColumnType("char(36)"); + + b.Property("Factor") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FromUomId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ToUomId"); + + b.ToTable("UomConversions"); + + b.HasData( + new + { + Id = new Guid("67000000-0000-0000-0000-000000000002"), + FK_FromUomId = new Guid("67000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ToUomId = new Guid("22000000-0000-0000-0000-000000000001"), + Factor = 24m + }); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ReportsToUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_ReportsToUserId"); + + b.HasIndex("FK_RoleId"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"), + FullName = "Sunil Fernando", + IsActive = true, + Username = "sunil.fernando" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Nimal Perera", + IsActive = true, + Username = "nimal.perera" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Kamal Silva", + IsActive = true, + Username = "kamal.silva" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Ruwan Jayasuriya", + IsActive = true, + Username = "ruwan.jayasuriya" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Priyantha Kumara", + IsActive = true, + Username = "priyantha.kumara" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"), + FullName = "Priya Rajapaksa", + IsActive = true, + Username = "priya.rajapaksa" + }, + new + { + Id = new Guid("70000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"), + FullName = "Channa De Silva", + IsActive = true, + Username = "channa.desilva" + }, + new + { + Id = new Guid("00000001-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 10, 13, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FullName = "Super Admin", + IsActive = true, + Username = "superadmin" + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RegionId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("UserRegionScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000006"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RouteId"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("UserRouteScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000002"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_StoreId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("UserStoreScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000003"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultWarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NumberPlate") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DefaultWarehouseId"); + + b.ToTable("Vehicles"); + + b.HasData( + new + { + Id = new Guid("28000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + NumberPlate = "CAB-1234", + RegNumber = "WP-CAB-1234", + Type = 2 + }, + new + { + Id = new Guid("28000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000002"), + IsActive = true, + NumberPlate = "KY-5678", + RegNumber = "CP-KY-5678", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Location") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("VehicleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("VehicleId"); + + b.ToTable("Warehouses"); + + b.HasData( + new + { + Id = new Guid("27000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Peliyagoda, Colombo", + Name = "Colombo Distribution Center", + Type = 0 + }, + new + { + Id = new Guid("27000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Katugastota, Kandy", + Name = "Kandy Fulfillment Center", + Type = 1 + }); + }); + + 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") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "SettledByUser") + .WithMany() + .HasForeignKey("SettledByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AgencyUser"); + + b.Navigation("SettledByUser"); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany() + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.HasOne("Distribution.Models.DriverProfile", "DriverProfile") + .WithMany() + .HasForeignKey("FK_DriverProfileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("FK_VehicleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DriverProfile"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.Expense", b => + { + b.HasOne("Distribution.Models.BankAccount", "BankAccount") + .WithMany() + .HasForeignKey("FK_BankAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ChequePage", "ChequePage") + .WithMany() + .HasForeignKey("FK_ChequePageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.RecurringExpenseTemplate", "RecurringTemplate") + .WithMany() + .HasForeignKey("FK_RecurringTemplateId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Distribution.Models.ExpenseVendor", "Vendor") + .WithMany() + .HasForeignKey("FK_VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BankAccount"); + + b.Navigation("Category"); + + b.Navigation("ChequePage"); + + b.Navigation("RecurringTemplate"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseAttachment", b => + { + b.HasOne("Distribution.Models.Expense", "Expense") + .WithMany("Attachments") + .HasForeignKey("FK_ExpenseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseCategory", b => + { + b.HasOne("Distribution.Models.ExpenseCategory", "Parent") + .WithMany() + .HasForeignKey("FK_ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Supplier"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany("Items") + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrderItem", "PurchaseOrderItem") + .WithMany() + .HasForeignKey("FK_PurchaseOrderItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrderItem"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Batches") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemBatch"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Serials") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.HasOne("Distribution.Models.Uom", "BaseUom") + .WithMany() + .HasForeignKey("FK_BaseUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Brand", "Brand") + .WithMany() + .HasForeignKey("FK_BrandId"); + + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("FK_SubCategoryId"); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("SubCategory"); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Items") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPOItem", "ShopPOItem") + .WithMany() + .HasForeignKey("FK_ShopPOItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPOItem"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Orders") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.HasOne("Distribution.Models.BankAccount", "BankAccount") + .WithMany() + .HasForeignKey("FK_BankAccountId"); + + b.HasOne("Distribution.Models.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("FK_InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ReceivedCheque", "ReceivedCheque") + .WithMany() + .HasForeignKey("FK_ReceivedChequeId"); + + b.Navigation("BankAccount"); + + b.Navigation("Invoice"); + + b.Navigation("ReceivedCheque"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.PriceList", "PriceList") + .WithMany("Items") + .HasForeignKey("FK_PriceListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceList"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany("Items") + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseReturn", "PurchaseReturn") + .WithMany("Items") + .HasForeignKey("FK_PurchaseReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + 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.RecurringExpenseTemplate", b => + { + b.HasOne("Distribution.Models.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ExpenseVendor", "Vendor") + .WithMany() + .HasForeignKey("FK_VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Category"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AgencyUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.HasOne("Distribution.Models.Permission", "Permission") + .WithMany() + .HasForeignKey("FK_PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.HasOne("Distribution.Models.User", "OwnerUser") + .WithMany() + .HasForeignKey("FK_OwnerUserId"); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OwnerUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.HasOne("Distribution.Models.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany("Items") + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "TriggerItem") + .WithMany() + .HasForeignKey("FK_TriggerItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("RegionId"); + + b.Navigation("FreeItem"); + + b.Navigation("Region"); + + b.Navigation("TriggerItem"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("BundleComponents") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Scheme"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("Redemptions") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("FreeItem"); + + b.Navigation("Scheme"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany("Items") + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Distribution.Models.StoreCreditMovement", b => + { + b.HasOne("Distribution.Models.Invoice", "Invoice") + .WithMany() + .HasForeignKey("FK_InvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Payment", "Payment") + .WithMany() + .HasForeignKey("FK_PaymentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Invoice"); + + b.Navigation("Payment"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany() + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "RaisedByUser") + .WithMany() + .HasForeignKey("RaisedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("RaisedByUser"); + + b.Navigation("ResolvedByUser"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SupplierClaim", "SupplierClaim") + .WithMany("Items") + .HasForeignKey("FK_SupplierClaimId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SupplierClaim"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierPayment", b => + { + b.HasOne("Distribution.Models.BankAccount", "BankAccount") + .WithMany() + .HasForeignKey("FK_BankAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.ChequePage", "ChequePage") + .WithMany() + .HasForeignKey("FK_ChequePageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BankAccount"); + + b.Navigation("ChequePage"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierPaymentGRN", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany() + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SupplierPayment", "SupplierPayment") + .WithMany("Allocations") + .HasForeignKey("FK_SupplierPaymentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GoodsReceipt"); + + b.Navigation("SupplierPayment"); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.HasOne("Distribution.Models.Uom", "FromUom") + .WithMany() + .HasForeignKey("FK_FromUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany("Conversions") + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Uom", "ToUom") + .WithMany() + .HasForeignKey("FK_ToUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.HasOne("Distribution.Models.User", "ReportsTo") + .WithMany() + .HasForeignKey("FK_ReportsToUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReportsTo"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.HasOne("Distribution.Models.Warehouse", null) + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("VehicleId"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.ChequeBook", b => + { + b.Navigation("Pages"); + }); + + modelBuilder.Entity("Distribution.Models.Expense", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Navigation("Batches"); + + b.Navigation("Serials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Navigation("Conversions"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Navigation("Items"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Navigation("BundleComponents"); + + b.Navigation("Redemptions"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierPayment", b => + { + b.Navigation("Allocations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Distribution/Migrations/20261021060022_AddExpenseModule.cs b/Distribution/Migrations/20261021060022_AddExpenseModule.cs new file mode 100644 index 0000000..fc658f1 --- /dev/null +++ b/Distribution/Migrations/20261021060022_AddExpenseModule.cs @@ -0,0 +1,1141 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Distribution.Migrations +{ + /// + public partial class AddExpenseModule : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ExpenseCategories", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Code = table.Column(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + FK_ParentCategoryId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExpenseCategories", x => x.Id); + table.ForeignKey( + name: "FK_ExpenseCategories_ExpenseCategories_FK_ParentCategoryId", + column: x => x.FK_ParentCategoryId, + principalTable: "ExpenseCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ExpenseVendors", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ContactInfo = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExpenseVendors", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "RecurringExpenseTemplates", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Name = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + FK_CategoryId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_WarehouseId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + FK_VendorId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Amount = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + Payee = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Frequency = table.Column(type: "int", nullable: false), + DayOfMonth = table.Column(type: "int", nullable: true), + DayOfWeek = table.Column(type: "int", nullable: true), + StartDate = table.Column(type: "datetime(6)", nullable: false), + EndDate = table.Column(type: "datetime(6)", nullable: true), + NextRunDate = table.Column(type: "datetime(6)", nullable: false), + AutoPost = table.Column(type: "tinyint(1)", nullable: false), + IsPaused = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RecurringExpenseTemplates", x => x.Id); + table.ForeignKey( + name: "FK_RecurringExpenseTemplates_ExpenseCategories_FK_CategoryId", + column: x => x.FK_CategoryId, + principalTable: "ExpenseCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_RecurringExpenseTemplates_ExpenseVendors_FK_VendorId", + column: x => x.FK_VendorId, + principalTable: "ExpenseVendors", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_RecurringExpenseTemplates_Warehouses_FK_WarehouseId", + column: x => x.FK_WarehouseId, + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Expenses", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_CategoryId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_WarehouseId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Amount = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + BillDate = table.Column(type: "datetime(6)", nullable: false), + DueDate = table.Column(type: "datetime(6)", nullable: true), + PeriodCovered = table.Column(type: "datetime(6)", nullable: true), + FK_VendorId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + Payee = table.Column(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + BillRef = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column(type: "int", nullable: false), + Method = table.Column(type: "int", nullable: true), + FK_BankAccountId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + FK_ChequePageId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + PaidAt = table.Column(type: "datetime(6)", nullable: true), + RecordedByUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + PostedByUserId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + PostedAt = table.Column(type: "datetime(6)", nullable: true), + PaidByUserId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + VoidedByUserId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + VoidedAt = table.Column(type: "datetime(6)", nullable: true), + VoidReason = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + FK_RecurringTemplateId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Expenses", x => x.Id); + table.ForeignKey( + name: "FK_Expenses_BankAccounts_FK_BankAccountId", + column: x => x.FK_BankAccountId, + principalTable: "BankAccounts", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Expenses_ChequePages_FK_ChequePageId", + column: x => x.FK_ChequePageId, + principalTable: "ChequePages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Expenses_ExpenseCategories_FK_CategoryId", + column: x => x.FK_CategoryId, + principalTable: "ExpenseCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Expenses_ExpenseVendors_FK_VendorId", + column: x => x.FK_VendorId, + principalTable: "ExpenseVendors", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Expenses_RecurringExpenseTemplates_FK_RecurringTemplateId", + column: x => x.FK_RecurringTemplateId, + principalTable: "RecurringExpenseTemplates", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Expenses_Warehouses_FK_WarehouseId", + column: x => x.FK_WarehouseId, + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ExpenseAttachments", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_ExpenseId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OriginalFileName = table.Column(type: "varchar(260)", maxLength: 260, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + StoredPath = table.Column(type: "varchar(500)", maxLength: 500, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ContentType = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + SizeBytes = table.Column(type: "bigint", nullable: false), + UploadedByUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ExpenseAttachments", x => x.Id); + table.ForeignKey( + name: "FK_ExpenseAttachments_Expenses_FK_ExpenseId", + column: x => x.FK_ExpenseId, + principalTable: "Expenses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.InsertData( + table: "ExpenseCategories", + columns: new[] { "Id", "Code", "CreatedAt", "FK_ParentCategoryId", "IsActive", "Name" }, + values: new object[,] + { + { new Guid("30000000-0000-0000-0000-000000000001"), "SAL", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Salaries" }, + { new Guid("30000000-0000-0000-0000-000000000002"), "UTL", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Utilities" }, + { new Guid("30000000-0000-0000-0000-000000000003"), "RNT", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Rent" }, + { new Guid("30000000-0000-0000-0000-000000000004"), "VEH", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Vehicle" }, + { new Guid("30000000-0000-0000-0000-000000000005"), "OFF", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Office" }, + { new Guid("30000000-0000-0000-0000-000000000006"), "BNK", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Bank Fees" }, + { new Guid("30000000-0000-0000-0000-000000000007"), "MSC", new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), null, true, "Miscellaneous" } + }); + + migrationBuilder.InsertData( + table: "Permissions", + columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" }, + values: new object[,] + { + { new Guid("20000000-0000-0000-0000-000000000051"), "expense.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View expenses, categories, vendors and recurring templates", true }, + { new Guid("20000000-0000-0000-0000-000000000052"), "expense.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create and edit draft expenses", true }, + { new Guid("20000000-0000-0000-0000-000000000053"), "expense.post", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Post a draft expense (approve it)", true }, + { new Guid("20000000-0000-0000-0000-000000000054"), "expense.pay", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Pay a posted expense (debit a bank account or issue a cheque)", true }, + { new Guid("20000000-0000-0000-0000-000000000055"), "expense.void", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Void an expense", true }, + { new Guid("20000000-0000-0000-0000-000000000056"), "expense.manage_categories", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Manage expense categories, titles and vendors", true }, + { new Guid("20000000-0000-0000-0000-000000000057"), "expense.manage_recurring", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Manage recurring expense templates", true } + }); + + 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("fa88ee94-4387-45a8-8e0b-67a3eb0d9537")); + + 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("007f96a6-dbb2-4745-be0f-7566ebab4f72")); + + 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("855bb33c-4507-435f-86e7-b86128d61099")); + + 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("86b89230-02bb-42d8-90d4-f93a75d729f8")); + + 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("2dcd53da-35e2-44c5-9fd9-84e5eeecb099")); + + 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("d32844a6-5e5d-4ac8-a56c-4bad70be7ceb")); + + 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("1a49ce67-81e2-4c6f-8e16-e07824964fc8")); + + 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("f4c6729b-5347-4be2-8686-db7347958f35")); + + 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("d35f14b7-34ca-4637-8a22-9ca8e3ac52ad")); + + 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("fcbb69ec-6023-4fb3-8d41-30658666015d")); + + 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("88c5c40c-d211-40d9-8bf5-df11e9995738")); + + 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("0d5c388f-08f1-4ee5-adcd-5e0d8e061128")); + + 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("7e726a5d-f487-4e29-8730-75fd64dc0100")); + + 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("9f6e9256-cb78-4a44-863e-6d31e69ff8d5")); + + 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("8fbdda8b-9eb3-490c-8c21-7a8f51c82404")); + + 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("56f26354-9548-42c2-8b58-30c936db1bcd")); + + 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("69d9de8f-107b-42d0-b345-e215b5cd5ccc")); + + 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("21fa8dd9-596d-4c2c-86de-0a27e4e8cfd4")); + + 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("750380a2-57da-4b98-b26f-2d893055e83f")); + + 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("9a628e80-953f-4b55-8f46-aafb51a2f03a")); + + 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("82e0dcfc-9df0-4135-b1bc-29bafca0e203")); + + 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("40b78431-d5fc-4ff3-b12c-cfd9560b24e4")); + + 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("9b3dfd17-cedd-4d7c-940c-5123f3a41b04")); + + 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("7230884d-f645-4aac-974e-9b4f8b46b9ff")); + + 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("9e688eea-f12f-4e19-aefb-1e19e781dee5")); + + 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("9d6babbb-3775-4780-ab60-740d6d51f228")); + + 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("7386b804-bc20-4c7f-9a83-7de92a554f3d")); + + 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("28228445-b041-46a1-b44f-c6b782bdb306")); + + 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("e8f3a603-3493-41ef-b1fa-a0747c93903e")); + + 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("8157e3d4-f167-47ce-a94c-a13de1ecbbea")); + + 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("9c587621-e2a4-48b6-863e-7b95ba0d4615")); + + 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("b5c889df-6dee-49fc-809f-396711d8eebb")); + + 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("3a892eaf-8fd4-4db0-ada0-619af8e6da11")); + + 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("c1504432-86de-4bdc-bf61-47bbb5e40f19")); + + 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("ea52442b-2106-4ef0-9d99-73ef24620f8d")); + + 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("e5d0003f-4d2e-4d49-9085-7141477d7536")); + + 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("795c1b6a-d2f7-4be1-9ba4-cb9c9015bac1")); + + 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("2c9af086-aec2-43f8-8bd6-bcd03e2acb31")); + + 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("f2b6da6b-c0ca-4674-878b-13609840cf9a")); + + 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("7580af1a-9570-4817-b2da-d0fdd25e6402")); + + 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("1f6e4e53-7821-4c2a-a749-5be09b39e26d")); + + 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("a41c5218-e402-47d1-a0a6-67c0fa0228a8")); + + 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("98ef584e-58fb-462b-9cdf-439228aa3be4")); + + 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("a4a06976-e48c-4cb4-9933-e9c222f6c80d")); + + 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("068494b3-4cc7-4b70-b771-a73035f26ca4")); + + 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("0e54ed62-3450-49af-9c57-b247e5b9a813")); + + 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("80f35776-d931-435c-a02c-eed567593b5b")); + + 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("69e6f0fb-75ef-41ee-a229-40ceef5bbf3e")); + + 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("be136f8c-f847-4881-a723-60cf2cd874e4")); + + 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("b3abf6c8-5317-4688-a5c4-426e843fb93c")); + + migrationBuilder.InsertData( + table: "ExpenseCategories", + columns: new[] { "Id", "Code", "CreatedAt", "FK_ParentCategoryId", "IsActive", "Name" }, + values: new object[,] + { + { new Guid("30000000-0000-0000-0000-000000000101"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000001"), true, "Basic Salary" }, + { new Guid("30000000-0000-0000-0000-000000000102"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000001"), true, "Bonus" }, + { new Guid("30000000-0000-0000-0000-000000000103"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000001"), true, "Overtime" }, + { new Guid("30000000-0000-0000-0000-000000000201"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000002"), true, "Electricity" }, + { new Guid("30000000-0000-0000-0000-000000000202"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000002"), true, "Water" }, + { new Guid("30000000-0000-0000-0000-000000000203"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000002"), true, "Internet" }, + { new Guid("30000000-0000-0000-0000-000000000301"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000003"), true, "Warehouse Rent" }, + { new Guid("30000000-0000-0000-0000-000000000302"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000003"), true, "Office Rent" }, + { new Guid("30000000-0000-0000-0000-000000000401"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000004"), true, "Fuel" }, + { new Guid("30000000-0000-0000-0000-000000000402"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000004"), true, "Maintenance" }, + { new Guid("30000000-0000-0000-0000-000000000403"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000004"), true, "Insurance" }, + { new Guid("30000000-0000-0000-0000-000000000501"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000005"), true, "Stationery" }, + { new Guid("30000000-0000-0000-0000-000000000502"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000005"), true, "Cleaning" }, + { new Guid("30000000-0000-0000-0000-000000000601"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000006"), true, "Service Charges" }, + { new Guid("30000000-0000-0000-0000-000000000701"), null, new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30000000-0000-0000-0000-000000000007"), true, "Other" } + }); + + migrationBuilder.InsertData( + table: "RolePermissions", + columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" }, + values: new object[,] + { + { new Guid("20000000-0000-0000-0000-000000000051"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("756c4a39-7754-4b97-b967-29e7745084f6"), true }, + { new Guid("20000000-0000-0000-0000-000000000052"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0dee3b9c-f54e-4373-a33c-f774ab005787"), true }, + { new Guid("20000000-0000-0000-0000-000000000053"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0132bd0a-4baa-47fb-b2e2-e056dd662b0f"), true }, + { new Guid("20000000-0000-0000-0000-000000000054"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("6cfbcfa8-c46e-4d57-8481-b6c695a6d2d8"), true }, + { new Guid("20000000-0000-0000-0000-000000000055"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("04bdabea-6f18-4090-b21a-98afb14cc1b9"), true }, + { new Guid("20000000-0000-0000-0000-000000000056"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ae57197c-20f8-40be-9f3b-6a614c02403c"), true }, + { new Guid("20000000-0000-0000-0000-000000000057"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b27b94e1-1779-40ad-b212-53298735b0f2"), true } + }); + + migrationBuilder.CreateIndex( + name: "IX_ExpenseAttachments_FK_ExpenseId", + table: "ExpenseAttachments", + column: "FK_ExpenseId"); + + migrationBuilder.CreateIndex( + name: "IX_ExpenseCategories_FK_ParentCategoryId_Name", + table: "ExpenseCategories", + columns: new[] { "FK_ParentCategoryId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_FK_BankAccountId", + table: "Expenses", + column: "FK_BankAccountId"); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_FK_CategoryId", + table: "Expenses", + column: "FK_CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_FK_ChequePageId", + table: "Expenses", + column: "FK_ChequePageId"); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_FK_RecurringTemplateId", + table: "Expenses", + column: "FK_RecurringTemplateId"); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_FK_VendorId", + table: "Expenses", + column: "FK_VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_FK_WarehouseId", + table: "Expenses", + column: "FK_WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_Expenses_Status_BillDate", + table: "Expenses", + columns: new[] { "Status", "BillDate" }); + + migrationBuilder.CreateIndex( + name: "IX_ExpenseVendors_Name", + table: "ExpenseVendors", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RecurringExpenseTemplates_FK_CategoryId", + table: "RecurringExpenseTemplates", + column: "FK_CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_RecurringExpenseTemplates_FK_VendorId", + table: "RecurringExpenseTemplates", + column: "FK_VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_RecurringExpenseTemplates_FK_WarehouseId", + table: "RecurringExpenseTemplates", + column: "FK_WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_RecurringExpenseTemplates_IsPaused_NextRunDate", + table: "RecurringExpenseTemplates", + columns: new[] { "IsPaused", "NextRunDate" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ExpenseAttachments"); + + migrationBuilder.DropTable( + name: "Expenses"); + + migrationBuilder.DropTable( + name: "RecurringExpenseTemplates"); + + migrationBuilder.DropTable( + name: "ExpenseCategories"); + + migrationBuilder.DropTable( + name: "ExpenseVendors"); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000051"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000052"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000053"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000054"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000055"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000056"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000057"), new Guid("10000000-0000-0000-0000-000000000007") }); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000051")); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000052")); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000053")); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000054")); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000055")); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000056")); + + migrationBuilder.DeleteData( + table: "Permissions", + keyColumn: "Id", + keyValue: new Guid("20000000-0000-0000-0000-000000000057")); + + 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("be712eda-98a5-4a30-820a-1737e84d1e4b")); + + 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("47ea9420-d6a0-4376-a2b8-211639d95b27")); + + 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("369eed1f-df4c-4131-a3c6-ebbe63d2dc91")); + + 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("14b6944f-2d4a-4378-a974-c6fc0f9dfb81")); + + 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("14a01e0b-7be5-4c65-ae6a-899f9aa7ad28")); + + 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("5d486ccd-3866-4e7d-8ef1-fb177fb637c1")); + + 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("d830e59b-f451-4bde-9a77-78daea5f9a61")); + + 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("096d3837-a649-4184-8987-2ea0dfa2d4a1")); + + 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("5e43bbde-689b-4d07-baf9-0764078b5586")); + + 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("f317d30c-1644-44f0-b0aa-6bcf1fa66f44")); + + 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("31ffac6f-2aa8-48ee-bc87-213f35933de4")); + + 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("fb525bc4-9f16-455a-bfd5-ca6f3e7ad141")); + + 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("2388918f-6392-4384-b269-0a1469f5bf4c")); + + 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("7159012e-5f25-4881-b5e8-5300e382332e")); + + 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("cc4b2e82-ad42-4440-92ba-9b2100bb5f84")); + + 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("6b15c133-ea0e-473d-b58a-7f140e9f03bf")); + + 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("c2dd52a9-380f-479f-8e5e-6b56bfc4738f")); + + 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("bf5f6c7a-2fde-4a01-83f4-aacf91333c4c")); + + 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("162a0346-c4c0-4c4e-96e9-aa8154d25b39")); + + 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("5cc76807-25d1-45e5-9da3-cfd6d6f3ad7c")); + + 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("51a2653e-4b1a-4f6f-80c2-f533b26109ea")); + + 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("e2a382e2-d712-4a97-a7ec-cd6232d77a78")); + + 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("b60260b5-70b0-4f9e-9ab9-ba4abbbdfb02")); + + 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("a73fe794-383c-4157-89cf-227c4e487a6d")); + + 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("a9743e96-15b2-4c1d-b410-698cd6db0b7b")); + + 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("a94d92ab-66d8-4dd9-8ba3-c242eb450e68")); + + 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("9cb1a028-a98e-492a-aafc-70456fe46c96")); + + 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("5bc99a62-f5d7-48fe-b25c-518e4774614c")); + + 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("eceee678-5cee-4ee9-b16d-8d8d21fbb5ea")); + + 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("3a049ed7-75b7-4b1d-b3bb-402ca77c350f")); + + 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("aeaab85c-5b02-4bc2-b0e3-318c0f5b07d3")); + + 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("b79800e0-baa6-4add-9374-7d1e2ed650e8")); + + 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("0da6bf0b-26ff-46b9-bb8c-f9c641b37a89")); + + 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("ff84aeeb-93c0-465d-9a4a-8b686ba7bc75")); + + 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("921f8073-7a66-49bb-ae23-cf8fcde12c9c")); + + 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("bff05501-4988-4c8a-b61d-e32389e795a8")); + + 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("2c4f7432-e697-4d65-97df-069f8913a662")); + + 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("18d27746-3178-4c57-afd5-b75f0061637f")); + + 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("a5d1978b-c497-4314-857b-a7e3b8323a53")); + + 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("88300763-b8c3-4bd2-803b-df0e43bc691e")); + + 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("7aaad5a9-4497-429f-b3dc-9b59664e2e19")); + + 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("2db130c1-4b6f-4719-b3bb-7bd8a0662d75")); + + 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("be349d14-38b5-4a13-a152-9ea055de26b1")); + + 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("5292e02d-9a7d-4a18-be14-86c2f3351cac")); + + 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("97021474-1218-4d21-9115-4b9b2726e38b")); + + 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("56f68355-7b1b-49e4-829c-1a4d47fc93de")); + + 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("3017c8cb-f9e4-41c8-ac2e-f03d23fe944a")); + + 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("619a08e5-f6cb-4515-8b4d-a094d19277d8")); + + 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("cb8b4e96-7026-4485-8b52-3145b5289350")); + + 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("f0f57dfd-21a5-4a0b-a77e-e641f00300fa")); + } + } +} diff --git a/Distribution/Migrations/AppDBContextModelSnapshot.cs b/Distribution/Migrations/AppDBContextModelSnapshot.cs index 016f5df..a500f2f 100644 --- a/Distribution/Migrations/AppDBContextModelSnapshot.cs +++ b/Distribution/Migrations/AppDBContextModelSnapshot.cs @@ -625,6 +625,390 @@ namespace Distribution.Migrations }); }); + modelBuilder.Entity("Distribution.Models.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("BillDate") + .HasColumnType("datetime(6)"); + + b.Property("BillRef") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DueDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_BankAccountId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_ChequePageId") + .HasColumnType("char(36)"); + + b.Property("FK_RecurringTemplateId") + .HasColumnType("char(36)"); + + b.Property("FK_VendorId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("PaidAt") + .HasColumnType("datetime(6)"); + + b.Property("PaidByUserId") + .HasColumnType("char(36)"); + + b.Property("Payee") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PeriodCovered") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedByUserId") + .HasColumnType("char(36)"); + + b.Property("RecordedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("VoidReason") + .HasColumnType("longtext"); + + b.Property("VoidedAt") + .HasColumnType("datetime(6)"); + + b.Property("VoidedByUserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_BankAccountId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_ChequePageId"); + + b.HasIndex("FK_RecurringTemplateId"); + + b.HasIndex("FK_VendorId"); + + b.HasIndex("FK_WarehouseId"); + + b.HasIndex("Status", "BillDate"); + + b.ToTable("Expenses"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ExpenseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StoredPath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UploadedByUserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ExpenseId"); + + b.ToTable("ExpenseAttachments"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ParentCategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ParentCategoryId", "Name") + .IsUnique(); + + b.ToTable("ExpenseCategories"); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + Code = "SAL", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Salaries" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + Code = "UTL", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Utilities" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000003"), + Code = "RNT", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Rent" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000004"), + Code = "VEH", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Vehicle" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000005"), + Code = "OFF", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Office" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000006"), + Code = "BNK", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Bank Fees" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000007"), + Code = "MSC", + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Miscellaneous" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000101"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Basic Salary" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000102"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Bonus" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000103"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Overtime" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000201"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000002"), + IsActive = true, + Name = "Electricity" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000202"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000002"), + IsActive = true, + Name = "Water" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000203"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000002"), + IsActive = true, + Name = "Internet" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000301"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000003"), + IsActive = true, + Name = "Warehouse Rent" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000302"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000003"), + IsActive = true, + Name = "Office Rent" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000401"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000004"), + IsActive = true, + Name = "Fuel" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000402"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000004"), + IsActive = true, + Name = "Maintenance" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000403"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000004"), + IsActive = true, + Name = "Insurance" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000501"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000005"), + IsActive = true, + Name = "Stationery" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000502"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000005"), + IsActive = true, + Name = "Cleaning" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000601"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000006"), + IsActive = true, + Name = "Service Charges" + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000701"), + CreatedAt = new DateTime(2026, 10, 21, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ParentCategoryId = new Guid("30000000-0000-0000-0000-000000000007"), + IsActive = true, + Name = "Other" + }); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseVendor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ExpenseVendors"); + }); + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => { b.Property("Id") @@ -2029,6 +2413,62 @@ namespace Distribution.Migrations CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), Description = "Update system configuration settings", IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000051"), + Code = "expense.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View expenses, categories, vendors and recurring templates", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000052"), + Code = "expense.create", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Create and edit draft expenses", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000053"), + Code = "expense.post", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Post a draft expense (approve it)", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000054"), + Code = "expense.pay", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Pay a posted expense (debit a bank account or issue a cheque)", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000055"), + Code = "expense.void", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Void an expense", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000056"), + Code = "expense.manage_categories", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Manage expense categories, titles and vendors", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000057"), + Code = "expense.manage_recurring", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Manage recurring expense templates", + IsActive = true }); }); @@ -2431,6 +2871,79 @@ namespace Distribution.Migrations b.ToTable("ReceivedCheques"); }); + modelBuilder.Entity("Distribution.Models.RecurringExpenseTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("AutoPost") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfMonth") + .HasColumnType("int"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_VendorId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("Frequency") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsPaused") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("Payee") + .HasColumnType("longtext"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_VendorId"); + + b.HasIndex("FK_WarehouseId"); + + b.HasIndex("IsPaused", "NextRunDate"); + + b.ToTable("RecurringExpenseTemplates"); + }); + modelBuilder.Entity("Distribution.Models.Region", b => { b.Property("Id") @@ -2746,7 +3259,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("2a4abd94-1720-49a7-89f3-4e835781b40a"), + Id = new Guid("fa88ee94-4387-45a8-8e0b-67a3eb0d9537"), IsActive = true }, new @@ -2754,7 +3267,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("dea99413-dea7-4353-adb6-2684f628ed6d"), + Id = new Guid("007f96a6-dbb2-4745-be0f-7566ebab4f72"), IsActive = true }, new @@ -2762,7 +3275,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("9ca45530-7f89-4aab-81f3-34b70ddf9bde"), + Id = new Guid("855bb33c-4507-435f-86e7-b86128d61099"), IsActive = true }, new @@ -2770,7 +3283,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("12a89406-c43a-483f-9b78-97ca64aca95b"), + Id = new Guid("86b89230-02bb-42d8-90d4-f93a75d729f8"), IsActive = true }, new @@ -2778,7 +3291,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("4859dde8-21b4-412b-9bfd-8adc5b68410a"), + Id = new Guid("2dcd53da-35e2-44c5-9fd9-84e5eeecb099"), IsActive = true }, new @@ -2786,7 +3299,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("74038a6f-5fd5-4bc5-a78a-a83602bc22db"), + Id = new Guid("d32844a6-5e5d-4ac8-a56c-4bad70be7ceb"), IsActive = true }, new @@ -2794,7 +3307,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("ae20b416-a508-4ffc-92a1-e98aaeadb8b6"), + Id = new Guid("1a49ce67-81e2-4c6f-8e16-e07824964fc8"), IsActive = true }, new @@ -2802,7 +3315,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("1fe41f0e-a01e-4481-b4f6-451de1e9481d"), + Id = new Guid("f4c6729b-5347-4be2-8686-db7347958f35"), IsActive = true }, new @@ -2810,7 +3323,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("252ff69b-fe2b-48dc-97cc-29bc1b605c4d"), + Id = new Guid("d35f14b7-34ca-4637-8a22-9ca8e3ac52ad"), IsActive = true }, new @@ -2818,7 +3331,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("78617da6-504a-4c8f-b4db-22679230b6de"), + Id = new Guid("fcbb69ec-6023-4fb3-8d41-30658666015d"), IsActive = true }, new @@ -2826,7 +3339,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("731cc179-ec73-4b36-8345-3b7f961738cd"), + Id = new Guid("88c5c40c-d211-40d9-8bf5-df11e9995738"), IsActive = true }, new @@ -2834,7 +3347,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("462c0177-9604-4dde-8946-be1f003c26d5"), + Id = new Guid("0d5c388f-08f1-4ee5-adcd-5e0d8e061128"), IsActive = true }, new @@ -2842,7 +3355,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("9401e464-23ea-4c0f-89b3-166a5a52c2b2"), + Id = new Guid("7e726a5d-f487-4e29-8730-75fd64dc0100"), IsActive = true }, new @@ -2850,7 +3363,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("ad7d3861-64b7-482c-90d1-34a1c588a4bc"), + Id = new Guid("9f6e9256-cb78-4a44-863e-6d31e69ff8d5"), IsActive = true }, new @@ -2858,7 +3371,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("33bbfe87-566f-4f7f-a140-8557242b38bd"), + Id = new Guid("8fbdda8b-9eb3-490c-8c21-7a8f51c82404"), IsActive = true }, new @@ -2866,7 +3379,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("9e20caa7-c5fd-461b-8345-0688a436461f"), + Id = new Guid("56f26354-9548-42c2-8b58-30c936db1bcd"), IsActive = true }, new @@ -2874,7 +3387,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("75dfbd5d-43de-41d8-b8c3-1fc222d4ecec"), + Id = new Guid("69d9de8f-107b-42d0-b345-e215b5cd5ccc"), IsActive = true }, new @@ -2882,7 +3395,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("52dcde7a-122e-4721-9c81-f1a413eff01e"), + Id = new Guid("21fa8dd9-596d-4c2c-86de-0a27e4e8cfd4"), IsActive = true }, new @@ -2890,7 +3403,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("1646eda4-31b4-47ba-b11b-77b5a1e389d4"), + Id = new Guid("750380a2-57da-4b98-b26f-2d893055e83f"), IsActive = true }, new @@ -2898,7 +3411,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("cf3a7f0c-9877-494f-a07d-d693edec4cde"), + Id = new Guid("9a628e80-953f-4b55-8f46-aafb51a2f03a"), IsActive = true }, new @@ -2906,7 +3419,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("b36eefd7-54a5-465a-9b3b-43418b6c8f5b"), + Id = new Guid("82e0dcfc-9df0-4135-b1bc-29bafca0e203"), IsActive = true }, new @@ -2914,7 +3427,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("615f12f1-07d3-463b-8259-f7e3926284b4"), + Id = new Guid("40b78431-d5fc-4ff3-b12c-cfd9560b24e4"), IsActive = true }, new @@ -2922,7 +3435,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("f3b1cf5c-ecb6-4b63-982c-feaa9afc73f5"), + Id = new Guid("9b3dfd17-cedd-4d7c-940c-5123f3a41b04"), IsActive = true }, new @@ -2930,7 +3443,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("b370f645-4b32-4275-8ae1-c92714d30731"), + Id = new Guid("7230884d-f645-4aac-974e-9b4f8b46b9ff"), IsActive = true }, new @@ -2938,7 +3451,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("1575ffb9-123f-4625-8c5f-d68d56a33bd3"), + Id = new Guid("9e688eea-f12f-4e19-aefb-1e19e781dee5"), IsActive = true }, new @@ -2946,7 +3459,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("8341015a-a078-4f4a-9d32-c8cc0c92654a"), + Id = new Guid("9d6babbb-3775-4780-ab60-740d6d51f228"), IsActive = true }, new @@ -2954,7 +3467,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("6078f921-206b-431c-956a-50ed8d99a004"), + Id = new Guid("7386b804-bc20-4c7f-9a83-7de92a554f3d"), IsActive = true }, new @@ -2962,7 +3475,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("67a3e0b2-26e7-4d57-8318-c17b6f4788f2"), + Id = new Guid("28228445-b041-46a1-b44f-c6b782bdb306"), IsActive = true }, new @@ -2970,7 +3483,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("5949601b-3458-4fd3-adc7-3ca4ede91272"), + Id = new Guid("e8f3a603-3493-41ef-b1fa-a0747c93903e"), IsActive = true }, new @@ -2978,7 +3491,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("4c85ff7a-854b-4c07-8101-fa14a8699dd8"), + Id = new Guid("8157e3d4-f167-47ce-a94c-a13de1ecbbea"), IsActive = true }, new @@ -2986,7 +3499,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("36837347-3d26-453b-a445-97331e82fa43"), + Id = new Guid("9c587621-e2a4-48b6-863e-7b95ba0d4615"), IsActive = true }, new @@ -2994,7 +3507,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("437e060a-ebf0-4cc0-bbb0-63ad7be9f177"), + Id = new Guid("b5c889df-6dee-49fc-809f-396711d8eebb"), IsActive = true }, new @@ -3002,7 +3515,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("7a589bc4-ce8c-437a-b91b-a4153f36c9fd"), + Id = new Guid("3a892eaf-8fd4-4db0-ada0-619af8e6da11"), IsActive = true }, new @@ -3010,7 +3523,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("b13dffb9-2a6c-4d57-8194-d0fb93eedc0a"), + Id = new Guid("c1504432-86de-4bdc-bf61-47bbb5e40f19"), IsActive = true }, new @@ -3018,7 +3531,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("fcbd982e-a963-4818-9361-d0af9b112e79"), + Id = new Guid("ea52442b-2106-4ef0-9d99-73ef24620f8d"), IsActive = true }, new @@ -3026,7 +3539,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("0e544a73-7236-41a7-acbb-3fe7b807383c"), + Id = new Guid("e5d0003f-4d2e-4d49-9085-7141477d7536"), IsActive = true }, new @@ -3034,7 +3547,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("b85957c9-3f01-4043-8659-4cfee8b83c2c"), + Id = new Guid("795c1b6a-d2f7-4be1-9ba4-cb9c9015bac1"), IsActive = true }, new @@ -3042,7 +3555,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("42c8240d-dbfa-45b9-a87f-4130683722d1"), + Id = new Guid("2c9af086-aec2-43f8-8bd6-bcd03e2acb31"), IsActive = true }, new @@ -3050,7 +3563,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("b7693644-4a51-4fe9-93e3-e70b0093b772"), + Id = new Guid("f2b6da6b-c0ca-4674-878b-13609840cf9a"), IsActive = true }, new @@ -3058,7 +3571,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("34b1213b-bc79-4d79-b2e6-d607abdd9467"), + Id = new Guid("7580af1a-9570-4817-b2da-d0fdd25e6402"), IsActive = true }, new @@ -3066,7 +3579,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("feb1c1cb-3b64-4ff7-8f67-ff1146ced327"), + Id = new Guid("1f6e4e53-7821-4c2a-a749-5be09b39e26d"), IsActive = true }, new @@ -3074,7 +3587,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("1e2ef870-17a5-494b-9d62-a2725fd420f4"), + Id = new Guid("a41c5218-e402-47d1-a0a6-67c0fa0228a8"), IsActive = true }, new @@ -3082,7 +3595,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("efb242ec-dfee-4338-9b36-ecca208d4035"), + Id = new Guid("98ef584e-58fb-462b-9cdf-439228aa3be4"), IsActive = true }, new @@ -3090,7 +3603,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("a9fb51c9-19b5-4297-b798-00ae0a6dced5"), + Id = new Guid("a4a06976-e48c-4cb4-9933-e9c222f6c80d"), IsActive = true }, new @@ -3098,7 +3611,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("1ded2baa-f75d-4305-91e4-1778b0c43f95"), + Id = new Guid("068494b3-4cc7-4b70-b771-a73035f26ca4"), IsActive = true }, new @@ -3106,7 +3619,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("ffe7a2b9-fe25-46a6-bd71-fa170cf39511"), + Id = new Guid("0e54ed62-3450-49af-9c57-b247e5b9a813"), IsActive = true }, new @@ -3114,7 +3627,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("efca52dc-4ae3-4135-a293-77da540943d7"), + Id = new Guid("80f35776-d931-435c-a02c-eed567593b5b"), IsActive = true }, new @@ -3122,7 +3635,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("f1901597-9fb0-45a8-841f-eab0a7af9287"), + Id = new Guid("69e6f0fb-75ef-41ee-a229-40ceef5bbf3e"), IsActive = true }, new @@ -3130,7 +3643,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("763e5759-b12c-434a-b117-ea09d773f4f4"), + Id = new Guid("be136f8c-f847-4881-a723-60cf2cd874e4"), IsActive = true }, new @@ -3138,7 +3651,63 @@ 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("ec9db3b2-59b0-48f3-a6e0-0c81d6fbc854"), + Id = new Guid("b3abf6c8-5317-4688-a5c4-426e843fb93c"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000051"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("756c4a39-7754-4b97-b967-29e7745084f6"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000052"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("0dee3b9c-f54e-4373-a33c-f774ab005787"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000053"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("0132bd0a-4baa-47fb-b2e2-e056dd662b0f"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000054"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("6cfbcfa8-c46e-4d57-8481-b6c695a6d2d8"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000055"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("04bdabea-6f18-4090-b21a-98afb14cc1b9"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000056"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("ae57197c-20f8-40be-9f3b-6a614c02403c"), + IsActive = true + }, + new + { + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000007"), + FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000057"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("b27b94e1-1779-40ad-b212-53298735b0f2"), IsActive = true }); }); @@ -5030,6 +5599,73 @@ namespace Distribution.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Distribution.Models.Expense", b => + { + b.HasOne("Distribution.Models.BankAccount", "BankAccount") + .WithMany() + .HasForeignKey("FK_BankAccountId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ChequePage", "ChequePage") + .WithMany() + .HasForeignKey("FK_ChequePageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.RecurringExpenseTemplate", "RecurringTemplate") + .WithMany() + .HasForeignKey("FK_RecurringTemplateId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Distribution.Models.ExpenseVendor", "Vendor") + .WithMany() + .HasForeignKey("FK_VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BankAccount"); + + b.Navigation("Category"); + + b.Navigation("ChequePage"); + + b.Navigation("RecurringTemplate"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseAttachment", b => + { + b.HasOne("Distribution.Models.Expense", "Expense") + .WithMany("Attachments") + .HasForeignKey("FK_ExpenseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("Distribution.Models.ExpenseCategory", b => + { + b.HasOne("Distribution.Models.ExpenseCategory", "Parent") + .WithMany() + .HasForeignKey("FK_ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => { b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") @@ -5449,6 +6085,31 @@ namespace Distribution.Migrations b.Navigation("DepositBankAccount"); }); + modelBuilder.Entity("Distribution.Models.RecurringExpenseTemplate", b => + { + b.HasOne("Distribution.Models.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ExpenseVendor", "Vendor") + .WithMany() + .HasForeignKey("FK_VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Category"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => { b.HasOne("Distribution.Models.User", "AgencyUser") @@ -6016,6 +6677,11 @@ namespace Distribution.Migrations b.Navigation("Pages"); }); + modelBuilder.Entity("Distribution.Models.Expense", b => + { + b.Navigation("Attachments"); + }); + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => { b.Navigation("Items"); diff --git a/Distribution/Models/Enums.cs b/Distribution/Models/Enums.cs index 680a7a8..bfb426b 100644 --- a/Distribution/Models/Enums.cs +++ b/Distribution/Models/Enums.cs @@ -68,4 +68,10 @@ namespace Distribution.Models public enum ReceivedChequeStatusAction { Deposit, Clear, Return, Cancel } public enum ReceivedFromType { Customer, Supplier, Other } + + public enum ExpenseStatus { Draft, Posted, Paid, Void } + + public enum ExpenseAction { Draft, Post, Pay } + + public enum RecurringFrequency { Weekly, Monthly, Quarterly, Yearly } } diff --git a/Distribution/Models/Expense.cs b/Distribution/Models/Expense.cs new file mode 100644 index 0000000..0571713 --- /dev/null +++ b/Distribution/Models/Expense.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Models +{ + public class Expense : Template + { + /// Leaf category (Title). Parent is the user-facing Category group. + public Guid FK_CategoryId { get; set; } + [ForeignKey("FK_CategoryId")] + public ExpenseCategory? Category { get; set; } + + /// Branch the expense is attributed to. Null = company-wide. + public Guid? FK_WarehouseId { get; set; } + [ForeignKey("FK_WarehouseId")] + public Warehouse? Warehouse { get; set; } + + [Precision(18, 2)] + public decimal Amount { get; set; } + + public DateTime BillDate { get; set; } + public DateTime? DueDate { get; set; } + /// First day of the month the expense covers (YYYY-MM-01). + public DateTime? PeriodCovered { get; set; } + + public Guid? FK_VendorId { get; set; } + [ForeignKey("FK_VendorId")] + public ExpenseVendor? Vendor { get; set; } + + public string? Payee { get; set; } + public string? BillRef { get; set; } + public string? Description { get; set; } + + public ExpenseStatus Status { get; set; } = ExpenseStatus.Draft; + + /// Payment settlement — null until Status transitions to Paid. + public PaymentMethod? Method { get; set; } + public Guid? FK_BankAccountId { get; set; } + [ForeignKey("FK_BankAccountId")] + public BankAccount? BankAccount { get; set; } + + public Guid? FK_ChequePageId { get; set; } + [ForeignKey("FK_ChequePageId")] + public ChequePage? ChequePage { get; set; } + + public DateTime? PaidAt { get; set; } + + public Guid RecordedByUserId { get; set; } + public Guid? PostedByUserId { get; set; } + public DateTime? PostedAt { get; set; } + public Guid? PaidByUserId { get; set; } + public Guid? VoidedByUserId { get; set; } + public DateTime? VoidedAt { get; set; } + public string? VoidReason { get; set; } + + /// Null for manually-entered expenses; set when generated by run-due. + public Guid? FK_RecurringTemplateId { get; set; } + [ForeignKey("FK_RecurringTemplateId")] + public RecurringExpenseTemplate? RecurringTemplate { get; set; } + + public ICollection Attachments { get; set; } = []; + } +} diff --git a/Distribution/Models/ExpenseAttachment.cs b/Distribution/Models/ExpenseAttachment.cs new file mode 100644 index 0000000..de7f963 --- /dev/null +++ b/Distribution/Models/ExpenseAttachment.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class ExpenseAttachment : Template + { + public Guid FK_ExpenseId { get; set; } + [ForeignKey("FK_ExpenseId")] + public Expense? Expense { get; set; } + + public string OriginalFileName { get; set; } = string.Empty; + + /// Absolute path on disk. Served through a streaming endpoint, never statically. + public string StoredPath { get; set; } = string.Empty; + + public string ContentType { get; set; } = string.Empty; + public long SizeBytes { get; set; } + + public Guid UploadedByUserId { get; set; } + } +} diff --git a/Distribution/Models/ExpenseCategory.cs b/Distribution/Models/ExpenseCategory.cs new file mode 100644 index 0000000..b0d91a8 --- /dev/null +++ b/Distribution/Models/ExpenseCategory.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + /// + /// Two-level tree: top-level categories (e.g. "Salaries", "Utilities") and + /// child "titles" (e.g. "Bonus", "Electricity"). Expenses link to a leaf (Title). + /// + public class ExpenseCategory : Template + { + public string Name { get; set; } = string.Empty; + public string? Code { get; set; } + + public Guid? FK_ParentCategoryId { get; set; } + [ForeignKey("FK_ParentCategoryId")] + public ExpenseCategory? Parent { get; set; } + } +} diff --git a/Distribution/Models/ExpenseVendor.cs b/Distribution/Models/ExpenseVendor.cs new file mode 100644 index 0000000..f531ab7 --- /dev/null +++ b/Distribution/Models/ExpenseVendor.cs @@ -0,0 +1,12 @@ +namespace Distribution.Models +{ + /// + /// Lightweight vendor directory used only by the Expenses module. Kept + /// separate from Supplier (which has performance scores, PO/GRN linkage). + /// + public class ExpenseVendor : Template + { + public string Name { get; set; } = string.Empty; + public string? ContactInfo { get; set; } + } +} diff --git a/Distribution/Models/RecurringExpenseTemplate.cs b/Distribution/Models/RecurringExpenseTemplate.cs new file mode 100644 index 0000000..bc2e54e --- /dev/null +++ b/Distribution/Models/RecurringExpenseTemplate.cs @@ -0,0 +1,47 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Models +{ + /// + /// Blueprint for a recurring expense. Materialized into Expense rows by + /// POST /api/recurring-expenses/run-due based on NextRunDate. + /// + public class RecurringExpenseTemplate : Template + { + public string Name { get; set; } = string.Empty; + + public Guid FK_CategoryId { get; set; } + [ForeignKey("FK_CategoryId")] + public ExpenseCategory? Category { get; set; } + + public Guid? FK_WarehouseId { get; set; } + [ForeignKey("FK_WarehouseId")] + public Warehouse? Warehouse { get; set; } + + public Guid? FK_VendorId { get; set; } + [ForeignKey("FK_VendorId")] + public ExpenseVendor? Vendor { get; set; } + + [Precision(18, 2)] + public decimal Amount { get; set; } + + public string? Payee { get; set; } + public string? Description { get; set; } + + public RecurringFrequency Frequency { get; set; } + /// 1..31 for Monthly/Quarterly/Yearly. Ignored for Weekly. + public int? DayOfMonth { get; set; } + /// 0..6 (Sun=0) for Weekly. Ignored otherwise. + public int? DayOfWeek { get; set; } + + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public DateTime NextRunDate { get; set; } + + /// When true, generated expenses jump straight to Posted; otherwise Draft. + public bool AutoPost { get; set; } + + public bool IsPaused { get; set; } + } +} diff --git a/Distribution/Program.cs b/Distribution/Program.cs index 1afd464..3dff0fb 100644 --- a/Distribution/Program.cs +++ b/Distribution/Program.cs @@ -31,6 +31,10 @@ using Distribution.Features.Scheme; using Distribution.Features.Store; using Distribution.Features.Supplier; using Distribution.Features.SupplierPayment; +using Distribution.Features.Expense; +using Distribution.Features.ExpenseCategory; +using Distribution.Features.ExpenseVendor; +using Distribution.Features.RecurringExpense; using Distribution.Features.Sync; using Distribution.Features.Uom; using Distribution.Features.User; @@ -103,6 +107,10 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -143,6 +151,11 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Distribution/appsettings.Production.json b/Distribution/appsettings.Production.json new file mode 100644 index 0000000..a803290 --- /dev/null +++ b/Distribution/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "Expense": { + "AttachmentsRoot": "/home/distribution/core/assets/expenses" + } +} diff --git a/Distribution/appsettings.json b/Distribution/appsettings.json index 58ac152..c5b122e 100644 --- a/Distribution/appsettings.json +++ b/Distribution/appsettings.json @@ -26,6 +26,11 @@ "StoreManagement": { "UnverifiedKycOrderThreshold": 0 }, + "Expense": { + // Dev default. Production overrides to /home/distribution/core/assets/expenses + // via appsettings.Production.json or an ENV var. + "AttachmentsRoot": "D:\\erp-storage\\expenses" + }, "AuthService": { "BaseUrl": "" } diff --git a/distribution_fe/app/(dashboard)/payments/expense-categories/page.tsx b/distribution_fe/app/(dashboard)/payments/expense-categories/page.tsx new file mode 100644 index 0000000..c4a6062 --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expense-categories/page.tsx @@ -0,0 +1,227 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Field, FieldLabel } from '@/components/ui/field' +import { Input } from '@/components/ui/input' +import { toast } from '@/components/ui/toast' +import { EmptyState } from '@/components/shared/empty-state' +import { useTopLoader } from '@/components/shared/top-loader' +import { useAuth } from '@/contexts/auth-context' +import { expenseCategories as api, getApiError } from '@/lib/api/index' +import type { ExpenseCategoryResponse } from '@/types/expenses' +import { HugeiconsIcon } from '@hugeicons/react' +import { Add01Icon, Folder01Icon } from '@hugeicons/core-free-icons' + +export default function ExpenseCategoriesPage() { + const { hasPermission } = useAuth() + const canManage = hasPermission('expense.manage_categories') + + const [tree, setTree] = useState([]) + const [loading, setLoading] = useState(true) + const [dialogOpen, setDialogOpen] = useState(null) + const [editing, setEditing] = useState(null) + + useTopLoader(loading) + + const load = useCallback(async () => { + setLoading(true) + try { setTree(await api.tree()) } + catch (err) { toast.add({ title: 'Failed to load categories', description: getApiError(err), type: 'error' }) } + finally { setLoading(false) } + }, []) + + useEffect(() => { load() }, [load]) + + return ( +
+
+
+

Expense Categories

+

+ Two-level tree. Expenses are tagged with a Title (a child under a Category). +

+
+ {canManage && ( + + )} +
+ +
+ {!loading && tree.length === 0 ? ( + + ) : ( +
    + {tree.map(parent => ( +
  • +
    +
    + {parent.name} + {parent.code && {parent.code}} + {!parent.isActive && Inactive} +
    + {canManage && ( +
    + + +
    + )} +
    + {parent.children.length > 0 && ( +
      + {parent.children.map(child => ( +
    • +
      + {child.name} + {!child.isActive && Inactive} +
      + {canManage && ( + + )} +
    • + ))} +
    + )} +
  • + ))} +
+ )} +
+ + setDialogOpen(null)} + onSaved={() => { setDialogOpen(null); load() }} + /> + + setEditing(null)} + onSaved={() => { setEditing(null); load() }} + /> +
+ ) +} + +function CategoryDialog({ + open, parentId, parentName, onClose, onSaved, +}: { + open: boolean + parentId: string | null + parentName?: string + onClose: () => void + onSaved: () => void +}) { + const [name, setName] = useState('') + const [code, setCode] = useState('') + const [busy, setBusy] = useState(false) + + useEffect(() => { if (open) { setName(''); setCode('') } }, [open]) + + async function submit() { + if (!name.trim()) return + setBusy(true) + try { + await api.create({ name: name.trim(), code: code || null, parentCategoryId: parentId }) + toast.add({ title: 'Saved', type: 'success' }) + onSaved() + } catch (err) { + toast.add({ title: 'Failed to save', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + {parentId ? `New title under ${parentName}` : 'New category'} + +
+ + Name + setName(e.target.value)} /> + + {!parentId && ( + + Code (optional) + setCode(e.target.value)} placeholder="e.g. SAL" /> + + )} +
+ + + + +
+
+ ) +} + +function EditCategoryDialog({ + category, onClose, onSaved, +}: { + category: ExpenseCategoryResponse | null + onClose: () => void + onSaved: () => void +}) { + const [name, setName] = useState('') + const [code, setCode] = useState('') + const [active, setActive] = useState(true) + const [busy, setBusy] = useState(false) + + useEffect(() => { + if (category) { setName(category.name); setCode(category.code ?? ''); setActive(category.isActive) } + }, [category]) + + if (!category) return null + + async function submit() { + if (!category) return + if (!name.trim()) return + setBusy(true) + try { + await api.update(category.id, { name: name.trim(), code: code || null, isActive: active }) + toast.add({ title: 'Updated', type: 'success' }) + onSaved() + } catch (err) { + toast.add({ title: 'Failed to update', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + Edit {category.name} + +
+ + Name + setName(e.target.value)} /> + + + Code + setCode(e.target.value)} /> + + +
+ + + + +
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expense-recurring/page.tsx b/distribution_fe/app/(dashboard)/payments/expense-recurring/page.tsx new file mode 100644 index 0000000..0d1a7ee --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expense-recurring/page.tsx @@ -0,0 +1,345 @@ +'use client' + +import { useCallback, useEffect, useMemo, useState } from 'react' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogFooter, 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { toast } from '@/components/ui/toast' +import { EmptyState } from '@/components/shared/empty-state' +import { useTopLoader } from '@/components/shared/top-loader' +import { useAuth } from '@/contexts/auth-context' +import { + expenseCategories as categoriesApi, + recurringExpenses as api, + warehouses as warehousesApi, + getApiError, + getPaginatedItems, +} from '@/lib/api/index' +import type { + CreateRecurringExpenseRequest, + ExpenseCategoryResponse, + RecurringExpenseResponse, + RecurringFrequency, +} from '@/types/expenses' +import type { WarehouseResponse } from '@/types/api' +import { HugeiconsIcon } from '@hugeicons/react' +import { Add01Icon, Calendar03Icon } from '@hugeicons/core-free-icons' + +const fmtDate = (v: string) => + new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) +const fmtAmount = (n: number) => + `Rs. ${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` + +export default function RecurringExpensesPage() { + const { hasPermission } = useAuth() + const canManage = hasPermission('expense.manage_recurring') + + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [editing, setEditing] = useState(null) + const [runBusy, setRunBusy] = useState(false) + + useTopLoader(loading || runBusy) + + const load = useCallback(async () => { + setLoading(true) + try { setItems((await api.list({ pageSize: 100 })).items) } + catch (err) { toast.add({ title: 'Failed to load templates', description: getApiError(err), type: 'error' }) } + finally { setLoading(false) } + }, []) + + useEffect(() => { load() }, [load]) + + async function toggle(t: RecurringExpenseResponse) { + try { + await api.toggle(t.id, !t.isPaused) + await load() + } catch (err) { + toast.add({ title: 'Failed to toggle', description: getApiError(err), type: 'error' }) + } + } + + async function runDue() { + setRunBusy(true) + try { + const res = await api.runDue() + toast.add({ title: `Generated ${res.generated} expense(s)`, type: 'success' }) + await load() + } catch (err) { + toast.add({ title: 'Failed to run', description: getApiError(err), type: 'error' }) + } finally { setRunBusy(false) } + } + + return ( +
+
+
+

Recurring Expenses

+

+ Templates that materialize into drafts (or posted expenses) when their next-run date arrives. +

+
+
+ {canManage && ( + <> + + + + )} +
+
+ +
+ {!loading && items.length === 0 ? ( + + ) : ( + + + + Name + Category + Branch + Amount + Frequency + Next run + Status + + + + + {items.map(t => ( + + {t.name} + {t.categoryName} + {t.warehouseName ?? Company-wide} + {fmtAmount(t.amount)} + {t.frequency}{t.autoPost ? ' · auto-post' : ''} + {fmtDate(t.nextRunDate)} + {t.isPaused ? Paused : Active} + + {canManage && ( + <> + + + + )} + + + ))} + +
+ )} +
+ + setEditing(null)} + onSaved={() => { setEditing(null); load() }} + /> +
+ ) +} + +function TemplateDialog({ + template, onClose, onSaved, +}: { + template: RecurringExpenseResponse | 'new' | null + onClose: () => void + onSaved: () => void +}) { + const isNew = template === 'new' + const current = template !== 'new' ? template : null + + const [cats, setCats] = useState([]) + const [whs, setWhs] = useState([]) + + const [name, setName] = useState('') + const [categoryId, setCategoryId] = useState('') + const [titleId, setTitleId] = useState('') + const [warehouseId, setWarehouseId] = useState('__company__') + const [amount, setAmount] = useState('') + const [frequency, setFrequency] = useState('Monthly') + const [dayOfMonth, setDayOfMonth] = useState('1') + const [dayOfWeek, setDayOfWeek] = useState('1') + const [startDate, setStartDate] = useState(() => new Date().toISOString().slice(0, 10)) + const [endDate, setEndDate] = useState('') + const [autoPost, setAutoPost] = useState(false) + const [description, setDescription] = useState('') + const [busy, setBusy] = useState(false) + + useEffect(() => { + if (!template) return + categoriesApi.tree().then(setCats).catch(() => {}) + warehousesApi.list({ pageSize: 200 }).then(r => setWhs(getPaginatedItems(r))).catch(() => {}) + + if (current) { + setName(current.name) + // Figure out parent category for the title + categoriesApi.tree().then(tree => { + const parent = tree.find(c => c.children.some(ch => ch.id === current.categoryId)) + setCategoryId(parent?.id ?? '') + setTitleId(current.categoryId) + }) + setWarehouseId(current.warehouseId ?? '__company__') + setAmount(String(current.amount)) + setFrequency(current.frequency) + setDayOfMonth(current.dayOfMonth ? String(current.dayOfMonth) : '1') + setDayOfWeek(current.dayOfWeek != null ? String(current.dayOfWeek) : '1') + setStartDate(current.startDate.slice(0, 10)) + setEndDate(current.endDate?.slice(0, 10) ?? '') + setAutoPost(current.autoPost) + setDescription(current.description ?? '') + } else { + setName(''); setCategoryId(''); setTitleId(''); setWarehouseId('__company__'); setAmount('') + setFrequency('Monthly'); setDayOfMonth('1'); setDayOfWeek('1') + setStartDate(new Date().toISOString().slice(0, 10)); setEndDate(''); setAutoPost(false); setDescription('') + } + }, [template, current]) + + const titles = useMemo(() => cats.find(c => c.id === categoryId)?.children ?? [], [cats, categoryId]) + + if (!template) return null + + async function submit() { + if (!name.trim() || !titleId) { toast.add({ title: 'Name and Title are required', type: 'error' }); return } + const amt = parseFloat(amount) + if (!amt || amt <= 0) { toast.add({ title: 'Amount must be greater than zero', type: 'error' }); return } + + const body: CreateRecurringExpenseRequest = { + name: name.trim(), + categoryId: titleId, + warehouseId: warehouseId === '__company__' ? null : warehouseId, + amount: amt, + payee: null, + description: description || null, + frequency, + dayOfMonth: frequency === 'Weekly' ? null : parseInt(dayOfMonth), + dayOfWeek: frequency === 'Weekly' ? parseInt(dayOfWeek) : null, + startDate, + endDate: endDate || null, + autoPost, + } + + setBusy(true) + try { + if (isNew) await api.create(body) + else if (current) await api.update(current.id, body) + toast.add({ title: 'Saved', type: 'success' }) + onSaved() + } catch (err) { + toast.add({ title: 'Failed', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + {isNew ? 'New Recurring Template' : `Edit ${current?.name}`} + +
+ + Name + setName(e.target.value)} placeholder="e.g. Office rent — Colombo HQ" /> + +
+ + Category + + + + Title + + +
+
+ + Branch + + + + Amount + setAmount(e.target.value)} /> + +
+
+ + Frequency + + + {frequency === 'Weekly' ? ( + + Day of week + + + ) : ( + + Day of month + setDayOfMonth(e.target.value)} /> + + )} +
+ +
+
+
+ + Start date + setStartDate(e.target.value)} /> + + + End date (optional) + setEndDate(e.target.value)} /> + +
+ + Description + setDescription(e.target.value)} /> + +
+ + + + +
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expense-vendors/page.tsx b/distribution_fe/app/(dashboard)/payments/expense-vendors/page.tsx new file mode 100644 index 0000000..0c0c843 --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expense-vendors/page.tsx @@ -0,0 +1,174 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Field, FieldLabel } from '@/components/ui/field' +import { Input } from '@/components/ui/input' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { toast } from '@/components/ui/toast' +import { EmptyState } from '@/components/shared/empty-state' +import { useTopLoader } from '@/components/shared/top-loader' +import { useAuth } from '@/contexts/auth-context' +import { expenseVendors as api, getApiError } from '@/lib/api/index' +import type { ExpenseVendorResponse } from '@/types/expenses' +import { HugeiconsIcon } from '@hugeicons/react' +import { Add01Icon, UserGroup03Icon } from '@hugeicons/core-free-icons' + +export default function ExpenseVendorsPage() { + const { hasPermission } = useAuth() + const canManage = hasPermission('expense.manage_categories') + + const [items, setItems] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const pageSize = 20 + const [search, setSearch] = useState('') + const [loading, setLoading] = useState(true) + const [editing, setEditing] = useState(null) + + useTopLoader(loading) + + const load = useCallback(async () => { + setLoading(true) + try { + const res = await api.list({ page, pageSize, search: search || undefined }) + setItems(res.items); setTotal(res.total) + } catch (err) { + toast.add({ title: 'Failed to load vendors', description: getApiError(err), type: 'error' }) + } finally { setLoading(false) } + }, [page, search]) + + useEffect(() => { load() }, [load]) + + const pageCount = Math.max(1, Math.ceil(total / pageSize)) + + return ( +
+
+
+

Expense Vendors

+

Lightweight vendor directory used by the Expenses module.

+
+ {canManage && ( + + )} +
+ +
+ { setPage(1); setSearch(e.target.value) }} /> +
+ +
+ {!loading && items.length === 0 ? ( + + ) : ( + + + + Name + Contact + Status + + + + + {items.map(v => ( + + {v.name} + {v.contactInfo ?? '—'} + {v.isActive ? Active : Inactive} + + {canManage && } + + + ))} + +
+ )} +
+ +
+ {total} vendor(s) +
+ + {page} / {pageCount} + +
+
+ + setEditing(null)} + onSaved={() => { setEditing(null); load() }} + /> +
+ ) +} + +function VendorDialog({ + vendor, onClose, onSaved, +}: { + vendor: ExpenseVendorResponse | 'new' | null + onClose: () => void + onSaved: () => void +}) { + const [name, setName] = useState('') + const [contact, setContact] = useState('') + const [active, setActive] = useState(true) + const [busy, setBusy] = useState(false) + + useEffect(() => { + if (vendor && vendor !== 'new') { setName(vendor.name); setContact(vendor.contactInfo ?? ''); setActive(vendor.isActive) } + else if (vendor === 'new') { setName(''); setContact(''); setActive(true) } + }, [vendor]) + + if (!vendor) return null + + async function submit() { + if (!vendor || !name.trim()) return + setBusy(true) + try { + if (vendor === 'new') await api.create({ name: name.trim(), contactInfo: contact || null }) + else await api.update(vendor.id, { name: name.trim(), contactInfo: contact || null, isActive: active }) + toast.add({ title: 'Saved', type: 'success' }) + onSaved() + } catch (err) { + toast.add({ title: 'Failed', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + {vendor === 'new' ? 'Add Vendor' : `Edit ${vendor.name}`} + +
+ + Name + setName(e.target.value)} /> + + + Contact + setContact(e.target.value)} placeholder="Phone / email" /> + + {vendor !== 'new' && ( + + )} +
+ + + + +
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/[id]/page.tsx b/distribution_fe/app/(dashboard)/payments/expenses/[id]/page.tsx new file mode 100644 index 0000000..d09b55c --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/[id]/page.tsx @@ -0,0 +1,6 @@ +import { ExpenseViewClient } from '../_components/expense-view-client' + +export default async function ExpenseViewPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params + return +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/_components/expense-view-client.tsx b/distribution_fe/app/(dashboard)/payments/expenses/_components/expense-view-client.tsx new file mode 100644 index 0000000..e2a4a67 --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/_components/expense-view-client.tsx @@ -0,0 +1,255 @@ +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react' +import { useRouter } from 'next/navigation' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Field, FieldLabel } from '@/components/ui/field' +import { Input } from '@/components/ui/input' +import { toast } from '@/components/ui/toast' +import { useTopLoader } from '@/components/shared/top-loader' +import { useAuth } from '@/contexts/auth-context' +import { expenses as expensesApi, getApiError } from '@/lib/api/index' +import type { ExpenseResponse, ExpenseStatus } from '@/types/expenses' + +import { PayExpenseDialog } from './pay-expense-dialog' + +const STATUS_VARIANT: Record = { + Draft: 'secondary', Posted: 'outline', Paid: 'default', Void: 'destructive', +} + +const fmtDate = (v: string | null) => + v ? new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) : '—' + +const fmtAmount = (n: number) => + `Rs. ${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` + +export function ExpenseViewClient({ expenseId }: { expenseId: string }) { + const router = useRouter() + const { hasPermission } = useAuth() + const canPost = hasPermission('expense.post') + const canPay = hasPermission('expense.pay') + const canVoid = hasPermission('expense.void') + const canCreate = hasPermission('expense.create') + + const [expense, setExpense] = useState(null) + const [loading, setLoading] = useState(true) + const [payOpen, setPayOpen] = useState(false) + const [voidOpen, setVoidOpen] = useState(false) + const [voidReason, setVoidReason] = useState('') + const [busy, setBusy] = useState(false) + const fileInputRef = useRef(null) + + useTopLoader(loading || busy) + + const load = useCallback(async () => { + setLoading(true) + try { setExpense(await expensesApi.get(expenseId)) } + catch (err) { + toast.add({ title: 'Failed to load expense', description: getApiError(err), type: 'error' }) + router.push('/payments/expenses') + } finally { setLoading(false) } + }, [expenseId, router]) + + useEffect(() => { load() }, [load]) + + async function handlePost() { + setBusy(true) + try { await expensesApi.post(expenseId); toast.add({ title: 'Expense posted', type: 'success' }); await load() } + catch (err) { toast.add({ title: 'Failed to post', description: getApiError(err), type: 'error' }) } + finally { setBusy(false) } + } + + async function confirmVoid() { + const reason = voidReason.trim() + if (!reason) return + setBusy(true) + try { + await expensesApi.void(expenseId, reason) + toast.add({ title: 'Expense voided', type: 'success' }) + setVoidOpen(false) + setVoidReason('') + await load() + } + catch (err) { toast.add({ title: 'Failed to void', description: getApiError(err), type: 'error' }) } + finally { setBusy(false) } + } + + async function handleFile(e: React.ChangeEvent) { + const file = e.target.files?.[0] + if (!file) return + setBusy(true) + try { + await expensesApi.uploadAttachment(expenseId, file) + toast.add({ title: 'Attachment uploaded', type: 'success' }) + await load() + } catch (err) { + toast.add({ title: 'Upload failed', description: getApiError(err), type: 'error' }) + } finally { + setBusy(false) + if (fileInputRef.current) fileInputRef.current.value = '' + } + } + + async function handleDeleteAttachment(id: string) { + if (!window.confirm('Delete this attachment?')) return + setBusy(true) + try { await expensesApi.deleteAttachment(id); await load() } + catch (err) { toast.add({ title: 'Delete failed', description: getApiError(err), type: 'error' }) } + finally { setBusy(false) } + } + + if (loading && !expense) return null + if (!expense) return null + + return ( +
+
+
+ +

+ {fmtAmount(expense.amount)} + {expense.status} +

+

+ {expense.parentCategoryName ? `${expense.parentCategoryName} → ${expense.categoryName}` : expense.categoryName} + {' · '} + {expense.warehouseName ?? 'Company-wide'} +

+
+ +
+ {expense.status === 'Draft' && canPost && ( + + )} + {expense.status === 'Posted' && canPay && ( + + )} + {expense.status !== 'Void' && expense.status !== 'Paid' && canVoid && ( + + )} +
+
+ +
+ + + + + + + + +
+ + {expense.description && ( +
+

Description

+

{expense.description}

+
+ )} + +
+
+

Attachments

+ {canCreate && ( + <> + + + + )} +
+ + {expense.attachments.length === 0 ? ( +

No attachments.

+ ) : ( +
    + {expense.attachments.map(a => ( +
  • +
    + + {a.originalFileName} + + + {(a.sizeBytes / 1024).toFixed(1)} KB · {a.contentType} + +
    + {canCreate && ( + + )} +
  • + ))} +
+ )} +
+ + setPayOpen(false)} + amount={expense.amount} + onConfirm={async (payment) => { + setBusy(true) + try { + await expensesApi.pay(expenseId, payment) + toast.add({ title: 'Expense paid', type: 'success' }) + setPayOpen(false) + await load() + } catch (err) { + toast.add({ title: 'Payment failed', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + }} + /> + + !v && setVoidOpen(false)}> + + + Void expense + This marks the expense as void. Enter a reason — it will be stored on the record. + + + Reason * + setVoidReason(e.target.value)} + placeholder="e.g. Duplicate entry" + /> + + + + + + + +
+ ) +} + +function Stat({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/_components/expenses-client.tsx b/distribution_fe/app/(dashboard)/payments/expenses/_components/expenses-client.tsx new file mode 100644 index 0000000..2e62c2c --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/_components/expenses-client.tsx @@ -0,0 +1,152 @@ +'use client' + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useRouter } from 'next/navigation' +import { HugeiconsIcon } from '@hugeicons/react' +import { Add01Icon, ReceiptDollarIcon } from '@hugeicons/core-free-icons' + +import { DataTable } from '@/components/shared/data-table' +import type { TableConfig } from '@/components/shared/data-table' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { toast } from '@/components/ui/toast' +import { EmptyState } from '@/components/shared/empty-state' +import { useTopLoader } from '@/components/shared/top-loader' +import { expenses as expensesApi, getApiError } from '@/lib/api/index' +import { useAuth } from '@/contexts/auth-context' +import type { ExpenseResponse, ExpenseStatus } from '@/types/expenses' + +const STATUS_VARIANT: Record = { + Draft: 'secondary', + Posted: 'outline', + Paid: 'default', + Void: 'destructive', +} + +const fmtDate = (v: string) => + new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) +const fmtAmount = (n: number) => `Rs. ${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` +const truncId = (id: string) => id.substring(0, 8) + +export function ExpensesClient() { + const router = useRouter() + const { hasPermission } = useAuth() + const canCreate = hasPermission('expense.create') + + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(20) + + useTopLoader(loading) + + const fetchData = useCallback(async (p: number, ps: number) => { + setLoading(true) + try { + const res = await expensesApi.list({ page: p, pageSize: ps }) + setData(res.items) + setTotal(res.total) + } catch (err) { + toast.add({ title: 'Failed to load expenses', description: getApiError(err), type: 'error' }) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { fetchData(page, pageSize) }, [fetchData, page, pageSize]) + + const tableConfig = useMemo>(() => ({ + rowKey: 'id', + serverSide: true, + stickyHeader: true, + pagination: { enabled: true, pageSize, pageSizeOptions: [10, 20, 50] }, + emptyState: ( + router.push('/payments/expenses/new')}> + + New Expense + + ) : undefined} + /> + ), + columns: [ + { + key: 'billDate', title: 'Bill Date', fields: ['billDate'], width: 120, + cellRender: (r) => fmtDate(r.billDate), + }, + { + key: 'category', title: 'Category', fields: [{ key: 'categoryName', primary: true }], minWidth: 180, + cellRender: (r) => ( +
+ {r.categoryName} + {r.parentCategoryName && ( + {r.parentCategoryName} + )} +
+ ), + }, + { + key: 'vendor', title: 'Vendor / Payee', fields: ['vendorName'], minWidth: 160, + cellRender: (r) => {r.vendorName ?? r.payee ?? '—'}, + }, + { + key: 'amount', title: 'Amount', fields: ['amount'], width: 140, + align: 'right', + cellRender: (r) => {fmtAmount(r.amount)}, + }, + { + key: 'status', title: 'Status', fields: ['status'], width: 120, + cellRender: (r) => {r.status}, + }, + { + key: 'branch', title: 'Branch', fields: ['warehouseName'], width: 160, + cellRender: (r) => {r.warehouseName ?? Company-wide}, + }, + { + key: 'id', title: 'Ref', fields: ['id'], width: 110, + cellRender: (r) => {truncId(r.id)}, + }, + ], + rowActions: { + actions: () => [ + { + action: 'view', + label: 'View expense', + onClick: (r: ExpenseResponse) => router.push(`/payments/expenses/${r.id}`), + }, + ], + }, + }), [pageSize, router, canCreate]) + + return ( + <> +
+
+

Expenses

+

+ Record and track company expenses — salaries, utilities, rent, fuel and more +

+
+ {canCreate && ( + + )} +
+ + { setPage(p.page); setPageSize(p.pageSize) }} + /> + + ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/_components/new-expense-client.tsx b/distribution_fe/app/(dashboard)/payments/expenses/_components/new-expense-client.tsx new file mode 100644 index 0000000..32adaa0 --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/_components/new-expense-client.tsx @@ -0,0 +1,379 @@ +'use client' + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useRouter } from 'next/navigation' +import { HugeiconsIcon } from '@hugeicons/react' +import { Add01Icon } from '@hugeicons/core-free-icons' + +import { Button } from '@/components/ui/button' +import { Field, 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' +import { useTopLoader } from '@/components/shared/top-loader' +import { useAuth } from '@/contexts/auth-context' +import { + expenseCategories as categoriesApi, + expenseVendors as vendorsApi, + expenses as expensesApi, + warehouses as warehousesApi, + getApiError, + getPaginatedItems, +} from '@/lib/api/index' +import type { + CreateExpenseCategoryRequest, + CreateExpenseRequest, + CreateExpenseVendorRequest, + ExpenseAction, + ExpenseCategoryResponse, + ExpensePaymentRequest, + ExpenseVendorResponse, +} from '@/types/expenses' +import type { WarehouseResponse } from '@/types/api' + +import { PayExpenseDialog } from './pay-expense-dialog' + +export function NewExpenseClient() { + const router = useRouter() + const { hasPermission } = useAuth() + const canPost = hasPermission('expense.post') + const canPay = hasPermission('expense.pay') && canPost + const canManageCats = hasPermission('expense.manage_categories') + + const [categories, setCategories] = useState([]) + const [vendors, setVendors] = useState([]) + const [whs, setWhs] = useState([]) + + const [categoryId, setCategoryId] = useState('') + const [titleId, setTitleId] = useState('') + const [warehouseId, setWarehouseId] = useState('__company__') + const [amount, setAmount] = useState('') + const [billDate, setBillDate] = useState(() => new Date().toISOString().slice(0, 10)) + const [dueDate, setDueDate] = useState('') + const [periodCovered, setPeriodCovered] = useState('') + const [vendorId, setVendorId] = useState('') + const [payee, setPayee] = useState('') + const [billRef, setBillRef] = useState('') + const [description, setDescription] = useState('') + + const [submitting, setSubmitting] = useState(false) + const [payDialogOpen, setPayDialogOpen] = useState(false) + const [addTitleOpen, setAddTitleOpen] = useState(false) + const [addVendorOpen, setAddVendorOpen] = useState(false) + + useTopLoader(submitting) + + const loadCategories = useCallback(async () => { + try { setCategories(getPaginatedItems(await categoriesApi.tree())) } + catch (err) { toast.add({ title: 'Failed to load categories', description: getApiError(err), type: 'error' }) } + }, []) + + useEffect(() => { + loadCategories() + vendorsApi.list({ pageSize: 200 }) + .then(r => setVendors(getPaginatedItems(r))) + .catch(() => {}) + warehousesApi.list({ pageSize: 200 }) + .then(r => setWhs(getPaginatedItems(r))) + .catch(() => {}) + }, [loadCategories]) + + const titles = useMemo( + () => categories.find(c => c.id === categoryId)?.children ?? [], + [categoryId, categories], + ) + + function buildBody(): CreateExpenseRequest | null { + if (!titleId) { toast.add({ title: 'Title is required', type: 'error' }); return null } + const amt = parseFloat(amount) + if (!amt || amt <= 0) { toast.add({ title: 'Amount must be greater than zero', type: 'error' }); return null } + if (!billDate) { toast.add({ title: 'Bill date is required', type: 'error' }); return null } + + return { + categoryId: titleId, + warehouseId: warehouseId === '__company__' ? null : warehouseId, + amount: amt, + billDate, + dueDate: dueDate || null, + periodCovered: periodCovered ? `${periodCovered}-01` : null, + vendorId: vendorId || null, + payee: payee || null, + billRef: billRef || null, + description: description || null, + } + } + + async function submit(action: ExpenseAction, payment?: ExpensePaymentRequest) { + const body = buildBody() + if (!body) return + if (action === 'Pay') body.payment = payment + + setSubmitting(true) + try { + const created = await expensesApi.create(body, action) + toast.add({ title: `Expense ${action === 'Draft' ? 'saved as draft' : action === 'Post' ? 'posted' : 'posted and paid'}`, type: 'success' }) + router.push(`/payments/expenses/${created.id}`) + } catch (err) { + toast.add({ title: 'Failed to save expense', description: getApiError(err), type: 'error' }) + } finally { + setSubmitting(false) + } + } + + return ( +
+ + +

New Expense

+

+ Record a bill — pick its category and title, then save it as a draft, post it, or post and pay it in one step. +

+ +
+ + Branch + + + +
+ + Category * + + + +
+ Title * + {canManageCats && categoryId && ( + + )} +
+ +
+
+ +
+ + Amount * + setAmount(e.target.value)} /> + + + Bill date * + setBillDate(e.target.value)} /> + + + Due date + setDueDate(e.target.value)} /> + +
+ +
+ +
+ Vendor (optional) + {canManageCats && ( + + )} +
+ +
+ + Payee + setPayee(e.target.value)} placeholder="Name on the cheque / cash paid to" /> + + + Bill / account ref + setBillRef(e.target.value)} placeholder="Bill no, meter reading, …" /> + +
+ +
+ + Period covered + setPeriodCovered(e.target.value)} /> + + + Description + setDescription(e.target.value)} /> + +
+
+ +
+ + + {canPost && ( + + )} + {canPay && ( + + )} +
+ + setPayDialogOpen(false)} + amount={parseFloat(amount) || 0} + onConfirm={async (payment) => { setPayDialogOpen(false); await submit('Pay', payment) }} + /> + + setAddTitleOpen(false)} + parentCategoryId={categoryId} + onCreated={async (createdId) => { await loadCategories(); setTitleId(createdId) }} + /> + + setAddVendorOpen(false)} + onCreated={(v) => { setVendors(prev => [...prev, v]); setVendorId(v.id) }} + /> +
+ ) +} + +// ─── inline sub-components kept here to avoid file explosion ────────────────── + +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' + +function AddTitleDialog({ + open, onClose, parentCategoryId, onCreated, +}: { + open: boolean + onClose: () => void + parentCategoryId: string + onCreated: (createdId: string) => void +}) { + const [name, setName] = useState('') + const [busy, setBusy] = useState(false) + + async function submit() { + if (!name.trim()) return + setBusy(true) + try { + const body: CreateExpenseCategoryRequest = { name: name.trim(), parentCategoryId } + const created = await categoriesApi.create(body) + toast.add({ title: 'Title added', type: 'success' }) + onCreated(created.id) + setName('') + onClose() + } catch (err) { + toast.add({ title: 'Failed to add title', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + Add Title + Create a new title under the selected category. + + + Name + setName(e.target.value)} /> + + + + + + + + ) +} + +function AddVendorDialog({ + open, onClose, onCreated, +}: { + open: boolean + onClose: () => void + onCreated: (v: ExpenseVendorResponse) => void +}) { + const [name, setName] = useState('') + const [contact, setContact] = useState('') + const [busy, setBusy] = useState(false) + + async function submit() { + if (!name.trim()) return + setBusy(true) + try { + const body: CreateExpenseVendorRequest = { name: name.trim(), contactInfo: contact || null } + const created = await vendorsApi.create(body) + toast.add({ title: 'Vendor added', type: 'success' }) + onCreated(created) + setName(''); setContact('') + onClose() + } catch (err) { + toast.add({ title: 'Failed to add vendor', description: getApiError(err), type: 'error' }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + Add Vendor + Vendors are shared across the Expenses module only. + +
+ + Name + setName(e.target.value)} /> + + + Contact (optional) + setContact(e.target.value)} placeholder="Phone / email" /> + +
+ + + + +
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/_components/pay-expense-dialog.tsx b/distribution_fe/app/(dashboard)/payments/expenses/_components/pay-expense-dialog.tsx new file mode 100644 index 0000000..c986c90 --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/_components/pay-expense-dialog.tsx @@ -0,0 +1,156 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' + +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Field, FieldLabel } from '@/components/ui/field' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { toast } from '@/components/ui/toast' +import { + bankAccounts as bankAccountsApi, + chequeBooks as chequeBooksApi, + getApiError, +} from '@/lib/api/index' +import { + BankAccountType, + ChequePageIssueStatus, + type BankAccountResponse, + type ChequePageResponse, +} from '@/types/payments' +import type { ExpensePaymentRequest } from '@/types/expenses' + +type Method = 'Cash' | 'Cheque' + +/** + * Shared dialog for both "Save & pay" (on new expense) and "Pay" (on view). + * Cash → pick a BankAccount (Petty Cash is a Cash account; one-click button up top). + * Cheque → pick an Unused page from an Active cheque book. + */ +export function PayExpenseDialog({ + open, onClose, amount, onConfirm, +}: { + open: boolean + onClose: () => void + amount: number + onConfirm: (payment: ExpensePaymentRequest) => void | Promise +}) { + const [method, setMethod] = useState('Cash') + const [bankAccountId, setBankAccountId] = useState('') + const [chequePageId, setChequePageId] = useState('') + const [accounts, setAccounts] = useState([]) + const [pages, setPages] = useState([]) + const [busy, setBusy] = useState(false) + + useEffect(() => { + if (!open) return + setMethod('Cash'); setBankAccountId(''); setChequePageId('') + bankAccountsApi.list() + .then(list => setAccounts(list.filter(a => a.isActive !== false))) + .catch(() => {}) + }, [open]) + + useEffect(() => { + if (!open || method !== 'Cheque') return + chequeBooksApi.list({ status: 'Active' as never, page: 1, pageSize: 50 }) + .then(async res => { + const expanded = await Promise.all(res.items.map(b => chequeBooksApi.get(b.chequeBookNo, true))) + const unused: ChequePageResponse[] = [] + expanded.forEach(book => book.pages.filter(p => p.issueStatus === ChequePageIssueStatus.Unused).forEach(p => unused.push(p))) + setPages(unused) + }) + .catch(err => toast.add({ title: 'Failed to load cheque pages', description: getApiError(err), type: 'error' })) + }, [open, method]) + + const defaultPettyCash = useMemo( + () => accounts.find(a => a.accountType === BankAccountType.Cash && a.isDefault), + [accounts], + ) + + async function submit() { + if (method === 'Cash' && !bankAccountId) { toast.add({ title: 'Pick an account', type: 'error' }); return } + if (method === 'Cheque' && !chequePageId) { toast.add({ title: 'Pick a cheque page', type: 'error' }); return } + + setBusy(true) + try { + await onConfirm({ + method, + bankAccountId: method === 'Cash' ? bankAccountId : null, + chequePageId: method === 'Cheque' ? chequePageId : null, + }) + } finally { setBusy(false) } + } + + return ( + !v && onClose()}> + + + Pay Expense + + {amount > 0 ? `Settling Rs. ${amount.toLocaleString(undefined, { minimumFractionDigits: 2 })}.` : 'Pick the payment source.'} + + + +
+ {defaultPettyCash && method === 'Cash' && !bankAccountId && ( + + )} + + + Method + + + + {method === 'Cash' && ( + + Account + + + )} + + {method === 'Cheque' && ( + + Cheque Page + + + )} +
+ + + + + +
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/new/page.tsx b/distribution_fe/app/(dashboard)/payments/expenses/new/page.tsx new file mode 100644 index 0000000..a7ff00a --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/new/page.tsx @@ -0,0 +1,5 @@ +import { NewExpenseClient } from '../_components/new-expense-client' + +export default function NewExpensePage() { + return +} diff --git a/distribution_fe/app/(dashboard)/payments/expenses/page.tsx b/distribution_fe/app/(dashboard)/payments/expenses/page.tsx new file mode 100644 index 0000000..d5e47b0 --- /dev/null +++ b/distribution_fe/app/(dashboard)/payments/expenses/page.tsx @@ -0,0 +1,5 @@ +import { ExpensesClient } from './_components/expenses-client' + +export default function ExpensesPage() { + return +} diff --git a/distribution_fe/config/nav.ts b/distribution_fe/config/nav.ts index 54cc6ab..a77c595 100644 --- a/distribution_fe/config/nav.ts +++ b/distribution_fe/config/nav.ts @@ -27,6 +27,10 @@ import { BankIcon, PackageMoving01Icon, Settings01Icon, + ReceiptDollarIcon, + Folder01Icon, + Calendar03Icon, + UserGroup03Icon, } from '@hugeicons/core-free-icons' export interface NavItem { @@ -127,7 +131,7 @@ export const NAV_GROUPS: NavGroup[] = [ ], }, { - title: 'Accounts', + title: 'Accounting', icon: BankIcon, items: [ { title: 'Analytics', href: '/payments/analytics', icon: BankIcon, description: 'Overview of bank accounts, cheque books and received cheques' }, @@ -136,6 +140,16 @@ export const NAV_GROUPS: NavGroup[] = [ { title: 'Received Cheques', href: '/payments/received-cheques', icon: DollarSquareIcon, description: 'Cheques received from customers, suppliers, or others' }, ], }, + { + title: 'Expenses', + icon: ReceiptDollarIcon, + items: [ + { title: 'Expenses', href: '/payments/expenses', icon: ReceiptDollarIcon, description: 'Record and track company expenses — salaries, utilities, rent and more', permission: 'expense.view' }, + { title: 'Recurring Expenses', href: '/payments/expense-recurring', icon: Calendar03Icon, description: 'Templates that generate expenses on a schedule', permission: 'expense.view' }, + { title: 'Expense Categories', href: '/payments/expense-categories', icon: Folder01Icon, description: 'Two-level category tree used to classify expenses', permission: 'expense.view' }, + { title: 'Expense Vendors', href: '/payments/expense-vendors', icon: UserGroup03Icon, description: 'Vendor directory used by the Expenses module', permission: 'expense.view' }, + ], + }, { title: 'Returns & Claims', icon: ClipboardListIcon, diff --git a/distribution_fe/config/permissions.ts b/distribution_fe/config/permissions.ts index b9e438d..83f81d1 100644 --- a/distribution_fe/config/permissions.ts +++ b/distribution_fe/config/permissions.ts @@ -99,6 +99,19 @@ export const PERMISSION_CATEGORIES: PermissionCategory[] = [ 'compliance.manage', ], }, + { + id: 'expenses', + label: 'Expenses', + codes: [ + 'expense.view', + 'expense.create', + 'expense.post', + 'expense.pay', + 'expense.void', + 'expense.manage_categories', + 'expense.manage_recurring', + ], + }, { id: 'users', label: 'User Management', diff --git a/distribution_fe/lib/api/expenses.ts b/distribution_fe/lib/api/expenses.ts new file mode 100644 index 0000000..d6e12fd --- /dev/null +++ b/distribution_fe/lib/api/expenses.ts @@ -0,0 +1,117 @@ +import { api } from '@/lib/api' +import type { + CreateExpenseCategoryRequest, + CreateExpenseRequest, + CreateExpenseVendorRequest, + CreateRecurringExpenseRequest, + ExpenseAction, + ExpenseAttachmentResponse, + ExpenseCategoryResponse, + ExpensePaymentRequest, + ExpenseResponse, + ExpenseStatus, + ExpenseVendorResponse, + PagedExpenseVendorsResponse, + PagedExpensesResponse, + PagedRecurringExpensesResponse, + RecurringExpenseResponse, + RunDueResponse, + UpdateExpenseCategoryRequest, + UpdateExpenseDraftRequest, + UpdateExpenseVendorRequest, + UpdateRecurringExpenseRequest, +} from '@/types/expenses' + +// Categories + +export const expenseCategories = { + tree: () => + api.get('/expense-categories').then(r => r.data), + + create: (body: CreateExpenseCategoryRequest) => + api.post('/expense-categories', body).then(r => r.data), + + update: (id: string, body: UpdateExpenseCategoryRequest) => + api.put(`/expense-categories/${id}`, body).then(r => r.data), +} + +// Vendors + +export const expenseVendors = { + list: (params?: { search?: string; page?: number; pageSize?: number }) => + api.get('/expense-vendors', { params }).then(r => r.data), + + create: (body: CreateExpenseVendorRequest) => + api.post('/expense-vendors', body).then(r => r.data), + + update: (id: string, body: UpdateExpenseVendorRequest) => + api.put(`/expense-vendors/${id}`, body).then(r => r.data), +} + +// Expenses + +export interface ExpenseListParams { + status?: ExpenseStatus + categoryId?: string + warehouseId?: string + from?: string + to?: string + page?: number + pageSize?: number +} + +export const expenses = { + list: (params?: ExpenseListParams) => + api.get('/expenses', { params }).then(r => r.data), + + get: (id: string) => + api.get(`/expenses/${id}`).then(r => r.data), + + create: (body: CreateExpenseRequest, action: ExpenseAction = 'Draft') => + api.post('/expenses', body, { params: { action } }).then(r => r.data), + + updateDraft: (id: string, body: UpdateExpenseDraftRequest) => + api.put(`/expenses/${id}`, body).then(r => r.data), + + post: (id: string) => + api.post(`/expenses/${id}/post`).then(r => r.data), + + pay: (id: string, body: ExpensePaymentRequest) => + api.post(`/expenses/${id}/pay`, body).then(r => r.data), + + void: (id: string, reason?: string) => + api.post(`/expenses/${id}/void`, { reason }).then(r => r.data), + + uploadAttachment: (expenseId: string, file: File) => { + const form = new FormData() + form.append('file', file) + return api.post(`/expenses/${expenseId}/attachments`, form, { + headers: { 'Content-Type': 'multipart/form-data' }, + }).then(r => r.data) + }, + + downloadAttachmentUrl: (attachmentId: string) => + `${api.defaults.baseURL}/expense-attachments/${attachmentId}/download`, + + deleteAttachment: (attachmentId: string) => + api.delete(`/expense-attachments/${attachmentId}`), +} + +// Recurring + +export const recurringExpenses = { + list: (params?: { page?: number; pageSize?: number }) => + api.get('/recurring-expenses', { params }).then(r => r.data), + + create: (body: CreateRecurringExpenseRequest) => + api.post('/recurring-expenses', body).then(r => r.data), + + update: (id: string, body: UpdateRecurringExpenseRequest) => + api.put(`/recurring-expenses/${id}`, body).then(r => r.data), + + toggle: (id: string, paused: boolean) => + api.post(`/recurring-expenses/${id}/toggle`, { paused }).then(r => r.data), + + runDue: () => + api.post('/recurring-expenses/run-due').then(r => r.data), +} diff --git a/distribution_fe/lib/api/index.ts b/distribution_fe/lib/api/index.ts index ed57b7e..1050e94 100644 --- a/distribution_fe/lib/api/index.ts +++ b/distribution_fe/lib/api/index.ts @@ -17,6 +17,7 @@ export { chequeBooks, chequePages } from './cheques' export { receivedCheques } from './received-cheques' export { supplierPayments } from './supplier-payments' export { notifications } from './notifications' +export { expenseCategories, expenseVendors, expenses, recurringExpenses } from './expenses' export { getPaginatedItems } from './paginated' export type { PageParams } from './master' diff --git a/distribution_fe/types/expenses.ts b/distribution_fe/types/expenses.ts new file mode 100644 index 0000000..36a1686 --- /dev/null +++ b/distribution_fe/types/expenses.ts @@ -0,0 +1,184 @@ +export type ExpenseStatus = 'Draft' | 'Posted' | 'Paid' | 'Void' +export type ExpenseAction = 'Draft' | 'Post' | 'Pay' +export type RecurringFrequency = 'Weekly' | 'Monthly' | 'Quarterly' | 'Yearly' + +export interface ExpenseCategoryResponse { + id: string + name: string + code: string | null + parentCategoryId: string | null + isActive: boolean + children: ExpenseCategoryResponse[] +} + +export interface CreateExpenseCategoryRequest { + name: string + code?: string | null + parentCategoryId?: string | null +} + +export interface UpdateExpenseCategoryRequest { + name: string + code?: string | null + isActive: boolean +} + +export interface ExpenseVendorResponse { + id: string + name: string + contactInfo: string | null + isActive: boolean +} + +export interface PagedExpenseVendorsResponse { + items: ExpenseVendorResponse[] + page: number + pageSize: number + total: number +} + +export interface CreateExpenseVendorRequest { + name: string + contactInfo?: string | null +} + +export interface UpdateExpenseVendorRequest { + name: string + contactInfo?: string | null + isActive: boolean +} + +export interface ExpenseAttachmentResponse { + id: string + originalFileName: string + contentType: string + sizeBytes: number + createdAt: string +} + +export interface ExpensePaymentRequest { + method: 'Cash' | 'Cheque' + bankAccountId?: string | null + chequePageId?: string | null +} + +export interface ExpenseResponse { + id: string + categoryId: string + categoryName: string + parentCategoryId: string | null + parentCategoryName: string | null + warehouseId: string | null + warehouseName: string | null + amount: number + billDate: string + dueDate: string | null + periodCovered: string | null + vendorId: string | null + vendorName: string | null + payee: string | null + billRef: string | null + description: string | null + status: ExpenseStatus + method: 'Cash' | 'Cheque' | 'Credit' | null + bankAccountId: string | null + chequePageId: string | null + chequeNo: string | null + paidAt: string | null + recordedByUserId: string + postedByUserId: string | null + paidByUserId: string | null + voidedByUserId: string | null + voidReason: string | null + recurringTemplateId: string | null + attachments: ExpenseAttachmentResponse[] + createdAt: string +} + +export interface PagedExpensesResponse { + items: ExpenseResponse[] + page: number + pageSize: number + total: number +} + +export interface CreateExpenseRequest { + categoryId: string + warehouseId?: string | null + amount: number + billDate: string + dueDate?: string | null + periodCovered?: string | null + vendorId?: string | null + payee?: string | null + billRef?: string | null + description?: string | null + payment?: ExpensePaymentRequest +} + +export interface UpdateExpenseDraftRequest { + categoryId: string + warehouseId?: string | null + amount: number + billDate: string + dueDate?: string | null + periodCovered?: string | null + vendorId?: string | null + payee?: string | null + billRef?: string | null + description?: string | null +} + +export interface RecurringExpenseResponse { + id: string + name: string + categoryId: string + categoryName: string + warehouseId: string | null + warehouseName: string | null + vendorId: string | null + vendorName: string | null + amount: number + payee: string | null + description: string | null + frequency: RecurringFrequency + dayOfMonth: number | null + dayOfWeek: number | null + startDate: string + endDate: string | null + nextRunDate: string + autoPost: boolean + isPaused: boolean + isActive: boolean + createdAt: string +} + +export interface PagedRecurringExpensesResponse { + items: RecurringExpenseResponse[] + page: number + pageSize: number + total: number +} + +export interface CreateRecurringExpenseRequest { + name: string + categoryId: string + warehouseId?: string | null + vendorId?: string | null + amount: number + payee?: string | null + description?: string | null + frequency: RecurringFrequency + dayOfMonth?: number | null + dayOfWeek?: number | null + startDate: string + endDate?: string | null + autoPost: boolean +} + +export type UpdateRecurringExpenseRequest = CreateRecurringExpenseRequest + +export interface RunDueResponse { + generated: number + expenseIds: string[] +}