From ca81e9bc4271f21cdb7572cc5de07ae06c132b2d Mon Sep 17 00:00:00 2001 From: Haritha Randunu Date: Tue, 20 Oct 2026 18:54:30 +0530 Subject: [PATCH] feat: add General Ledger integration and financial reporting features - Implemented General Ledger client component for managing GL integration. - Created a dedicated page for General Ledger configuration. - Developed Finance Report client component for generating various financial reports. - Added report blocks for structured display of financial data. - Introduced API endpoints for fetching GL accounts and financial reports. - Updated navigation to include links for GL integration and financial reports. - Enhanced permissions to manage GL and view financial reports. - Updated appsettings to include General Ledger API configuration. --- Distribution/Data/AppDBContext.cs | 11 + .../Data/Seed/RoleAndPermissionSeed.cs | 10 +- .../Features/ChequeBook/ChequeBookService.cs | 5 +- .../FinanceReport/FinanceReportController.cs | 74 + .../GeneralLedger/GeneralLedgerClient.cs | 48 + .../Features/GeneralLedger/GlController.cs | 100 + Distribution/Features/GeneralLedger/GlJson.cs | 33 + Distribution/Features/GeneralLedger/GlKeys.cs | 34 + .../GeneralLedger/GlPostingService.cs | 449 ++ .../Features/GeneralLedger/GlSetupService.cs | 226 + Distribution/Features/Grn/GrnService.cs | 5 +- .../Features/Invoice/InvoiceService.cs | 8 +- .../Features/Payment/PaymentService.cs | 11 +- .../ReceivedCheque/ReceivedChequeService.cs | 5 +- .../SupplierPayment/SupplierPaymentService.cs | 11 +- ...7034841_RefactorSalesToShopPOAndInvoice.cs | 6 + .../20261020082853_GlIntegration.Designer.cs | 5868 +++++++++++++++++ .../20261020082853_GlIntegration.cs | 814 +++ .../Migrations/AppDBContextModelSnapshot.cs | 320 +- Distribution/Models/BankAccount.cs | 3 + Distribution/Models/GlPostingLog.cs | 24 + Distribution/Program.cs | 10 + Distribution/appsettings.json | 6 + .../_components/general-ledger-client.tsx | 350 + .../configuration/general-ledger/page.tsx | 5 + .../_components/finance-report-client.tsx | 205 + .../finance/_components/report-blocks.ts | 153 + .../app/(dashboard)/reports/finance/page.tsx | 5 + distribution_fe/config/nav.ts | 2 + distribution_fe/config/permissions.ts | 8 + distribution_fe/lib/api/finance-reports.ts | 110 + distribution_fe/lib/api/index.ts | 1 + 32 files changed, 8786 insertions(+), 134 deletions(-) create mode 100644 Distribution/Features/FinanceReport/FinanceReportController.cs create mode 100644 Distribution/Features/GeneralLedger/GeneralLedgerClient.cs create mode 100644 Distribution/Features/GeneralLedger/GlController.cs create mode 100644 Distribution/Features/GeneralLedger/GlJson.cs create mode 100644 Distribution/Features/GeneralLedger/GlKeys.cs create mode 100644 Distribution/Features/GeneralLedger/GlPostingService.cs create mode 100644 Distribution/Features/GeneralLedger/GlSetupService.cs create mode 100644 Distribution/Migrations/20261020082853_GlIntegration.Designer.cs create mode 100644 Distribution/Migrations/20261020082853_GlIntegration.cs create mode 100644 Distribution/Models/GlPostingLog.cs create mode 100644 distribution_fe/app/(dashboard)/configuration/general-ledger/_components/general-ledger-client.tsx create mode 100644 distribution_fe/app/(dashboard)/configuration/general-ledger/page.tsx create mode 100644 distribution_fe/app/(dashboard)/reports/finance/_components/finance-report-client.tsx create mode 100644 distribution_fe/app/(dashboard)/reports/finance/_components/report-blocks.ts create mode 100644 distribution_fe/app/(dashboard)/reports/finance/page.tsx create mode 100644 distribution_fe/lib/api/finance-reports.ts diff --git a/Distribution/Data/AppDBContext.cs b/Distribution/Data/AppDBContext.cs index a856dce..be6e024 100644 --- a/Distribution/Data/AppDBContext.cs +++ b/Distribution/Data/AppDBContext.cs @@ -66,6 +66,7 @@ public class AppDBContext(DbContextOptions options) : DbContext(op public DbSet RouteVisitLogs { get; set; } public DbSet SysConfigs { get; set; } public DbSet BankAccounts { get; set; } + public DbSet GlPostingLogs { get; set; } public DbSet ChequeBooks { get; set; } public DbSet ChequePages { get; set; } public DbSet ReceivedCheques { get; set; } @@ -582,6 +583,16 @@ public class AppDBContext(DbContextOptions options) : DbContext(op .HasIndex(r => r.FK_RegionId) .IsUnique(); + // --- GL posting log --- + + modelBuilder.Entity() + .HasIndex(l => new { l.Event, l.SourceId }) + .IsUnique(); + + modelBuilder.Entity() + .Property(l => l.Event) + .HasMaxLength(60); + // --- SysConfig --- modelBuilder.Entity() diff --git a/Distribution/Data/Seed/RoleAndPermissionSeed.cs b/Distribution/Data/Seed/RoleAndPermissionSeed.cs index 1f72010..c6e021a 100644 --- a/Distribution/Data/Seed/RoleAndPermissionSeed.cs +++ b/Distribution/Data/Seed/RoleAndPermissionSeed.cs @@ -68,6 +68,8 @@ 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 PermFinanceReportView = new("20000000-0000-0000-0000-000000000051"); + private static readonly Guid PermGlManage = new("20000000-0000-0000-0000-000000000052"); public static void Apply(ModelBuilder modelBuilder) { @@ -134,7 +136,9 @@ 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 = PermFinanceReportView, Code = "finance-reports.view", Description = "View financial reports (trial balance, P&L, balance sheet, cash flow...)", CreatedAt = SeedDate, IsActive = true }, + new Permission { Id = PermGlManage, Code = "gl.manage", Description = "Set up the General Ledger link and retry failed postings", CreatedAt = SeedDate, IsActive = true } ); // SuperAdmin gets every permission @@ -191,7 +195,9 @@ 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 = PermFinanceReportView, CreatedAt = SeedDate, IsActive = true }, + new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermGlManage, CreatedAt = SeedDate, IsActive = true } ); } } diff --git a/Distribution/Features/ChequeBook/ChequeBookService.cs b/Distribution/Features/ChequeBook/ChequeBookService.cs index 28f812e..e1e6902 100644 --- a/Distribution/Features/ChequeBook/ChequeBookService.cs +++ b/Distribution/Features/ChequeBook/ChequeBookService.cs @@ -1,4 +1,5 @@ using Distribution.Features.BankAccount; +using Distribution.Features.GeneralLedger; using Distribution.Infra.UoW; using Distribution.Models; @@ -7,7 +8,8 @@ namespace Distribution.Features.ChequeBook; public class ChequeBookService( IChequeBookRepository repo, IBankAccountRepository bankAccountRepo, - IUnitOfWork uow) + IUnitOfWork uow, + GlPostingService gl) { private const string DefaultCurrency = "LKR"; @@ -171,6 +173,7 @@ public class ChequeBookService( repo.UpdatePage(page); await uow.CommitAsync(ct); + await gl.PostIssuedChequeAsync(page, request.Action, ct); return ToPageResponse(page, page.ChequeBook!.ChequeBookNo); } } diff --git a/Distribution/Features/FinanceReport/FinanceReportController.cs b/Distribution/Features/FinanceReport/FinanceReportController.cs new file mode 100644 index 0000000..6523fbb --- /dev/null +++ b/Distribution/Features/FinanceReport/FinanceReportController.cs @@ -0,0 +1,74 @@ +using System.Text; +using System.Text.Json; +using Distribution.Features.GeneralLedger; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.FinanceReport; + +/// +/// Proxies the General Ledger service's reports (TrialBalance, GeneralLedger, BalanceSheet, ProfitAndLoss, CashFlow, +/// BudgetVsActual, TaxSummary). Query parameters are forwarded as-is; Json is returned as the GL data payload and +/// Pdf/Csv are returned as downloadable files. +/// +[ApiController] +[Route("api/reports/finance")] +public class FinanceReportController(IGeneralLedgerClient gl) : ControllerBase +{ + /// Active GL accounts (code + name) for the report filters' account dropdown. + [HttpGet("accounts")] + [Authorize("finance-reports.view")] + public async Task Accounts(CancellationToken ct) + { + GeneralLedgerResult result; + try { result = await gl.SendAsync(HttpMethod.Get, "v1/accounts?isActive=true", null, ct); } + catch (HttpRequestException) { return StatusCode(502, new { error = "The General Ledger service is unreachable." }); } + + if (!result.Success || GlJson.Data(result) is not { } data) + return StatusCode(502, new { error = GlJson.Message(result) ?? "Could not load GL accounts." }); + + var items = GlJson.Get(data, "items"); + var accounts = items is { ValueKind: JsonValueKind.Array } + ? items.Value.EnumerateArray() + .Select(a => new { accountCode = GlJson.GetString(a, "accountCode") ?? "", accountName = GlJson.GetString(a, "accountName") ?? "" }) + .OrderBy(a => a.accountCode, StringComparer.Ordinal) + .ToList() + : []; + return Ok(accounts); + } + + [HttpGet("{reportType}")] + [Authorize("finance-reports.view")] + public async Task Get(string reportType, [FromQuery] string outputFormat = "Json", CancellationToken ct = default) + { + var query = new StringBuilder($"v1/reports?reportType={Uri.EscapeDataString(reportType)}&outputFormat={Uri.EscapeDataString(outputFormat)}"); + foreach (var (key, values) in Request.Query) + { + if (key is "reportType" or "outputFormat") continue; + foreach (var v in values) + query.Append('&').Append(Uri.EscapeDataString(key)).Append('=').Append(Uri.EscapeDataString(v ?? "")); + } + + GeneralLedgerResult result; + try { result = await gl.SendAsync(HttpMethod.Get, query.ToString(), null, ct); } + catch (HttpRequestException) { return StatusCode(502, new { error = "The General Ledger service is unreachable." }); } + + if (!result.Success) + return StatusCode(result.StatusCode is >= 400 and < 500 ? result.StatusCode : 502, + new { error = GlJson.Message(result) ?? "The General Ledger service rejected the request." }); + + var data = GlJson.Data(result); + if (data is null) return StatusCode(502, new { error = "The General Ledger service returned no data." }); + + if (!outputFormat.Equals("Json", StringComparison.OrdinalIgnoreCase)) + { + var base64 = GlJson.GetString(data.Value, "contentBase64"); + if (base64 is null) return StatusCode(502, new { error = "The General Ledger service returned no file." }); + return File(Convert.FromBase64String(base64), + GlJson.GetString(data.Value, "contentType") ?? "application/octet-stream", + GlJson.GetString(data.Value, "fileName")); + } + + return Content(data.Value.GetRawText(), "application/json"); + } +} diff --git a/Distribution/Features/GeneralLedger/GeneralLedgerClient.cs b/Distribution/Features/GeneralLedger/GeneralLedgerClient.cs new file mode 100644 index 0000000..934aa20 --- /dev/null +++ b/Distribution/Features/GeneralLedger/GeneralLedgerClient.cs @@ -0,0 +1,48 @@ +using System.Net.Http.Json; +using System.Text.Json; + +namespace Distribution.Features.GeneralLedger; + +public record GeneralLedgerResult(int StatusCode, bool Success, JsonElement? Body); + +public interface IGeneralLedgerClient +{ + Task IsHealthyAsync(CancellationToken ct = default); + Task SendAsync(HttpMethod method, string path, object? payload = null, CancellationToken ct = default); +} + +/// +/// Service-to-service client for the General Ledger microservice. The GL service is not user-facing: +/// every request carries the shared X-Api-Key (GeneralLedger:ApiKey, must equal the GL service's ApiKey:Value). +/// is relative to GeneralLedger:BaseUrl and carries the API version (e.g. "v1/accounts", "v1/journal-entries"), so moving a call to v2 is a one-path change. +/// +public class GeneralLedgerClient(HttpClient http) : IGeneralLedgerClient +{ + public async Task IsHealthyAsync(CancellationToken ct = default) + { + try + { + using var response = await http.GetAsync("/health", ct); + return response.IsSuccessStatusCode; + } + catch (HttpRequestException) + { + return false; + } + } + + public async Task SendAsync(HttpMethod method, string path, object? payload = null, CancellationToken ct = default) + { + using var request = new HttpRequestMessage(method, path.TrimStart('/')); + if (payload is not null) request.Content = JsonContent.Create(payload); + + using var response = await http.SendAsync(request, ct); + JsonElement? body = null; + if (response.Content.Headers.ContentLength != 0) + { + try { body = await response.Content.ReadFromJsonAsync(cancellationToken: ct); } + catch (JsonException) { } + } + return new GeneralLedgerResult((int)response.StatusCode, response.IsSuccessStatusCode, body); + } +} diff --git a/Distribution/Features/GeneralLedger/GlController.cs b/Distribution/Features/GeneralLedger/GlController.cs new file mode 100644 index 0000000..f87ac9c --- /dev/null +++ b/Distribution/Features/GeneralLedger/GlController.cs @@ -0,0 +1,100 @@ +using Distribution.Data; +using Distribution.Infra.Authorization; +using Distribution.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.GeneralLedger; + +public record GlSetupRequest(int? Year); + +public record YearEndCloseRequest(int Year); + +public record OpeningBalanceRequest(DateOnly Date, string? Narration, List Lines); + +[ApiController] +[Route("api/gl")] +public class GlController( + GlSetupService setup, + GlPostingService posting, + IGeneralLedgerClient gl, + Distribution.Features.SysConfig.ISysConfigRepository configRepo, + AppDBContext context, + ICurrentUserService currentUser) : ControllerBase +{ + [HttpGet("status")] + [Authorize("gl.manage")] + public async Task Status() + { + var mapping = new Dictionary(); + foreach (var key in GlKeys.AllAccountKeys) + mapping[key] = (await configRepo.GetByKeyAsync(key))?.Value; + + return Ok(new + { + glReachable = await gl.IsHealthyAsync(), + setupDone = (await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value == "true", + openingBalancePosted = await context.GlPostingLogs.AnyAsync(l => l.Event == "OpeningBalance" && l.Status == GlPostingStatus.Posted), + closedYears = await posting.GetClosedYearsAsync(), + mapping, + posted = await context.GlPostingLogs.CountAsync(l => l.Status == GlPostingStatus.Posted), + failed = await context.GlPostingLogs.CountAsync(l => l.Status != GlPostingStatus.Posted) + }); + } + + [HttpPost("setup")] + [Authorize("gl.manage")] + public async Task Setup(GlSetupRequest request, CancellationToken ct) + { + try { return Ok(await setup.RunAsync(request.Year, currentUser.UserId, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("opening-balances")] + [Authorize("gl.manage")] + public async Task OpeningBalances(OpeningBalanceRequest request, CancellationToken ct) + { + try + { + var row = await posting.PostOpeningBalanceAsync(request.Date, request.Narration, request.Lines, ct); + return row.Status == GlPostingStatus.Posted + ? Ok(new { status = row.Status, glJournalNo = row.GlJournalNo }) + : StatusCode(502, new { error = row.Error ?? "The General Ledger service did not accept the entry. It was saved and can be retried." }); + } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpGet("year-end/preview")] + [Authorize("gl.manage")] + public async Task YearEndPreview([FromQuery] int year, CancellationToken ct) + { + try { return Ok(await posting.GetYearEndPreviewAsync(year, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("year-end/close")] + [Authorize("gl.manage")] + public async Task YearEndClose(YearEndCloseRequest request, CancellationToken ct) + { + try { return Ok(await posting.CloseYearAsync(request.Year, ct)); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpGet("postings")] + [Authorize("gl.manage")] + public async Task Postings([FromQuery] GlPostingStatus? status, [FromQuery] int take = 100) + { + var query = context.GlPostingLogs.AsQueryable(); + if (status is not null) query = query.Where(l => l.Status == status); + var rows = await query.OrderByDescending(l => l.CreatedAt).Take(Math.Clamp(take, 1, 500)) + .Select(l => new { l.Id, l.Event, l.SourceId, l.Reference, l.Status, l.GlJournalNo, l.Error, l.Attempts, l.LastAttemptAt, l.CreatedAt }) + .ToListAsync(); + return Ok(rows); + } + + [HttpPost("postings/retry")] + [Authorize("gl.manage")] + public async Task Retry(CancellationToken ct) => + Ok(new { posted = await posting.RetryFailedAsync(ct) }); +} diff --git a/Distribution/Features/GeneralLedger/GlJson.cs b/Distribution/Features/GeneralLedger/GlJson.cs new file mode 100644 index 0000000..be31107 --- /dev/null +++ b/Distribution/Features/GeneralLedger/GlJson.cs @@ -0,0 +1,33 @@ +using System.Text.Json; + +namespace Distribution.Features.GeneralLedger; + +/// Small readers for the GL service's ApiResponse envelope ({statusCode, success, message, data}). +public static class GlJson +{ + public static JsonElement? Data(GeneralLedgerResult result) => + result.Body is { ValueKind: JsonValueKind.Object } body && Get(body, "data") is { } data ? data : null; + + public static string? Message(GeneralLedgerResult result) => + result.Body is { ValueKind: JsonValueKind.Object } body ? Get(body, "message")?.GetString() + ?? Get(body, "Message")?.GetString() : null; + + /// Case-insensitive property lookup (GL success bodies are camelCase, its error bodies PascalCase). + public static JsonElement? Get(JsonElement element, string name) + { + if (element.ValueKind != JsonValueKind.Object) return null; + foreach (var p in element.EnumerateObject()) + if (string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)) return p.Value; + return null; + } + + public static string? GetString(JsonElement element, string name) + { + var v = Get(element, name); + return v is null || v.Value.ValueKind == JsonValueKind.Null ? null + : v.Value.ValueKind == JsonValueKind.String ? v.Value.GetString() : v.Value.ToString(); + } + + public static long? GetLong(JsonElement element, string name) => + long.TryParse(GetString(element, name), out var n) ? n : null; +} diff --git a/Distribution/Features/GeneralLedger/GlKeys.cs b/Distribution/Features/GeneralLedger/GlKeys.cs new file mode 100644 index 0000000..b9f392a --- /dev/null +++ b/Distribution/Features/GeneralLedger/GlKeys.cs @@ -0,0 +1,34 @@ +namespace Distribution.Features.GeneralLedger; + +/// SysConfig keys holding the GL account codes Distribution posts to (created by GlSetupService). +public static class GlKeys +{ + public const string SetupDone = "Gl.Setup.Done"; + + public const string CashEquivalents = "Gl.Account.CashEquivalents"; // parent of Cash in Hand + Bank Accounts (GL cash root) + public const string CashInHand = "Gl.Account.CashInHand"; // parent of every Distribution Cash account + public const string BankAccounts = "Gl.Account.BankAccounts"; // parent of every Distribution Bank account + public const string ChequesInHand = "Gl.Account.ChequesInHand"; + public const string Receivable = "Gl.Account.Receivable"; + public const string Inventory = "Gl.Account.Inventory"; + public const string InputVat = "Gl.Account.InputVat"; + public const string InputNbt = "Gl.Account.InputNbt"; + public const string Payable = "Gl.Account.Payable"; + public const string ChequesIssued = "Gl.Account.ChequesIssued"; + public const string Equity = "Gl.Account.Equity"; // header; parent of the equity accounts below + public const string ShareCapital = "Gl.Account.ShareCapital"; + public const string RetainedEarnings = "Gl.Account.RetainedEarnings"; + public const string Drawings = "Gl.Account.Drawings"; + public const string GeneralReserve = "Gl.Account.GeneralReserve"; + public const string RevaluationReserve = "Gl.Account.RevaluationReserve"; + public const string Sales = "Gl.Account.Sales"; + + public static readonly string[] AllAccountKeys = + [ + CashEquivalents, CashInHand, BankAccounts, ChequesInHand, Receivable, Inventory, + InputVat, InputNbt, Payable, ChequesIssued, Equity, ShareCapital, RetainedEarnings, Drawings, + GeneralReserve, RevaluationReserve, Sales + ]; + + public static bool IsGlKey(string key) => key == SetupDone || AllAccountKeys.Contains(key); +} diff --git a/Distribution/Features/GeneralLedger/GlPostingService.cs b/Distribution/Features/GeneralLedger/GlPostingService.cs new file mode 100644 index 0000000..a0ef823 --- /dev/null +++ b/Distribution/Features/GeneralLedger/GlPostingService.cs @@ -0,0 +1,449 @@ +using System.Globalization; +using System.Text.Json; +using Distribution.Data; +using Distribution.Features.Grn; +using Distribution.Features.SysConfig; +using Distribution.Infra.UoW; +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.GeneralLedger; + +public record GlLine(string AccountCode, decimal Debit, decimal Credit); + +/// What closing a financial year would post. Closable is false with a Reason when it cannot be closed. +public record YearEndPreview(int Year, string StartDate, string EndDate, decimal TotalIncome, decimal TotalExpenses, + decimal NetProfit, bool Closable, string? Reason, bool AlreadyClosed); + +/// +/// One opening balance: Account is a key from OpeningAccounts (e.g. "Inventory", "ShareCapital") or "BankAccount" +/// with BankAccountId. Amount is the account's normal balance (>= 0); the debit/credit side comes from the account. +/// +public record OpeningBalanceLine(string Account, Guid? BankAccountId, decimal Amount); + +/// +/// Mirrors Distribution business events into the General Ledger as journal entries. Every public Post* method is +/// called AFTER the business transaction has committed and never throws: a GL problem is recorded in GlPostingLogs +/// (status Failed, payload kept) and can be retried later, so it can never block a sale, payment or cheque action. +/// +public class GlPostingService( + IGeneralLedgerClient gl, + ISysConfigRepository configRepo, + GlSetupService setup, + AppDBContext context, + IUnitOfWork uow, + ILogger logger) +{ + private record Payload(string EntryDate, string JournalType, string SourceModule, string Reference, string Narration, + string PreparedBy, List Lines); + + private record PayloadLine(string AccountCode, decimal DebitAmount, decimal CreditAmount); + + // ── Events ─────────────────────────────────────────────────────────────── + + public Task PostInvoiceFinalizedAsync(Models.Invoice invoice, CancellationToken ct = default) => + PostAsync("InvoiceFinalized", invoice.Id, invoice.FinalizedAt ?? DateTime.Now, "INVOICE", $"Invoice {invoice.Id} finalized", + async () => invoice.TotalAmount <= 0 ? null : + [ + new GlLine(await Acct(GlKeys.Receivable), invoice.TotalAmount, 0), + new GlLine(await Acct(GlKeys.Sales), 0, invoice.TotalAmount) + ], ct); + + public Task PostCustomerPaymentAsync(Models.Payment payment, CancellationToken ct = default) => + PostAsync("CustomerPayment", payment.Id, payment.CollectedAt, "PAYMENT", $"Customer payment for invoice {payment.FK_InvoiceId}", + async () => + { + if (payment.Amount <= 0 || payment.Method == PaymentMethod.Credit) return null; + var debit = payment.Method == PaymentMethod.Cash + ? await BankCode(payment.FK_BankAccountId) + : await Acct(GlKeys.ChequesInHand); + return [new GlLine(debit, payment.Amount, 0), new GlLine(await Acct(GlKeys.Receivable), 0, payment.Amount)]; + }, ct); + + /// Deposit / Return / Cancel of a received cheque that came from a customer payment. + public Task PostReceivedChequeAsync(Models.ReceivedCheque cheque, ReceivedChequeStatusAction action, CancellationToken ct = default) + { + if (action is not (ReceivedChequeStatusAction.Deposit or ReceivedChequeStatusAction.Return or ReceivedChequeStatusAction.Cancel)) + return Task.CompletedTask; + + var date = action == ReceivedChequeStatusAction.Deposit ? cheque.DepositDate ?? DateTime.Now : DateTime.Now; + return PostAsync($"ReceivedCheque{action}", cheque.Id, date, "CHEQUE", $"Received cheque {cheque.ChequeNo} {action}", + async () => + { + // Only cheques created by a customer payment were posted to Cheques-in-hand, so only they can move. + if (!await context.Payments.AnyAsync(p => p.FK_ReceivedChequeId == cheque.Id, ct)) return null; + var inHand = await Acct(GlKeys.ChequesInHand); + return action switch + { + ReceivedChequeStatusAction.Deposit => + [new GlLine(await BankCode(cheque.FK_DepositBankAccountId), cheque.Amount, 0), new GlLine(inHand, 0, cheque.Amount)], + ReceivedChequeStatusAction.Return => + [new GlLine(inHand, cheque.Amount, 0), new GlLine(await BankCode(cheque.FK_DepositBankAccountId), 0, cheque.Amount)], + _ => [new GlLine(await Acct(GlKeys.Receivable), cheque.Amount, 0), new GlLine(inHand, 0, cheque.Amount)] + }; + }, ct); + } + + public Task PostGrnAsync(Models.GoodsReceipt grn, CancellationToken ct = default) => + PostAsync("Grn", grn.Id, grn.ReceivedAt, "GRN", $"Goods received {grn.Id}", + async () => + { + decimal net = 0, nbt = 0, vat = 0; + foreach (var i in grn.Items.Where(i => i.IsActive)) + { + var a = GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.NbtPercent, i.VatPercent); + net += a.Gross - a.Discount; + nbt += a.Nbt; + vat += a.Vat; + } + var total = net + nbt + vat; + if (total <= 0) return null; + + var lines = new List { new(await Acct(GlKeys.Inventory), net, 0) }; + if (nbt > 0) lines.Add(new GlLine(await Acct(GlKeys.InputNbt), nbt, 0)); + if (vat > 0) lines.Add(new GlLine(await Acct(GlKeys.InputVat), vat, 0)); + lines.Add(new GlLine(await Acct(GlKeys.Payable), 0, total)); + return lines; + }, ct); + + public Task PostSupplierPaymentAsync(Models.SupplierPayment payment, CancellationToken ct = default) => + PostAsync("SupplierPayment", payment.Id, payment.PaidAt, "SUPPLIER_PAYMENT", $"Supplier payment {payment.Id}", + async () => + { + if (payment.Amount <= 0) return null; + var credit = payment.Method == PaymentMethod.Cash + ? await BankCode(payment.FK_BankAccountId) + : await Acct(GlKeys.ChequesIssued); + return [new GlLine(await Acct(GlKeys.Payable), payment.Amount, 0), new GlLine(credit, 0, payment.Amount)]; + }, ct); + + /// Clear / Bounce / Cancel of a cheque issued through a supplier payment. + public Task PostIssuedChequeAsync(Models.ChequePage page, ChequePageStatusAction action, CancellationToken ct = default) + { + if (action == ChequePageStatusAction.Void) return Task.CompletedTask; + + var date = action == ChequePageStatusAction.Clear ? page.ClearedDate ?? DateTime.Now : DateTime.Now; + return PostAsync($"IssuedCheque{action}", page.Id, date, "CHEQUE", $"Issued cheque {page.ChequeNo} {action}", + async () => + { + var amount = page.Amount ?? 0; + if (amount <= 0 || !await context.SupplierPayments.AnyAsync(p => p.FK_ChequePageId == page.Id, ct)) return null; + var issued = await Acct(GlKeys.ChequesIssued); + if (action == ChequePageStatusAction.Clear) + { + var bookBankId = await context.ChequeBooks.Where(b => b.Id == page.FK_ChequeBookId) + .Select(b => (Guid?)b.FK_BankAccountId).FirstOrDefaultAsync(ct); + return [new GlLine(issued, amount, 0), new GlLine(await BankCode(bookBankId), 0, amount)]; + } + // Bounce / Cancel: the cheque no longer settles the payable, so put it back. + return [new GlLine(issued, amount, 0), new GlLine(await Acct(GlKeys.Payable), 0, amount)]; + }, ct); + } + + /// + /// Posts the opening balances (asset side debited, liability/equity side credited). Zero amounts are skipped, so the + /// form can list every account with a default of 0. Reports the outcome so the caller can show a failure. + /// + public async Task PostOpeningBalanceAsync(DateOnly date, string? narration, List input, CancellationToken ct = default) + { + if ((await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value != "true") + throw new InvalidOperationException("Run the General Ledger setup first."); + + var lines = new List(); + foreach (var l in input ?? []) + { + if (l.Amount < 0) throw new InvalidOperationException($"'{l.Account}': the amount cannot be negative."); + if (l.Amount == 0) continue; + + if (l.Account == "BankAccount") + { + lines.Add(new GlLine(await BankCode(l.BankAccountId), l.Amount, 0)); + continue; + } + if (!OpeningAccounts.TryGetValue(l.Account, out var def)) + throw new InvalidOperationException($"Unknown account '{l.Account}'."); + var code = await Acct(def.Key); + lines.Add(def.DebitNormal ? new GlLine(code, l.Amount, 0) : new GlLine(code, 0, l.Amount)); + } + + if (lines.Count == 0) + throw new InvalidOperationException("Enter at least one opening balance greater than zero."); + + var debit = lines.Sum(x => x.Debit); + var credit = lines.Sum(x => x.Credit); + if (debit != credit) + throw new InvalidOperationException( + $"The opening balances do not balance: assets (debits) total {debit:N2} but liabilities + equity (credits) total {credit:N2}. " + + "Adjust Share Capital / Retained Earnings so they match."); + + var sourceId = Guid.NewGuid(); + await PostAsync("OpeningBalance", sourceId, date.ToDateTime(TimeOnly.MinValue), "OPENING_BALANCE", + string.IsNullOrWhiteSpace(narration) ? "Opening balances" : narration.Trim(), () => Task.FromResult?>(lines), ct); + + return await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == "OpeningBalance" && l.SourceId == sourceId, ct) + ?? throw new InvalidOperationException("The opening balances could not be prepared. Check the server log."); + } + + /// Accounts that can carry an opening balance (besides individual cash/bank accounts) and their normal side. + public static readonly Dictionary OpeningAccounts = new() + { + ["ChequesInHand"] = (GlKeys.ChequesInHand, true), + ["Receivable"] = (GlKeys.Receivable, true), + ["Inventory"] = (GlKeys.Inventory, true), + ["InputVat"] = (GlKeys.InputVat, true), + ["InputNbt"] = (GlKeys.InputNbt, true), + ["Drawings"] = (GlKeys.Drawings, true), + ["Payable"] = (GlKeys.Payable, false), + ["ChequesIssued"] = (GlKeys.ChequesIssued, false), + ["ShareCapital"] = (GlKeys.ShareCapital, false), + ["RetainedEarnings"] = (GlKeys.RetainedEarnings, false), + ["GeneralReserve"] = (GlKeys.GeneralReserve, false), + ["RevaluationReserve"] = (GlKeys.RevaluationReserve, false), + }; + + // ── Year-end close ─────────────────────────────────────────────────────── + + private const string YearEndEvent = "YearEndClose"; + + private static Guid YearEndSourceId(int year) => + new(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes($"{YearEndEvent}:{year}"))); + + /// Years that have a posted closing journal. + public async Task> GetClosedYearsAsync(CancellationToken ct = default) => + (await context.GlPostingLogs + .Where(l => l.Event == YearEndEvent && l.Status == GlPostingStatus.Posted) + .Select(l => l.Reference) + .ToListAsync(ct)) + .Select(r => int.TryParse(r.Split(':').Last(), out var y) ? y : 0) + .Where(y => y > 0).OrderBy(y => y).ToList(); + + private record CloseBuild(List Lines, decimal Income, decimal Expenses); + + /// Reads the year's own P&L from GL (which excludes earlier closing journals) and builds the closing lines. + private async Task BuildYearEndCloseAsync(int year, CancellationToken ct) + { + var result = await gl.SendAsync(HttpMethod.Get, + $"v1/reports?reportType=ProfitAndLoss&outputFormat=Json&periodStart={year}-01-01&periodEnd={year}-12-31", null, ct); + if (!result.Success || GlJson.Data(result) is not { } pl) + throw new InvalidOperationException($"Could not read the {year} profit and loss from the General Ledger: {GlJson.Message(result) ?? result.StatusCode.ToString()}"); + + // Net CREDIT balance per account: income sections carry their amount as is, expense sections are reported + // as positive debits (so flip), unclassified rows are already credit - debit. + var netCredit = new Dictionary(); + decimal income = 0, expenses = 0; + void Collect(string section, int sign) + { + if (GlJson.Get(pl, section) is not { } s || GlJson.Get(s, "lines") is not { ValueKind: JsonValueKind.Array } sectionLines) return; + foreach (var l in sectionLines.EnumerateArray()) + { + var code = GlJson.GetString(l, "accountCode"); + if (code is null || !decimal.TryParse(GlJson.GetString(l, "amount"), NumberStyles.Any, CultureInfo.InvariantCulture, out var amount)) continue; + var credit = sign * amount; + netCredit[code] = netCredit.GetValueOrDefault(code) + credit; + if (credit > 0) income += credit; else expenses += -credit; + } + } + Collect("sales", 1); Collect("otherIncome", 1); + Collect("costOfSales", -1); Collect("distributionExpenses", -1); Collect("administrationExpenses", -1); + Collect("otherExpenses", -1); Collect("financialExpenses", -1); + Collect("unclassified", 1); + + // Closing: an account with a net credit balance is debited, one with a net debit balance is credited. + var lines = netCredit.Where(kv => kv.Value != 0) + .Select(kv => kv.Value > 0 ? new GlLine(kv.Key, kv.Value, 0) : new GlLine(kv.Key, 0, -kv.Value)).ToList(); + var debit = lines.Sum(x => x.Debit); + var credit2 = lines.Sum(x => x.Credit); + if (debit != credit2) + { + var retained = await Acct(GlKeys.RetainedEarnings); + lines.Add(debit > credit2 ? new GlLine(retained, 0, debit - credit2) : new GlLine(retained, credit2 - debit, 0)); + } + return new CloseBuild(lines, income, expenses); + } + + public async Task GetYearEndPreviewAsync(int year, CancellationToken ct = default) + { + var start = $"{year}-01-01"; + var end = $"{year}-12-31"; + + if ((await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value != "true") + return new(year, start, end, 0, 0, 0, false, "Run the General Ledger setup first.", false); + + var sourceId = YearEndSourceId(year); + var existing = await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == YearEndEvent && l.SourceId == sourceId, ct); + if (existing?.Status == GlPostingStatus.Posted) + return new(year, start, end, 0, 0, 0, false, $"{year} has already been closed.", true); + + var build = await BuildYearEndCloseAsync(year, ct); + var net = build.Income - build.Expenses; + string? reason = null; + if (new DateOnly(year, 12, 31) > DateOnly.FromDateTime(DateTime.Now)) + reason = $"{year} has not ended yet. A year can only be closed after 31 December."; + else if (await context.GlPostingLogs.AnyAsync(l => l.Status != GlPostingStatus.Posted && !(l.Event == YearEndEvent && l.SourceId == sourceId), ct)) + reason = "There are failed or pending General Ledger postings. Retry them first so the year's figures are complete."; + else if (build.Lines.Count == 0) + reason = $"There is no income or expense activity in {year} to close."; + + return new(year, start, end, build.Income, build.Expenses, net, reason is null, reason, false); + } + + /// Posts the closing journal for the year (income/expense into Retained Earnings), then locks the year's periods. + public async Task CloseYearAsync(int year, CancellationToken ct = default) + { + var preview = await GetYearEndPreviewAsync(year, ct); + if (!preview.Closable) throw new InvalidOperationException(preview.Reason ?? "This year cannot be closed."); + + var build = await BuildYearEndCloseAsync(year, ct); + var sourceId = YearEndSourceId(year); + await PostAsync(YearEndEvent, sourceId, new DateTime(year, 12, 31), "YEAR_END_CLOSE", $"Year-end closing entry for {year}", + () => Task.FromResult?>(build.Lines), ct, journalType: "CLOSING", reference: $"{YearEndEvent}:{year}"); + + var row = await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == YearEndEvent && l.SourceId == sourceId, ct) + ?? throw new InvalidOperationException("The closing entry could not be prepared. Check the server log."); + if (row.Status != GlPostingStatus.Posted) + throw new InvalidOperationException((row.Error ?? "The General Ledger did not accept the closing entry.") + " Fix the cause and press Close again."); + + var locked = await LockYearPeriodsAsync(year, ct); + return new { year, glJournalNo = row.GlJournalNo, netProfit = preview.NetProfit, periodsLocked = locked }; + } + + private async Task LockYearPeriodsAsync(int year, CancellationToken ct) + { + var ids = new HashSet(); + for (var month = 1; month <= 12; month++) + { + var p = await gl.SendAsync(HttpMethod.Get, $"v1/fiscal-years/periods/by-date?date={year}-{month:00}-15", null, ct); + if (p.Success && GlJson.Data(p) is { } pd && GlJson.GetLong(pd, "periodId") is { } id) ids.Add(id); + } + + var locked = 0; + foreach (var id in ids) + { + var r = await gl.SendAsync(HttpMethod.Put, $"v1/periods/{id}/lock", new { action = "Close", reason = $"Year-end close {year}" }, ct); + if (r.Success) locked++; + else logger.LogWarning("Could not lock GL period {PeriodId} after closing {Year}: {Message}", id, year, GlJson.Message(r)); + } + return locked; + } + + // ── Retry ──────────────────────────────────────────────────────────────── + + /// Re-sends every Failed/Pending posting. Returns how many are now Posted. + public async Task RetryFailedAsync(CancellationToken ct = default) + { + var rows = await context.GlPostingLogs + .Where(l => l.Status != GlPostingStatus.Posted) + .OrderBy(l => l.CreatedAt) + .ToListAsync(ct); + + var posted = 0; + foreach (var row in rows) + { + await SendAsync(row, ct); + if (row.Status == GlPostingStatus.Posted) posted++; + } + return posted; + } + + // ── Core ───────────────────────────────────────────────────────────────── + + private async Task PostAsync(string evt, Guid sourceId, DateTime date, string sourceModule, string narration, + Func?>> buildLines, CancellationToken ct, string journalType = "GENERAL", string? reference = null) + { + try + { + if ((await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value != "true") return; // GL not set up yet + + var row = await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == evt && l.SourceId == sourceId, ct); + if (row is not null && row.Status == GlPostingStatus.Posted) return; + + if (row is null) + { + var lines = await buildLines(); + if (lines is null || lines.Count == 0) return; + + reference ??= $"{evt}:{sourceId}"; + var payload = new Payload(date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), journalType, sourceModule, reference, + narration, "Distribution", lines.Select(l => new PayloadLine(l.AccountCode, l.Debit, l.Credit)).ToList()); + + row = new GlPostingLog + { + Event = evt, + SourceId = sourceId, + Reference = reference, + PayloadJson = JsonSerializer.Serialize(payload, JsonOptions) + }; + await uow.BeginAsync(ct); + try { await context.GlPostingLogs.AddAsync(row, ct); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + } + + await SendAsync(row, ct); + } + catch (Exception ex) + { + logger.LogError(ex, "GL posting {Event}:{SourceId} failed before it could be sent", evt, sourceId); + } + } + + private async Task SendAsync(GlPostingLog row, CancellationToken ct) + { + string? journalNo = null; + string? error = null; + try + { + var payload = JsonSerializer.Deserialize(row.PayloadJson); + var entryDate = payload.GetProperty("entryDate").GetString()!; + + var period = await gl.SendAsync(HttpMethod.Get, $"v1/fiscal-years/periods/by-date?date={entryDate}", null, ct); + var periodId = period.Success && GlJson.Data(period) is { } pd ? GlJson.GetLong(pd, "periodId") : null; + if (periodId is null) + { + error = $"No accounting period in GL covers {entryDate}. Run GL setup for that year."; + } + else + { + var body = JsonSerializer.Deserialize>(row.PayloadJson, JsonOptions)!; + body["periodId"] = JsonSerializer.SerializeToElement(periodId.Value); + var result = await gl.SendAsync(HttpMethod.Post, "v1/journal-entries", body, ct); + if (result.Success) + journalNo = GlJson.Data(result) is { } d ? GlJson.GetString(d, "journalNo") ?? "" : ""; + else + error = GlJson.Message(result) ?? $"GL returned HTTP {result.StatusCode}"; + } + } + catch (Exception ex) + { + error = ex.Message; + logger.LogWarning(ex, "GL posting {Reference} failed", row.Reference); + } + + row.Attempts++; + row.LastAttemptAt = DateTime.Now; + row.Status = error is null ? GlPostingStatus.Posted : GlPostingStatus.Failed; + row.GlJournalNo = journalNo; + row.Error = error is null ? null : error.Length > 1000 ? error[..1000] : error; + + await uow.BeginAsync(ct); + try { context.GlPostingLogs.Update(row); await uow.CommitAsync(ct); } + catch { await uow.RollbackAsync(ct); throw; } + } + + // ── Account lookups ────────────────────────────────────────────────────── + + private async Task Acct(string key) => + (await configRepo.GetByKeyAsync(key))?.Value is { Length: > 0 } code + ? code + : throw new InvalidOperationException($"GL account mapping '{key}' is missing. Run GL setup."); + + private async Task BankCode(Guid? bankAccountId) + { + if (bankAccountId is null) throw new InvalidOperationException("No cash/bank account on this transaction."); + var account = await context.BankAccounts.FirstOrDefaultAsync(a => a.Id == bankAccountId.Value) + ?? throw new InvalidOperationException("Cash/bank account not found."); + return await setup.EnsureBankAccountAsync(account) + ?? throw new InvalidOperationException("Could not map the cash/bank account to a GL account. Run GL setup."); + } + + private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; +} diff --git a/Distribution/Features/GeneralLedger/GlSetupService.cs b/Distribution/Features/GeneralLedger/GlSetupService.cs new file mode 100644 index 0000000..614f0de --- /dev/null +++ b/Distribution/Features/GeneralLedger/GlSetupService.cs @@ -0,0 +1,226 @@ +using System.Text.Json; +using Distribution.Data; +using Distribution.Features.SysConfig; +using Distribution.Infra.UoW; +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.GeneralLedger; + +public record GlSetupResult(bool Done, List Actions); + +/// +/// One-time (re-runnable) bootstrap of the General Ledger service for Distribution: creates the chart of accounts +/// Distribution posts to, a fiscal year with its periods, the GL cash-root config, and a GL account for every +/// Distribution cash/bank account. The resulting GL account codes are stored in SysConfig (see GlKeys). +/// +public class GlSetupService( + IGeneralLedgerClient gl, + ISysConfigRepository configRepo, + AppDBContext context, + IUnitOfWork uow) +{ + private const short Asset = 1, Liability = 2, Equity = 3, Income = 4; + + // key, GL account name, account type, parent key, postable, tag name, tag value. + // Operating = working-capital account for the Cash Flow report (cashFlowCategory OPERATING). + private record AccountDef(string Key, string Name, short TypeId, string? ParentKey, bool Postable, string? TagField, string? TagValue, bool Operating = false); + + private static readonly AccountDef[] Chart = + [ + new(GlKeys.CashEquivalents, "Cash and Cash Equivalents", Asset, null, false, "balanceSheetClassification", "CURRENT_ASSET"), + new(GlKeys.CashInHand, "Cash in Hand", Asset, GlKeys.CashEquivalents, false, null, null), + new(GlKeys.BankAccounts, "Bank Accounts", Asset, GlKeys.CashEquivalents, false, null, null), + new(GlKeys.ChequesInHand, "Cheques in Hand", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true), + new(GlKeys.Receivable, "Trade Receivables", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true), + new(GlKeys.Inventory, "Inventory", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true), + new(GlKeys.InputVat, "Input VAT", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true), + new(GlKeys.InputNbt, "Input NBT", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true), + new(GlKeys.Payable, "Trade Payables", Liability, null, true, "balanceSheetClassification", "CURRENT_LIABILITY", true), + new(GlKeys.ChequesIssued, "Cheques Issued - Uncleared", Liability, null, true, "balanceSheetClassification", "CURRENT_LIABILITY", true), + new(GlKeys.Equity, "Owner's Equity", Equity, null, false, null, null), + new(GlKeys.ShareCapital, "Share Capital / Owner's Capital", Equity, GlKeys.Equity, true, null, null), + new(GlKeys.RetainedEarnings, "Retained Earnings", Equity, GlKeys.Equity, true, null, null), + new(GlKeys.Drawings, "Drawings and Dividends", Equity, GlKeys.Equity, true, null, null), + new(GlKeys.GeneralReserve, "General Reserve", Equity, GlKeys.Equity, true, null, null), + new(GlKeys.RevaluationReserve, "Revaluation Reserve", Equity, GlKeys.Equity, true, null, null), + new(GlKeys.Sales, "Sales Revenue", Income, null, true, "incomeStatementSection", "SALES"), + ]; + + public async Task RunAsync(int? year, Guid? userId, CancellationToken ct = default) + { + if (!await gl.IsHealthyAsync(ct)) + throw new InvalidOperationException("The General Ledger service is not reachable. Check GeneralLedger:BaseUrl and that the service is running."); + + var actions = new List(); + var codes = new Dictionary(); + + foreach (var key in GlKeys.AllAccountKeys) + { + var existing = (await configRepo.GetByKeyAsync(key))?.Value; + if (!string.IsNullOrWhiteSpace(existing)) codes[key] = existing; + } + + // 1. Chart of accounts (skips accounts whose code is already stored and still exists in GL). + foreach (var def in Chart) + { + if (codes.TryGetValue(def.Key, out var code) && await AccountIdAsync(code, ct) is not null) + continue; + + long? parentId = null; + if (def.ParentKey is not null) + parentId = await AccountIdAsync(codes[def.ParentKey], ct); + + var created = await CreateAccountAsync(def.Name, def.TypeId, parentId, def.Postable, ct); + codes[def.Key] = created.Code; + actions.Add($"Created GL account {created.Code} - {def.Name}"); + } + + // Tags are applied on every run (idempotent), so accounts created by an earlier run are corrected too. + foreach (var def in Chart) + { + var tags = new Dictionary(); + if (def.TagField is not null) tags[def.TagField] = def.TagValue; + if (def.Operating) tags["cashFlowCategory"] = "OPERATING"; + if (!def.Postable) tags["isPostable"] = false; // header accounts (re-applied so older runs are corrected) + if (tags.Count > 0) await UpdateAccountAsync(codes[def.Key], tags, ct); + } + + // 2. Fiscal year + periods for the requested year (default: current year). + var fy = year ?? DateTime.Now.Year; + var probe = await gl.SendAsync(HttpMethod.Get, $"v1/fiscal-years/periods/by-date?date={fy}-06-30", null, ct); + if (!probe.Success) + { + var fyResult = await gl.SendAsync(HttpMethod.Post, "v1/fiscal-years", new + { + yearName = $"FY{fy}", + startDate = $"{fy}-01-01", + endDate = $"{fy}-12-31", + autoGeneratePeriods = true, + periodCount = 12 + }, ct); + if (!fyResult.Success) + throw new InvalidOperationException($"Could not create fiscal year {fy} in GL: {GlJson.Message(fyResult) ?? fyResult.StatusCode.ToString()}"); + actions.Add($"Created fiscal year FY{fy} with 12 periods"); + } + + // 3. GL cash root (GL locks this key after one edit, so only set it when empty). + var cashRootId = await AccountIdAsync(codes[GlKeys.CashEquivalents], ct); + var cfgResult = await gl.SendAsync(HttpMethod.Get, "v1/system-config", null, ct); + var currentRoot = ReadGlConfigValue(cfgResult, "CASH_ACCOUNT_ROOT_ID"); + if (string.IsNullOrWhiteSpace(currentRoot) && cashRootId is not null) + { + var set = await gl.SendAsync(HttpMethod.Put, "v1/system-config/CASH_ACCOUNT_ROOT_ID", + new { configValue = cashRootId.Value.ToString() }, ct); + if (!set.Success) + throw new InvalidOperationException($"Could not set CASH_ACCOUNT_ROOT_ID in GL: {GlJson.Message(set) ?? set.StatusCode.ToString()}"); + actions.Add("Set GL CASH_ACCOUNT_ROOT_ID"); + } + + // 4. Persist the mapping, then mirror every existing Distribution cash/bank account. + await SaveConfigAsync(codes, userId, ct); + + foreach (var account in await context.BankAccounts.Where(a => a.GlAccountCode == null).ToListAsync(ct)) + { + var code = await EnsureBankAccountAsync(account, ct); + if (code is not null) actions.Add($"Mirrored {account.AccountName} as GL account {code}"); + } + + await SaveConfigAsync(new Dictionary { [GlKeys.SetupDone] = "true" }, userId, ct); + return new GlSetupResult(true, actions); + } + + /// + /// Returns the GL account code for a Distribution cash/bank account, creating the GL child account on first use. + /// Saves the code on the BankAccount. Returns null if GL setup has not been run. + /// + public async Task EnsureBankAccountAsync(Models.BankAccount account, CancellationToken ct = default) + { + if (!string.IsNullOrWhiteSpace(account.GlAccountCode)) return account.GlAccountCode; + + var parentKey = account.AccountType == BankAccountType.Bank ? GlKeys.BankAccounts : GlKeys.CashInHand; + var parentCode = (await configRepo.GetByKeyAsync(parentKey))?.Value; + if (string.IsNullOrWhiteSpace(parentCode)) return null; + + var parentId = await AccountIdAsync(parentCode, ct); + if (parentId is null) return null; + + var name = account.AccountType == BankAccountType.Bank && !string.IsNullOrWhiteSpace(account.BankName) + ? $"{account.BankName} - {account.AccountName}" + : account.AccountName; + + var created = await CreateAccountAsync(name, Asset, parentId, true, ct); + + account.GlAccountCode = created.Code; + await uow.BeginAsync(ct); + try + { + context.BankAccounts.Update(account); + await uow.CommitAsync(ct); + } + catch { await uow.RollbackAsync(ct); throw; } + + return created.Code; + } + + private async Task SaveConfigAsync(Dictionary values, Guid? userId, CancellationToken ct) + { + await uow.BeginAsync(ct); + try + { + foreach (var (key, value) in values) + await configRepo.UpsertAsync(new Models.SysConfig { Key = key, Value = value, UpdatedAt = DateTime.Now, UpdatedBy = userId }); + await uow.CommitAsync(ct); + } + catch { await uow.RollbackAsync(ct); throw; } + } + + private async Task AccountIdAsync(string code, CancellationToken ct) + { + var result = await gl.SendAsync(HttpMethod.Get, $"v1/accounts/{Uri.EscapeDataString(code)}", null, ct); + if (!result.Success) return null; + var data = GlJson.Data(result); + if (data is null) return null; + // GET /accounts/{code} wraps the account: { account: {...}, ... } + var account = GlJson.Get(data.Value, "account") ?? data.Value; + return GlJson.GetLong(account, "accountId"); + } + + private async Task<(long Id, string Code)> CreateAccountAsync(string name, short typeId, long? parentId, bool postable, CancellationToken ct) + { + var result = await gl.SendAsync(HttpMethod.Post, "v1/accounts", new + { + accountName = name, + parentAccountId = parentId, + accountTypeId = typeId, + isControlAccount = false, + isPostable = postable + }, ct); + if (!result.Success) + throw new InvalidOperationException($"Could not create GL account '{name}': {GlJson.Message(result) ?? result.StatusCode.ToString()}"); + + var data = GlJson.Data(result) ?? throw new InvalidOperationException($"GL returned no data when creating account '{name}'."); + var id = GlJson.GetLong(data, "accountId"); + var code = GlJson.GetString(data, "accountCode"); + if (id is null || code is null) + throw new InvalidOperationException($"GL returned an unexpected body when creating account '{name}'."); + return (id.Value, code); + } + + private async Task UpdateAccountAsync(string code, Dictionary fields, CancellationToken ct) + { + var result = await gl.SendAsync(HttpMethod.Put, $"v1/accounts/{Uri.EscapeDataString(code)}", fields, ct); + if (!result.Success) + throw new InvalidOperationException($"Could not update GL account {code}: {GlJson.Message(result) ?? result.StatusCode.ToString()}"); + } + + private static string? ReadGlConfigValue(GeneralLedgerResult result, string key) + { + if (!result.Success || GlJson.Data(result) is not { } data) return null; + var items = data.ValueKind == JsonValueKind.Array ? data : GlJson.Get(data, "items"); + if (items is not { ValueKind: JsonValueKind.Array }) return null; + foreach (var item in items.Value.EnumerateArray()) + if (GlJson.GetString(item, "configKey") == key) return GlJson.GetString(item, "configValue"); + return null; + } +} diff --git a/Distribution/Features/Grn/GrnService.cs b/Distribution/Features/Grn/GrnService.cs index 2e871cd..a86ae3d 100644 --- a/Distribution/Features/Grn/GrnService.cs +++ b/Distribution/Features/Grn/GrnService.cs @@ -1,3 +1,4 @@ +using Distribution.Features.GeneralLedger; using Distribution.Features.Po; using Distribution.Features.Stock; using Distribution.Features.SysConfig; @@ -13,7 +14,8 @@ public class GrnService( IUnitOfWork uow, Distribution.Features.Item.IItemRepository itemRepo, Distribution.Features.Supplier.ISupplierRepository supplierRepo, - Distribution.Features.SysConfig.SysConfigService sysConfig) + Distribution.Features.SysConfig.SysConfigService sysConfig, + GlPostingService gl) { private static GrnItemResponse MapItem(Models.GoodsReceiptItem i) { @@ -482,6 +484,7 @@ public class GrnService( catch { await uow.RollbackAsync(ct); throw; } var saved = await repo.GetByIdAsync(goodsReceipt.Id); + await gl.PostGrnAsync(saved!, ct); return Map(saved!); } } diff --git a/Distribution/Features/Invoice/InvoiceService.cs b/Distribution/Features/Invoice/InvoiceService.cs index dcc2891..c385925 100644 --- a/Distribution/Features/Invoice/InvoiceService.cs +++ b/Distribution/Features/Invoice/InvoiceService.cs @@ -1,4 +1,5 @@ -using Distribution.Features.Stock; +using Distribution.Features.GeneralLedger; +using Distribution.Features.Stock; using Distribution.Infra.Authorization; using Distribution.Infra.UoW; using Distribution.Models; @@ -8,7 +9,8 @@ namespace Distribution.Features.Invoice; public class InvoiceService( IInvoiceRepository repo, IUnitOfWork uow, - ICurrentUserService currentUser) + ICurrentUserService currentUser, + GlPostingService gl) { private static InvoiceResponse ToResponse(Models.Invoice i) => new(i.Id, i.FK_ShopPOId, @@ -96,6 +98,8 @@ public class InvoiceService( try { repo.Update(invoice); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } + await gl.PostInvoiceFinalizedAsync(invoice, ct); + return ToResponse(invoice); } } diff --git a/Distribution/Features/Payment/PaymentService.cs b/Distribution/Features/Payment/PaymentService.cs index 57810ab..13bfc31 100644 --- a/Distribution/Features/Payment/PaymentService.cs +++ b/Distribution/Features/Payment/PaymentService.cs @@ -1,4 +1,5 @@ using Distribution.Features.BankAccount; +using Distribution.Features.GeneralLedger; using Distribution.Features.Invoice; using Distribution.Features.ReceivedCheque; using Distribution.Features.StoreCredit; @@ -15,7 +16,8 @@ public class PaymentService( IReceivedChequeRepository receivedChequeRepo, IStoreCreditMovementRepository creditRepo, IUnitOfWork uow, - ICurrentUserService currentUser) + ICurrentUserService currentUser, + GlPostingService gl) { private static PaymentResponse ToResponse(Models.Payment p) => new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.EntryMode, @@ -150,14 +152,13 @@ public class PaymentService( invoiceRepo.Update(invoice); - // TODO ledger: when accounting is wired in, post a journal entry here — - // Debit Cash/Bank (BankAccount) OR Cheques-in-hand (for Cheque payments) - // Credit Accounts Receivable (invoice.Store) - await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } + // Mirrors to the General Ledger after commit: Dr Cash/Bank (or Cheques-in-hand) / Cr Receivable. Never throws. + await gl.PostCustomerPaymentAsync(payment, ct); + return ToResponse(payment); } } diff --git a/Distribution/Features/ReceivedCheque/ReceivedChequeService.cs b/Distribution/Features/ReceivedCheque/ReceivedChequeService.cs index bf29679..2f47956 100644 --- a/Distribution/Features/ReceivedCheque/ReceivedChequeService.cs +++ b/Distribution/Features/ReceivedCheque/ReceivedChequeService.cs @@ -1,5 +1,6 @@ using Distribution.Features.BankAccount; using Distribution.Features.ChequeBook; +using Distribution.Features.GeneralLedger; using Distribution.Infra.UoW; using Distribution.Models; @@ -8,7 +9,8 @@ namespace Distribution.Features.ReceivedCheque; public class ReceivedChequeService( IReceivedChequeRepository repo, IBankAccountRepository bankAccountRepo, - IUnitOfWork uow) + IUnitOfWork uow, + GlPostingService gl) { private static ReceivedChequeResponse ToResponse(Models.ReceivedCheque c) => new(c.Id, c.CompanyId, c.BranchId, c.ReceivedFromType, c.ReceivedFromId, c.ReceivedFromName, @@ -110,6 +112,7 @@ public class ReceivedChequeService( repo.Update(cheque); await uow.CommitAsync(ct); + await gl.PostReceivedChequeAsync(cheque, request.Action, ct); return ToResponse(cheque); } } diff --git a/Distribution/Features/SupplierPayment/SupplierPaymentService.cs b/Distribution/Features/SupplierPayment/SupplierPaymentService.cs index bf57769..b6d032c 100644 --- a/Distribution/Features/SupplierPayment/SupplierPaymentService.cs +++ b/Distribution/Features/SupplierPayment/SupplierPaymentService.cs @@ -1,6 +1,7 @@ using Distribution.Data; using Distribution.Features.BankAccount; using Distribution.Features.ChequeBook; +using Distribution.Features.GeneralLedger; using Distribution.Features.Grn; using Distribution.Features.Supplier; using Distribution.Infra.Authorization; @@ -17,7 +18,8 @@ public class SupplierPaymentService( IBankAccountRepository bankAccountRepo, IChequeBookRepository chequeBookRepo, IUnitOfWork uow, - ICurrentUserService currentUser) + ICurrentUserService currentUser, + GlPostingService gl) { private static SupplierPaymentResponse ToResponse(Models.SupplierPayment p) => new(p.Id, p.FK_SupplierId, p.Amount, p.Method, @@ -161,14 +163,13 @@ public class SupplierPaymentService( await repo.AddAsync(payment); - // TODO ledger: when accounting is wired in, post a journal entry here — - // Debit Accounts Payable (supplier) - // Credit Cash/Bank (BankAccount) OR Cheques-outstanding (for Cheque payments) - await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } + // Mirrors to the General Ledger after commit: Dr Payable / Cr Cash/Bank (or Cheques-issued). Never throws. + await gl.PostSupplierPaymentAsync(payment, ct); + return ToResponse(payment); } } diff --git a/Distribution/Migrations/20260917034841_RefactorSalesToShopPOAndInvoice.cs b/Distribution/Migrations/20260917034841_RefactorSalesToShopPOAndInvoice.cs index 468488d..4656c8e 100644 --- a/Distribution/Migrations/20260917034841_RefactorSalesToShopPOAndInvoice.cs +++ b/Distribution/Migrations/20260917034841_RefactorSalesToShopPOAndInvoice.cs @@ -98,6 +98,12 @@ namespace Distribution.Migrations table: "DeliveryTasks", newName: "FK_ShopPOId"); + // MariaDB drops the ascii collation on CHANGE COLUMN; restore it so the FK to ShopPOs.Id can be created. + migrationBuilder.Sql("ALTER TABLE `DeliveryTasks` MODIFY `FK_ShopPOId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL;"); + migrationBuilder.Sql("ALTER TABLE `SchemeRedemptions` MODIFY `FK_ShopPOId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NULL;"); + migrationBuilder.Sql("ALTER TABLE `SaleReturns` MODIFY `FK_ShopPOId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL;"); + migrationBuilder.Sql("ALTER TABLE `Payments` MODIFY `FK_InvoiceId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL;"); + migrationBuilder.RenameIndex( name: "IX_DeliveryTasks_FK_OrderId", table: "DeliveryTasks", diff --git a/Distribution/Migrations/20261020082853_GlIntegration.Designer.cs b/Distribution/Migrations/20261020082853_GlIntegration.Designer.cs new file mode 100644 index 0000000..f057cf1 --- /dev/null +++ b/Distribution/Migrations/20261020082853_GlIntegration.Designer.cs @@ -0,0 +1,5868 @@ +// +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("20261020082853_GlIntegration")] + partial class GlIntegration + { + /// + 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("GlAccountCode") + .HasColumnType("longtext"); + + 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.GlPostingLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("Event") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("GlJournalNo") + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastAttemptAt") + .HasColumnType("datetime(6)"); + + b.Property("PayloadJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Reference") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SourceId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Event", "SourceId") + .IsUnique(); + + b.ToTable("GlPostingLogs"); + }); + + 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 = "finance-reports.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View financial reports (trial balance, P&L, balance sheet, cash flow...)", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000052"), + Code = "gl.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Set up the General Ledger link and retry failed postings", + 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.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("52dedbb8-6251-4131-82de-41c2064a663b"), + 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("47450af3-cf95-4725-a208-eeca124022b7"), + 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("44deed45-fc6f-420c-a026-f4483efab782"), + 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("12ae0586-2ff1-4333-92be-9e4660d0f34d"), + 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("cd258515-3abd-448f-9972-1b9ae9a953d8"), + 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("9d2dcbbe-cf49-4842-bbcd-1ed0c3bf5f05"), + 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("16ad892c-b785-4872-a426-1015f32c90b9"), + 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("3ac5579e-2ba0-44e2-b194-e8a34c91d831"), + 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("498230b5-7d98-4826-afaf-944123658f69"), + 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("e960268d-ad0e-4cdc-a387-e2b4fa4ede47"), + 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("61b598f0-1138-4733-8dac-b84911b1ac18"), + 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("91ab95eb-c25b-44f9-be90-9b0ffeba4e7a"), + 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("96ffe8c1-2d62-4d56-8933-02bc4f79972e"), + 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("1f38d01e-e333-40cd-811c-cc1bcaaaa97b"), + 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("8c768b0a-0920-4b28-b48a-3f62ba8a16d5"), + 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("7a87ddf5-e185-4978-9390-09388ab1b264"), + 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("86229744-f68d-4e7e-9b07-2aad58584331"), + 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("646c3d0c-801a-4382-8a50-3a8d57d3225e"), + 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("e2b513a0-5fae-4642-8bf5-c2e3dc734aca"), + 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("59ce3bc1-2466-4090-97fe-9ae81d718a83"), + 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("48772797-f181-423d-bfb3-f6226a9abe11"), + 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("e99740a5-b681-4b6b-9a42-754ca3114e00"), + 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("a1b180a1-fbc5-4f18-aeaa-82b443a4fc3f"), + 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("83070521-f8f2-4b54-ae59-8cd2972de2a1"), + 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("132b8143-0836-4a3b-b5b3-90cdbe6df12d"), + 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("9ad2e440-e355-41c8-9e9e-00a78de66abb"), + 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("b76e221a-fb92-43a3-a723-dcd5fa3327f6"), + 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("8f63c426-38bb-456d-adff-75e477857607"), + 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("e4a559d1-bc00-438b-b636-a85803a7ef02"), + 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("d0a1ab21-0809-4605-a1d2-29e546cb4ae8"), + 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("1e3fb35b-0de5-4d15-860f-51e0c9ec1bb1"), + 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("12e98296-456e-40a3-b11c-61d3393b335e"), + 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("1d47dc32-9faa-418e-8241-a0dc8a32c974"), + 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("5e701ad5-f462-4642-86d1-303148323a32"), + 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("52dfa802-8a12-4554-b7ab-1e286998b3b9"), + 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("7944f075-4eb3-4ccd-98bf-584f79f9f4d5"), + 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("6ce15356-7240-4a79-bfc8-340c8f31e270"), + 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("ac248603-3c71-4c1b-84dc-174f25cade65"), + 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("7805ec5e-8b99-4b45-b47a-be01405a2151"), + 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("7ea67b95-2918-48e9-b569-71123adb82a0"), + 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("a2515783-f099-4332-a07c-be81f2e85160"), + 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("2bff0498-d66e-4564-b0ec-5924cbab96a3"), + 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("5fa95034-9d7f-4350-b754-9a16b03804fe"), + 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("f1a82d54-68b8-4992-a4ba-28bd58dd5281"), + 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("fa183267-8eb0-4831-8f5f-ba1af4d5a78d"), + 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("fd75e8b3-8751-4b67-870b-6370098a5e1e"), + 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("c6632177-d567-4cce-98dd-80d735c2e5db"), + 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("b463deda-4bb9-4d86-a0fc-513d10fc57b1"), + 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("b44bfcb4-ff1d-4848-9098-1ae54bace236"), + 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("93c036e4-d811-4915-b73c-1cb358f17065"), + 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("9c27d325-1915-41c6-9b62-d85a12f102e1"), + 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("1f48dd7b-2f14-451d-b80d-71579d5625e6"), + 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.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.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.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/20261020082853_GlIntegration.cs b/Distribution/Migrations/20261020082853_GlIntegration.cs new file mode 100644 index 0000000..c4b6451 --- /dev/null +++ b/Distribution/Migrations/20261020082853_GlIntegration.cs @@ -0,0 +1,814 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Distribution.Migrations +{ + /// + public partial class GlIntegration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GlAccountCode", + table: "BankAccounts", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "GlPostingLogs", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Event = table.Column(type: "varchar(60)", maxLength: 60, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + SourceId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Reference = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column(type: "int", nullable: false), + PayloadJson = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + GlJournalNo = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Error = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Attempts = table.Column(type: "int", nullable: false), + LastAttemptAt = table.Column(type: "datetime(6)", nullable: true), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GlPostingLogs", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.UpdateData( + table: "BankAccounts", + keyColumn: "Id", + keyValue: new Guid("11111111-1111-1111-1111-000000000001"), + column: "GlAccountCode", + value: null); + + migrationBuilder.InsertData( + table: "Permissions", + columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" }, + values: new object[,] + { + { new Guid("20000000-0000-0000-0000-000000000051"), "finance-reports.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View financial reports (trial balance, P&L, balance sheet, cash flow...)", true }, + { new Guid("20000000-0000-0000-0000-000000000052"), "gl.manage", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Set up the General Ledger link and retry failed postings", 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("52dedbb8-6251-4131-82de-41c2064a663b")); + + 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("47450af3-cf95-4725-a208-eeca124022b7")); + + 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("44deed45-fc6f-420c-a026-f4483efab782")); + + 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("12ae0586-2ff1-4333-92be-9e4660d0f34d")); + + 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("cd258515-3abd-448f-9972-1b9ae9a953d8")); + + 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("9d2dcbbe-cf49-4842-bbcd-1ed0c3bf5f05")); + + 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("16ad892c-b785-4872-a426-1015f32c90b9")); + + 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("3ac5579e-2ba0-44e2-b194-e8a34c91d831")); + + 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("498230b5-7d98-4826-afaf-944123658f69")); + + 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("e960268d-ad0e-4cdc-a387-e2b4fa4ede47")); + + 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("61b598f0-1138-4733-8dac-b84911b1ac18")); + + 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("91ab95eb-c25b-44f9-be90-9b0ffeba4e7a")); + + 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("96ffe8c1-2d62-4d56-8933-02bc4f79972e")); + + 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("1f38d01e-e333-40cd-811c-cc1bcaaaa97b")); + + 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("8c768b0a-0920-4b28-b48a-3f62ba8a16d5")); + + 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("7a87ddf5-e185-4978-9390-09388ab1b264")); + + 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("86229744-f68d-4e7e-9b07-2aad58584331")); + + 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("646c3d0c-801a-4382-8a50-3a8d57d3225e")); + + 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("e2b513a0-5fae-4642-8bf5-c2e3dc734aca")); + + 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("59ce3bc1-2466-4090-97fe-9ae81d718a83")); + + 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("48772797-f181-423d-bfb3-f6226a9abe11")); + + 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("e99740a5-b681-4b6b-9a42-754ca3114e00")); + + 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("a1b180a1-fbc5-4f18-aeaa-82b443a4fc3f")); + + 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("83070521-f8f2-4b54-ae59-8cd2972de2a1")); + + 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("132b8143-0836-4a3b-b5b3-90cdbe6df12d")); + + 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("9ad2e440-e355-41c8-9e9e-00a78de66abb")); + + 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("b76e221a-fb92-43a3-a723-dcd5fa3327f6")); + + 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("8f63c426-38bb-456d-adff-75e477857607")); + + 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("e4a559d1-bc00-438b-b636-a85803a7ef02")); + + 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("d0a1ab21-0809-4605-a1d2-29e546cb4ae8")); + + 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("1e3fb35b-0de5-4d15-860f-51e0c9ec1bb1")); + + 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("12e98296-456e-40a3-b11c-61d3393b335e")); + + 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("1d47dc32-9faa-418e-8241-a0dc8a32c974")); + + 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("5e701ad5-f462-4642-86d1-303148323a32")); + + 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("52dfa802-8a12-4554-b7ab-1e286998b3b9")); + + 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("7944f075-4eb3-4ccd-98bf-584f79f9f4d5")); + + 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("6ce15356-7240-4a79-bfc8-340c8f31e270")); + + 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("ac248603-3c71-4c1b-84dc-174f25cade65")); + + 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("7805ec5e-8b99-4b45-b47a-be01405a2151")); + + 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("7ea67b95-2918-48e9-b569-71123adb82a0")); + + 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("a2515783-f099-4332-a07c-be81f2e85160")); + + 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("2bff0498-d66e-4564-b0ec-5924cbab96a3")); + + 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("5fa95034-9d7f-4350-b754-9a16b03804fe")); + + 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("f1a82d54-68b8-4992-a4ba-28bd58dd5281")); + + 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("fa183267-8eb0-4831-8f5f-ba1af4d5a78d")); + + 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("fd75e8b3-8751-4b67-870b-6370098a5e1e")); + + 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("c6632177-d567-4cce-98dd-80d735c2e5db")); + + 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("b463deda-4bb9-4d86-a0fc-513d10fc57b1")); + + 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("b44bfcb4-ff1d-4848-9098-1ae54bace236")); + + 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("93c036e4-d811-4915-b73c-1cb358f17065")); + + 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("9c27d325-1915-41c6-9b62-d85a12f102e1"), 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("1f48dd7b-2f14-451d-b80d-71579d5625e6"), true } + }); + + migrationBuilder.CreateIndex( + name: "IX_GlPostingLogs_Event_SourceId", + table: "GlPostingLogs", + columns: new[] { "Event", "SourceId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "GlPostingLogs"); + + 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: "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.DropColumn( + name: "GlAccountCode", + table: "BankAccounts"); + + migrationBuilder.UpdateData( + table: "RolePermissions", + keyColumns: new[] { "FK_PermissionId", "FK_RoleId" }, + keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") }, + column: "Id", + value: new Guid("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 4b564ad..57c83a3 100644 --- a/Distribution/Migrations/AppDBContextModelSnapshot.cs +++ b/Distribution/Migrations/AppDBContextModelSnapshot.cs @@ -56,6 +56,9 @@ namespace Distribution.Migrations .HasMaxLength(10) .HasColumnType("varchar(10)"); + b.Property("GlAccountCode") + .HasColumnType("longtext"); + b.Property("IsActive") .HasColumnType("tinyint(1)"); @@ -67,7 +70,7 @@ namespace Distribution.Migrations b.HasIndex("AccountType", "AccountName") .IsUnique(); - b.ToTable("BankAccounts", (string)null); + b.ToTable("BankAccounts"); b.HasData( new @@ -104,7 +107,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("Brands", (string)null); + b.ToTable("Brands"); b.HasData( new @@ -147,7 +150,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("Categories", (string)null); + b.ToTable("Categories"); b.HasData( new @@ -223,7 +226,7 @@ namespace Distribution.Migrations b.HasIndex("FK_BankAccountId"); - b.ToTable("ChequeBooks", (string)null); + b.ToTable("ChequeBooks"); }); modelBuilder.Entity("Distribution.Models.ChequePage", b => @@ -311,7 +314,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ChequeBookId"); - b.ToTable("ChequePages", (string)null); + b.ToTable("ChequePages"); }); modelBuilder.Entity("Distribution.Models.Claim", b => @@ -351,7 +354,7 @@ namespace Distribution.Migrations b.HasIndex("SettledByUserId"); - b.ToTable("Claims", (string)null); + b.ToTable("Claims"); b.HasData( new @@ -393,7 +396,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SaleReturnId"); - b.ToTable("CreditNotes", (string)null); + b.ToTable("CreditNotes"); b.HasData( new @@ -452,7 +455,7 @@ namespace Distribution.Migrations b.HasIndex("FK_VehicleId"); - b.ToTable("DeliveryRuns", (string)null); + b.ToTable("DeliveryRuns"); b.HasData( new @@ -521,7 +524,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ShopPOId"); - b.ToTable("DeliveryTasks", (string)null); + b.ToTable("DeliveryTasks"); b.HasData( new @@ -557,7 +560,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("DriverComplianceConfigs", (string)null); + b.ToTable("DriverComplianceConfigs"); b.HasData( new @@ -600,7 +603,7 @@ namespace Distribution.Migrations b.HasIndex("FK_UserId") .IsUnique(); - b.ToTable("DriverProfiles", (string)null); + b.ToTable("DriverProfiles"); b.HasData( new @@ -625,6 +628,57 @@ namespace Distribution.Migrations }); }); + modelBuilder.Entity("Distribution.Models.GlPostingLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("Event") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("GlJournalNo") + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastAttemptAt") + .HasColumnType("datetime(6)"); + + b.Property("PayloadJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Reference") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SourceId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Event", "SourceId") + .IsUnique(); + + b.ToTable("GlPostingLogs"); + }); + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => { b.Property("Id") @@ -672,7 +726,7 @@ namespace Distribution.Migrations b.HasIndex("FK_WarehouseId"); - b.ToTable("GoodsReceipts", (string)null); + b.ToTable("GoodsReceipts"); b.HasData( new @@ -744,7 +798,7 @@ namespace Distribution.Migrations b.HasIndex("FK_PurchaseOrderItemId"); - b.ToTable("GoodsReceiptItems", (string)null); + b.ToTable("GoodsReceiptItems"); b.HasData( new @@ -806,7 +860,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ItemBatchId"); - b.ToTable("GoodsReceiptItemBatches", (string)null); + b.ToTable("GoodsReceiptItemBatches"); }); modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => @@ -833,7 +887,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ItemSerialNumberId"); - b.ToTable("GoodsReceiptItemSerials", (string)null); + b.ToTable("GoodsReceiptItemSerials"); }); modelBuilder.Entity("Distribution.Models.Invoice", b => @@ -879,7 +933,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ShopPOId"); - b.ToTable("Invoices", (string)null); + b.ToTable("Invoices"); b.HasData( new @@ -993,7 +1047,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SubCategoryId"); - b.ToTable("Items", (string)null); + b.ToTable("Items"); b.HasData( new @@ -1102,7 +1156,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ItemId", "BatchNumber"); - b.ToTable("ItemBatches", (string)null); + b.ToTable("ItemBatches"); }); modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => @@ -1147,7 +1201,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ItemId", "SerialNumber") .IsUnique(); - b.ToTable("ItemSerialNumbers", (string)null); + b.ToTable("ItemSerialNumbers"); }); modelBuilder.Entity("Distribution.Models.LoadingSheet", b => @@ -1184,7 +1238,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RideId"); - b.ToTable("LoadingSheets", (string)null); + b.ToTable("LoadingSheets"); b.HasData( new @@ -1238,7 +1292,7 @@ namespace Distribution.Migrations b.HasIndex("FK_WarehouseId"); - b.ToTable("LoadingSheetItems", (string)null); + b.ToTable("LoadingSheetItems"); b.HasData( new @@ -1275,7 +1329,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ShopPOId"); - b.ToTable("LoadingSheetOrders", (string)null); + b.ToTable("LoadingSheetOrders"); }); modelBuilder.Entity("Distribution.Models.Payment", b => @@ -1331,7 +1385,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ReceivedChequeId"); - b.ToTable("Payments", (string)null); + b.ToTable("Payments"); b.HasData( new @@ -1409,7 +1463,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("Permissions", (string)null); + b.ToTable("Permissions"); b.HasData( new @@ -1811,6 +1865,22 @@ 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 = "finance-reports.view", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "View financial reports (trial balance, P&L, balance sheet, cash flow...)", + IsActive = true + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000052"), + Code = "gl.manage", + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Set up the General Ledger link and retry failed postings", + IsActive = true }); }); @@ -1843,7 +1913,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId"); - b.ToTable("PriceLists", (string)null); + b.ToTable("PriceLists"); b.HasData( new @@ -1895,7 +1965,7 @@ namespace Distribution.Migrations b.HasIndex("FK_PriceListId", "FK_ItemId") .IsUnique(); - b.ToTable("PriceListItems", (string)null); + b.ToTable("PriceListItems"); b.HasData( new @@ -1948,7 +2018,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SupplierId"); - b.ToTable("PurchaseOrders", (string)null); + b.ToTable("PurchaseOrders"); b.HasData( new @@ -2010,7 +2080,7 @@ namespace Distribution.Migrations b.HasIndex("FK_PurchaseOrderId"); - b.ToTable("PurchaseOrderItems", (string)null); + b.ToTable("PurchaseOrderItems"); b.HasData( new @@ -2077,7 +2147,7 @@ namespace Distribution.Migrations b.HasIndex("FK_PurchaseOrderId"); - b.ToTable("PurchaseReturns", (string)null); + b.ToTable("PurchaseReturns"); b.HasData( new @@ -2117,7 +2187,7 @@ namespace Distribution.Migrations b.HasIndex("FK_PurchaseReturnId"); - b.ToTable("PurchaseReturnItems", (string)null); + b.ToTable("PurchaseReturnItems"); b.HasData( new @@ -2210,7 +2280,7 @@ namespace Distribution.Migrations b.HasIndex("FK_DepositBankAccountId"); - b.ToTable("ReceivedCheques", (string)null); + b.ToTable("ReceivedCheques"); }); modelBuilder.Entity("Distribution.Models.Region", b => @@ -2239,7 +2309,7 @@ namespace Distribution.Migrations b.HasIndex("Code") .IsUnique(); - b.ToTable("Regions", (string)null); + b.ToTable("Regions"); b.HasData( new @@ -2285,7 +2355,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId", "FK_AgencyUserId") .IsUnique(); - b.ToTable("RegionAgencyMaps", (string)null); + b.ToTable("RegionAgencyMaps"); b.HasData( new @@ -2325,7 +2395,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId", "Period") .IsUnique(); - b.ToTable("RegionalBudgets", (string)null); + b.ToTable("RegionalBudgets"); b.HasData( new @@ -2366,7 +2436,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId", "Period") .IsUnique(); - b.ToTable("RegionalTargets", (string)null); + b.ToTable("RegionalTargets"); b.HasData( new @@ -2404,7 +2474,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId") .IsUnique(); - b.ToTable("RegionalTaxRules", (string)null); + b.ToTable("RegionalTaxRules"); b.HasData( new @@ -2438,7 +2508,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("Roles", (string)null); + b.ToTable("Roles"); b.HasData( new @@ -2520,7 +2590,7 @@ namespace Distribution.Migrations b.HasIndex("FK_PermissionId"); - b.ToTable("RolePermissions", (string)null); + b.ToTable("RolePermissions"); b.HasData( new @@ -2528,7 +2598,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("be712eda-98a5-4a30-820a-1737e84d1e4b"), + Id = new Guid("52dedbb8-6251-4131-82de-41c2064a663b"), IsActive = true }, new @@ -2536,7 +2606,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("47ea9420-d6a0-4376-a2b8-211639d95b27"), + Id = new Guid("47450af3-cf95-4725-a208-eeca124022b7"), IsActive = true }, new @@ -2544,7 +2614,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("369eed1f-df4c-4131-a3c6-ebbe63d2dc91"), + Id = new Guid("44deed45-fc6f-420c-a026-f4483efab782"), IsActive = true }, new @@ -2552,7 +2622,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("14b6944f-2d4a-4378-a974-c6fc0f9dfb81"), + Id = new Guid("12ae0586-2ff1-4333-92be-9e4660d0f34d"), IsActive = true }, new @@ -2560,7 +2630,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("14a01e0b-7be5-4c65-ae6a-899f9aa7ad28"), + Id = new Guid("cd258515-3abd-448f-9972-1b9ae9a953d8"), IsActive = true }, new @@ -2568,7 +2638,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("5d486ccd-3866-4e7d-8ef1-fb177fb637c1"), + Id = new Guid("9d2dcbbe-cf49-4842-bbcd-1ed0c3bf5f05"), IsActive = true }, new @@ -2576,7 +2646,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("d830e59b-f451-4bde-9a77-78daea5f9a61"), + Id = new Guid("16ad892c-b785-4872-a426-1015f32c90b9"), IsActive = true }, new @@ -2584,7 +2654,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("096d3837-a649-4184-8987-2ea0dfa2d4a1"), + Id = new Guid("3ac5579e-2ba0-44e2-b194-e8a34c91d831"), IsActive = true }, new @@ -2592,7 +2662,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("5e43bbde-689b-4d07-baf9-0764078b5586"), + Id = new Guid("498230b5-7d98-4826-afaf-944123658f69"), IsActive = true }, new @@ -2600,7 +2670,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("f317d30c-1644-44f0-b0aa-6bcf1fa66f44"), + Id = new Guid("e960268d-ad0e-4cdc-a387-e2b4fa4ede47"), IsActive = true }, new @@ -2608,7 +2678,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("31ffac6f-2aa8-48ee-bc87-213f35933de4"), + Id = new Guid("61b598f0-1138-4733-8dac-b84911b1ac18"), IsActive = true }, new @@ -2616,7 +2686,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("fb525bc4-9f16-455a-bfd5-ca6f3e7ad141"), + Id = new Guid("91ab95eb-c25b-44f9-be90-9b0ffeba4e7a"), IsActive = true }, new @@ -2624,7 +2694,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("2388918f-6392-4384-b269-0a1469f5bf4c"), + Id = new Guid("96ffe8c1-2d62-4d56-8933-02bc4f79972e"), IsActive = true }, new @@ -2632,7 +2702,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("7159012e-5f25-4881-b5e8-5300e382332e"), + Id = new Guid("1f38d01e-e333-40cd-811c-cc1bcaaaa97b"), IsActive = true }, new @@ -2640,7 +2710,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("cc4b2e82-ad42-4440-92ba-9b2100bb5f84"), + Id = new Guid("8c768b0a-0920-4b28-b48a-3f62ba8a16d5"), IsActive = true }, new @@ -2648,7 +2718,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("6b15c133-ea0e-473d-b58a-7f140e9f03bf"), + Id = new Guid("7a87ddf5-e185-4978-9390-09388ab1b264"), IsActive = true }, new @@ -2656,7 +2726,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("c2dd52a9-380f-479f-8e5e-6b56bfc4738f"), + Id = new Guid("86229744-f68d-4e7e-9b07-2aad58584331"), IsActive = true }, new @@ -2664,7 +2734,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("bf5f6c7a-2fde-4a01-83f4-aacf91333c4c"), + Id = new Guid("646c3d0c-801a-4382-8a50-3a8d57d3225e"), IsActive = true }, new @@ -2672,7 +2742,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("162a0346-c4c0-4c4e-96e9-aa8154d25b39"), + Id = new Guid("e2b513a0-5fae-4642-8bf5-c2e3dc734aca"), IsActive = true }, new @@ -2680,7 +2750,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("5cc76807-25d1-45e5-9da3-cfd6d6f3ad7c"), + Id = new Guid("59ce3bc1-2466-4090-97fe-9ae81d718a83"), IsActive = true }, new @@ -2688,7 +2758,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("51a2653e-4b1a-4f6f-80c2-f533b26109ea"), + Id = new Guid("48772797-f181-423d-bfb3-f6226a9abe11"), IsActive = true }, new @@ -2696,7 +2766,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("e2a382e2-d712-4a97-a7ec-cd6232d77a78"), + Id = new Guid("e99740a5-b681-4b6b-9a42-754ca3114e00"), IsActive = true }, new @@ -2704,7 +2774,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("b60260b5-70b0-4f9e-9ab9-ba4abbbdfb02"), + Id = new Guid("a1b180a1-fbc5-4f18-aeaa-82b443a4fc3f"), IsActive = true }, new @@ -2712,7 +2782,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("a73fe794-383c-4157-89cf-227c4e487a6d"), + Id = new Guid("83070521-f8f2-4b54-ae59-8cd2972de2a1"), IsActive = true }, new @@ -2720,7 +2790,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("a9743e96-15b2-4c1d-b410-698cd6db0b7b"), + Id = new Guid("132b8143-0836-4a3b-b5b3-90cdbe6df12d"), IsActive = true }, new @@ -2728,7 +2798,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("a94d92ab-66d8-4dd9-8ba3-c242eb450e68"), + Id = new Guid("9ad2e440-e355-41c8-9e9e-00a78de66abb"), IsActive = true }, new @@ -2736,7 +2806,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("9cb1a028-a98e-492a-aafc-70456fe46c96"), + Id = new Guid("b76e221a-fb92-43a3-a723-dcd5fa3327f6"), IsActive = true }, new @@ -2744,7 +2814,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("5bc99a62-f5d7-48fe-b25c-518e4774614c"), + Id = new Guid("8f63c426-38bb-456d-adff-75e477857607"), IsActive = true }, new @@ -2752,7 +2822,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("eceee678-5cee-4ee9-b16d-8d8d21fbb5ea"), + Id = new Guid("e4a559d1-bc00-438b-b636-a85803a7ef02"), IsActive = true }, new @@ -2760,7 +2830,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("3a049ed7-75b7-4b1d-b3bb-402ca77c350f"), + Id = new Guid("d0a1ab21-0809-4605-a1d2-29e546cb4ae8"), IsActive = true }, new @@ -2768,7 +2838,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("aeaab85c-5b02-4bc2-b0e3-318c0f5b07d3"), + Id = new Guid("1e3fb35b-0de5-4d15-860f-51e0c9ec1bb1"), IsActive = true }, new @@ -2776,7 +2846,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("b79800e0-baa6-4add-9374-7d1e2ed650e8"), + Id = new Guid("12e98296-456e-40a3-b11c-61d3393b335e"), IsActive = true }, new @@ -2784,7 +2854,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("0da6bf0b-26ff-46b9-bb8c-f9c641b37a89"), + Id = new Guid("1d47dc32-9faa-418e-8241-a0dc8a32c974"), IsActive = true }, new @@ -2792,7 +2862,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("ff84aeeb-93c0-465d-9a4a-8b686ba7bc75"), + Id = new Guid("5e701ad5-f462-4642-86d1-303148323a32"), IsActive = true }, new @@ -2800,7 +2870,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("921f8073-7a66-49bb-ae23-cf8fcde12c9c"), + Id = new Guid("52dfa802-8a12-4554-b7ab-1e286998b3b9"), IsActive = true }, new @@ -2808,7 +2878,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("bff05501-4988-4c8a-b61d-e32389e795a8"), + Id = new Guid("7944f075-4eb3-4ccd-98bf-584f79f9f4d5"), IsActive = true }, new @@ -2816,7 +2886,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("2c4f7432-e697-4d65-97df-069f8913a662"), + Id = new Guid("6ce15356-7240-4a79-bfc8-340c8f31e270"), IsActive = true }, new @@ -2824,7 +2894,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("18d27746-3178-4c57-afd5-b75f0061637f"), + Id = new Guid("ac248603-3c71-4c1b-84dc-174f25cade65"), IsActive = true }, new @@ -2832,7 +2902,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("a5d1978b-c497-4314-857b-a7e3b8323a53"), + Id = new Guid("7805ec5e-8b99-4b45-b47a-be01405a2151"), IsActive = true }, new @@ -2840,7 +2910,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("88300763-b8c3-4bd2-803b-df0e43bc691e"), + Id = new Guid("7ea67b95-2918-48e9-b569-71123adb82a0"), IsActive = true }, new @@ -2848,7 +2918,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("7aaad5a9-4497-429f-b3dc-9b59664e2e19"), + Id = new Guid("a2515783-f099-4332-a07c-be81f2e85160"), IsActive = true }, new @@ -2856,7 +2926,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("2db130c1-4b6f-4719-b3bb-7bd8a0662d75"), + Id = new Guid("2bff0498-d66e-4564-b0ec-5924cbab96a3"), IsActive = true }, new @@ -2864,7 +2934,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("be349d14-38b5-4a13-a152-9ea055de26b1"), + Id = new Guid("5fa95034-9d7f-4350-b754-9a16b03804fe"), IsActive = true }, new @@ -2872,7 +2942,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("5292e02d-9a7d-4a18-be14-86c2f3351cac"), + Id = new Guid("f1a82d54-68b8-4992-a4ba-28bd58dd5281"), IsActive = true }, new @@ -2880,7 +2950,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("97021474-1218-4d21-9115-4b9b2726e38b"), + Id = new Guid("fa183267-8eb0-4831-8f5f-ba1af4d5a78d"), IsActive = true }, new @@ -2888,7 +2958,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("56f68355-7b1b-49e4-829c-1a4d47fc93de"), + Id = new Guid("fd75e8b3-8751-4b67-870b-6370098a5e1e"), IsActive = true }, new @@ -2896,7 +2966,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("3017c8cb-f9e4-41c8-ac2e-f03d23fe944a"), + Id = new Guid("c6632177-d567-4cce-98dd-80d735c2e5db"), IsActive = true }, new @@ -2904,7 +2974,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("619a08e5-f6cb-4515-8b4d-a094d19277d8"), + Id = new Guid("b463deda-4bb9-4d86-a0fc-513d10fc57b1"), IsActive = true }, new @@ -2912,7 +2982,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("cb8b4e96-7026-4485-8b52-3145b5289350"), + Id = new Guid("b44bfcb4-ff1d-4848-9098-1ae54bace236"), IsActive = true }, new @@ -2920,7 +2990,23 @@ 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("f0f57dfd-21a5-4a0b-a77e-e641f00300fa"), + Id = new Guid("93c036e4-d811-4915-b73c-1cb358f17065"), + 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("9c27d325-1915-41c6-9b62-d85a12f102e1"), + 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("1f48dd7b-2f14-451d-b80d-71579d5625e6"), IsActive = true }); }); @@ -2962,7 +3048,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId"); - b.ToTable("Routes", (string)null); + b.ToTable("Routes"); b.HasData( new @@ -3059,7 +3145,7 @@ namespace Distribution.Migrations b.HasIndex("FK_UserId"); - b.ToTable("RouteVisitLogs", (string)null); + b.ToTable("RouteVisitLogs"); b.HasData( new @@ -3121,7 +3207,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ShopPOId"); - b.ToTable("SaleReturns", (string)null); + b.ToTable("SaleReturns"); b.HasData( new @@ -3169,7 +3255,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SaleReturnId"); - b.ToTable("SaleReturnItems", (string)null); + b.ToTable("SaleReturnItems"); b.HasData( new @@ -3199,7 +3285,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("SaleRoutes", (string)null); + b.ToTable("SaleRoutes"); b.HasData( new @@ -3279,7 +3365,7 @@ namespace Distribution.Migrations b.HasIndex("RegionId"); - b.ToTable("Schemes", (string)null); + b.ToTable("Schemes"); b.HasData( new @@ -3337,7 +3423,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SchemeId", "FK_ItemId") .IsUnique(); - b.ToTable("SchemeBundleComponents", (string)null); + b.ToTable("SchemeBundleComponents"); b.HasData( new @@ -3387,7 +3473,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ShopPOId"); - b.ToTable("SchemeRedemptions", (string)null); + b.ToTable("SchemeRedemptions"); b.HasData( new @@ -3453,7 +3539,7 @@ namespace Distribution.Migrations b.HasIndex("FK_StoreId"); - b.ToTable("ShopPOs", (string)null); + b.ToTable("ShopPOs"); b.HasData( new @@ -3529,7 +3615,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ShopPOId"); - b.ToTable("ShopPOItems", (string)null); + b.ToTable("ShopPOItems"); b.HasData( new @@ -3614,7 +3700,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ItemId", "LocationId", "FK_ItemBatchId", "FK_ItemSerialNumberId") .IsUnique(); - b.ToTable("StockBalances", (string)null); + b.ToTable("StockBalances"); b.HasData( new @@ -3699,7 +3785,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ItemSerialNumberId"); - b.ToTable("StockMovements", (string)null); + b.ToTable("StockMovements"); b.HasData( new @@ -3800,7 +3886,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RouteId"); - b.ToTable("Stores", (string)null); + b.ToTable("Stores"); b.HasData( new @@ -3950,7 +4036,7 @@ namespace Distribution.Migrations b.HasIndex("FK_StoreId", "OccurredAt"); - b.ToTable("StoreCreditMovements", (string)null); + b.ToTable("StoreCreditMovements"); }); modelBuilder.Entity("Distribution.Models.SubCategory", b => @@ -3979,7 +4065,7 @@ namespace Distribution.Migrations b.HasIndex("FK_CategoryId"); - b.ToTable("SubCategories", (string)null); + b.ToTable("SubCategories"); }); modelBuilder.Entity("Distribution.Models.Supplier", b => @@ -4006,7 +4092,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("Suppliers", (string)null); + b.ToTable("Suppliers"); b.HasData( new @@ -4084,7 +4170,7 @@ namespace Distribution.Migrations b.HasIndex("ResolvedByUserId"); - b.ToTable("SupplierClaims", (string)null); + b.ToTable("SupplierClaims"); b.HasData( new @@ -4135,7 +4221,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SupplierClaimId"); - b.ToTable("SupplierClaimItems", (string)null); + b.ToTable("SupplierClaimItems"); b.HasData( new @@ -4195,7 +4281,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SupplierId"); - b.ToTable("SupplierPayments", (string)null); + b.ToTable("SupplierPayments"); }); modelBuilder.Entity("Distribution.Models.SupplierPaymentGRN", b => @@ -4226,7 +4312,7 @@ namespace Distribution.Migrations b.HasIndex("FK_SupplierPaymentId"); - b.ToTable("SupplierPaymentGRNs", (string)null); + b.ToTable("SupplierPaymentGRNs"); }); modelBuilder.Entity("Distribution.Models.SysConfig", b => @@ -4247,7 +4333,7 @@ namespace Distribution.Migrations b.HasKey("Key"); - b.ToTable("SysConfigs", (string)null); + b.ToTable("SysConfigs"); b.HasData( new @@ -4315,7 +4401,7 @@ namespace Distribution.Migrations b.HasKey("Id"); - b.ToTable("Uoms", (string)null); + b.ToTable("Uoms"); b.HasData( new @@ -4360,7 +4446,7 @@ namespace Distribution.Migrations b.HasIndex("FK_ToUomId"); - b.ToTable("UomConversions", (string)null); + b.ToTable("UomConversions"); b.HasData( new @@ -4408,7 +4494,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RoleId"); - b.ToTable("Users", (string)null); + b.ToTable("Users"); b.HasData( new @@ -4510,7 +4596,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RegionId"); - b.ToTable("UserRegionScopes", (string)null); + b.ToTable("UserRegionScopes"); b.HasData( new @@ -4544,7 +4630,7 @@ namespace Distribution.Migrations b.HasIndex("FK_RouteId"); - b.ToTable("UserRouteScopes", (string)null); + b.ToTable("UserRouteScopes"); b.HasData( new @@ -4578,7 +4664,7 @@ namespace Distribution.Migrations b.HasIndex("FK_StoreId"); - b.ToTable("UserStoreScopes", (string)null); + b.ToTable("UserStoreScopes"); b.HasData( new @@ -4621,7 +4707,7 @@ namespace Distribution.Migrations b.HasIndex("DefaultWarehouseId"); - b.ToTable("Vehicles", (string)null); + b.ToTable("Vehicles"); b.HasData( new @@ -4676,7 +4762,7 @@ namespace Distribution.Migrations b.HasIndex("VehicleId"); - b.ToTable("Warehouses", (string)null); + b.ToTable("Warehouses"); b.HasData( new diff --git a/Distribution/Models/BankAccount.cs b/Distribution/Models/BankAccount.cs index da0e4ee..ba15665 100644 --- a/Distribution/Models/BankAccount.cs +++ b/Distribution/Models/BankAccount.cs @@ -13,5 +13,8 @@ namespace Distribution.Models public string CurrencyCode { get; set; } = "LKR"; public bool IsDefault { get; set; } + + /// Code of the mirrored General Ledger account (child of GL Cash in Hand / Bank Accounts). Null until synced. + public string? GlAccountCode { get; set; } } } diff --git a/Distribution/Models/GlPostingLog.cs b/Distribution/Models/GlPostingLog.cs new file mode 100644 index 0000000..ad845ed --- /dev/null +++ b/Distribution/Models/GlPostingLog.cs @@ -0,0 +1,24 @@ +namespace Distribution.Models +{ + public enum GlPostingStatus { Pending, Posted, Failed } + + /// + /// One row per journal entry Distribution sends to the General Ledger service. (Event, SourceId) is unique, + /// so a business event can never be posted twice; Failed rows keep their payload and can be retried. + /// + public class GlPostingLog : Template + { + public string Event { get; set; } = string.Empty; + public Guid SourceId { get; set; } + public string Reference { get; set; } = string.Empty; + public GlPostingStatus Status { get; set; } = GlPostingStatus.Pending; + + /// The journal entry body (without periodId, which is resolved from EntryDate at send time). + public string PayloadJson { get; set; } = string.Empty; + + public string? GlJournalNo { get; set; } + public string? Error { get; set; } + public int Attempts { get; set; } + public DateTime? LastAttemptAt { get; set; } + } +} diff --git a/Distribution/Program.cs b/Distribution/Program.cs index c5e4834..266e25f 100644 --- a/Distribution/Program.cs +++ b/Distribution/Program.cs @@ -11,6 +11,7 @@ using Distribution.Features.ChequeBook; using Distribution.Features.ReceivedCheque; using Distribution.Features.DriverComplianceConfig; using Distribution.Features.DriverProfile; +using Distribution.Features.GeneralLedger; using Distribution.Features.Grn; using Distribution.Features.Item; using Distribution.Features.ShopPO; @@ -141,6 +142,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddMemoryCache(); builder.Services.AddHttpContextAccessor(); @@ -150,6 +153,13 @@ builder.Services.AddHttpClient(client => var baseUrl = builder.Configuration["AuthHex:BaseUrl"] ?? throw new InvalidOperationException("AuthHex:BaseUrl is not configured."); client.BaseAddress = new Uri(baseUrl); }); +builder.Services.AddHttpClient(client => +{ + var baseUrl = builder.Configuration["GeneralLedger:BaseUrl"] ?? throw new InvalidOperationException("GeneralLedger:BaseUrl is not configured."); + var apiKey = builder.Configuration["GeneralLedger:ApiKey"] ?? throw new InvalidOperationException("GeneralLedger:ApiKey is not configured."); + client.BaseAddress = new Uri(baseUrl); + client.DefaultRequestHeaders.Add("X-Api-Key", apiKey); +}); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Distribution/appsettings.json b/Distribution/appsettings.json index 58ac152..093f915 100644 --- a/Distribution/appsettings.json +++ b/Distribution/appsettings.json @@ -1,8 +1,14 @@ { "Urls": "http://localhost:8802", "ConnectionStrings": { + // "DefaultConnection": "Server=localhost;Port=3306;Database=distribution;User=root;Password=" + // live server "DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution;User=kalana;Password=kalana@hexdive" }, + "GeneralLedger": { + "BaseUrl": "http://localhost:6704/api/", + "ApiKey": "ff303a4b1919092501a2ab2b8725709177bbb8f0025c58d39de4ad678e2b4eda" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/distribution_fe/app/(dashboard)/configuration/general-ledger/_components/general-ledger-client.tsx b/distribution_fe/app/(dashboard)/configuration/general-ledger/_components/general-ledger-client.tsx new file mode 100644 index 0000000..d8fbc74 --- /dev/null +++ b/distribution_fe/app/(dashboard)/configuration/general-ledger/_components/general-ledger-client.tsx @@ -0,0 +1,350 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' + +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardTitle } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { toast } from '@/components/ui/toast' +import { usePermission } from '@/hooks/use-permission' +import { getApiError } from '@/lib/api/index' +import { bankAccounts } from '@/lib/api/bank-accounts' +import { generalLedger, type GlPostingRow, type GlStatus, type YearEndPreview } from '@/lib/api/finance-reports' +import type { BankAccountResponse } from '@/types/payments' + +const ACCOUNT_LABELS: Record = { + 'Gl.Account.CashEquivalents': 'Cash and cash equivalents', + 'Gl.Account.CashInHand': 'Cash in hand', + 'Gl.Account.BankAccounts': 'Bank accounts', + 'Gl.Account.ChequesInHand': 'Cheques in hand', + 'Gl.Account.Receivable': 'Trade receivables', + 'Gl.Account.Inventory': 'Inventory', + 'Gl.Account.InputVat': 'Input VAT', + 'Gl.Account.InputNbt': 'Input NBT', + 'Gl.Account.Payable': 'Trade payables', + 'Gl.Account.ChequesIssued': 'Cheques issued (uncleared)', + 'Gl.Account.Equity': "Owner's equity (header)", + 'Gl.Account.ShareCapital': "Share capital / owner's capital", + 'Gl.Account.RetainedEarnings': 'Retained earnings', + 'Gl.Account.Drawings': 'Drawings and dividends', + 'Gl.Account.GeneralReserve': 'General reserve', + 'Gl.Account.RevaluationReserve': 'Revaluation reserve', + 'Gl.Account.Sales': 'Sales revenue', +} + +// Opening balance form rows. Amounts are each account's normal balance and default to 0 (zero rows are skipped). +const OPENING_ROWS: { key: string; label: string; group: 'Assets' | 'Liabilities' | 'Equity' }[] = [ + { key: 'ChequesInHand', label: 'Cheques in hand', group: 'Assets' }, + { key: 'Receivable', label: 'Trade receivables', group: 'Assets' }, + { key: 'Inventory', label: 'Inventory', group: 'Assets' }, + { key: 'InputVat', label: 'Input VAT', group: 'Assets' }, + { key: 'InputNbt', label: 'Input NBT', group: 'Assets' }, + { key: 'Payable', label: 'Trade payables', group: 'Liabilities' }, + { key: 'ChequesIssued', label: 'Cheques issued (uncleared)', group: 'Liabilities' }, + { key: 'ShareCapital', label: "Share capital / owner's capital", group: 'Equity' }, + { key: 'RetainedEarnings', label: 'Retained earnings', group: 'Equity' }, + { key: 'GeneralReserve', label: 'General reserve', group: 'Equity' }, + { key: 'RevaluationReserve', label: 'Revaluation reserve', group: 'Equity' }, + { key: 'Drawings', label: 'Drawings and dividends (reduces equity)', group: 'Equity' }, +] + +const toNum = (v: string) => (v.trim() === '' ? 0 : Number(v)) +const fmt = (v: number) => v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + +async function fetchState() { + const status = await generalLedger.status() + const waiting = status.failed > 0 + ? [...(await generalLedger.postings('Failed')), ...(await generalLedger.postings('Pending'))] + : [] + return { status, waiting } +} + +export function GeneralLedgerClient() { + const allowed = usePermission('gl.manage') + const [status, setStatus] = useState(null) + const [failed, setFailed] = useState([]) + const [busy, setBusy] = useState<'setup' | 'retry' | 'opening' | 'preview' | 'close' | null>(null) + const [closeYear, setCloseYear] = useState(new Date().getFullYear() - 1) + const [preview, setPreview] = useState(null) + const [cashBank, setCashBank] = useState([]) + const [amounts, setAmounts] = useState>({}) + const [openingDate, setOpeningDate] = useState(`${new Date().getFullYear()}-01-01`) + + const apply = useCallback((d: { status: GlStatus; waiting: GlPostingRow[] }) => { + setStatus(d.status) + setFailed(d.waiting) + }, []) + + const reload = useCallback( + () => fetchState().then(apply).catch(err => toast.add({ title: 'Could not load General Ledger status', description: getApiError(err), type: 'error' })), + [apply], + ) + + useEffect(() => { + if (!allowed) return + fetchState() + .then(apply) + .catch(err => toast.add({ title: 'Could not load General Ledger status', description: getApiError(err), type: 'error' })) + }, [allowed, apply]) + + useEffect(() => { + if (allowed) bankAccounts.list().then(setCashBank).catch(() => setCashBank([])) + }, [allowed]) + + const rowsTotal = (group: 'Assets' | 'Liabilities' | 'Equity') => + OPENING_ROWS.filter(r => r.group === group && r.key !== 'Drawings').reduce((t, r) => t + toNum(amounts[r.key] ?? ''), 0) + const bankTotal = cashBank.reduce((t, b) => t + toNum(amounts[`bank:${b.accountId}`] ?? ''), 0) + // Drawings is debit-side (reduces equity), so it sits with the assets side of the balance check. + const debitsTotal = rowsTotal('Assets') + bankTotal + toNum(amounts.Drawings ?? '') + const creditsTotal = rowsTotal('Liabilities') + rowsTotal('Equity') + const difference = debitsTotal - creditsTotal + + async function loadPreview() { + setBusy('preview') + try { + setPreview(await generalLedger.yearEndPreview(closeYear)) + } catch (err) { + setPreview(null) + toast.add({ title: 'Could not load the year-end preview', description: getApiError(err), type: 'error' }) + } finally { + setBusy(null) + } + } + + async function runClose() { + setBusy('close') + try { + const result = await generalLedger.closeYear(closeYear) + toast.add({ + title: `${closeYear} closed`, + description: `Journal ${result.glJournalNo ?? ''} posted, ${result.periodsLocked} periods locked`, + type: 'success', + }) + setPreview(null) + await reload() + } catch (err) { + toast.add({ title: 'Could not close the year', description: getApiError(err), type: 'error' }) + } finally { + setBusy(null) + } + } + + async function postOpening() { + setBusy('opening') + try { + const lines = [ + ...cashBank.map(b => ({ account: 'BankAccount', bankAccountId: b.accountId, amount: toNum(amounts[`bank:${b.accountId}`] ?? '') })), + ...OPENING_ROWS.map(r => ({ account: r.key, amount: toNum(amounts[r.key] ?? '') })), + ] + const result = await generalLedger.postOpeningBalances(openingDate, 'Opening balances', lines) + toast.add({ title: 'Opening balances posted', description: result.glJournalNo ? `Journal ${result.glJournalNo}` : undefined, type: 'success' }) + setAmounts({}) + await reload() + } catch (err) { + toast.add({ title: 'Could not post opening balances', description: getApiError(err), type: 'error' }) + } finally { + setBusy(null) + } + } + + async function runSetup() { + setBusy('setup') + try { + const result = await generalLedger.setup() + toast.add({ + title: 'General Ledger set up', + description: result.actions.length ? `${result.actions.length} change(s) applied.` : 'Nothing to change — already up to date.', + type: 'success', + }) + await reload() + } catch (err) { + toast.add({ title: 'Setup failed', description: getApiError(err), type: 'error' }) + } finally { + setBusy(null) + } + } + + async function retry() { + setBusy('retry') + try { + const result = await generalLedger.retry() + toast.add({ title: 'Retry finished', description: `${result.posted} posting(s) sent to the General Ledger.`, type: 'success' }) + await reload() + } catch (err) { + toast.add({ title: 'Retry failed', description: getApiError(err), type: 'error' }) + } finally { + setBusy(null) + } + } + + if (!allowed) { + return

You do not have permission to manage the General Ledger link.

+ } + + return ( + <> +
+

General Ledger Integration

+

+ Sales, goods receipts, payments and cheque movements are mirrored into the General Ledger so financial reports can be produced. + Sales, goods receipts and payments made before setup are not posted. +

+
+ + {status && ( +
+ {([ + ['General Ledger service', status.glReachable ? 'Reachable' : 'Unreachable'], + ['Setup', status.setupDone ? 'Done' : 'Not run'], + ['Posted entries', String(status.posted)], + ['Waiting / failed', String(status.failed)], + ] as const).map(([label, value]) => ( + + + {label} + {value} + + + ))} +
+ )} + +
+ + +
+ + {status?.setupDone && ( + + +

Close financial year

+

+ Moves the year's sales and expenses into Retained Earnings with one closing entry and locks the year's periods. + Anything dated in that year afterwards will be rejected by the General Ledger. Reports for the year still show its own figures. +

+
+ { setCloseYear(Number(e.target.value)); setPreview(null) }} /> + +
+ {preview && ( +
+

+ Income {fmt(preview.totalIncome)} · Expenses {fmt(preview.totalExpenses)} · Net {preview.netProfit >= 0 ? 'profit' : 'loss'} {fmt(Math.abs(preview.netProfit))} +

+ {preview.reason &&

{preview.reason}

} + +
+ )} + {status && status.closedYears.length > 0 && ( +

Closed years: {status.closedYears.join(', ')}

+ )} +
+
+ )} + + {status?.setupDone && !status.openingBalancePosted && ( +
+

Opening balances have not been entered.

+

+ The ledger and every financial report start from zero. If the business already has cash, bank balances, stock, receivables, + payables or capital, enter them below (all amounts default to 0). Equity accounts such as Share Capital only appear in the + Balance Sheet once they hold a balance. +

+
+ )} + + {status?.setupDone && ( + + +

Opening balances

+
+ As of + setOpeningDate(e.target.value)} /> +
+ {(['Assets', 'Liabilities', 'Equity'] as const).map(group => ( +
+

{group}

+
+ {group === 'Assets' && cashBank.map(b => ( + + ))} + {OPENING_ROWS.filter(r => r.group === group).map(r => ( + + ))} +
+
+ ))} +

+ Debit side (assets, drawings) {fmt(debitsTotal)} · Credit side (liabilities, equity) {fmt(creditsTotal)} + {difference !== 0 && ` · difference ${fmt(Math.abs(difference))} (adjust share capital or retained earnings)`} +

+ +
+
+ )} + + {status?.setupDone && ( + + +

Account mapping

+ + + PurposeGL account code + + + {Object.entries(status.mapping).map(([key, code]) => ( + + {ACCOUNT_LABELS[key] ?? key} + {code ?? '—'} + + ))} + +
+
+
+ )} + + {failed.length > 0 && ( + + +

Postings waiting to be sent

+ + + EventReferenceAttemptsError + + + {failed.map(p => ( + + {p.event} + {p.sourceId} + {p.attempts} + {p.error ?? '—'} + + ))} + +
+
+
+ )} + + ) +} diff --git a/distribution_fe/app/(dashboard)/configuration/general-ledger/page.tsx b/distribution_fe/app/(dashboard)/configuration/general-ledger/page.tsx new file mode 100644 index 0000000..0d0c292 --- /dev/null +++ b/distribution_fe/app/(dashboard)/configuration/general-ledger/page.tsx @@ -0,0 +1,5 @@ +import { GeneralLedgerClient } from './_components/general-ledger-client' + +export default function GeneralLedgerPage() { + return +} diff --git a/distribution_fe/app/(dashboard)/reports/finance/_components/finance-report-client.tsx b/distribution_fe/app/(dashboard)/reports/finance/_components/finance-report-client.tsx new file mode 100644 index 0000000..b216fd9 --- /dev/null +++ b/distribution_fe/app/(dashboard)/reports/finance/_components/finance-report-client.tsx @@ -0,0 +1,205 @@ +'use client' + +import { useEffect, useState } from 'react' + +import { Button } from '@/components/ui/button' +import { Card, CardContent } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { toast } from '@/components/ui/toast' +import { usePermission } from '@/hooks/use-permission' +import { getApiError } from '@/lib/api/index' +import { financeReports, type FileFormat, type GlAccountOption, type FinanceReportParams, type FinanceReportType } from '@/lib/api/finance-reports' + +import { toBlocks, type Block } from './report-blocks' + +type Filter = 'asOf' | 'range' | 'account' | 'budget' + +const REPORTS: { type: FinanceReportType; label: string; filters: Filter[]; hint: string }[] = [ + { type: 'TrialBalance', label: 'Trial Balance', filters: ['asOf'], hint: 'Debit and credit balance of every ledger account as of a date.' }, + { type: 'GeneralLedger', label: 'General Ledger', filters: ['range', 'account'], hint: 'Every posted transaction line with the account debited or credited, grouped by account.' }, + { type: 'ProfitAndLoss', label: 'Profit & Loss', filters: ['range'], hint: 'Income and expenses for a period.' }, + { type: 'BalanceSheet', label: 'Balance Sheet', filters: ['asOf'], hint: 'Assets, liabilities and equity as of a date.' }, + { type: 'CashFlow', label: 'Cash Flow', filters: ['range'], hint: 'Cash movement for a period (indirect method).' }, + { type: 'BudgetVsActual', label: 'Budget vs Actual', filters: ['budget'], hint: 'Budget lines against posted actuals. Budgets are created in the General Ledger service.' }, + { type: 'TaxSummary', label: 'Tax Summary', filters: ['range'], hint: 'Income tax computation for a period.' }, +] + +const ALL_ACCOUNTS = 'all' + +const today = () => new Date().toISOString().slice(0, 10) +const yearStart = () => `${new Date().getFullYear()}-01-01` + +export function FinanceReportClient() { + const allowed = usePermission('finance-reports.view') + const [type, setType] = useState('TrialBalance') + const [asOfDate, setAsOfDate] = useState(today()) + const [periodStart, setPeriodStart] = useState(yearStart()) + const [periodEnd, setPeriodEnd] = useState(today()) + const [accountCode, setAccountCode] = useState(ALL_ACCOUNTS) + const [accounts, setAccounts] = useState([]) + const [budgetId, setBudgetId] = useState('') + const [blocks, setBlocks] = useState(null) + const [loading, setLoading] = useState(false) + const [busy, setBusy] = useState(null) + + useEffect(() => { + if (!allowed) return + financeReports.accounts().then(setAccounts).catch(() => setAccounts([])) + }, [allowed]) + + const report = REPORTS.find(r => r.type === type)! + + function params(): FinanceReportParams { + const p: FinanceReportParams = {} + if (report.filters.includes('asOf')) p.asOfDate = asOfDate + if (report.filters.includes('range')) { p.periodStart = periodStart; p.periodEnd = periodEnd } + if (report.filters.includes('account') && accountCode !== ALL_ACCOUNTS) p.accountCode = accountCode + if (report.filters.includes('budget')) p.budgetId = Number(budgetId) || undefined + return p + } + + function changeType(next: FinanceReportType) { + setType(next) + setBlocks(null) + } + + async function run() { + setLoading(true) + try { + const data = await financeReports.get(type, params()) + setBlocks(toBlocks(type, data)) + } catch (err) { + setBlocks(null) + toast.add({ title: 'Could not load report', description: getApiError(err), type: 'error' }) + } finally { + setLoading(false) + } + } + + async function download(format: FileFormat) { + setBusy(format) + try { + await financeReports.download(type, params(), format) + } catch (err) { + toast.add({ title: 'Download failed', description: getApiError(err), type: 'error' }) + } finally { + setBusy(null) + } + } + + if (!allowed) { + return

You do not have permission to view financial reports.

+ } + + return ( + <> +
+

Financial Reports

+

+ Produced by the General Ledger from sales, purchases and payments. Cash, bank and cheque balances are mirrored from the Payments module. +

+
+ +
+ {REPORTS.map(r => ( + + ))} +
+ +

{report.hint}

+ +
+ {report.filters.includes('asOf') && ( + setAsOfDate(e.target.value)} aria-label="As of date" /> + )} + {report.filters.includes('range') && ( + <> + setPeriodStart(e.target.value)} aria-label="From" /> + setPeriodEnd(e.target.value)} aria-label="To" /> + + )} + {report.filters.includes('account') && ( + + )} + {report.filters.includes('budget') && ( + setBudgetId(e.target.value)} /> + )} + +
+ + +
+
+ + {blocks === null ? ( +

Choose the filters and run the report.

+ ) : blocks.length === 0 ? ( +

No data for this selection.

+ ) : ( +
+ {blocks.map((b, i) => )} +
+ )} + + ) +} + +function BlockTable({ block }: { block: Block }) { + const numeric = new Set(block.numericCols) + return ( + + + {block.title &&

{block.title}

} +
+ + + + {block.headers.map((h, c) => ( + {h} + ))} + + + + {block.rows.length === 0 ? ( + + No entries. + + ) : ( + block.rows.map((row, r) => ( + + {row.map((cell, c) => ( + {cell} + ))} + + )) + )} + + {block.footer && ( + + + {block.footer.map((cell, c) => ( + {cell} + ))} + + + )} +
+
+
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/reports/finance/_components/report-blocks.ts b/distribution_fe/app/(dashboard)/reports/finance/_components/report-blocks.ts new file mode 100644 index 0000000..603b0c4 --- /dev/null +++ b/distribution_fe/app/(dashboard)/reports/finance/_components/report-blocks.ts @@ -0,0 +1,153 @@ +import type { FinanceReportType } from '@/lib/api/finance-reports' + +/** A table the report page renders; every GL report is converted into one or more of these. */ +export interface Block { + title?: string + headers: string[] + rows: string[][] + footer?: string[] + numericCols: number[] +} + +type Obj = Record + +const money = (v: unknown) => + Number(v ?? 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + +const text = (v: unknown) => (v == null ? '' : String(v)) + +const sum = (rows: Obj[], key: string) => rows.reduce((t, r) => t + Number(r[key] ?? 0), 0) + +const humanize = (key: string) => + key.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase()).replace(/\bApit\b/, 'APIT').replace(/\bWht\b/, 'WHT') + +function summary(title: string | undefined, entries: [string, unknown][]): Block { + return { title, headers: ['', 'Amount'], rows: entries.map(([k, v]) => [k, money(v)]), numericCols: [1] } +} + +function section(title: string, s: Obj | undefined, amountKey: string, totalLabel: string): Block | null { + const lines = (s?.lines as Obj[] | undefined) ?? [] + if (lines.length === 0 && !Number(s?.total ?? 0)) return null + return { + title, + headers: ['Code', 'Account', 'Amount'], + rows: lines.map(l => [text(l.accountCode), text(l.accountName), money(l[amountKey])]), + footer: ['', totalLabel, money(s?.total)], + numericCols: [2], + } +} + +const compact = (blocks: (Block | null)[]) => blocks.filter((b): b is Block => b !== null) + +export function toBlocks(type: FinanceReportType, data: unknown): Block[] { + switch (type) { + case 'TrialBalance': { + const rows = (data as Obj[]) ?? [] + return [{ + headers: ['Code', 'Account', 'Debit', 'Credit'], + rows: rows.map(r => [text(r.accountCode), text(r.accountName), money(r.debit), money(r.credit)]), + footer: ['', 'Total', money(sum(rows, 'debit')), money(sum(rows, 'credit'))], + numericCols: [2, 3], + }] + } + + case 'GeneralLedger': { + const rows = (data as Obj[]) ?? [] + return [{ + headers: ['Date', 'Journal', 'Account', 'Narration', 'Debit', 'Credit'], + rows: rows.map(r => [ + text(r.entryDate), text(r.journalNo), `${text(r.accountCode)} ${text(r.accountName)}`, text(r.narration), + r.debitAmount ? money(r.debitAmount) : '', r.creditAmount ? money(r.creditAmount) : '', + ]), + footer: ['', '', '', 'Total', money(sum(rows, 'debitAmount')), money(sum(rows, 'creditAmount'))], + numericCols: [4, 5], + }] + } + + case 'ProfitAndLoss': { + const d = (data as Obj) ?? {} + return compact([ + section('Sales', d.sales as Obj, 'amount', 'Total sales'), + section('Cost of sales', d.costOfSales as Obj, 'amount', 'Total cost of sales'), + summary(undefined, [['Gross profit', d.grossProfit]]), + section('Other income', d.otherIncome as Obj, 'amount', 'Total other income'), + section('Distribution expenses', d.distributionExpenses as Obj, 'amount', 'Total'), + section('Administration expenses', d.administrationExpenses as Obj, 'amount', 'Total'), + section('Other expenses', d.otherExpenses as Obj, 'amount', 'Total'), + section('Financial expenses', d.financialExpenses as Obj, 'amount', 'Total'), + section('Unclassified accounts', d.unclassified as Obj, 'amount', 'Total'), + summary(undefined, [['Net profit for the period', d.netProfitForPeriod]]), + ]) + } + + case 'BalanceSheet': { + const d = (data as Obj) ?? {} + return compact([ + section('Non-current assets', d.nonCurrentAssets as Obj, 'balance', 'Total'), + section('Current assets', d.currentAssets as Obj, 'balance', 'Total'), + section('Unclassified assets', d.unclassifiedAssets as Obj, 'balance', 'Total'), + summary(undefined, [['Total assets', d.totalAssets]]), + section('Equity', d.equity as Obj, 'balance', 'Total equity'), + section('Non-current liabilities', d.nonCurrentLiabilities as Obj, 'balance', 'Total'), + section('Current liabilities', d.currentLiabilities as Obj, 'balance', 'Total'), + section('Unclassified liabilities', d.unclassifiedLiabilities as Obj, 'balance', 'Total'), + summary(undefined, [['Total equity and liabilities', d.totalEquityAndLiabilities]]), + ]) + } + + case 'CashFlow': { + const d = (data as Obj) ?? {} + const op = (d.operatingActivities as Obj) ?? {} + const adjustments = (op.nonCashAdjustments as Obj[]) ?? [] + const changes = (op.workingCapitalChanges as Obj[]) ?? [] + const lines = (a: unknown) => ((a as Obj)?.lines as Obj[]) ?? [] + return [ + { + title: 'Operating activities', + headers: ['Item', 'Amount'], + rows: [ + ['Profit for the period', money(op.profitForPeriod)], + ...adjustments.map(a => [text(a.description), money(a.amount)]), + ...changes.map(c => [`${text(c.accountName)} (${text(c.direction)})`, money(c.changeAmount)]), + ], + footer: ['Net cash from operating activities', money(op.netCashFromOperatingActivities)], + numericCols: [1], + }, + { + title: 'Investing activities', + headers: ['Item', 'Amount'], + rows: lines(d.investingActivities).map(l => [text(l.accountName), money(l.amount)]), + footer: ['Net cash from investing activities', money((d.investingActivities as Obj)?.netCashFromInvestingActivities)], + numericCols: [1], + }, + { + title: 'Financing activities', + headers: ['Item', 'Amount'], + rows: lines(d.financingActivities).map(l => [text(l.accountName), money(l.amount)]), + footer: ['Net cash from financing activities', money((d.financingActivities as Obj)?.netCashFromFinancingActivities)], + numericCols: [1], + }, + summary(undefined, [ + ['Net increase / decrease in cash', d.netIncreaseDecreaseInCash], + ['Opening cash balance', d.openingCashBalance], + ['Closing cash balance', d.closingCashBalance], + ]), + ] + } + + case 'BudgetVsActual': { + const rows = (data as Obj[]) ?? [] + return [{ + headers: ['Account', 'Period', 'Budgeted', 'Actual', 'Variance'], + rows: rows.map(r => [`${text(r.accountCode)} ${text(r.accountName)}`, text(r.periodId), money(r.budgetedAmount), money(r.actualAmount), money(r.variance)]), + numericCols: [2, 3, 4], + }] + } + + case 'TaxSummary': { + const d = (data as Obj) ?? {} + const entries = Object.entries(d).filter(([k]) => k !== 'periodStart' && k !== 'periodEnd') + return [summary(`Income tax computation (${text(d.periodStart)} to ${text(d.periodEnd)})`, entries.map(([k, v]) => [humanize(k), v]))] + } + } +} diff --git a/distribution_fe/app/(dashboard)/reports/finance/page.tsx b/distribution_fe/app/(dashboard)/reports/finance/page.tsx new file mode 100644 index 0000000..73611fb --- /dev/null +++ b/distribution_fe/app/(dashboard)/reports/finance/page.tsx @@ -0,0 +1,5 @@ +import { FinanceReportClient } from './_components/finance-report-client' + +export default function FinanceReportPage() { + return +} diff --git a/distribution_fe/config/nav.ts b/distribution_fe/config/nav.ts index 98dcabf..d87d3b3 100644 --- a/distribution_fe/config/nav.ts +++ b/distribution_fe/config/nav.ts @@ -134,6 +134,7 @@ export const NAV_GROUPS: NavGroup[] = [ { title: 'Bank Accounts', href: '/payments/bank-accounts', icon: BankIcon, description: 'Cash and bank accounts used for payments and deposits' }, { title: 'Cheque Books', href: '/payments/cheque-books', icon: ClipboardListIcon, description: 'Cheque books issued from the company’s own supply' }, { title: 'Received Cheques', href: '/payments/received-cheques', icon: DollarSquareIcon, description: 'Cheques received from customers, suppliers, or others' }, + { title: 'GL Integration', href: '/configuration/general-ledger', icon: Settings01Icon, description: 'Set up the General Ledger link and retry failed postings', permission: 'gl.manage' }, ], }, { @@ -179,6 +180,7 @@ export const NAV_GROUPS: NavGroup[] = [ items: [ { title: 'Product Report', href: '/reports/products', icon: ClipboardListIcon, description: 'Stock, sales and returns per product', permission: 'warehouses.view' }, { title: 'Stock Report', href: '/reports/stock', icon: Package01Icon, description: 'Opening, in, out and closing stock, low stock and expiry', permission: 'warehouses.view' }, + { title: 'Financial Reports', href: '/reports/finance', icon: DollarSquareIcon, description: 'Trial balance, ledger, profit & loss, balance sheet and cash flow from the General Ledger', permission: 'finance-reports.view' }, ], }, ] diff --git a/distribution_fe/config/permissions.ts b/distribution_fe/config/permissions.ts index b9e438d..c463fad 100644 --- a/distribution_fe/config/permissions.ts +++ b/distribution_fe/config/permissions.ts @@ -107,6 +107,14 @@ export const PERMISSION_CATEGORIES: PermissionCategory[] = [ 'roles.view', ], }, + { + id: 'finance', + label: 'Finance', + codes: [ + 'finance-reports.view', + 'gl.manage', + ], + }, { id: 'system', label: 'System', diff --git a/distribution_fe/lib/api/finance-reports.ts b/distribution_fe/lib/api/finance-reports.ts new file mode 100644 index 0000000..73d51c4 --- /dev/null +++ b/distribution_fe/lib/api/finance-reports.ts @@ -0,0 +1,110 @@ +import { api } from '@/lib/api' + +export type FinanceReportType = + | 'TrialBalance' + | 'GeneralLedger' + | 'ProfitAndLoss' + | 'BalanceSheet' + | 'CashFlow' + | 'BudgetVsActual' + | 'TaxSummary' + +export interface FinanceReportParams { + asOfDate?: string + periodStart?: string + periodEnd?: string + accountCode?: string + budgetId?: number +} + +export type FileFormat = 'Pdf' | 'Csv' + +const cleanParams = (params: FinanceReportParams) => + Object.fromEntries(Object.entries(params).filter(([, v]) => v !== undefined && v !== '')) + +// Reports come from the General Ledger service through the Distribution backend (/reports/finance/{type}). +export interface GlAccountOption { + accountCode: string + accountName: string +} + +export const financeReports = { + /** Active GL accounts for the account dropdown. */ + accounts: () => api.get('/reports/finance/accounts').then(r => r.data), + + get: (type: FinanceReportType, params: FinanceReportParams) => + api.get(`/reports/finance/${type}`, { params: cleanParams(params) }).then(r => r.data), + + /** Downloads the GL-rendered PDF or CSV and hands it to the browser. */ + async download(type: FinanceReportType, params: FinanceReportParams, format: FileFormat) { + const res = await api.get(`/reports/finance/${type}`, { + params: { ...cleanParams(params), outputFormat: format }, + responseType: 'blob', + }) + const disposition = String(res.headers['content-disposition'] ?? '') + const name = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition)?.[1] ?? `${type}.${format.toLowerCase()}` + const url = URL.createObjectURL(res.data) + const a = document.createElement('a') + a.href = url + a.download = decodeURIComponent(name) + a.click() + URL.revokeObjectURL(url) + }, +} + +export interface GlStatus { + glReachable: boolean + setupDone: boolean + openingBalancePosted: boolean + closedYears: number[] + mapping: Record + posted: number + failed: number +} + +export interface GlPostingRow { + id: string + event: string + sourceId: string + reference: string + status: 'Pending' | 'Posted' | 'Failed' + glJournalNo: string | null + error: string | null + attempts: number + lastAttemptAt: string | null + createdAt: string +} + +export interface OpeningBalanceLine { + /** A key such as 'Inventory' or 'ShareCapital', or 'BankAccount' together with bankAccountId. */ + account: string + bankAccountId?: string + /** The account's normal balance (>= 0). Debit/credit side is decided by the backend. */ + amount: number +} + +export interface YearEndPreview { + year: number + startDate: string + endDate: string + totalIncome: number + totalExpenses: number + netProfit: number + closable: boolean + reason: string | null + alreadyClosed: boolean +} + +export const generalLedger = { + yearEndPreview: (year: number) => + api.get('/gl/year-end/preview', { params: { year } }).then(r => r.data), + closeYear: (year: number) => + api.post<{ year: number; glJournalNo: string | null; netProfit: number; periodsLocked: number }>('/gl/year-end/close', { year }).then(r => r.data), + postOpeningBalances: (date: string, narration: string, lines: OpeningBalanceLine[]) => + api.post<{ status: string; glJournalNo: string | null }>('/gl/opening-balances', { date, narration, lines }).then(r => r.data), + status: () => api.get('/gl/status').then(r => r.data), + setup: (year?: number) => api.post<{ done: boolean; actions: string[] }>('/gl/setup', { year }).then(r => r.data), + postings: (status?: GlPostingRow['status']) => + api.get('/gl/postings', { params: { status } }).then(r => r.data), + retry: () => api.post<{ posted: number }>('/gl/postings/retry').then(r => r.data), +} diff --git a/distribution_fe/lib/api/index.ts b/distribution_fe/lib/api/index.ts index 2b22eeb..0a3986f 100644 --- a/distribution_fe/lib/api/index.ts +++ b/distribution_fe/lib/api/index.ts @@ -9,6 +9,7 @@ export { purchaseOrders, grn, purchaseReturns } from './procurement' export { rides, loadingSheets, deliveryTasks } from './operations' export { stock } from './stock' export { productReports, stockReports, dashboardReports } from './reports' +export { financeReports, generalLedger } from './finance-reports' export { shopPos, invoices, payments } from './sales' export { saleReturns, claims, supplierClaims } from './returns-claims' export { sysConfig, ITEM_TRACKING_KEYS, GRN_TAX_KEYS, grnTaxSettings, DEFAULT_GRN_TAX_SETTINGS } from './sys-config'