Compare commits

...

76 Commits

Author SHA1 Message Date
HarithaRandunu ca81e9bc42 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.
2026-10-20 18:54:30 +05:30
kalana_Thilakarathna cc33ecdd54 Merge pull request 'Implement store credit tracking and enforcement' (#77) from dev into production
Reviewed-on: #77
2026-10-20 11:51:10 +05:30
Kalana B. Thilakarathna 5707bd4c31 Implement store credit tracking and enforcement
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 12m30s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 16m1s
Introduced StoreCreditMovement entity and migration to track store credit usage and settlements. Updated payment flow to enforce credit limits and record credit movements. Modified outstanding balance calculation to use credit movements. Enhanced frontend to display real-time credit info and prevent over-credit payments. Updated API types, seed data, and DI registrations accordingly.
2026-10-20 11:45:11 +05:30
kalana_Thilakarathna f77db46a02 Merge pull request 'Dev' (#76) from dev into production
Reviewed-on: #76
2026-10-19 23:38:07 +05:30
Kalana B. Thilakarathna 28c465f59b feat: refactor TopLoader to use a hook for active state management and improve CSS for fixed positioning
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 10m33s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 14m21s
2026-10-19 23:37:45 +05:30
Kalana B. Thilakarathna 21b38b5276 feat: enhance DataTable with custom empty state support and update GrnClient to utilize it 2026-10-19 23:23:18 +05:30
kalana_Thilakarathna 267144f7a1 Merge pull request 'feat: add EmptyState component for improved no-data display in supplier payments' (#75) from dev into production
Reviewed-on: #75
2026-10-19 22:35:48 +05:30
Kalana B. Thilakarathna 992cd292fa feat: add EmptyState component for improved no-data display in supplier payments
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 12m51s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 16m39s
2026-10-19 22:35:01 +05:30
kalana_Thilakarathna efb5657b6b Merge pull request 'feat: update row actions to include payment functionality and add corresponding icon' (#74) from dev into production
Reviewed-on: #74
2026-10-19 22:13:07 +05:30
Kalana B. Thilakarathna 7b6c9ac92a feat: update row actions to include payment functionality and add corresponding icon
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 10m33s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 14m20s
2026-10-19 22:12:40 +05:30
kalana_Thilakarathna 622b75e187 Merge pull request 'feat: integrate payment functionality with new payment sheet and enhance supplier payments UI' (#73) from dev into production
Reviewed-on: #73
2026-10-19 22:02:45 +05:30
Kalana B. Thilakarathna eb0b042f0c feat: integrate payment functionality with new payment sheet and enhance supplier payments UI
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Successful in 1m51s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 10m54s
2026-10-19 22:02:15 +05:30
kalana_Thilakarathna 293df41d4e Merge pull request 'Dev' (#72) from dev into production
Reviewed-on: #72
2026-10-19 21:41:42 +05:30
Kalana B. Thilakarathna a980e636f5 Merge branch 'dev' of https://gitea.hexdive.com/New_REP_SYSTEM/ERP_Distribution into dev
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 11m57s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 15m40s
2026-10-19 21:36:08 +05:30
Kalana B. Thilakarathna b992d07689 feat: add supplier payment and allocation models, API, and frontend components
- Created `SupplierPayment` and `SupplierPaymentGRN` models for managing supplier payments and their allocations.
- Implemented API endpoints for listing and creating supplier payments.
- Developed frontend components for displaying and recording supplier payments, including a dialog for payment creation.
- Integrated GRN allocations into the payment creation process, ensuring total allocations match the payment amount.
- Added pagination and loading states for the payments list.
2026-10-19 21:36:01 +05:30
DeepnaPooja 5e81fd77b1 Merge pull request 'Dev' (#71) from dev into production
Reviewed-on: #71
2026-10-19 16:47:06 +05:30
DeepnaPooja a4d58f3377 Merge pull request 'Fix changes' (#70) from fix-changes into dev
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 11m33s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 15m20s
Reviewed-on: #70
2026-10-19 16:45:55 +05:30
Kalana B. Thilakarathna 94db2a311c Refactor code structure for improved readability and maintainability 2026-10-19 16:42:12 +05:30
DeepnaPooja 6b608ad2ef Update permission feature with edit and create role functionality 2026-10-19 16:39:47 +05:30
kalana_Thilakarathna a1fd5f1a08 Merge pull request 'feat: add cheque books and received cheques management' (#69) from dev into production
Reviewed-on: #69
2026-10-19 16:12:32 +05:30
Kalana B. Thilakarathna 87c807c16c feat: add cheque books and received cheques management
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 11m8s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 14m49s
- Implemented ChequeBooksPage for viewing and filtering cheque books.
- Added NewReceivedChequePage for recording received cheques.
- Created ReceivedChequesPage for listing and managing received cheques.
- Introduced API endpoints for bank accounts, cheque books, and received cheques.
- Defined types for payments including bank accounts, cheque books, and received cheques.
2026-10-19 16:09:05 +05:30
DeepnaPooja 3c75f9786e Add multi-series charts and enhance dashboard functionality 2026-10-19 15:51:39 +05:30
DeepnaPooja 2d6723a040 Update dashboard 2026-10-19 15:48:45 +05:30
kalana_Thilakarathna 2fe2ba007c Merge pull request 'fix: update login function to handle profile data correctly' (#68) from dev into production
Reviewed-on: #68
2026-10-19 10:25:57 +05:30
Kalana B. Thilakarathna f3f2b71bd4 fix: update login function to handle profile data correctly
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 12m43s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 16m30s
2026-10-19 10:25:29 +05:30
kalana_Thilakarathna bc72426495 Merge pull request 'auth fix' (#67) from dev into production
Reviewed-on: #67
2026-10-19 10:20:29 +05:30
Kalana B. Thilakarathna da29492798 auth fix
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 13m10s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 16m51s
2026-10-19 10:19:40 +05:30
kalana_Thilakarathna ce21aa6588 Merge pull request 'feat: add toast notifications for successful and failed login attempts' (#66) from dev into production
Reviewed-on: #66
2026-10-19 02:50:47 +05:30
Kalana B. Thilakarathna f44cbf711b feat: add toast notifications for successful and failed login attempts
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 12m53s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 16m41s
2026-10-19 02:50:06 +05:30
kalana_Thilakarathna aaac42b481 Merge pull request 'Dev' (#65) from dev into production
Reviewed-on: #65
2026-10-19 02:45:35 +05:30
Kalana B. Thilakarathna 7a21cde217 refactor: simplify user API calls by removing pageSize parameter in multiple components
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 11m52s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 13m5s
2026-10-19 02:44:19 +05:30
Kalana B. Thilakarathna 531b3a7fef Redesign login UI, add theme toggle, refactor user update
- Redesigned login page with two-panel layout and branding
- Added HexaDistro logo and theme toggle to sidebar/header
- Refactored user update: roles via update endpoint, removed assignRole API/controller
- Extended UpdateUserRequest for username, role, password changes
- UserService.UpdateAsync syncs changes with AuthHex, with rollback
- Stock balances client dedupes by itemId-locationId
- Middleware matcher excludes static assets from auth checks
- Improved localStorage clearing for user context
- Added lucide-react for icons
- Minor UI/code cleanups for consistency and UX
2026-10-19 02:20:14 +05:30
kalana_Thilakarathna a81e32bbe2 Merge pull request 'fe fix for loggin redirection' (#64) from dev into production
Reviewed-on: #64
2026-10-19 02:16:31 +05:30
Kalana B. Thilakarathna 4ef63420b7 fe fix for loggin redirection
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 11m36s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 15m19s
2026-10-18 23:38:28 +05:30
Kalana B. Thilakarathna 9fdf5aa8e3 Merge branch 'dev' into production 2026-10-18 23:23:12 +05:30
Kalana B. Thilakarathna d9c339949e conflict fixed 2026-10-18 23:22:04 +05:30
DeepnaPooja 5d18aba1f9 Merge pull request 'Dev' (#63) from dev into production
Reviewed-on: #63
2026-10-16 10:53:09 +00:00
DeepnaPooja 5828879ddc Merge pull request 'chore: add exceljs, jspdf, and jspdf-autotable dependencies to package.json' (#62) from refactor/main_flow into dev
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 14m0s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 15m11s
Reviewed-on: #62
2026-10-16 10:52:30 +00:00
DeepnaPooja d75a43d45f chore: add exceljs, jspdf, and jspdf-autotable dependencies to package.json 2026-10-16 16:16:43 +05:30
DeepnaPooja 728f2a8b17 Merge pull request 'Dev' (#61) from dev into production
Reviewed-on: #61
2026-10-16 10:26:15 +00:00
DeepnaPooja 1b66d5d767 Merge pull request 'Refactor/main flow' (#60) from refactor/main_flow into dev
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 12m3s
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 15m51s
Reviewed-on: #60
2026-10-16 10:25:08 +00:00
DeepnaPooja c5a0992b5d Fe changes 2026-10-16 15:47:23 +05:30
DeepnaPooja 09d86835a2 Implement Warehouse Dashboard features 2026-10-16 15:44:35 +05:30
DeepnaPooja d1d2d55352 Add stock report features r 2026-10-16 15:41:00 +05:30
DeepnaPooja c626f56cdb Refactor Return and Ride services to integrate StockMover and enhance error handling 2026-10-16 15:37:05 +05:30
DeepnaPooja 263e2ea9c0 Refactor Delivery and Purchase Return services to use StockMover for inventory movements 2026-10-16 15:34:25 +05:30
DeepnaPooja 5c37f02f19 Merge pull request 'Dev' (#59) from dev into production
Reviewed-on: #59
2026-10-09 04:38:19 +00:00
DeepnaPooja 6b3f28d3ae Merge pull request 'Refactor/main flow' (#58) from refactor/main_flow into dev
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 13m37s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 15m0s
Reviewed-on: #58
2026-10-09 04:37:42 +00:00
DeepnaPooja e5f6aa15b9 Implement Store analytics and api integrations 2026-10-08 16:32:23 +05:30
DeepnaPooja 4e12b03e4b Add analytics functionality for stores 2026-10-08 16:28:56 +05:30
DeepnaPooja f5967497be Merge pull request 'Dev' (#57) from dev into production
Reviewed-on: #57
2026-10-08 07:36:24 +00:00
DeepnaPooja 4110e2fcfc Merge pull request 'Dev' (#55) from dev into production
Reviewed-on: #55
2026-10-07 05:17:54 +00:00
DeepnaPooja c52eaabec4 Merge pull request 'Dev' (#53) from dev into production
Reviewed-on: #53
2026-09-29 12:14:53 +00:00
DeepnaPooja 37f4a88014 Merge pull request 'Dev' (#48) from dev into production
Reviewed-on: #48
2026-09-28 07:39:55 +00:00
DeepnaPooja 2d365cd906 Merge pull request 'Dev' (#46) from dev into production
Reviewed-on: #46
2026-09-25 08:38:12 +00:00
DeepnaPooja c68c6e1ab9 Merge pull request 'Dev' (#44) from dev into production
Reviewed-on: #44
2026-09-24 12:00:43 +00:00
DeepnaPooja ddc3ecd3e6 Merge pull request 'Dev' (#42) from dev into production
Reviewed-on: #42
2026-09-23 08:38:21 +00:00
DeepnaPooja d7b6f29200 Merge pull request 'Dev' (#40) from dev into production
Reviewed-on: #40
2026-09-23 07:23:59 +00:00
DeepnaPooja d2277f626c Merge pull request 'Refactor/main flow' (#38) from refactor/main_flow into production
Reviewed-on: #38
2026-09-22 11:43:07 +00:00
kalana_Thilakarathna 3149902574 Merge pull request 'Dev' (#36) from dev into production
Reviewed-on: #36
2026-09-21 04:05:56 +00:00
kalana_Thilakarathna 37fb72376a Merge pull request 'Dev' (#35) from dev into production
Reviewed-on: #35
2026-09-21 03:47:22 +00:00
kalana_Thilakarathna 52a9a3fde7 Merge pull request 'Add build argument for NEXT_PUBLIC_API_URL in Dockerfile and CI/CD workflow' (#32) from dev into production
Reviewed-on: #32
2026-09-20 11:21:49 +00:00
kalana_Thilakarathna 1b310c86b3 Merge pull request 'Add Dockerfile and .dockerignore; configure Next.js output settings' (#31) from dev into production
Reviewed-on: #31
2026-09-20 11:07:29 +00:00
kalana_Thilakarathna e398ee9d6b Merge pull request 'docker log' (#30) from dev into production
Reviewed-on: #30
2026-09-20 11:03:16 +00:00
kalana_Thilakarathna a3c0211654 Merge pull request 'pipline update' (#29) from dev into production
Reviewed-on: #29
2026-09-20 10:53:45 +00:00
kalana_Thilakarathna e4e12aa05a Merge pull request 'build fix' (#28) from dev into production
Reviewed-on: #28
2026-09-20 10:42:24 +00:00
kalana_Thilakarathna 3103fb66ef Merge pull request 'Dev' (#27) from dev into production
Reviewed-on: #27
2026-09-20 10:35:40 +00:00
Kalana B. Thilakarathna 93355d34f1 Merge branch 'dev' into production 2026-09-16 11:30:37 +05:30
Kalana B. Thilakarathna 9151f47d15 Merge branch 'production' of https://gitea.hexdive.com/New_REP_SYSTEM/ERP_Distribution into production 2026-09-16 10:58:02 +05:30
Kalana B. Thilakarathna 661b522b7d Merge branch 'dev' into production 2026-09-16 10:57:55 +05:30
DeepnaPooja e0bb17d0ff Merge pull request 'Dev' (#25) from dev into production
Reviewed-on: #25
2026-09-16 05:13:39 +00:00
Kalana B. Thilakarathna 0f29413375 Merge branch 'dev' into production 2026-09-16 10:41:32 +05:30
DeepnaPooja ef17f7da0a Merge pull request 'Dev' (#23) from dev into production
Reviewed-on: #23
2026-09-14 06:29:20 +00:00
DeepnaPooja d9031f8e73 Merge pull request 'Dev' (#20) from dev into production
Reviewed-on: #20
2026-09-01 10:28:44 +00:00
Kalana B. Thilakarathna 75578ea7f6 Merge branch 'production' of https://gitea.hexdive.com/New_REP_SYSTEM/ERP_Distribution into production 2026-09-01 10:54:20 +05:30
DeepnaPooja 4f02fa303d Merge pull request 'Dev' (#18) from dev into production
Reviewed-on: #18
2026-08-31 07:41:03 +00:00
209 changed files with 48862 additions and 1179 deletions
+153
View File
@@ -65,6 +65,14 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
public DbSet<RegionalTaxRule> RegionalTaxRules { get; set; }
public DbSet<RouteVisitLog> RouteVisitLogs { get; set; }
public DbSet<SysConfig> SysConfigs { get; set; }
public DbSet<BankAccount> BankAccounts { get; set; }
public DbSet<GlPostingLog> GlPostingLogs { get; set; }
public DbSet<ChequeBook> ChequeBooks { get; set; }
public DbSet<ChequePage> ChequePages { get; set; }
public DbSet<ReceivedCheque> ReceivedCheques { get; set; }
public DbSet<SupplierPayment> SupplierPayments { get; set; }
public DbSet<SupplierPaymentGRN> SupplierPaymentGRNs { get; set; }
public DbSet<StoreCreditMovement> StoreCreditMovements { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -575,6 +583,16 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
.HasIndex(r => r.FK_RegionId)
.IsUnique();
// --- GL posting log ---
modelBuilder.Entity<GlPostingLog>()
.HasIndex(l => new { l.Event, l.SourceId })
.IsUnique();
modelBuilder.Entity<GlPostingLog>()
.Property(l => l.Event)
.HasMaxLength(60);
// --- SysConfig ---
modelBuilder.Entity<SysConfig>()
@@ -594,6 +612,141 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyDefaultWarrantyPeriodDays, Value = "0", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null }
);
// --- Payment service: Bank Accounts & Cheques ---
modelBuilder.Entity<BankAccount>()
.Property(a => a.AccountName).HasMaxLength(200);
modelBuilder.Entity<BankAccount>()
.Property(a => a.BankName).HasMaxLength(200);
modelBuilder.Entity<BankAccount>()
.Property(a => a.CashAccountTypeName).HasMaxLength(100);
modelBuilder.Entity<BankAccount>()
.Property(a => a.AccountNumber).HasMaxLength(100);
modelBuilder.Entity<BankAccount>()
.Property(a => a.CurrencyCode).HasMaxLength(10);
modelBuilder.Entity<BankAccount>()
.HasIndex(a => new { a.AccountType, a.AccountName })
.IsUnique();
modelBuilder.Entity<BankAccount>().HasData(new BankAccount
{
Id = new Guid("11111111-1111-1111-1111-000000000001"),
CreatedAt = new DateTime(2026, 10, 19),
IsActive = true,
AccountType = BankAccountType.Cash,
AccountName = "Petty Cash",
CashAccountTypeName = "Petty Cash",
CurrencyCode = "LKR",
IsDefault = true
});
modelBuilder.Entity<ChequeBook>()
.Property(b => b.ChequeBookNo).HasMaxLength(100);
modelBuilder.Entity<ChequeBook>()
.Property(b => b.StartChequeNo).HasMaxLength(50);
modelBuilder.Entity<ChequeBook>()
.Property(b => b.EndChequeNo).HasMaxLength(50);
modelBuilder.Entity<ChequeBook>()
.HasIndex(b => b.ChequeBookNo).IsUnique();
modelBuilder.Entity<ChequeBook>()
.HasOne(b => b.BankAccount)
.WithMany()
.HasForeignKey(b => b.FK_BankAccountId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ChequePage>()
.Property(p => p.ChequeNo).HasMaxLength(50);
modelBuilder.Entity<ChequePage>()
.Property(p => p.CurrencyCode).HasMaxLength(10);
modelBuilder.Entity<ChequePage>()
.HasIndex(p => p.ChequeNo).IsUnique();
modelBuilder.Entity<ChequePage>()
.HasOne(p => p.ChequeBook)
.WithMany(b => b.Pages)
.HasForeignKey(p => p.FK_ChequeBookId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ReceivedCheque>()
.Property(c => c.ChequeNo).HasMaxLength(50);
modelBuilder.Entity<ReceivedCheque>()
.Property(c => c.ReceivedFromName).HasMaxLength(200);
modelBuilder.Entity<ReceivedCheque>()
.HasIndex(c => c.ChequeNo);
modelBuilder.Entity<ReceivedCheque>()
.HasOne(c => c.DepositBankAccount)
.WithMany()
.HasForeignKey(c => c.FK_DepositBankAccountId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
// --- SupplierPayment ---
modelBuilder.Entity<SupplierPayment>()
.HasOne(p => p.Supplier)
.WithMany()
.HasForeignKey(p => p.FK_SupplierId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<SupplierPayment>()
.HasOne(p => p.BankAccount)
.WithMany()
.HasForeignKey(p => p.FK_BankAccountId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
modelBuilder.Entity<SupplierPayment>()
.HasOne(p => p.ChequePage)
.WithMany()
.HasForeignKey(p => p.FK_ChequePageId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
modelBuilder.Entity<SupplierPayment>()
.Property(p => p.Amount).HasPrecision(18, 2);
modelBuilder.Entity<SupplierPaymentGRN>()
.HasOne(a => a.SupplierPayment)
.WithMany(p => p.Allocations)
.HasForeignKey(a => a.FK_SupplierPaymentId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<SupplierPaymentGRN>()
.HasOne(a => a.GoodsReceipt)
.WithMany()
.HasForeignKey(a => a.FK_GoodsReceiptId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<SupplierPaymentGRN>()
.HasIndex(a => a.FK_GoodsReceiptId);
// --- StoreCreditMovement ---
modelBuilder.Entity<StoreCreditMovement>()
.HasOne(m => m.Store)
.WithMany()
.HasForeignKey(m => m.FK_StoreId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<StoreCreditMovement>()
.HasOne(m => m.Invoice)
.WithMany()
.HasForeignKey(m => m.FK_InvoiceId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
modelBuilder.Entity<StoreCreditMovement>()
.HasOne(m => m.Payment)
.WithMany()
.HasForeignKey(m => m.FK_PaymentId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
modelBuilder.Entity<StoreCreditMovement>()
.Property(m => m.Amount).HasPrecision(18, 2);
modelBuilder.Entity<StoreCreditMovement>()
.HasIndex(m => new { m.FK_StoreId, m.OccurredAt });
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
Seed.MasterDataSeed.Apply(modelBuilder);
Seed.OperationsDataSeed.Apply(modelBuilder);
@@ -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 }
);
}
}
@@ -0,0 +1,66 @@
using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.BankAccount;
[ApiController]
[Route("api")]
public class BankAccountController(BankAccountService service) : ControllerBase
{
[HttpGet("bank-accounts")]
public async Task<IActionResult> List([FromQuery] BankAccountType? accountType)
{
var result = await service.ListAsync(accountType);
return Ok(result);
}
[HttpPost("bank-accounts")]
public async Task<IActionResult> CreateBank(CreateBankAccountRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateBankAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPost("cash-accounts")]
public async Task<IActionResult> CreateCash(CreateCashAccountRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateCashAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("cash-accounts/{id:guid}")]
public async Task<IActionResult> UpdateCash(Guid id, UpdateCashAccountRequest request, CancellationToken ct)
{
try
{
var result = await service.UpdateCashAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPatch("cash-accounts/{id:guid}/status")]
public async Task<IActionResult> SetCashStatus(Guid id, SetCashAccountStatusRequest request, CancellationToken ct)
{
try
{
var result = await service.SetCashStatusAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpDelete("cash-accounts/{id:guid}")]
public async Task<IActionResult> DeleteCash(Guid id, CancellationToken ct)
{
try
{
var ok = await service.DeleteCashAsync(id, ct);
return ok ? NoContent() : NotFound();
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpGet("cash-account-types")]
public async Task<IActionResult> ListCashAccountTypes() => Ok(await service.ListCashAccountTypesAsync());
}
@@ -0,0 +1,33 @@
using Distribution.Models;
namespace Distribution.Features.BankAccount;
public record BankAccountResponse(
BankAccountType AccountType,
Guid AccountId,
string AccountName,
string? BankName,
string? CashAccountTypeName,
string? AccountNumber,
string CurrencyCode,
DateTime CreatedAt,
bool? IsActive,
bool? IsDefault);
public record CreateBankAccountRequest(
string AccountName,
string? BankName,
string? AccountNumber,
string? CurrencyCode);
public record CreateCashAccountRequest(
string AccountName,
string CashAccountTypeName,
string? AccountNumber,
string? CurrencyCode);
public record UpdateCashAccountRequest(string AccountName);
public record SetCashAccountStatusRequest(bool IsActive);
public record CashAccountTypeDto(string Name);
@@ -0,0 +1,40 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.BankAccount;
public class BankAccountRepository(AppDBContext context) : IBankAccountRepository
{
public async Task<Models.BankAccount?> GetByIdAsync(Guid id) =>
await context.BankAccounts.FirstOrDefaultAsync(a => a.Id == id);
public async Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType)
{
var query = context.BankAccounts.AsQueryable();
if (accountType is not null)
query = query.Where(a => a.AccountType == accountType);
return await query.OrderByDescending(a => a.CreatedAt).ToListAsync();
}
public async Task<List<string>> ListCashAccountTypeNamesAsync() =>
await context.BankAccounts
.Where(a => a.AccountType == BankAccountType.Cash && a.CashAccountTypeName != null)
.Select(a => a.CashAccountTypeName!)
.Distinct()
.OrderBy(n => n)
.ToListAsync();
public async Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null) =>
await context.BankAccounts.AnyAsync(a =>
a.AccountType == type &&
a.AccountName == accountName &&
(excludeId == null || a.Id != excludeId));
public async Task AddAsync(Models.BankAccount account) =>
await context.BankAccounts.AddAsync(account);
public void Update(Models.BankAccount account) => context.BankAccounts.Update(account);
public void Remove(Models.BankAccount account) => context.BankAccounts.Remove(account);
}
@@ -0,0 +1,116 @@
using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.BankAccount;
public class BankAccountService(IBankAccountRepository repo, IUnitOfWork uow)
{
private const string DefaultCurrency = "LKR";
private static BankAccountResponse ToResponse(Models.BankAccount a) =>
new(
a.AccountType,
a.Id,
a.AccountName,
a.AccountType == BankAccountType.Bank ? a.BankName : null,
a.AccountType == BankAccountType.Cash ? a.CashAccountTypeName : null,
a.AccountNumber,
a.CurrencyCode,
a.CreatedAt,
a.AccountType == BankAccountType.Cash ? a.IsActive : null,
a.AccountType == BankAccountType.Cash ? a.IsDefault : null);
public async Task<List<BankAccountResponse>> ListAsync(BankAccountType? accountType) =>
(await repo.ListAsync(accountType)).Select(ToResponse).ToList();
public async Task<List<CashAccountTypeDto>> ListCashAccountTypesAsync() =>
(await repo.ListCashAccountTypeNamesAsync()).Select(n => new CashAccountTypeDto(n)).ToList();
public async Task<BankAccountResponse> CreateBankAsync(CreateBankAccountRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.AccountName))
throw new InvalidOperationException("AccountName is required");
if (await repo.ExistsByNameAsync(BankAccountType.Bank, request.AccountName))
throw new InvalidOperationException("A bank account with this name already exists");
var account = new Models.BankAccount
{
AccountType = BankAccountType.Bank,
AccountName = request.AccountName,
BankName = request.BankName,
AccountNumber = request.AccountNumber,
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
IsActive = true,
IsDefault = false
};
await repo.AddAsync(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<BankAccountResponse> CreateCashAsync(CreateCashAccountRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.AccountName))
throw new InvalidOperationException("AccountName is required");
if (string.IsNullOrWhiteSpace(request.CashAccountTypeName))
throw new InvalidOperationException("CashAccountTypeName is required");
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName))
throw new InvalidOperationException("A cash account with this name already exists");
var account = new Models.BankAccount
{
AccountType = BankAccountType.Cash,
AccountName = request.AccountName,
CashAccountTypeName = request.CashAccountTypeName,
AccountNumber = request.AccountNumber,
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
IsActive = true,
IsDefault = false
};
await repo.AddAsync(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<BankAccountResponse?> UpdateCashAsync(Guid id, UpdateCashAccountRequest request, CancellationToken ct = default)
{
var account = await repo.GetByIdAsync(id);
if (account is null || account.AccountType != BankAccountType.Cash) return null;
if (string.IsNullOrWhiteSpace(request.AccountName))
throw new InvalidOperationException("AccountName is required");
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName, excludeId: id))
throw new InvalidOperationException("A cash account with this name already exists");
account.AccountName = request.AccountName;
repo.Update(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<BankAccountResponse?> SetCashStatusAsync(Guid id, SetCashAccountStatusRequest request, CancellationToken ct = default)
{
var account = await repo.GetByIdAsync(id);
if (account is null || account.AccountType != BankAccountType.Cash) return null;
if (account.IsDefault && !request.IsActive)
throw new InvalidOperationException("The default cash account cannot be deactivated");
account.IsActive = request.IsActive;
repo.Update(account);
await uow.CommitAsync(ct);
return ToResponse(account);
}
public async Task<bool> DeleteCashAsync(Guid id, CancellationToken ct = default)
{
var account = await repo.GetByIdAsync(id);
if (account is null || account.AccountType != BankAccountType.Cash) return false;
if (account.IsDefault)
throw new InvalidOperationException("The default cash account cannot be deleted");
repo.Remove(account);
await uow.CommitAsync(ct);
return true;
}
}
@@ -0,0 +1,14 @@
using Distribution.Models;
namespace Distribution.Features.BankAccount;
public interface IBankAccountRepository
{
Task<Models.BankAccount?> GetByIdAsync(Guid id);
Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType);
Task<List<string>> ListCashAccountTypeNamesAsync();
Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null);
Task AddAsync(Models.BankAccount account);
void Update(Models.BankAccount account);
void Remove(Models.BankAccount account);
}
@@ -0,0 +1,55 @@
using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.ChequeBook;
[ApiController]
[Route("api")]
public class ChequeBookController(ChequeBookService service) : ControllerBase
{
[HttpGet("cheque-books")]
public async Task<IActionResult> List(
[FromQuery] Guid? bankAccountId,
[FromQuery] int? branchId,
[FromQuery] ChequeBookStatus? status,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
=> Ok(await service.ListAsync(bankAccountId, branchId, status, page, pageSize));
[HttpGet("cheque-books/{chequeBookNo}")]
public async Task<IActionResult> Get(string chequeBookNo, [FromQuery] string? expand)
{
var expandPages = string.Equals(expand, "pages", StringComparison.OrdinalIgnoreCase);
var result = await service.GetAsync(chequeBookNo, expandPages);
return result is null ? NotFound() : Ok(result);
}
[HttpPost("cheque-books")]
public async Task<IActionResult> Create(CreateChequeBookRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("cheque-pages/{chequeNo}/issue")]
public async Task<IActionResult> Issue(string chequeNo, IssueChequePageRequest request, CancellationToken ct)
{
try
{
var result = await service.IssueAsync(chequeNo, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("cheque-pages/{chequeNo}/status")]
public async Task<IActionResult> UpdateStatus(string chequeNo, UpdateChequePageStatusRequest request, CancellationToken ct)
{
try
{
var result = await service.UpdatePageStatusAsync(chequeNo, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
}
@@ -0,0 +1,75 @@
using Distribution.Models;
namespace Distribution.Features.ChequeBook;
public record ChequePageResponse(
Guid Id,
string ChequeNo,
string ChequeBookNo,
ChequePageIssueStatus IssueStatus,
PayeeType? PayeeType,
int? PayeeId,
string? PayeeName,
DateTime? IssueDate,
decimal? Amount,
string? CurrencyCode,
int? VoucherId,
string? ReferenceNo,
string? Purpose,
bool? IsCrossCheque,
bool? IsAccountPayee,
bool? IsPostDated,
string? Notes,
string? PrintedBy,
DateTime? PrintedAt,
DateTime? ClearedDate,
bool? ClearedByBank,
string? CancelReason);
public record ChequeBookResponse(
string ChequeBookNo,
int BranchId,
Guid BankAccountId,
string StartChequeNo,
string EndChequeNo,
int TotalLeaves,
DateTime ReceivedDate,
string? Description,
string? CreatedBy,
ChequeBookStatus Status,
List<ChequePageResponse> Pages);
public record PagedResult<T>(IReadOnlyList<T> Items, int TotalCount, int? Page, int? PageSize);
public record CreateChequeBookRequest(
int BranchId,
Guid BankAccountId,
string ChequeBookNo,
string StartChequeNo,
string EndChequeNo,
int TotalLeaves,
DateTime ReceivedDate,
string? Description,
string? CreatedBy);
public record IssueChequePageRequest(
PayeeType PayeeType,
int? PayeeId,
string PayeeName,
DateTime IssueDate,
decimal Amount,
string? CurrencyCode,
int? VoucherId,
string? ReferenceNo,
string? Purpose,
bool? IsCrossCheque,
bool? IsAccountPayee,
bool? IsPostDated,
string? Notes,
string? PrintedBy);
public record UpdateChequePageStatusRequest(
ChequePageStatusAction Action,
DateTime? ClearedDate,
string? CancelReason,
string? PerformedBy);
@@ -0,0 +1,46 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.ChequeBook;
public class ChequeBookRepository(AppDBContext context) : IChequeBookRepository
{
public async Task<Models.ChequeBook?> GetByNoAsync(string chequeBookNo, bool includePages)
{
var query = context.ChequeBooks.AsQueryable();
if (includePages) query = query.Include(b => b.Pages);
return await query.FirstOrDefaultAsync(b => b.ChequeBookNo == chequeBookNo);
}
public async Task<(List<Models.ChequeBook> Items, int TotalCount)> ListAsync(
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize)
{
var query = context.ChequeBooks.AsQueryable();
if (bankAccountId is not null) query = query.Where(b => b.FK_BankAccountId == bankAccountId);
if (branchId is not null) query = query.Where(b => b.BranchId == branchId);
if (status is not null) query = query.Where(b => b.Status == status);
var total = await query.CountAsync();
var items = await query
.OrderByDescending(b => b.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, total);
}
public async Task<bool> BookNoExistsAsync(string chequeBookNo) =>
await context.ChequeBooks.AnyAsync(b => b.ChequeBookNo == chequeBookNo);
public async Task AddAsync(Models.ChequeBook book) => await context.ChequeBooks.AddAsync(book);
public void Update(Models.ChequeBook book) => context.ChequeBooks.Update(book);
public async Task<Models.ChequePage?> GetPageByNoAsync(string chequeNo) =>
await context.ChequePages
.Include(p => p.ChequeBook)
.FirstOrDefaultAsync(p => p.ChequeNo == chequeNo);
public void UpdatePage(Models.ChequePage page) => context.ChequePages.Update(page);
}
@@ -0,0 +1,179 @@
using Distribution.Features.BankAccount;
using Distribution.Features.GeneralLedger;
using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.ChequeBook;
public class ChequeBookService(
IChequeBookRepository repo,
IBankAccountRepository bankAccountRepo,
IUnitOfWork uow,
GlPostingService gl)
{
private const string DefaultCurrency = "LKR";
private static ChequePageResponse ToPageResponse(Models.ChequePage p, string chequeBookNo) =>
new(p.Id, p.ChequeNo, chequeBookNo, p.IssueStatus, p.PayeeType, p.PayeeId, p.PayeeName,
p.IssueDate, p.Amount, p.CurrencyCode, p.VoucherId, p.ReferenceNo, p.Purpose,
p.IsCrossCheque, p.IsAccountPayee, p.IsPostDated, p.Notes,
p.PrintedBy, p.PrintedAt, p.ClearedDate, p.ClearedByBank, p.CancelReason);
private static ChequeBookResponse ToBookResponse(Models.ChequeBook b, bool includePages) =>
new(b.ChequeBookNo, b.BranchId, b.FK_BankAccountId, b.StartChequeNo, b.EndChequeNo,
b.TotalLeaves, b.ReceivedDate, b.Description, b.CreatedBy, b.Status,
includePages ? b.Pages.OrderBy(p => p.ChequeNo).Select(p => ToPageResponse(p, b.ChequeBookNo)).ToList() : new());
public async Task<PagedResult<ChequeBookResponse>> ListAsync(
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize)
{
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 200) pageSize = 50;
var (items, total) = await repo.ListAsync(bankAccountId, branchId, status, page, pageSize);
return new PagedResult<ChequeBookResponse>(
items.Select(b => ToBookResponse(b, includePages: false)).ToList(),
total, page, pageSize);
}
public async Task<ChequeBookResponse?> GetAsync(string chequeBookNo, bool expandPages)
{
var book = await repo.GetByNoAsync(chequeBookNo, includePages: expandPages);
return book is null ? null : ToBookResponse(book, includePages: expandPages);
}
public async Task<ChequeBookResponse> CreateAsync(CreateChequeBookRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.ChequeBookNo))
throw new InvalidOperationException("ChequeBookNo is required");
if (request.TotalLeaves <= 0)
throw new InvalidOperationException("TotalLeaves must be greater than zero");
if (!long.TryParse(request.StartChequeNo, out var start) ||
!long.TryParse(request.EndChequeNo, out var end))
throw new InvalidOperationException("StartChequeNo and EndChequeNo must be numeric");
if (end < start)
throw new InvalidOperationException("EndChequeNo must be greater than or equal to StartChequeNo");
if (end - start + 1 != request.TotalLeaves)
throw new InvalidOperationException("TotalLeaves does not match the Start/End cheque range");
if (await repo.BookNoExistsAsync(request.ChequeBookNo))
throw new InvalidOperationException("A cheque book with this number already exists");
var bankAccount = await bankAccountRepo.GetByIdAsync(request.BankAccountId);
if (bankAccount is null || bankAccount.AccountType != BankAccountType.Bank)
throw new InvalidOperationException("BankAccountId does not reference a valid bank account");
var book = new Models.ChequeBook
{
ChequeBookNo = request.ChequeBookNo,
BranchId = request.BranchId,
FK_BankAccountId = request.BankAccountId,
StartChequeNo = request.StartChequeNo,
EndChequeNo = request.EndChequeNo,
TotalLeaves = request.TotalLeaves,
ReceivedDate = request.ReceivedDate,
Description = request.Description,
CreatedBy = request.CreatedBy,
Status = ChequeBookStatus.Active
};
var width = request.StartChequeNo.Length;
for (var n = start; n <= end; n++)
{
book.Pages.Add(new Models.ChequePage
{
ChequeNo = n.ToString().PadLeft(width, '0'),
IssueStatus = ChequePageIssueStatus.Unused
});
}
await uow.BeginAsync(ct);
try
{
await repo.AddAsync(book);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return ToBookResponse(book, includePages: true);
}
public async Task<ChequePageResponse?> IssueAsync(string chequeNo, IssueChequePageRequest request, CancellationToken ct = default)
{
var page = await repo.GetPageByNoAsync(chequeNo);
if (page is null) return null;
if (page.IssueStatus != ChequePageIssueStatus.Unused)
throw new InvalidOperationException($"Cannot issue a cheque in status '{page.IssueStatus}'");
if (page.ChequeBook is null || page.ChequeBook.Status != ChequeBookStatus.Active)
throw new InvalidOperationException("The cheque book is not active");
if (request.Amount <= 0)
throw new InvalidOperationException("Amount must be greater than zero");
if (string.IsNullOrWhiteSpace(request.PayeeName))
throw new InvalidOperationException("PayeeName is required");
page.IssueStatus = ChequePageIssueStatus.Issued;
page.PayeeType = request.PayeeType;
page.PayeeId = request.PayeeId;
page.PayeeName = request.PayeeName;
page.IssueDate = request.IssueDate;
page.Amount = request.Amount;
page.CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode;
page.VoucherId = request.VoucherId;
page.ReferenceNo = request.ReferenceNo;
page.Purpose = request.Purpose;
page.IsCrossCheque = request.IsCrossCheque;
page.IsAccountPayee = request.IsAccountPayee;
page.IsPostDated = request.IsPostDated;
page.Notes = request.Notes;
page.PrintedBy = request.PrintedBy;
page.PrintedAt = DateTime.Now;
repo.UpdatePage(page);
await uow.CommitAsync(ct);
return ToPageResponse(page, page.ChequeBook!.ChequeBookNo);
}
public async Task<ChequePageResponse?> UpdatePageStatusAsync(string chequeNo, UpdateChequePageStatusRequest request, CancellationToken ct = default)
{
var page = await repo.GetPageByNoAsync(chequeNo);
if (page is null) return null;
switch (request.Action)
{
case ChequePageStatusAction.Clear:
if (page.IssueStatus != ChequePageIssueStatus.Issued)
throw new InvalidOperationException("Only issued cheques can be cleared");
if (request.ClearedDate is null)
throw new InvalidOperationException("ClearedDate is required when clearing a cheque");
page.IssueStatus = ChequePageIssueStatus.Cleared;
page.ClearedDate = request.ClearedDate;
page.ClearedByBank = true;
break;
case ChequePageStatusAction.Bounce:
if (page.IssueStatus != ChequePageIssueStatus.Issued)
throw new InvalidOperationException("Only issued cheques can be bounced");
page.IssueStatus = ChequePageIssueStatus.Bounced;
break;
case ChequePageStatusAction.Cancel:
if (page.IssueStatus != ChequePageIssueStatus.Issued)
throw new InvalidOperationException("Only issued cheques can be cancelled");
if (string.IsNullOrWhiteSpace(request.CancelReason))
throw new InvalidOperationException("CancelReason is required when cancelling a cheque");
page.IssueStatus = ChequePageIssueStatus.Cancelled;
page.CancelReason = request.CancelReason;
break;
case ChequePageStatusAction.Void:
if (page.IssueStatus != ChequePageIssueStatus.Unused)
throw new InvalidOperationException("Only unused cheques can be voided");
page.IssueStatus = ChequePageIssueStatus.Void;
break;
}
repo.UpdatePage(page);
await uow.CommitAsync(ct);
await gl.PostIssuedChequeAsync(page, request.Action, ct);
return ToPageResponse(page, page.ChequeBook!.ChequeBookNo);
}
}
@@ -0,0 +1,16 @@
using Distribution.Models;
namespace Distribution.Features.ChequeBook;
public interface IChequeBookRepository
{
Task<Models.ChequeBook?> GetByNoAsync(string chequeBookNo, bool includePages);
Task<(List<Models.ChequeBook> Items, int TotalCount)> ListAsync(
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize);
Task<bool> BookNoExistsAsync(string chequeBookNo);
Task AddAsync(Models.ChequeBook book);
void Update(Models.ChequeBook book);
Task<Models.ChequePage?> GetPageByNoAsync(string chequeNo);
void UpdatePage(Models.ChequePage page);
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.DashboardReport;
[ApiController]
[Route("api/reports/dashboard")]
public class DashboardReportController(DashboardReportService service) : ControllerBase
{
[HttpGet("sales")]
public async Task<IActionResult> Sales(
[FromQuery] DateTime? from, [FromQuery] DateTime? to,
[FromQuery] Guid? regionId, [FromQuery] Guid? categoryId)
{
var f = DashboardReportService.BuildFilter(from, to, regionId, categoryId, null);
var error = DashboardReportService.Validate(f);
return error is null ? Ok(await service.GetSalesSummaryAsync(f)) : BadRequest(new { message = error });
}
[HttpGet("inventory")]
public async Task<IActionResult> Inventory(
[FromQuery] DateTime? from, [FromQuery] DateTime? to,
[FromQuery] Guid? warehouseId, [FromQuery] Guid? categoryId)
{
var f = DashboardReportService.BuildFilter(from, to, null, categoryId, warehouseId);
var error = DashboardReportService.Validate(f);
return error is null ? Ok(await service.GetInventorySummaryAsync(f)) : BadRequest(new { message = error });
}
}
@@ -0,0 +1,32 @@
namespace Distribution.Features.DashboardReport;
public record DashboardFilter(DateTime From, DateTime To, Guid? RegionId, Guid? CategoryId, Guid? WarehouseId);
public record SalesTotals(decimal GrossSales, decimal ReturnValue, int Orders, decimal CostedSales, decimal CostOfSales);
public record SalesKpis(
decimal GrossSales, decimal ReturnValue, decimal NetSales, int Orders, decimal AverageOrderValue,
decimal GrossProfit, decimal? GrossProfitMarginPercent);
public record SalesKpiComparison(SalesKpis Current, SalesKpis Previous, DateTime PreviousFrom, DateTime PreviousTo);
public record TrendPoint(string Period, decimal Sales, decimal Returns);
public record NamedAmount(Guid? Id, string Name, decimal Amount);
public record SalesSummaryResponse(
DateTime From, DateTime To, string Granularity,
SalesKpiComparison Kpis, List<TrendPoint> Trend,
List<NamedAmount> ByCategory, List<NamedAmount> ByRegion, List<NamedAmount> ByRep);
public record PeriodAmount(DateTime Date, decimal Amount);
public record MovementPoint(string Period, decimal Received, decimal Issued, decimal Net);
public record WarehouseStockRow(Guid WarehouseId, string Name, decimal Quantity, decimal Value);
public record CategoryValueRow(string Category, decimal Quantity, decimal Value);
public record InventorySummaryResponse(
DateTime From, DateTime To, string Granularity,
List<CategoryValueRow> ValueByCategory, List<WarehouseStockRow> ByWarehouse, List<MovementPoint> MovementTrend);
@@ -0,0 +1,171 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.DashboardReport;
public class DashboardReportRepository(AppDBContext db) : IDashboardReportRepository
{
private sealed class SoldLine
{
public DateTime Date { get; set; }
public Guid PoId { get; set; }
public decimal Value { get; set; }
public decimal? Cost { get; set; }
public Guid CategoryId { get; set; }
public string CategoryName { get; set; } = "";
public Guid RegionId { get; set; }
public string RegionName { get; set; } = "";
public Guid RepId { get; set; }
}
// Same definition of "sold" as ProductReportRepository: fulfilled lines on non-cancelled POs
// whose invoice is Finalized, dated by Invoice.FinalizedAt. 'To' is inclusive (whole day).
private IQueryable<SoldLine> SoldLines(DashboardFilter f)
{
var start = f.From.Date;
var end = f.To.Date.AddDays(1);
var q =
from li in db.ShopPOItems.AsNoTracking()
join inv in db.Invoices.AsNoTracking() on li.FK_ShopPOId equals inv.FK_ShopPOId
where inv.Status == InvoiceStatus.Finalized
&& li.ShopPO.Status != ShopPOStatus.Cancelled
&& inv.FinalizedAt >= start && inv.FinalizedAt < end
select new SoldLine
{
Date = inv.FinalizedAt!.Value.Date,
PoId = li.FK_ShopPOId,
Value = li.QuantityFulfilled * li.UnitPrice,
Cost = li.Item.CostPrice == null ? null : li.QuantityFulfilled * li.Item.CostPrice,
CategoryId = li.Item.FK_CategoryId,
CategoryName = li.Item.Category.Name,
RegionId = li.ShopPO.Store.Route.FK_RegionId,
RegionName = li.ShopPO.Store.Route.Region.Name,
RepId = li.ShopPO.RaisedById,
};
if (f.RegionId.HasValue) q = q.Where(l => l.RegionId == f.RegionId.Value);
if (f.CategoryId.HasValue) q = q.Where(l => l.CategoryId == f.CategoryId.Value);
return q;
}
private IQueryable<SaleReturnItem> ReturnLines(DashboardFilter f)
{
var start = f.From.Date;
var end = f.To.Date.AddDays(1);
var q = db.SaleReturnItems.AsNoTracking()
.Where(r => r.SaleReturn.CreatedAt >= start && r.SaleReturn.CreatedAt < end);
if (f.RegionId.HasValue) q = q.Where(r => r.SaleReturn.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
if (f.CategoryId.HasValue) q = q.Where(r => r.Item.FK_CategoryId == f.CategoryId.Value);
return q;
}
public async Task<SalesTotals> GetSalesTotalsAsync(DashboardFilter f)
{
var lines = SoldLines(f);
var gross = await lines.SumAsync(l => (decimal?)l.Value) ?? 0m;
var orders = await lines.Select(l => l.PoId).Distinct().CountAsync();
var costed = lines.Where(l => l.Cost != null);
var costedSales = await costed.SumAsync(l => (decimal?)l.Value) ?? 0m;
var cost = await costed.SumAsync(l => l.Cost) ?? 0m;
var returns = await ReturnLines(f).SumAsync(r => (decimal?)(r.Quantity * r.UnitPrice)) ?? 0m;
return new SalesTotals(gross, returns, orders, costedSales, cost);
}
public async Task<List<PeriodAmount>> GetDailySalesAsync(DashboardFilter f) =>
(await SoldLines(f).GroupBy(l => l.Date)
.Select(g => new { Date = g.Key, Amount = g.Sum(x => x.Value) })
.ToListAsync())
.Select(x => new PeriodAmount(x.Date, x.Amount)).ToList();
public async Task<List<PeriodAmount>> GetDailyReturnsAsync(DashboardFilter f) =>
(await ReturnLines(f).GroupBy(r => r.SaleReturn.CreatedAt.Date)
.Select(g => new { Date = g.Key, Amount = g.Sum(x => x.Quantity * x.UnitPrice) })
.ToListAsync())
.Select(x => new PeriodAmount(x.Date, x.Amount)).ToList();
public async Task<List<GroupAggregate>> GetSalesByCategoryAsync(DashboardFilter f) =>
(await SoldLines(f).GroupBy(l => new { l.CategoryId, l.CategoryName })
.Select(g => new { g.Key.CategoryId, g.Key.CategoryName, Amount = g.Sum(x => x.Value) })
.OrderByDescending(x => x.Amount).ToListAsync())
.Select(x => new GroupAggregate(x.CategoryId, x.CategoryName, x.Amount)).ToList();
public async Task<List<GroupAggregate>> GetSalesByRegionAsync(DashboardFilter f) =>
(await SoldLines(f).GroupBy(l => new { l.RegionId, l.RegionName })
.Select(g => new { g.Key.RegionId, g.Key.RegionName, Amount = g.Sum(x => x.Value) })
.OrderByDescending(x => x.Amount).ToListAsync())
.Select(x => new GroupAggregate(x.RegionId, x.RegionName, x.Amount)).ToList();
public async Task<List<GroupAggregate>> GetSalesByRepAsync(DashboardFilter f)
{
var rows = await SoldLines(f).GroupBy(l => l.RepId)
.Select(g => new { RepId = g.Key, Amount = g.Sum(x => x.Value) })
.OrderByDescending(x => x.Amount).Take(20).ToListAsync();
var ids = rows.Select(r => r.RepId).ToList();
var names = await db.Users.AsNoTracking().Where(u => ids.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName);
return rows.Select(r => new GroupAggregate(r.RepId, names.GetValueOrDefault(r.RepId, "Unknown"), r.Amount)).ToList();
}
private IQueryable<StockBalance> Balances(DashboardFilter f)
{
var q = db.StockBalances.AsNoTracking().AsQueryable();
if (f.WarehouseId.HasValue)
q = q.Where(b => b.LocationType == LocationType.Warehouse && b.LocationId == f.WarehouseId.Value);
if (f.CategoryId.HasValue) q = q.Where(b => b.Item.FK_CategoryId == f.CategoryId.Value);
return q;
}
// Value = quantity x cost price; items without a cost price contribute quantity only.
public async Task<List<CategoryValueRow>> GetStockValueByCategoryAsync(DashboardFilter f) =>
(await Balances(f).GroupBy(b => b.Item.Category.Name)
.Select(g => new
{
Category = g.Key,
Quantity = g.Sum(x => x.Quantity),
Value = g.Sum(x => x.Quantity * (x.Item.CostPrice ?? 0m)),
})
.OrderByDescending(x => x.Value).ToListAsync())
.Select(x => new CategoryValueRow(x.Category, x.Quantity, x.Value)).ToList();
public async Task<List<WarehouseStockRow>> GetStockByWarehouseAsync(DashboardFilter f)
{
var rows = await Balances(f).Where(b => b.LocationType == LocationType.Warehouse)
.GroupBy(b => b.LocationId)
.Select(g => new
{
Id = g.Key,
Quantity = g.Sum(x => x.Quantity),
Value = g.Sum(x => x.Quantity * (x.Item.CostPrice ?? 0m)),
})
.ToListAsync();
var ids = rows.Select(r => r.Id).ToList();
var names = await db.Warehouses.AsNoTracking().Where(w => ids.Contains(w.Id))
.ToDictionaryAsync(w => w.Id, w => w.Name);
return rows.Select(r => new WarehouseStockRow(r.Id, names.GetValueOrDefault(r.Id, "Unknown"), r.Quantity, r.Value))
.OrderByDescending(r => r.Value).ToList();
}
// Received = purchase receipts + customer returns; Issued = sales + purchase returns + damages.
// Transfers, loads and adjustments are internal/signed and excluded from this trend.
public async Task<List<(DateTime Date, decimal Received, decimal Issued)>> GetDailyMovementsAsync(DashboardFilter f)
{
var start = f.From.Date;
var end = f.To.Date.AddDays(1);
var q = db.StockMovements.AsNoTracking()
.Where(m => m.CreatedAt >= start && m.CreatedAt < end
&& (m.Type == MovementType.PurchaseReceipt || m.Type == MovementType.CustomerReturn
|| m.Type == MovementType.Sale || m.Type == MovementType.PurchaseReturn || m.Type == MovementType.Damage));
if (f.CategoryId.HasValue) q = q.Where(m => m.Item.FK_CategoryId == f.CategoryId.Value);
if (f.WarehouseId.HasValue)
q = q.Where(m => m.ToLocationId == f.WarehouseId.Value || m.FromLocationId == f.WarehouseId.Value);
var rows = await q.GroupBy(m => new { m.CreatedAt.Date, m.Type })
.Select(g => new { g.Key.Date, g.Key.Type, Qty = g.Sum(x => x.Quantity) })
.ToListAsync();
return rows.GroupBy(r => r.Date)
.Select(g => (g.Key,
g.Where(r => r.Type is MovementType.PurchaseReceipt or MovementType.CustomerReturn).Sum(r => r.Qty),
g.Where(r => r.Type is MovementType.Sale or MovementType.PurchaseReturn or MovementType.Damage).Sum(r => r.Qty)))
.ToList();
}
}
@@ -0,0 +1,83 @@
namespace Distribution.Features.DashboardReport;
public class DashboardReportService(IDashboardReportRepository repo)
{
public const int MaxRangeDays = 366;
private const int DailyGranularityMaxDays = 62;
public static DashboardFilter BuildFilter(DateTime? from, DateTime? to, Guid? regionId, Guid? categoryId, Guid? warehouseId)
{
var end = (to ?? DateTime.Now).Date;
var start = (from ?? end.AddDays(-29)).Date;
return new DashboardFilter(start, end, regionId, categoryId, warehouseId);
}
public static string? Validate(DashboardFilter f)
{
if (f.To < f.From) return "'to' must not be before 'from'.";
if ((f.To - f.From).TotalDays > MaxRangeDays) return $"Date range cannot exceed {MaxRangeDays} days.";
return null;
}
private static bool Daily(DashboardFilter f) => (f.To - f.From).TotalDays <= DailyGranularityMaxDays;
private static string Bucket(DateTime d, bool daily) => daily ? d.ToString("yyyy-MM-dd") : d.ToString("yyyy-MM");
private static SalesKpis ToKpis(SalesTotals t)
{
var net = t.GrossSales - t.ReturnValue;
var gp = t.CostedSales - t.CostOfSales;
return new SalesKpis(
t.GrossSales, t.ReturnValue, net, t.Orders,
t.Orders > 0 ? Math.Round(t.GrossSales / t.Orders, 2) : 0m,
gp, t.CostedSales > 0 ? Math.Round(gp / t.CostedSales * 100, 2) : null);
}
public async Task<SalesSummaryResponse> GetSalesSummaryAsync(DashboardFilter f)
{
var days = (f.To - f.From).Days + 1;
var prev = f with { From = f.From.AddDays(-days), To = f.From.AddDays(-1) };
var daily = Daily(f);
var current = await repo.GetSalesTotalsAsync(f);
var previous = await repo.GetSalesTotalsAsync(prev);
var sales = await repo.GetDailySalesAsync(f);
var returns = await repo.GetDailyReturnsAsync(f);
var trend = sales.Select(s => (s.Date, Sales: s.Amount, Returns: 0m))
.Concat(returns.Select(r => (r.Date, Sales: 0m, Returns: r.Amount)))
.GroupBy(x => Bucket(x.Date, daily))
.OrderBy(g => g.Key)
.Select(g => new TrendPoint(g.Key, g.Sum(x => x.Sales), g.Sum(x => x.Returns)))
.ToList();
static NamedAmount Map(GroupAggregate g) => new(g.Key, g.Name, g.Amount);
return new SalesSummaryResponse(
f.From, f.To, daily ? "day" : "month",
new SalesKpiComparison(ToKpis(current), ToKpis(previous), prev.From, prev.To),
trend,
(await repo.GetSalesByCategoryAsync(f)).Select(Map).ToList(),
(await repo.GetSalesByRegionAsync(f)).Select(Map).ToList(),
(await repo.GetSalesByRepAsync(f)).Select(Map).ToList());
}
public async Task<InventorySummaryResponse> GetInventorySummaryAsync(DashboardFilter f)
{
var daily = Daily(f);
var movements = (await repo.GetDailyMovementsAsync(f))
.GroupBy(m => Bucket(m.Date, daily))
.OrderBy(g => g.Key)
.Select(g =>
{
var rec = g.Sum(x => x.Received);
var iss = g.Sum(x => x.Issued);
return new MovementPoint(g.Key, rec, iss, rec - iss);
}).ToList();
return new InventorySummaryResponse(
f.From, f.To, daily ? "day" : "month",
await repo.GetStockValueByCategoryAsync(f),
await repo.GetStockByWarehouseAsync(f),
movements);
}
}
@@ -0,0 +1,16 @@
namespace Distribution.Features.DashboardReport;
public record GroupAggregate(Guid? Key, string Name, decimal Amount);
public interface IDashboardReportRepository
{
Task<SalesTotals> GetSalesTotalsAsync(DashboardFilter f);
Task<List<PeriodAmount>> GetDailySalesAsync(DashboardFilter f);
Task<List<PeriodAmount>> GetDailyReturnsAsync(DashboardFilter f);
Task<List<GroupAggregate>> GetSalesByCategoryAsync(DashboardFilter f);
Task<List<GroupAggregate>> GetSalesByRegionAsync(DashboardFilter f);
Task<List<GroupAggregate>> GetSalesByRepAsync(DashboardFilter f);
Task<List<CategoryValueRow>> GetStockValueByCategoryAsync(DashboardFilter f);
Task<List<WarehouseStockRow>> GetStockByWarehouseAsync(DashboardFilter f);
Task<List<(DateTime Date, decimal Received, decimal Issued)>> GetDailyMovementsAsync(DashboardFilter f);
}
@@ -1,3 +1,4 @@
using Distribution.Infra.Authorization;
using Distribution.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -6,7 +7,7 @@ namespace Distribution.Features.DeliveryRide;
[ApiController]
[Route("api")]
public class DeliveryTaskController(DeliveryTaskService service) : ControllerBase
public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserService currentUser) : ControllerBase
{
[HttpPost("rides/{rideId:guid}/tasks/generate")]
[Authorize("delivery-task.manage")]
@@ -68,7 +69,7 @@ public class DeliveryTaskController(DeliveryTaskService service) : ControllerBas
{
try
{
var result = await service.ConfirmAsync(id, ct);
var result = await service.ConfirmAsync(id, currentUser.UserId ?? Guid.Empty, ct);
return Ok(result);
}
catch (KeyNotFoundException) { return NotFound(); }
@@ -12,7 +12,7 @@ public class DeliveryTaskService(
IRideRepository rideRepo,
IShopPORepository shopPORepo,
ILoadingSheetRepository loadingSheetRepo,
IStockRepository stockRepo,
StockMover mover,
IUnitOfWork uow)
{
private static DeliveryTaskResponse ToResponse(DeliveryTask t) =>
@@ -134,7 +134,7 @@ public class DeliveryTaskService(
return ToResponse(saved!);
}
public async Task<DeliveryTaskResponse> ConfirmAsync(Guid taskId, CancellationToken ct = default)
public async Task<DeliveryTaskResponse> ConfirmAsync(Guid taskId, Guid userId, CancellationToken ct = default)
{
var task = await repo.GetByIdAsync(taskId)
?? throw new KeyNotFoundException("Delivery task not found");
@@ -166,29 +166,12 @@ public class DeliveryTaskService(
{
foreach (var item in task.ShopPO.Items.Where(i => i.QuantityFulfilled > 0))
{
var vehicleBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId);
var availableQuantity = vehicleBalance?.Quantity ?? 0;
var itemName = item.Item?.Name ?? item.FK_ItemId.ToString();
if (availableQuantity < item.QuantityFulfilled)
throw new InvalidOperationException(
$"Insufficient vehicle stock for item '{itemName}': available {availableQuantity}, required {item.QuantityFulfilled}");
vehicleBalance!.Quantity -= item.QuantityFulfilled;
vehicleBalance.UpdatedAt = DateTime.Now;
stockRepo.UpdateBalance(vehicleBalance);
await stockRepo.AddMovementAsync(new StockMovement
{
FK_ItemId = item.FK_ItemId,
FromLocationId = vehicleId,
FromLocationType = LocationType.Vehicle,
ToLocationId = StockService.ExternalLocationId,
ToLocationType = LocationType.External,
Quantity = item.QuantityFulfilled,
Type = MovementType.Sale,
ReferenceId = task.FK_ShopPOId
});
await mover.MoveAsync(item.FK_ItemId, itemName,
vehicleId, LocationType.Vehicle,
StockService.ExternalLocationId, LocationType.External,
item.QuantityFulfilled, MovementType.Sale, task.FK_ShopPOId, userId);
}
repo.Update(task);
@@ -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;
/// <summary>
/// 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.
/// </summary>
[ApiController]
[Route("api/reports/finance")]
public class FinanceReportController(IGeneralLedgerClient gl) : ControllerBase
{
/// <summary>Active GL accounts (code + name) for the report filters' account dropdown.</summary>
[HttpGet("accounts")]
[Authorize("finance-reports.view")]
public async Task<IActionResult> 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<IActionResult> 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");
}
}
@@ -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<bool> IsHealthyAsync(CancellationToken ct = default);
Task<GeneralLedgerResult> SendAsync(HttpMethod method, string path, object? payload = null, CancellationToken ct = default);
}
/// <summary>
/// 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).
/// <paramref name="path"/> 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.
/// </summary>
public class GeneralLedgerClient(HttpClient http) : IGeneralLedgerClient
{
public async Task<bool> IsHealthyAsync(CancellationToken ct = default)
{
try
{
using var response = await http.GetAsync("/health", ct);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}
public async Task<GeneralLedgerResult> 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<JsonElement>(cancellationToken: ct); }
catch (JsonException) { }
}
return new GeneralLedgerResult((int)response.StatusCode, response.IsSuccessStatusCode, body);
}
}
@@ -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<OpeningBalanceLine> 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<IActionResult> Status()
{
var mapping = new Dictionary<string, string?>();
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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> Retry(CancellationToken ct) =>
Ok(new { posted = await posting.RetryFailedAsync(ct) });
}
@@ -0,0 +1,33 @@
using System.Text.Json;
namespace Distribution.Features.GeneralLedger;
/// <summary>Small readers for the GL service's ApiResponse envelope ({statusCode, success, message, data}).</summary>
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;
/// <summary>Case-insensitive property lookup (GL success bodies are camelCase, its error bodies PascalCase).</summary>
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;
}
@@ -0,0 +1,34 @@
namespace Distribution.Features.GeneralLedger;
/// <summary>SysConfig keys holding the GL account codes Distribution posts to (created by GlSetupService).</summary>
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);
}
@@ -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);
/// <summary>What closing a financial year would post. Closable is false with a Reason when it cannot be closed.</summary>
public record YearEndPreview(int Year, string StartDate, string EndDate, decimal TotalIncome, decimal TotalExpenses,
decimal NetProfit, bool Closable, string? Reason, bool AlreadyClosed);
/// <summary>
/// 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.
/// </summary>
public record OpeningBalanceLine(string Account, Guid? BankAccountId, decimal Amount);
/// <summary>
/// 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.
/// </summary>
public class GlPostingService(
IGeneralLedgerClient gl,
ISysConfigRepository configRepo,
GlSetupService setup,
AppDBContext context,
IUnitOfWork uow,
ILogger<GlPostingService> logger)
{
private record Payload(string EntryDate, string JournalType, string SourceModule, string Reference, string Narration,
string PreparedBy, List<PayloadLine> 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);
/// <summary>Deposit / Return / Cancel of a received cheque that came from a customer payment.</summary>
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<GlLine> { 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);
/// <summary>Clear / Bounce / Cancel of a cheque issued through a supplier payment.</summary>
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);
}
/// <summary>
/// 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.
/// </summary>
public async Task<GlPostingLog> PostOpeningBalanceAsync(DateOnly date, string? narration, List<OpeningBalanceLine> 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<GlLine>();
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<List<GlLine>?>(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.");
}
/// <summary>Accounts that can carry an opening balance (besides individual cash/bank accounts) and their normal side.</summary>
public static readonly Dictionary<string, (string Key, bool DebitNormal)> 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}")));
/// <summary>Years that have a posted closing journal.</summary>
public async Task<List<int>> 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<GlLine> Lines, decimal Income, decimal Expenses);
/// <summary>Reads the year's own P&amp;L from GL (which excludes earlier closing journals) and builds the closing lines.</summary>
private async Task<CloseBuild> 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<string, decimal>();
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<YearEndPreview> 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);
}
/// <summary>Posts the closing journal for the year (income/expense into Retained Earnings), then locks the year's periods.</summary>
public async Task<object> 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<List<GlLine>?>(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<int> LockYearPeriodsAsync(int year, CancellationToken ct)
{
var ids = new HashSet<long>();
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 ────────────────────────────────────────────────────────────────
/// <summary>Re-sends every Failed/Pending posting. Returns how many are now Posted.</summary>
public async Task<int> 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<Task<List<GlLine>?>> 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<JsonElement>(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<Dictionary<string, JsonElement>>(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<string> 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<string> 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 };
}
@@ -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<string> Actions);
/// <summary>
/// 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).
/// </summary>
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<GlSetupResult> 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<string>();
var codes = new Dictionary<string, string>();
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<string, object?>();
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<string, string> { [GlKeys.SetupDone] = "true" }, userId, ct);
return new GlSetupResult(true, actions);
}
/// <summary>
/// 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.
/// </summary>
public async Task<string?> 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<string, string> 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<long?> 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<string, object?> 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;
}
}
+4 -1
View File
@@ -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!);
}
}
@@ -10,6 +10,7 @@ public record PrintInvoiceRequest(Guid? PrintedByUserId = null);
public record InvoiceResponse(
Guid Id,
Guid ShopPOId,
Guid StoreId,
string StoreName,
InvoiceStatus Status,
PaymentStatus PaymentStatus,
@@ -19,6 +20,7 @@ public record InvoiceResponse(
DateTime? FinalizedAt,
Guid? FinalizedBy,
decimal TotalAmount,
decimal PaidAmount,
bool IsActive,
DateTime CreatedAt);
@@ -21,6 +21,7 @@ public class InvoiceRepository(AppDBContext context) : IInvoiceRepository
public async Task<List<Models.Invoice>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus, int page, int pageSize) =>
await context.Invoices
.Include(i => i.ShopPO).ThenInclude(p => p.Store)
.Include(i => i.Payments.Where(p => p.IsActive))
.Where(i => i.IsActive
&& (agentId == null || i.ShopPO.RaisedById == agentId)
&& (routeId == null || i.ShopPO.Store.FK_RouteId == routeId)
@@ -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,15 +9,19 @@ 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,
i.ShopPO?.FK_StoreId ?? Guid.Empty,
i.ShopPO?.Store?.StoreName ?? string.Empty,
i.Status, i.PaymentStatus, i.CreatedBy,
i.PrintedAt, i.PrintedBy,
i.FinalizedAt, i.FinalizedBy,
i.TotalAmount, i.IsActive, i.CreatedAt);
i.TotalAmount,
i.Payments?.Where(p => p.IsActive).Sum(p => p.Amount) ?? 0m,
i.IsActive, i.CreatedAt);
public async Task<InvoiceResponse?> GetByIdAsync(Guid id)
{
@@ -93,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);
}
}
@@ -8,7 +8,7 @@ namespace Distribution.Features.LoadingEvent;
public class LoadingSheetService(
ILoadingSheetRepository repo,
IRideRepository rideRepo,
IStockRepository stockRepo,
StockMover mover,
IUnitOfWork uow)
{
public async Task<PagedResponse<LoadingSheetResponse>> GetAllAsync(Guid? rideId, LoadingSheetStatus? status, Guid? routeId, Guid? regionId, int page, int pageSize)
@@ -154,59 +154,14 @@ public class LoadingSheetService(
private async Task ApplyLoadMovementsAsync(Guid sheetId, Guid vehicleId, List<LoadingSheetItem> items, Guid userId)
{
// Vehicle balances created during this confirm are only tracked, not yet saved, so a
// later row for the same item would not find them via the repository and would try to
// insert a duplicate (item, vehicle) balance.
var createdVehicleBalances = new Dictionary<Guid, StockBalance>();
foreach (var item in items.Where(i => i.QuantityDispatched > 0))
{
var warehouseBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, item.FK_WarehouseId)
?? throw new InvalidOperationException(
$"No stock found for '{item.Item?.Name ?? item.FK_ItemId.ToString()}' in warehouse '{item.Warehouse?.Name ?? item.FK_WarehouseId.ToString()}'");
var label = $"{item.Item?.Name ?? item.FK_ItemId.ToString()} (warehouse {item.Warehouse?.Name ?? item.FK_WarehouseId.ToString()})";
if (warehouseBalance.Quantity < item.QuantityDispatched)
throw new InvalidOperationException(
$"Insufficient stock for '{item.Item?.Name ?? item.FK_ItemId.ToString()}' in warehouse '{item.Warehouse?.Name ?? item.FK_WarehouseId.ToString()}' " +
$"(available {warehouseBalance.Quantity}, needed {item.QuantityDispatched})");
warehouseBalance.Quantity -= item.QuantityDispatched;
warehouseBalance.UpdatedAt = DateTime.Now;
stockRepo.UpdateBalance(warehouseBalance);
var vehicleBalance = createdVehicleBalances.GetValueOrDefault(item.FK_ItemId)
?? await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId);
if (vehicleBalance is null)
{
var created = new StockBalance
{
FK_ItemId = item.FK_ItemId,
LocationId = vehicleId,
LocationType = LocationType.Vehicle,
Quantity = item.QuantityDispatched
};
createdVehicleBalances[item.FK_ItemId] = created;
await stockRepo.AddBalanceAsync(created);
}
else
{
vehicleBalance.Quantity += item.QuantityDispatched;
vehicleBalance.UpdatedAt = DateTime.Now;
stockRepo.UpdateBalance(vehicleBalance);
}
await stockRepo.AddMovementAsync(new StockMovement
{
FK_ItemId = item.FK_ItemId,
FromLocationId = item.FK_WarehouseId,
FromLocationType = LocationType.Warehouse,
ToLocationId = vehicleId,
ToLocationType = LocationType.Vehicle,
Quantity = item.QuantityDispatched,
Type = MovementType.Load,
ReferenceId = sheetId,
CreatedBy = userId
});
await mover.MoveAsync(item.FK_ItemId, label,
item.FK_WarehouseId, LocationType.Warehouse,
vehicleId, LocationType.Vehicle,
item.QuantityDispatched, MovementType.Load, sheetId, userId);
}
}
+9 -1
View File
@@ -1,4 +1,4 @@
using Distribution.Models;
using Distribution.Models;
namespace Distribution.Features.Payment;
@@ -6,8 +6,14 @@ public record CreatePaymentRequest(
decimal Amount,
PaymentMethod Method,
EntryMode EntryMode = EntryMode.RealTime,
// Cash: required. Cheque: optional (used as the deposit account hint if provided).
Guid? BankAccountId = null,
// Cheque: required.
string? ChequeNumber = null,
DateTime? ChequeDate = null,
// Cheque: optional metadata forwarded to the auto-created ReceivedCheque.
string? DrawerBankName = null,
string? AccountHolderName = null,
Guid? CollectedByUserId = null);
public record PaymentResponse(
@@ -16,6 +22,8 @@ public record PaymentResponse(
decimal Amount,
PaymentMethod Method,
EntryMode EntryMode,
Guid? BankAccountId,
Guid? ReceivedChequeId,
string? ChequeNumber,
DateTime? ChequeDate,
DateTime CollectedAt,
@@ -1,4 +1,8 @@
using Distribution.Features.Invoice;
using Distribution.Features.BankAccount;
using Distribution.Features.GeneralLedger;
using Distribution.Features.Invoice;
using Distribution.Features.ReceivedCheque;
using Distribution.Features.StoreCredit;
using Distribution.Infra.Authorization;
using Distribution.Infra.UoW;
using Distribution.Models;
@@ -8,11 +12,17 @@ namespace Distribution.Features.Payment;
public class PaymentService(
IPaymentRepository repo,
IInvoiceRepository invoiceRepo,
IBankAccountRepository bankAccountRepo,
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, p.ChequeNumber, p.ChequeDate,
new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.EntryMode,
p.FK_BankAccountId, p.FK_ReceivedChequeId,
p.ChequeNumber, p.ChequeDate,
p.CollectedAt, p.CollectedBy, p.RecordedBy, p.CreatedAt);
public async Task<List<PaymentResponse>?> GetByInvoiceIdAsync(Guid invoiceId)
@@ -28,13 +38,46 @@ public class PaymentService(
{
var invoice = await invoiceRepo.GetByIdAsync(invoiceId);
if (invoice is null || !invoice.IsActive) return null;
if (invoice.Status == InvoiceStatus.Finalized)
throw new InvalidOperationException("Cannot record a payment against a finalized invoice");
if (request.Amount <= 0)
throw new InvalidOperationException("Amount must be greater than zero");
var existingPaid = (await repo.GetByInvoiceIdAsync(invoiceId)).Sum(p => p.Amount);
var remaining = invoice.TotalAmount - existingPaid;
if (request.Amount > remaining)
throw new InvalidOperationException(
$"Amount exceeds the outstanding balance of {remaining:0.##}");
if (request.Method == PaymentMethod.Cheque &&
(string.IsNullOrWhiteSpace(request.ChequeNumber) || request.ChequeDate is null))
throw new InvalidOperationException("ChequeNumber and ChequeDate are required when Method is Cheque");
if (request.Method == PaymentMethod.Cash && request.BankAccountId is null)
throw new InvalidOperationException("BankAccountId is required when Method is Cash");
if (request.Method == PaymentMethod.Credit)
{
var store = invoice.ShopPO?.Store
?? throw new InvalidOperationException("Invoice is missing the owning store; cannot record a credit payment");
var currentOutstanding = await creditRepo.GetOutstandingForStoreAsync(store.Id);
if (currentOutstanding + request.Amount > store.CreditLimit)
{
var available = store.CreditLimit - currentOutstanding;
throw new InvalidOperationException(
$"Credit limit exceeded. Available credit: {available:0.##}");
}
}
Models.BankAccount? bankAccount = null;
if (request.BankAccountId is not null)
{
bankAccount = await bankAccountRepo.GetByIdAsync(request.BankAccountId.Value);
if (bankAccount is null)
throw new InvalidOperationException("BankAccountId does not reference a valid account");
if (bankAccount.IsActive is false)
throw new InvalidOperationException("The selected account is inactive");
}
var recorderId = currentUser.UserId ?? request.CollectedByUserId
?? throw new InvalidOperationException("CollectedByUserId is required while auth is unavailable");
var collectedBy = request.CollectedByUserId ?? recorderId;
@@ -50,6 +93,7 @@ public class PaymentService(
EntryMode = request.EntryMode,
ChequeNumber = request.Method == PaymentMethod.Cheque ? request.ChequeNumber : null,
ChequeDate = request.Method == PaymentMethod.Cheque ? request.ChequeDate : null,
FK_BankAccountId = request.BankAccountId,
CollectedAt = DateTime.Now,
CollectedBy = collectedBy,
RecordedBy = recordedBy
@@ -58,11 +102,48 @@ public class PaymentService(
await uow.BeginAsync(ct);
try
{
// Auto-create a ReceivedCheque for the physical cheque. Status starts at Received;
// bookkeeping of the deposit/clear transitions happens through the Received Cheques screen.
if (request.Method == PaymentMethod.Cheque)
{
var cheque = new Models.ReceivedCheque
{
CompanyId = null,
BranchId = null,
ReceivedFromType = ReceivedFromType.Customer,
ReceivedFromId = null,
ReceivedFromName = $"Invoice {invoice.Id}",
DrawerBankName = request.DrawerBankName,
AccountHolderName = request.AccountHolderName,
ChequeNo = request.ChequeNumber!,
ChequeDate = request.ChequeDate!.Value,
Amount = request.Amount,
ReceivedDate = DateTime.Now,
ReferenceType = "Invoice",
Status = ReceivedChequeStatus.Received
};
await receivedChequeRepo.AddAsync(cheque);
payment.FK_ReceivedChequeId = cheque.Id;
}
await repo.AddAsync(payment);
var allPayments = await repo.GetByInvoiceIdAsync(invoiceId);
var total = allPayments.Sum(p => p.Amount) + request.Amount;
if (request.Method == PaymentMethod.Credit)
{
var movement = new StoreCreditMovement
{
FK_StoreId = invoice.ShopPO!.Store!.Id,
Direction = StoreCreditMovementDirection.Usage,
Amount = request.Amount,
FK_InvoiceId = invoice.Id,
FK_PaymentId = payment.Id,
OccurredAt = DateTime.Now,
RecordedBy = recorderId
};
await creditRepo.AddAsync(movement);
}
var total = existingPaid + request.Amount;
invoice.PaymentStatus = total == 0
? PaymentStatus.Pending
: total < invoice.TotalAmount
@@ -70,10 +151,14 @@ public class PaymentService(
: PaymentStatus.Paid;
invoiceRepo.Update(invoice);
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);
}
}
@@ -10,7 +10,7 @@ public class PurchaseReturnService(
IPurchaseReturnRepository repo,
IPoRepository poRepo,
IGrnRepository goodsReceiptRepo,
IStockRepository stockRepo,
StockMover mover,
IUnitOfWork uow)
{
private static PurchaseReturnItemResponse MapItem(Models.PurchaseReturnItem i) =>
@@ -89,29 +89,12 @@ public class PurchaseReturnService(
foreach (var line in purchaseReturn.Items)
{
var balance = await stockRepo.GetBalanceAsync(line.FK_ItemId, warehouseId)
?? throw new InvalidOperationException($"No stock balance found for item {line.FK_ItemId} at the receiving warehouse.");
var itemName = po.Items.FirstOrDefault(i => i.FK_ItemId == line.FK_ItemId)?.Item?.Name ?? line.FK_ItemId.ToString();
if (balance.Quantity < line.Quantity)
throw new InvalidOperationException($"Insufficient warehouse stock for item {line.FK_ItemId} to return.");
balance.Quantity -= line.Quantity;
balance.UpdatedAt = DateTime.Now;
stockRepo.UpdateBalance(balance);
await stockRepo.AddMovementAsync(new StockMovement
{
FK_ItemId = line.FK_ItemId,
FromLocationId = warehouseId,
FromLocationType = LocationType.Warehouse,
ToLocationId = StockService.ExternalLocationId,
ToLocationType = LocationType.External,
Quantity = line.Quantity,
Type = MovementType.PurchaseReturn,
ReasonCode = request.Reason,
ReferenceId = purchaseReturn.Id,
CreatedBy = createdByUserId
});
await mover.MoveAsync(line.FK_ItemId, itemName,
warehouseId, LocationType.Warehouse,
StockService.ExternalLocationId, LocationType.External,
line.Quantity, MovementType.PurchaseReturn, purchaseReturn.Id, createdByUserId, request.Reason);
}
await uow.CommitAsync(ct);
@@ -0,0 +1,13 @@
using Distribution.Models;
namespace Distribution.Features.ReceivedCheque;
public interface IReceivedChequeRepository
{
Task<Models.ReceivedCheque?> GetByIdAsync(Guid id);
Task<(List<Models.ReceivedCheque> Items, int TotalCount)> ListAsync(
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
int page, int pageSize);
Task AddAsync(Models.ReceivedCheque cheque);
void Update(Models.ReceivedCheque cheque);
}
@@ -0,0 +1,37 @@
using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.ReceivedCheque;
[ApiController]
[Route("api/received-cheques")]
public class ReceivedChequeController(ReceivedChequeService service) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> List(
[FromQuery] int? companyId,
[FromQuery] int? branchId,
[FromQuery] ReceivedChequeStatus? status,
[FromQuery] ReceivedFromType? receivedFromType,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
=> Ok(await service.ListAsync(companyId, branchId, status, receivedFromType, page, pageSize));
[HttpPost]
public async Task<IActionResult> Create(CreateReceivedChequeRequest request, CancellationToken ct)
{
try { return Ok(await service.CreateAsync(request, ct)); }
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
[HttpPut("{id:guid}/status")]
public async Task<IActionResult> UpdateStatus(Guid id, UpdateReceivedChequeStatusRequest request, CancellationToken ct)
{
try
{
var result = await service.UpdateStatusAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
}
@@ -0,0 +1,51 @@
using Distribution.Features.ChequeBook;
using Distribution.Models;
namespace Distribution.Features.ReceivedCheque;
public record ReceivedChequeResponse(
Guid ReceivedChequeId,
int? CompanyId,
int? BranchId,
ReceivedFromType ReceivedFromType,
int? ReceivedFromId,
string ReceivedFromName,
string? DrawerBankName,
string? DrawerBankBranch,
string? AccountHolderName,
string ChequeNo,
DateTime ChequeDate,
decimal Amount,
DateTime ReceivedDate,
string? ReferenceType,
int? ReferenceId,
string? Notes,
string? CreatedBy,
ReceivedChequeStatus Status,
Guid? DepositBankAccountId,
DateTime? DepositDate);
public record CreateReceivedChequeRequest(
int CompanyId,
int? BranchId,
ReceivedFromType ReceivedFromType,
int? ReceivedFromId,
string ReceivedFromName,
string? DrawerBankName,
string? DrawerBankBranch,
string? AccountHolderName,
string ChequeNo,
DateTime ChequeDate,
decimal Amount,
DateTime ReceivedDate,
string? ReferenceType,
int? ReferenceId,
string? Notes,
string? CreatedBy);
public record UpdateReceivedChequeStatusRequest(
ReceivedChequeStatusAction Action,
Guid? DepositBankAccountId,
DateTime? DepositDate,
string? Notes,
string? PerformedBy);
@@ -0,0 +1,34 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.ReceivedCheque;
public class ReceivedChequeRepository(AppDBContext context) : IReceivedChequeRepository
{
public async Task<Models.ReceivedCheque?> GetByIdAsync(Guid id) =>
await context.ReceivedCheques.FirstOrDefaultAsync(c => c.Id == id);
public async Task<(List<Models.ReceivedCheque> Items, int TotalCount)> ListAsync(
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
int page, int pageSize)
{
var query = context.ReceivedCheques.AsQueryable();
if (companyId is not null) query = query.Where(c => c.CompanyId == companyId);
if (branchId is not null) query = query.Where(c => c.BranchId == branchId);
if (status is not null) query = query.Where(c => c.Status == status);
if (receivedFromType is not null) query = query.Where(c => c.ReceivedFromType == receivedFromType);
var total = await query.CountAsync();
var items = await query
.OrderByDescending(c => c.ReceivedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (items, total);
}
public async Task AddAsync(Models.ReceivedCheque cheque) => await context.ReceivedCheques.AddAsync(cheque);
public void Update(Models.ReceivedCheque cheque) => context.ReceivedCheques.Update(cheque);
}
@@ -0,0 +1,118 @@
using Distribution.Features.BankAccount;
using Distribution.Features.ChequeBook;
using Distribution.Features.GeneralLedger;
using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.ReceivedCheque;
public class ReceivedChequeService(
IReceivedChequeRepository repo,
IBankAccountRepository bankAccountRepo,
IUnitOfWork uow,
GlPostingService gl)
{
private static ReceivedChequeResponse ToResponse(Models.ReceivedCheque c) =>
new(c.Id, c.CompanyId, c.BranchId, c.ReceivedFromType, c.ReceivedFromId, c.ReceivedFromName,
c.DrawerBankName, c.DrawerBankBranch, c.AccountHolderName,
c.ChequeNo, c.ChequeDate, c.Amount, c.ReceivedDate,
c.ReferenceType, c.ReferenceId, c.Notes, c.CreatedBy,
c.Status, c.FK_DepositBankAccountId, c.DepositDate);
public async Task<PagedResult<ReceivedChequeResponse>> ListAsync(
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
int page, int pageSize)
{
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 200) pageSize = 50;
var (items, total) = await repo.ListAsync(companyId, branchId, status, receivedFromType, page, pageSize);
return new PagedResult<ReceivedChequeResponse>(
items.Select(ToResponse).ToList(), total, page, pageSize);
}
public async Task<ReceivedChequeResponse> CreateAsync(CreateReceivedChequeRequest request, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(request.ReceivedFromName))
throw new InvalidOperationException("ReceivedFromName is required");
if (string.IsNullOrWhiteSpace(request.ChequeNo))
throw new InvalidOperationException("ChequeNo is required");
if (request.Amount <= 0)
throw new InvalidOperationException("Amount must be greater than zero");
var cheque = new Models.ReceivedCheque
{
CompanyId = request.CompanyId,
BranchId = request.BranchId,
ReceivedFromType = request.ReceivedFromType,
ReceivedFromId = request.ReceivedFromId,
ReceivedFromName = request.ReceivedFromName,
DrawerBankName = request.DrawerBankName,
DrawerBankBranch = request.DrawerBankBranch,
AccountHolderName = request.AccountHolderName,
ChequeNo = request.ChequeNo,
ChequeDate = request.ChequeDate,
Amount = request.Amount,
ReceivedDate = request.ReceivedDate,
ReferenceType = request.ReferenceType,
ReferenceId = request.ReferenceId,
Notes = request.Notes,
CreatedBy = request.CreatedBy,
Status = ReceivedChequeStatus.Received
};
await repo.AddAsync(cheque);
await uow.CommitAsync(ct);
return ToResponse(cheque);
}
public async Task<ReceivedChequeResponse?> UpdateStatusAsync(Guid id, UpdateReceivedChequeStatusRequest request, CancellationToken ct = default)
{
var cheque = await repo.GetByIdAsync(id);
if (cheque is null) return null;
switch (request.Action)
{
case ReceivedChequeStatusAction.Deposit:
if (cheque.Status != ReceivedChequeStatus.Received)
throw new InvalidOperationException("Only received cheques can be deposited");
if (request.DepositBankAccountId is null)
throw new InvalidOperationException("DepositBankAccountId is required when depositing");
if (request.DepositDate is null)
throw new InvalidOperationException("DepositDate is required when depositing");
var bankAccount = await bankAccountRepo.GetByIdAsync(request.DepositBankAccountId.Value);
if (bankAccount is null || bankAccount.AccountType != BankAccountType.Bank)
throw new InvalidOperationException("DepositBankAccountId does not reference a valid bank account");
cheque.Status = ReceivedChequeStatus.Deposited;
cheque.FK_DepositBankAccountId = request.DepositBankAccountId;
cheque.DepositDate = request.DepositDate;
break;
case ReceivedChequeStatusAction.Clear:
if (cheque.Status != ReceivedChequeStatus.Deposited)
throw new InvalidOperationException("Only deposited cheques can be cleared");
cheque.Status = ReceivedChequeStatus.Cleared;
break;
case ReceivedChequeStatusAction.Return:
if (cheque.Status != ReceivedChequeStatus.Deposited)
throw new InvalidOperationException("Only deposited cheques can be returned");
cheque.Status = ReceivedChequeStatus.Returned;
break;
case ReceivedChequeStatusAction.Cancel:
if (cheque.Status != ReceivedChequeStatus.Received)
throw new InvalidOperationException("Only received cheques can be cancelled");
cheque.Status = ReceivedChequeStatus.Cancelled;
break;
}
if (!string.IsNullOrWhiteSpace(request.Notes))
cheque.Notes = request.Notes;
repo.Update(cheque);
await uow.CommitAsync(ct);
await gl.PostReceivedChequeAsync(cheque, request.Action, ct);
return ToResponse(cheque);
}
}
@@ -48,7 +48,7 @@ public class ReturnController(ReturnService service, ICurrentUserService current
var result = await service.CreateAsync(id, request, userId.Value, ct);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
catch (NotImplementedException ex) { return StatusCode(501, new { error = ex.Message }); }
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
}
}
+71 -3
View File
@@ -1,3 +1,4 @@
using Distribution.Features.Category;
using Distribution.Features.Item;
using Distribution.Features.ShopPO;
using Distribution.Features.Stock;
@@ -10,7 +11,8 @@ public class ReturnService(
IReturnRepository repo,
IShopPORepository shopPORepo,
IItemRepository itemRepo,
IStockRepository stockRepo,
ICategoryRepository categoryRepo,
StockMover mover,
IUnitOfWork uow)
{
private static SaleReturnItemResponse MapItem(SaleReturnItem i) =>
@@ -57,6 +59,72 @@ public class ReturnService(
public async Task<List<SaleReturnResponse>> GetByShopPOIdAsync(Guid shopPOId) =>
(await repo.GetByShopPOIdAsync(shopPOId)).Select(Map).ToList();
public Task<SaleReturnResponse> CreateAsync(Guid shopPOId, CreateSaleReturnRequest request, Guid createdByUserId, CancellationToken ct = default) =>
throw new NotImplementedException("Return flow is being redesigned — not yet implemented.");
/// <summary>
/// Stock-only: records the return and puts the goods back on the vehicle that delivered them (restoring the
/// batches/serials that were sold). Credit/exchange money handling is not applied here, so TotalCreditAmount stays 0.
/// </summary>
public async Task<SaleReturnResponse> CreateAsync(Guid shopPOId, CreateSaleReturnRequest request, Guid createdByUserId, CancellationToken ct = default)
{
var shopPO = await shopPORepo.GetByIdAsync(shopPOId);
if (shopPO is null || !shopPO.IsActive)
throw new InvalidOperationException("Order not found.");
if (request.Items is null || request.Items.Count == 0)
throw new InvalidOperationException("A return must have at least one line item.");
if (request.Items.GroupBy(i => i.ItemId).Any(g => g.Count() > 1))
throw new InvalidOperationException("Each item can appear only once on a return.");
var saleReturn = new SaleReturn
{
FK_ShopPOId = shopPOId,
Mode = request.Mode,
TotalCreditAmount = 0,
CreatedByUserId = createdByUserId
};
foreach (var line in request.Items)
{
if (line.Quantity <= 0)
throw new InvalidOperationException("Return quantity must be greater than zero.");
var shopPOItem = shopPO.Items.FirstOrDefault(i => i.FK_ItemId == line.ItemId && i.IsActive)
?? throw new InvalidOperationException($"Item {line.ItemId} is not on this order.");
var item = await itemRepo.GetByIdAsync(line.ItemId)
?? throw new InvalidOperationException("Item not found.");
var category = await categoryRepo.GetByIdAsync(item.FK_CategoryId)
?? throw new InvalidOperationException("Category not found.");
if (!category.AllowCustomerReturn)
throw new InvalidOperationException($"Customer returns are not allowed for category '{category.Name}'.");
saleReturn.Items.Add(new SaleReturnItem
{
FK_ItemId = line.ItemId,
Quantity = line.Quantity,
ReasonCode = line.ReasonCode,
UnitPrice = shopPOItem.UnitPrice
});
}
await uow.BeginAsync(ct);
try
{
await repo.AddAsync(saleReturn);
foreach (var line in saleReturn.Items)
{
var itemName = shopPO.Items.First(i => i.FK_ItemId == line.FK_ItemId).Item?.Name ?? line.FK_ItemId.ToString();
await mover.ReturnToVehicleAsync(line.FK_ItemId, itemName, line.Quantity, shopPOId, createdByUserId, line.ReasonCode.ToString());
}
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
var saved = await repo.GetByIdAsync(saleReturn.Id);
return Map(saved!);
}
}
+4 -3
View File
@@ -1,11 +1,12 @@
using Distribution.Models;
using Distribution.Infra.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Ride;
[ApiController]
[Route("api/rides")]
public class RideController(RideService service) : ControllerBase
public class RideController(RideService service, ICurrentUserService currentUser) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAll(
@@ -54,7 +55,7 @@ public class RideController(RideService service) : ControllerBase
{
try
{
var result = await service.CloseAsync(id, request, ct);
var result = await service.CloseAsync(id, request, currentUser.UserId ?? Guid.Empty, ct);
return Ok(result);
}
catch (KeyNotFoundException) { return NotFound(); }
@@ -66,7 +67,7 @@ public class RideController(RideService service) : ControllerBase
{
try
{
var result = await service.CancelAsync(id, request, ct);
var result = await service.CancelAsync(id, request, currentUser.UserId ?? Guid.Empty, ct);
return Ok(result);
}
catch (KeyNotFoundException) { return NotFound(); }
+16 -14
View File
@@ -10,7 +10,7 @@ public class RideService(
IRideRepository rideRepo,
IDriverProfileRepository driverRepo,
IDriverComplianceConfigRepository complianceRepo,
IStockRepository stockRepo,
StockMover mover,
IUnitOfWork uow)
{
public async Task<PagedResponse<RideResponse>> GetAllAsync(RideStatus? status, Guid? driverProfileId, Guid? vehicleId, int page, int pageSize)
@@ -76,7 +76,7 @@ public class RideService(
return ToResponse(saved!);
}
public async Task<RideResponse> CloseAsync(Guid rideId, CloseRideRequest request, CancellationToken ct = default)
public async Task<RideResponse> CloseAsync(Guid rideId, CloseRideRequest request, Guid userId, CancellationToken ct = default)
{
var ride = await rideRepo.GetByIdAsync(rideId)
?? throw new KeyNotFoundException("Ride not found");
@@ -87,20 +87,24 @@ public class RideService(
if (request.Meterend < ride.Meterstart)
throw new InvalidOperationException($"Meter end ({request.Meterend}) cannot be less than meter start ({ride.Meterstart})");
await EnsureVehicleStockClearedAsync(ride.FK_VehicleId);
ride.Status = RideStatus.Closed;
ride.ClosedAt = DateTime.Now;
ride.Meterend = request.Meterend;
// Whatever wasn't delivered goes back to the warehouse it was loaded from.
await uow.BeginAsync(ct);
try { rideRepo.Update(ride); await uow.CommitAsync(ct); }
try
{
await mover.UnloadVehicleAsync(ride.FK_VehicleId, ride.Id, userId, "RideClosed");
rideRepo.Update(ride);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return ToResponse(ride);
}
public async Task<RideResponse> CancelAsync(Guid rideId, CancelRideRequest request, CancellationToken ct = default)
public async Task<RideResponse> CancelAsync(Guid rideId, CancelRideRequest request, Guid userId, CancellationToken ct = default)
{
var ride = await rideRepo.GetByIdAsync(rideId)
?? throw new KeyNotFoundException("Ride not found");
@@ -113,7 +117,12 @@ public class RideService(
ride.CancelReason = request.Reason;
await uow.BeginAsync(ct);
try { rideRepo.Update(ride); await uow.CommitAsync(ct); }
try
{
await mover.UnloadVehicleAsync(ride.FK_VehicleId, ride.Id, userId, "RideCancelled");
rideRepo.Update(ride);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return ToResponse(ride);
@@ -139,13 +148,6 @@ public class RideService(
throw new InvalidOperationException("Driver medical clearance has expired");
}
private async Task EnsureVehicleStockClearedAsync(Guid vehicleId)
{
var balances = await stockRepo.GetBalancesByLocationAsync(vehicleId, LocationType.Vehicle);
if (balances.Any(b => b.Quantity > 0))
throw new InvalidOperationException("All vehicle stock must be returned before this ride can be completed");
}
private static RideResponse ToResponse(DeliveryRun r) =>
new(r.Id, r.Status,
r.FK_VehicleId, r.Vehicle?.NumberPlate ?? string.Empty,
@@ -7,6 +7,8 @@ public interface IStockRepository
// Balance
Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId);
Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId);
/// <summary>Positive balances for the item at the location in pick order: batches by earliest expiry, serials oldest first, untracked last.</summary>
Task<List<StockBalance>> GetAllocatableBalancesAsync(Guid itemId, Guid locationId);
Task<List<StockBalance>> GetBalancesByLocationAsync(Guid locationId, LocationType locationType);
Task<List<StockBalance>> GetBalancesAsync(Guid? locationId, LocationType? locationType, Guid? itemId, int page, int pageSize);
Task<int> GetBalancesCountAsync(Guid? locationId, LocationType? locationType, Guid? itemId);
@@ -19,14 +21,21 @@ public interface IStockRepository
Task AddMovementAsync(StockMovement movement);
Task<StockMovement?> GetMovementByIdAsync(Guid id);
void UpdateMovement(StockMovement movement);
Task<List<StockMovement>> GetMovementsAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize);
Task<int> GetMovementsCountAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to);
Task<List<StockMovement>> GetMovementsAsync(MovementFilter filter, int page, int pageSize);
Task<int> GetMovementsCountAsync(MovementFilter filter);
/// <summary>Opening balance, quantity in/out within [from, toExclusive), closing balance, and the live balance to reconcile against.</summary>
Task<MovementSummary> GetMovementSummaryAsync(Guid itemId, Guid locationId, DateTime from, DateTime toExclusive);
Task<List<StockMovement>> GetMovementsByReferenceAsync(Guid referenceId, Guid itemId, MovementType type);
/// <summary>The warehouse the item most recently arrived at <paramref name="vehicleId"/> from, or null if it never did.</summary>
Task<Guid?> GetLastWarehouseSourceAsync(Guid vehicleId, Guid itemId);
// Batches — a null batchNumber always creates a fresh expiry-only lot (no dedup possible on null).
Task<ItemBatch?> FindBatchAsync(Guid itemId, string batchNumber);
Task AddItemBatchAsync(ItemBatch batch);
// Serial numbers
Task<ItemSerialNumber?> GetSerialAsync(Guid serialId);
Task<bool> SerialNumberExistsAsync(Guid itemId, string serialNumber);
/// <summary>One past the highest numeric suffix among the item's existing serials that start with <paramref name="prefix"/> (1 if none).</summary>
Task<int> GetNextSerialSequenceAsync(Guid itemId, string prefix);
+19 -1
View File
@@ -58,10 +58,28 @@ public class StockController(StockService service, ICurrentUserService currentUs
[FromQuery] MovementType? type,
[FromQuery] DateTime? from,
[FromQuery] DateTime? to,
[FromQuery] Guid? referenceId,
[FromQuery] Guid? batchId,
[FromQuery] Guid? serialId,
[FromQuery] Guid? createdBy,
[FromQuery] string? reasonCode,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var result = await service.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
var filter = new MovementFilter(locationId, itemId, type, from, to, referenceId, batchId, serialId, createdBy, reasonCode);
var result = await service.GetMovementsAsync(filter, page, pageSize);
return Ok(result);
}
/// <summary>Opening / in / out / closing quantity for one item at one location over a date range.</summary>
[HttpGet("movements/summary")]
public async Task<IActionResult> GetMovementSummary(
[FromQuery] Guid itemId,
[FromQuery] Guid locationId,
[FromQuery] DateTime from,
[FromQuery] DateTime to)
{
var result = await service.GetMovementSummaryAsync(itemId, locationId, from, to);
return Ok(result);
}
+26 -1
View File
@@ -45,7 +45,12 @@ public record StockMovementResponse(
DateTime CreatedAt,
Guid CreatedBy,
string? FromLocationName,
string? ToLocationName);
string? ToLocationName,
Guid? BatchId = null,
string? BatchNumber = null,
DateTime? BatchExpiryDate = null,
Guid? SerialId = null,
string? SerialNumber = null);
// Write requests
public record TransferRequest(
@@ -82,3 +87,23 @@ public record AdjustmentRequest(
// Movements are an immutable ledger — quantity, locations, and type can never change once recorded
// (the running stock balance is the sum of all movements). Only the free-text reason/note is editable.
public record UpdateMovementReasonRequest(string ReasonCode);
// Movement report filters
public record MovementFilter(
Guid? LocationId = null,
Guid? ItemId = null,
MovementType? Type = null,
DateTime? From = null,
DateTime? To = null,
Guid? ReferenceId = null,
Guid? BatchId = null,
Guid? SerialId = null,
Guid? CreatedBy = null,
string? ReasonCode = null);
public record MovementSummary(
decimal OpeningQuantity,
decimal QuantityIn,
decimal QuantityOut,
decimal ClosingQuantity,
decimal CurrentBalance);
+234
View File
@@ -0,0 +1,234 @@
using Distribution.Features.SysConfig;
using Distribution.Models;
namespace Distribution.Features.Stock;
/// <summary>
/// Applies a stock movement end to end: picks which batch/serial balances to take from (FEFO / FIFO),
/// updates the source and destination balances, keeps serial status/location in step, and writes one
/// StockMovement per slice so the ledger always carries the batch/serial that actually moved.
/// Tracks changes only — the calling service owns the unit of work.
/// </summary>
public class StockMover(IStockRepository repo, SysConfigService sysConfig)
{
private static readonly Guid External = StockService.ExternalLocationId;
private readonly record struct StockKey(Guid LocationId, Guid? BatchId, Guid? SerialId);
public async Task<List<StockMovement>> MoveAsync(
Guid itemId, string itemName,
Guid fromId, LocationType fromType,
Guid toId, LocationType toType,
decimal quantity, MovementType type,
Guid? referenceId, Guid userId, string? reasonCode = null)
{
if (quantity <= 0)
throw new InvalidOperationException("Quantity must be greater than zero.");
var balances = await repo.GetAllocatableBalancesAsync(itemId, fromId);
var available = balances.Sum(b => b.Quantity);
if (available < quantity)
throw new InvalidOperationException(
$"Insufficient stock for '{itemName}': available {available}, required {quantity}");
var created = new List<StockMovement>();
var remaining = quantity;
foreach (var balance in balances)
{
if (remaining <= 0) break;
var take = Math.Min(balance.Quantity, remaining);
balance.Quantity -= take;
balance.UpdatedAt = DateTime.Now;
repo.UpdateBalance(balance);
if (toType != LocationType.External)
await CreditAsync(itemId, toId, toType, balance.FK_ItemBatchId, balance.FK_ItemSerialNumberId, take);
if (balance.ItemSerialNumber is not null)
await ApplySerialAsync(balance.ItemSerialNumber, balance.Item, toId, toType, type);
var movement = new StockMovement
{
FK_ItemId = itemId,
FromLocationId = fromId,
FromLocationType = fromType,
ToLocationId = toId,
ToLocationType = toType,
FK_ItemBatchId = balance.FK_ItemBatchId,
FK_ItemSerialNumberId = balance.FK_ItemSerialNumberId,
Quantity = take,
Type = type,
ReasonCode = reasonCode,
ReferenceId = referenceId,
CreatedBy = userId
};
await repo.AddMovementAsync(movement);
created.Add(movement);
remaining -= take;
}
return created;
}
/// <summary>
/// Puts returned goods back on the vehicle that delivered them, restoring the same batches/serials that
/// were sold on that ShopPO. Quantity is capped at what was delivered and not yet returned.
/// </summary>
public async Task<List<StockMovement>> ReturnToVehicleAsync(
Guid itemId, string itemName, decimal quantity, Guid shopPoId, Guid userId, string? reasonCode = null)
{
if (quantity <= 0)
throw new InvalidOperationException("Quantity must be greater than zero.");
var sold = await repo.GetMovementsByReferenceAsync(shopPoId, itemId, MovementType.Sale);
if (sold.Count == 0)
throw new InvalidOperationException($"'{itemName}' was not delivered on this order, so it cannot be returned.");
var returned = await repo.GetMovementsByReferenceAsync(shopPoId, itemId, MovementType.CustomerReturn);
var outstanding = new Dictionary<StockKey, decimal>();
foreach (var m in sold)
{
var key = new StockKey(m.FromLocationId!.Value, m.FK_ItemBatchId, m.FK_ItemSerialNumberId);
outstanding[key] = outstanding.GetValueOrDefault(key) + m.Quantity;
}
foreach (var m in returned)
{
var key = new StockKey(m.ToLocationId!.Value, m.FK_ItemBatchId, m.FK_ItemSerialNumberId);
outstanding[key] = outstanding.GetValueOrDefault(key) - m.Quantity;
}
var returnable = outstanding.Where(kv => kv.Value > 0).Sum(kv => kv.Value);
if (quantity > returnable)
throw new InvalidOperationException(
$"Return quantity for '{itemName}' ({quantity}) exceeds the delivered-but-not-yet-returned quantity ({returnable}).");
var created = new List<StockMovement>();
var remaining = quantity;
foreach (var (key, qty) in outstanding.Where(kv => kv.Value > 0))
{
if (remaining <= 0) break;
var take = Math.Min(qty, remaining);
await CreditAsync(itemId, key.LocationId, LocationType.Vehicle, key.BatchId, key.SerialId, take);
if (key.SerialId is Guid serialId)
{
var serial = await repo.GetSerialAsync(serialId);
if (serial is not null)
await ApplySerialAsync(serial, null, key.LocationId, LocationType.Vehicle, MovementType.CustomerReturn);
}
var movement = new StockMovement
{
FK_ItemId = itemId,
FromLocationId = External,
FromLocationType = LocationType.External,
ToLocationId = key.LocationId,
ToLocationType = LocationType.Vehicle,
FK_ItemBatchId = key.BatchId,
FK_ItemSerialNumberId = key.SerialId,
Quantity = take,
Type = MovementType.CustomerReturn,
ReasonCode = reasonCode,
ReferenceId = shopPoId,
CreatedBy = userId
};
await repo.AddMovementAsync(movement);
created.Add(movement);
remaining -= take;
}
return created;
}
/// <summary>
/// Sends everything still on the vehicle back to the warehouse it was loaded from.
/// </summary>
public async Task UnloadVehicleAsync(Guid vehicleId, Guid referenceId, Guid userId, string reasonCode)
{
var onVehicle = (await repo.GetBalancesByLocationAsync(vehicleId, LocationType.Vehicle))
.Where(b => b.Quantity > 0)
.GroupBy(b => b.FK_ItemId);
foreach (var group in onVehicle)
{
var item = group.First().Item;
var warehouseId = await repo.GetLastWarehouseSourceAsync(vehicleId, group.Key)
?? throw new InvalidOperationException(
$"Cannot unload '{item.Name}': no source warehouse found for the stock on this vehicle.");
await MoveAsync(group.Key, item.Name, vehicleId, LocationType.Vehicle,
warehouseId, LocationType.Warehouse, group.Sum(b => b.Quantity),
MovementType.Transfer, referenceId, userId, reasonCode);
}
}
private async Task CreditAsync(Guid itemId, Guid locationId, LocationType locationType,
Guid? batchId, Guid? serialId, decimal quantity)
{
var balance = await repo.GetBalanceAsync(itemId, locationId, batchId, serialId);
if (balance is null)
{
await repo.AddBalanceAsync(new StockBalance
{
FK_ItemId = itemId,
LocationId = locationId,
LocationType = locationType,
FK_ItemBatchId = batchId,
FK_ItemSerialNumberId = serialId,
Quantity = quantity
});
return;
}
balance.Quantity += quantity;
balance.UpdatedAt = DateTime.Now;
repo.UpdateBalance(balance);
}
private async Task ApplySerialAsync(ItemSerialNumber serial, Models.Item? item, Guid toId, LocationType toType, MovementType type)
{
switch (type)
{
case MovementType.Sale:
serial.Status = SerialStatus.Sold;
serial.CurrentLocationId = null;
serial.CurrentLocationType = null;
// Warranty clock starts at sale, not receipt.
if (item is { HasWarranty: true } && await sysConfig.GetWarrantyTrackingEnabledAsync())
{
var days = serial.WarrantyPeriodDaysOverride ?? await sysConfig.GetDefaultWarrantyPeriodDaysAsync();
serial.WarrantyExpiresAt = DateTime.Now.AddDays(days);
}
break;
case MovementType.Damage:
serial.Status = SerialStatus.Damaged;
serial.CurrentLocationId = null;
serial.CurrentLocationType = null;
break;
case MovementType.CustomerReturn:
serial.Status = SerialStatus.Returned;
serial.CurrentLocationId = toId;
serial.CurrentLocationType = toType;
break;
default:
if (toType == LocationType.External)
{
serial.CurrentLocationId = null;
serial.CurrentLocationType = null;
}
else
{
if (type == MovementType.Load) serial.Status = SerialStatus.InStock;
serial.CurrentLocationId = toId;
serial.CurrentLocationType = toType;
}
break;
}
}
}
+97 -27
View File
@@ -7,16 +7,39 @@ namespace Distribution.Features.Stock;
public class StockRepository(AppDBContext db) : IStockRepository
{
public Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId) =>
db.StockBalances
.Include(b => b.Item)
.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.LocationId == locationId
&& b.FK_ItemBatchId == null && b.FK_ItemSerialNumberId == null);
GetBalanceAsync(itemId, locationId, null, null);
public Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId) =>
db.StockBalances
// Looks at rows added earlier in the same unit of work first — they aren't in the database yet,
// so a second movement for the same (item, location, batch, serial) would otherwise insert a duplicate.
public async Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId)
{
var pending = db.StockBalances.Local.FirstOrDefault(b => b.FK_ItemId == itemId && b.LocationId == locationId
&& b.FK_ItemBatchId == itemBatchId && b.FK_ItemSerialNumberId == itemSerialNumberId);
if (pending is not null) return pending;
return await db.StockBalances
.Include(b => b.Item)
.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.LocationId == locationId
&& b.FK_ItemBatchId == itemBatchId && b.FK_ItemSerialNumberId == itemSerialNumberId);
}
public async Task<List<StockBalance>> GetAllocatableBalancesAsync(Guid itemId, Guid locationId)
{
var balances = await db.StockBalances
.Include(b => b.Item)
.Include(b => b.ItemBatch)
.Include(b => b.ItemSerialNumber)
.Where(b => b.FK_ItemId == itemId && b.LocationId == locationId && b.Quantity > 0)
.ToListAsync();
// Tracked stock first: earliest expiry (FEFO), then oldest received serial (FIFO); untracked rows last.
return balances
.OrderBy(b => b.FK_ItemBatchId == null && b.FK_ItemSerialNumberId == null ? 1 : 0)
.ThenBy(b => b.ItemBatch?.ExpiryDate ?? DateTime.MaxValue)
.ThenBy(b => b.ItemSerialNumber?.ReceivedAt ?? DateTime.MaxValue)
.ThenBy(b => b.UpdatedAt)
.ToList();
}
public Task<List<StockBalance>> GetBalancesByLocationAsync(Guid locationId, LocationType locationType) =>
db.StockBalances
@@ -69,41 +92,88 @@ public class StockRepository(AppDBContext db) : IStockRepository
await db.StockMovements.AddAsync(movement);
public Task<StockMovement?> GetMovementByIdAsync(Guid id) =>
db.StockMovements.Include(m => m.Item).FirstOrDefaultAsync(m => m.Id == id);
db.StockMovements.Include(m => m.Item).Include(m => m.ItemBatch).Include(m => m.ItemSerialNumber).FirstOrDefaultAsync(m => m.Id == id);
public void UpdateMovement(StockMovement movement) =>
db.StockMovements.Update(movement);
public Task<List<StockMovement>> GetMovementsAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize)
{
var query = db.StockMovements.Include(m => m.Item).AsQueryable();
if (locationId.HasValue)
query = query.Where(m => m.FromLocationId == locationId.Value || m.ToLocationId == locationId.Value);
if (itemId.HasValue) query = query.Where(m => m.FK_ItemId == itemId.Value);
if (type.HasValue) query = query.Where(m => m.Type == type.Value);
if (from.HasValue) query = query.Where(m => m.CreatedAt >= from.Value);
if (to.HasValue) query = query.Where(m => m.CreatedAt <= to.Value);
return query.OrderByDescending(m => m.CreatedAt).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
}
public Task<int> GetMovementsCountAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to)
private IQueryable<StockMovement> FilterMovements(MovementFilter f)
{
var query = db.StockMovements.AsQueryable();
if (locationId.HasValue)
query = query.Where(m => m.FromLocationId == locationId.Value || m.ToLocationId == locationId.Value);
if (itemId.HasValue) query = query.Where(m => m.FK_ItemId == itemId.Value);
if (type.HasValue) query = query.Where(m => m.Type == type.Value);
if (from.HasValue) query = query.Where(m => m.CreatedAt >= from.Value);
if (to.HasValue) query = query.Where(m => m.CreatedAt <= to.Value);
return query.CountAsync();
if (f.LocationId.HasValue)
query = query.Where(m => m.FromLocationId == f.LocationId.Value || m.ToLocationId == f.LocationId.Value);
if (f.ItemId.HasValue) query = query.Where(m => m.FK_ItemId == f.ItemId.Value);
if (f.Type.HasValue) query = query.Where(m => m.Type == f.Type.Value);
if (f.ReferenceId.HasValue) query = query.Where(m => m.ReferenceId == f.ReferenceId.Value);
if (f.BatchId.HasValue) query = query.Where(m => m.FK_ItemBatchId == f.BatchId.Value);
if (f.SerialId.HasValue) query = query.Where(m => m.FK_ItemSerialNumberId == f.SerialId.Value);
if (f.CreatedBy.HasValue) query = query.Where(m => m.CreatedBy == f.CreatedBy.Value);
if (!string.IsNullOrWhiteSpace(f.ReasonCode)) query = query.Where(m => m.ReasonCode == f.ReasonCode);
if (f.From.HasValue) query = query.Where(m => m.CreatedAt >= f.From.Value);
if (f.To.HasValue)
{
// A bare date (midnight) means "through the end of that day".
var to = f.To.Value;
var toExclusive = to.TimeOfDay == TimeSpan.Zero ? to.AddDays(1) : to;
query = query.Where(m => m.CreatedAt < toExclusive);
}
return query;
}
public Task<List<StockMovement>> GetMovementsAsync(MovementFilter filter, int page, int pageSize) =>
FilterMovements(filter)
.Include(m => m.Item)
.Include(m => m.ItemBatch)
.Include(m => m.ItemSerialNumber)
.OrderByDescending(m => m.CreatedAt)
.Skip((page - 1) * pageSize).Take(pageSize)
.ToListAsync();
public Task<int> GetMovementsCountAsync(MovementFilter filter) =>
FilterMovements(filter).CountAsync();
public async Task<MovementSummary> GetMovementSummaryAsync(Guid itemId, Guid locationId, DateTime from, DateTime toExclusive)
{
var rows = db.StockMovements.AsNoTracking()
.Where(m => m.FK_ItemId == itemId && (m.FromLocationId == locationId || m.ToLocationId == locationId));
var before = await rows.Where(m => m.CreatedAt < from)
.GroupBy(_ => 1)
.Select(g => g.Sum(m => m.ToLocationId == locationId ? m.Quantity : 0)
- g.Sum(m => m.FromLocationId == locationId ? m.Quantity : 0))
.FirstOrDefaultAsync();
var inRange = rows.Where(m => m.CreatedAt >= from && m.CreatedAt < toExclusive);
var received = await inRange.Where(m => m.ToLocationId == locationId).SumAsync(m => (decimal?)m.Quantity) ?? 0;
var issued = await inRange.Where(m => m.FromLocationId == locationId).SumAsync(m => (decimal?)m.Quantity) ?? 0;
var current = await db.StockBalances.AsNoTracking()
.Where(b => b.FK_ItemId == itemId && b.LocationId == locationId)
.SumAsync(b => (decimal?)b.Quantity) ?? 0;
return new MovementSummary(before, received, issued, before + received - issued, current);
}
public Task<List<StockMovement>> GetMovementsByReferenceAsync(Guid referenceId, Guid itemId, MovementType type) =>
db.StockMovements.AsNoTracking()
.Where(m => m.ReferenceId == referenceId && m.FK_ItemId == itemId && m.Type == type)
.ToListAsync();
public Task<Guid?> GetLastWarehouseSourceAsync(Guid vehicleId, Guid itemId) =>
db.StockMovements.AsNoTracking()
.Where(m => m.ToLocationId == vehicleId && m.FK_ItemId == itemId
&& m.FromLocationType == LocationType.Warehouse)
.OrderByDescending(m => m.CreatedAt)
.Select(m => m.FromLocationId)
.FirstOrDefaultAsync();
public Task<ItemBatch?> FindBatchAsync(Guid itemId, string batchNumber) =>
db.ItemBatches.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.BatchNumber == batchNumber);
public async Task AddItemBatchAsync(ItemBatch batch) =>
await db.ItemBatches.AddAsync(batch);
public Task<ItemSerialNumber?> GetSerialAsync(Guid serialId) =>
db.ItemSerialNumbers.FirstOrDefaultAsync(s => s.Id == serialId);
public Task<bool> SerialNumberExistsAsync(Guid itemId, string serialNumber) =>
db.ItemSerialNumbers.AnyAsync(s => s.FK_ItemId == itemId && s.SerialNumber == serialNumber);
+103 -104
View File
@@ -13,6 +13,7 @@ public class StockService(
ICategoryRepository categoryRepo,
IWarehouseRepository warehouseRepo,
IVehicleRepository vehicleRepo,
StockMover mover,
IUnitOfWork uow)
{
private static StockBalanceResponse MapBalance(StockBalance b, string locationName) =>
@@ -37,7 +38,9 @@ public class StockService(
private static StockMovementResponse MapMovement(StockMovement m, string? fromLocationName, string? toLocationName) =>
new(m.Id, m.FK_ItemId, m.Item.Name, m.FromLocationId, m.FromLocationType,
m.ToLocationId, m.ToLocationType, m.Quantity, m.Type, m.ReasonCode,
m.ReferenceId, m.CreatedAt, m.CreatedBy, fromLocationName, toLocationName);
m.ReferenceId, m.CreatedAt, m.CreatedBy, fromLocationName, toLocationName,
m.FK_ItemBatchId, m.ItemBatch?.BatchNumber, m.ItemBatch?.ExpiryDate,
m.FK_ItemSerialNumberId, m.ItemSerialNumber?.SerialNumber);
private async Task<StockMovementResponse> MapMovementAsync(StockMovement m)
{
@@ -112,11 +115,16 @@ public class StockService(
balances.Select(b => MapBalance(b, names.GetValueOrDefault(b.LocationId, "—"))).ToList());
}
public async Task<PagedResponse<StockMovementResponse>> GetMovementsAsync(
Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize)
public async Task<MovementSummary> GetMovementSummaryAsync(Guid itemId, Guid locationId, DateTime from, DateTime to)
{
var items = await repo.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
var total = await repo.GetMovementsCountAsync(locationId, itemId, type, from, to);
var toExclusive = to.TimeOfDay == TimeSpan.Zero ? to.AddDays(1) : to;
return await repo.GetMovementSummaryAsync(itemId, locationId, from, toExclusive);
}
public async Task<PagedResponse<StockMovementResponse>> GetMovementsAsync(MovementFilter filter, int page, int pageSize)
{
var items = await repo.GetMovementsAsync(filter, page, pageSize);
var total = await repo.GetMovementsCountAsync(filter);
var locations = new List<(Guid, LocationType)>();
foreach (var m in items)
@@ -137,60 +145,42 @@ public class StockService(
public async Task<StockMovementResponse> TransferAsync(TransferRequest request, Guid userId, CancellationToken ct = default)
{
await EnsureStockAsync(request.ItemId, request.FromLocationId, request.Quantity);
var movement = new StockMovement
{
FK_ItemId = request.ItemId,
FromLocationId = request.FromLocationId,
FromLocationType = request.FromLocationType,
ToLocationId = request.ToLocationId,
ToLocationType = request.ToLocationType,
Quantity = request.Quantity,
Type = MovementType.Transfer,
ReferenceId = request.ReferenceId,
CreatedBy = userId
};
var item = await itemRepo.GetByIdAsync(request.ItemId)
?? throw new InvalidOperationException("Item not found.");
List<StockMovement> movements;
await uow.BeginAsync(ct);
try
{
await ApplyTransferAsync(request.ItemId, request.FromLocationId, request.ToLocationId, request.ToLocationType, request.Quantity);
await repo.AddMovementAsync(movement);
movements = await mover.MoveAsync(request.ItemId, item.Name,
request.FromLocationId, request.FromLocationType,
request.ToLocationId, request.ToLocationType,
request.Quantity, MovementType.Transfer, request.ReferenceId, userId);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return await MapMovementAsync(movement);
return await MapMovementsAsync(movements, item, request.Quantity);
}
public async Task<StockMovementResponse> RecordDamageAsync(DamageRequest request, Guid userId, CancellationToken ct = default)
{
await EnsureStockAsync(request.ItemId, request.LocationId, request.Quantity);
var movement = new StockMovement
{
FK_ItemId = request.ItemId,
FromLocationId = request.LocationId,
FromLocationType = request.LocationType,
ToLocationId = ExternalLocationId,
ToLocationType = LocationType.External,
Quantity = request.Quantity,
Type = MovementType.Damage,
ReasonCode = request.ReasonCode,
CreatedBy = userId
};
var item = await itemRepo.GetByIdAsync(request.ItemId)
?? throw new InvalidOperationException("Item not found.");
List<StockMovement> movements;
await uow.BeginAsync(ct);
try
{
await DebitBalanceAsync(request.ItemId, request.LocationId, request.Quantity);
await repo.AddMovementAsync(movement);
movements = await mover.MoveAsync(request.ItemId, item.Name,
request.LocationId, request.LocationType,
ExternalLocationId, LocationType.External,
request.Quantity, MovementType.Damage, null, userId, request.ReasonCode);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return await MapMovementAsync(movement);
return await MapMovementsAsync(movements, item, request.Quantity);
}
public async Task<StockMovementResponse> RecordCustomerReturnAsync(CustomerReturnRequest request, Guid userId, CancellationToken ct = default)
@@ -204,75 +194,89 @@ public class StockService(
if (!category.AllowCustomerReturn)
throw new InvalidOperationException($"Customer returns are not allowed for category '{category.Name}'.");
var movement = new StockMovement
{
FK_ItemId = request.ItemId,
FromLocationId = ExternalLocationId,
FromLocationType = LocationType.External,
ToLocationId = request.VehicleId,
ToLocationType = LocationType.Vehicle,
Quantity = request.Quantity,
Type = MovementType.CustomerReturn,
ReferenceId = request.ReferenceId,
CreatedBy = userId
};
var tracked = item.TracksSerialNumbers || item.TracksBatches || item.TracksExpiryDate;
if (tracked && request.ReferenceId is null)
throw new InvalidOperationException(
$"'{item.Name}' is batch/serial tracked — a customer return must reference the ShopPO it was delivered on.");
List<StockMovement> movements;
await uow.BeginAsync(ct);
try
{
await CreditBalanceAsync(request.ItemId, request.VehicleId, LocationType.Vehicle, request.Quantity);
await repo.AddMovementAsync(movement);
if (tracked)
{
movements = await mover.ReturnToVehicleAsync(request.ItemId, item.Name, request.Quantity,
request.ReferenceId!.Value, userId);
}
else
{
var movement = new StockMovement
{
FK_ItemId = request.ItemId,
FromLocationId = ExternalLocationId,
FromLocationType = LocationType.External,
ToLocationId = request.VehicleId,
ToLocationType = LocationType.Vehicle,
Quantity = request.Quantity,
Type = MovementType.CustomerReturn,
ReferenceId = request.ReferenceId,
CreatedBy = userId
};
await CreditBalanceAsync(request.ItemId, request.VehicleId, LocationType.Vehicle, request.Quantity);
await repo.AddMovementAsync(movement);
movements = [movement];
}
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return await MapMovementAsync(movement);
return await MapMovementsAsync(movements, item, request.Quantity);
}
public async Task<StockMovementResponse> AdjustAsync(AdjustmentRequest request, Guid userId, CancellationToken ct = default)
{
var balance = await repo.GetBalanceAsync(request.ItemId, request.LocationId);
if (balance is not null && balance.Quantity + request.Quantity < 0)
throw new InvalidOperationException("Adjustment would result in negative stock.");
var movement = new StockMovement
{
FK_ItemId = request.ItemId,
FromLocationId = request.Quantity < 0 ? request.LocationId : ExternalLocationId,
FromLocationType = request.Quantity < 0 ? request.LocationType : LocationType.External,
ToLocationId = request.Quantity > 0 ? request.LocationId : ExternalLocationId,
ToLocationType = request.Quantity > 0 ? request.LocationType : LocationType.External,
Quantity = Math.Abs(request.Quantity),
Type = MovementType.Adjustment,
ReasonCode = request.ReasonCode,
CreatedBy = userId
};
var item = await itemRepo.GetByIdAsync(request.ItemId)
?? throw new InvalidOperationException("Item not found.");
List<StockMovement> movements;
await uow.BeginAsync(ct);
try
{
if (balance is null)
if (request.Quantity < 0)
{
await repo.AddBalanceAsync(new StockBalance
{
FK_ItemId = request.ItemId,
LocationId = request.LocationId,
LocationType = request.LocationType,
Quantity = request.Quantity
});
movements = await mover.MoveAsync(request.ItemId, item.Name,
request.LocationId, request.LocationType,
ExternalLocationId, LocationType.External,
-request.Quantity, MovementType.Adjustment, null, userId, request.ReasonCode);
}
else
{
balance.Quantity += request.Quantity;
balance.UpdatedAt = DateTime.Now;
repo.UpdateBalance(balance);
// A positive adjustment can't say which batch/serial it adds — those enter stock through a GRN.
if (item.TracksSerialNumbers || item.TracksBatches || item.TracksExpiryDate)
throw new InvalidOperationException(
$"'{item.Name}' is batch/serial tracked — increase its stock by receiving a GRN, not by adjustment.");
var movement = new StockMovement
{
FK_ItemId = request.ItemId,
FromLocationId = ExternalLocationId,
FromLocationType = LocationType.External,
ToLocationId = request.LocationId,
ToLocationType = request.LocationType,
Quantity = request.Quantity,
Type = MovementType.Adjustment,
ReasonCode = request.ReasonCode,
CreatedBy = userId
};
await CreditBalanceAsync(request.ItemId, request.LocationId, request.LocationType, request.Quantity);
await repo.AddMovementAsync(movement);
movements = [movement];
}
await repo.AddMovementAsync(movement);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return await MapMovementAsync(movement);
return await MapMovementsAsync(movements, item, Math.Abs(request.Quantity));
}
// Movements are an immutable ledger — only the reason/note can be edited, never quantity/locations/type.
@@ -282,28 +286,29 @@ public class StockService(
?? throw new InvalidOperationException("Movement not found.");
movement.ReasonCode = request.ReasonCode;
repo.UpdateMovement(movement);
await uow.SaveChangesAsync(ct);
await uow.BeginAsync(ct);
try
{
repo.UpdateMovement(movement);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
return await MapMovementAsync(movement);
}
// ── Helpers ────────────────────────────────────────────────────────────
private async Task EnsureStockAsync(Guid itemId, Guid locationId, decimal quantity)
/// <summary>
/// One logical operation can produce several movements (one per batch/serial slice). The caller gets a
/// single response for the operation: the first slice's locations, with the total quantity moved.
/// </summary>
private async Task<StockMovementResponse> MapMovementsAsync(List<StockMovement> movements, Models.Item item, decimal totalQuantity)
{
var balance = await repo.GetBalanceAsync(itemId, locationId);
if (balance is null || balance.Quantity < quantity)
throw new InvalidOperationException("Insufficient stock at the specified location.");
}
private async Task DebitBalanceAsync(Guid itemId, Guid locationId, decimal quantity)
{
var balance = await repo.GetBalanceAsync(itemId, locationId)
?? throw new InvalidOperationException("No stock balance found.");
balance.Quantity -= quantity;
balance.UpdatedAt = DateTime.Now;
repo.UpdateBalance(balance);
var first = movements[0];
first.Item ??= item;
return (await MapMovementAsync(first)) with { Quantity = totalQuantity };
}
private async Task CreditBalanceAsync(Guid itemId, Guid locationId, LocationType locationType, decimal quantity)
@@ -326,10 +331,4 @@ public class StockService(
repo.UpdateBalance(balance);
}
}
private async Task ApplyTransferAsync(Guid itemId, Guid fromLocationId, Guid toLocationId, LocationType toLocationType, decimal quantity)
{
await DebitBalanceAsync(itemId, fromLocationId, quantity);
await CreditBalanceAsync(itemId, toLocationId, toLocationType, quantity);
}
}
@@ -0,0 +1,22 @@
namespace Distribution.Features.StockReport;
public record LedgerAggregate(
Guid ItemId, string Sku, string Name, string? Brand, string Category, string Uom,
decimal? CostPrice, decimal? ReorderLevel,
decimal Opening, decimal In, decimal Out, decimal Current, decimal WarehouseQty);
public record ExpiryAggregate(
Guid ItemId, string Sku, string Name, Guid BatchId, string? BatchNumber, DateTime ExpiryDate,
decimal Quantity, decimal? CostPrice, Guid LocationId, string LocationType, string LocationName);
public record OverviewAggregate(
int ActiveItems, int InStockItems, int LowStockItems, decimal TotalQty, decimal TotalValue,
int ExpiringSoonBatches, int ExpiredBatches);
public interface IStockReportRepository
{
Task<bool> IsWarehouseAsync(Guid locationId);
Task<OverviewAggregate> GetOverviewAsync(Guid? locationId, bool includeWarehouseLow, DateTime today, int expiryDays);
Task<(List<LedgerAggregate> Rows, int Total)> GetLedgerAsync(StockReportFilter f, bool includeWarehouseLow, int page, int pageSize);
Task<(List<ExpiryAggregate> Rows, int Total)> GetExpiryAsync(Guid? locationId, string? search, DateTime cutoff, int page, int pageSize);
}
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.StockReport;
[ApiController]
[Route("api/reports/stock")]
public class StockReportController(StockReportService service) : ControllerBase
{
[HttpGet("overview")]
public async Task<IActionResult> Overview([FromQuery] Guid? locationId, [FromQuery] int expiryDays = 30)
{
var error = StockReportService.ValidateExpiry(expiryDays, 1, 1);
return error is null ? Ok(await service.GetOverviewAsync(locationId, expiryDays)) : BadRequest(new { message = error });
}
[HttpGet("ledger")]
public async Task<IActionResult> Ledger(
[FromQuery] Guid? locationId, [FromQuery] Guid? categoryId, [FromQuery] Guid? brandId,
[FromQuery] DateTime? from, [FromQuery] DateTime? to, [FromQuery] string? status = null,
[FromQuery] bool activeOnly = true, [FromQuery] string? search = null,
[FromQuery] int page = 1, [FromQuery] int pageSize = 50)
{
var end = (to ?? DateTime.Now).Date;
var start = (from ?? end.AddDays(-30)).Date;
var f = new StockReportFilter(locationId, categoryId, brandId, search, start, end, status, activeOnly);
var error = StockReportService.Validate(f, page, pageSize);
return error is null ? Ok(await service.GetLedgerAsync(f, page, pageSize)) : BadRequest(new { message = error });
}
[HttpGet("expiry")]
public async Task<IActionResult> Expiry(
[FromQuery] Guid? locationId, [FromQuery] string? search = null, [FromQuery] int days = 30,
[FromQuery] int page = 1, [FromQuery] int pageSize = 50)
{
var error = StockReportService.ValidateExpiry(days, page, pageSize);
return error is null
? Ok(await service.GetExpiryAsync(locationId, search, days, page, pageSize))
: BadRequest(new { message = error });
}
}
@@ -0,0 +1,25 @@
namespace Distribution.Features.StockReport;
public static class StockStatus
{
public const string Ok = "OK";
public const string Low = "Low";
public const string Out = "Out";
}
public record StockReportFilter(
Guid? LocationId, Guid? CategoryId, Guid? BrandId, string? Search,
DateTime From, DateTime To, string? Status, bool ActiveOnly);
public record StockOverview(
int TotalSkus, decimal TotalQty, decimal TotalValue,
int LowStockCount, int OutOfStockCount, int ExpiringSoonCount, int ExpiredCount);
public record StockLedgerRow(
Guid ItemId, string Sku, string Name, string? Brand, string Category, string Uom,
decimal OpeningQty, decimal QtyIn, decimal QtyOut, decimal ClosingQty, decimal CurrentQty,
decimal? CostPrice, decimal StockValue, decimal? ReorderLevel, string Status);
public record StockExpiryRow(
Guid ItemId, string Sku, string Name, Guid BatchId, string? BatchNumber, DateTime ExpiryDate, int DaysLeft,
decimal Quantity, decimal? CostPrice, decimal Value, Guid LocationId, string LocationType, string LocationName);
@@ -0,0 +1,182 @@
using System.Linq.Expressions;
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.StockReport;
public class StockReportRepository(AppDBContext db) : IStockReportRepository
{
// One location when picked, otherwise every internal location (warehouses + vehicles; External is not tracked).
private IQueryable<StockBalance> ScopedBalances(Guid? locationId)
{
var q = db.StockBalances.AsNoTracking().AsQueryable();
return locationId.HasValue
? q.Where(b => b.LocationId == locationId.Value)
: q.Where(b => b.LocationType == LocationType.Warehouse || b.LocationType == LocationType.Vehicle);
}
public Task<bool> IsWarehouseAsync(Guid locationId) =>
db.Warehouses.AsNoTracking().AnyAsync(w => w.Id == locationId);
public async Task<OverviewAggregate> GetOverviewAsync(Guid? locationId, bool includeWarehouseLow, DateTime today, int expiryDays)
{
var balances = ScopedBalances(locationId);
var whBalances = balances.Where(b => b.LocationType == LocationType.Warehouse);
var perItem = db.Items.AsNoTracking().Where(i => i.IsActive).Select(i => new
{
i.ReorderLevel,
i.CostPrice,
Qty = balances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
Wh = whBalances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
});
var activeItems = await perItem.CountAsync();
var inStockItems = await perItem.CountAsync(x => x.Qty > 0);
var lowItems = includeWarehouseLow
? await perItem.CountAsync(x => x.Qty > 0 && x.ReorderLevel != null && x.Wh <= x.ReorderLevel)
: 0;
var totalQty = await perItem.SumAsync(x => (decimal?)x.Qty) ?? 0m;
var totalValue = await perItem.SumAsync(x => (decimal?)(x.Qty * (x.CostPrice ?? 0m))) ?? 0m;
var soonEnd = today.AddDays(expiryDays + 1);
var batchBalances = balances.Where(b => b.FK_ItemBatchId != null && b.Quantity > 0
&& b.ItemBatch!.ExpiryDate != null);
var expired = await batchBalances.Where(b => b.ItemBatch!.ExpiryDate < today)
.Select(b => b.FK_ItemBatchId).Distinct().CountAsync();
var soon = await batchBalances.Where(b => b.ItemBatch!.ExpiryDate >= today && b.ItemBatch.ExpiryDate < soonEnd)
.Select(b => b.FK_ItemBatchId).Distinct().CountAsync();
return new OverviewAggregate(activeItems, inStockItems, lowItems, totalQty, totalValue, soon, expired);
}
public async Task<(List<LedgerAggregate> Rows, int Total)> GetLedgerAsync(
StockReportFilter f, bool includeWarehouseLow, int page, int pageSize)
{
var balances = ScopedBalances(f.LocationId);
var whBalances = balances.Where(b => b.LocationType == LocationType.Warehouse);
var items = db.Items.AsNoTracking().AsQueryable();
if (f.ActiveOnly) items = items.Where(i => i.IsActive);
if (f.CategoryId.HasValue) items = items.Where(i => i.FK_CategoryId == f.CategoryId.Value);
if (f.BrandId.HasValue) items = items.Where(i => i.FK_BrandId == f.BrandId.Value);
if (!string.IsNullOrWhiteSpace(f.Search))
{
var s = f.Search.Trim();
items = items.Where(i => i.Name.Contains(s) || i.Sku.Contains(s));
}
var withQty = items.Select(i => new
{
Item = i,
Qty = balances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
Wh = whBalances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
});
if (f.Status == "out")
withQty = withQty.Where(x => x.Qty <= 0);
else if (f.Status == "low")
withQty = includeWarehouseLow
? withQty.Where(x => x.Qty > 0 && x.Item.ReorderLevel != null && x.Wh <= x.Item.ReorderLevel)
: withQty.Where(x => false);
var total = await withQty.CountAsync();
var pageRows = await withQty
.OrderBy(x => x.Item.Name)
.Skip((page - 1) * pageSize).Take(pageSize)
.Select(x => new
{
x.Item.Id, x.Item.Sku, x.Item.Name,
Brand = x.Item.Brand != null ? x.Item.Brand.Name : null,
Category = x.Item.Category.Name,
Uom = x.Item.BaseUom.Name,
x.Item.CostPrice, x.Item.ReorderLevel,
x.Qty, x.Wh,
})
.ToListAsync();
var ids = pageRows.Select(r => r.Id).ToList();
var toExclusive = f.To.TimeOfDay == TimeSpan.Zero ? f.To.AddDays(1) : f.To;
var from = f.From;
Expression<Func<StockMovement, bool>> isIn, isOut;
if (f.LocationId.HasValue)
{
var loc = f.LocationId.Value;
isIn = m => m.ToLocationId == loc;
isOut = m => m.FromLocationId == loc;
}
else
{
// All locations: only movements that cross the boundary of the internal network count;
// warehouse <-> vehicle transfers net to zero and are left out of in/out.
isIn = m => m.ToLocationId != null && m.ToLocationType != LocationType.External
&& (m.FromLocationId == null || m.FromLocationType == LocationType.External);
isOut = m => m.FromLocationId != null && m.FromLocationType != LocationType.External
&& (m.ToLocationId == null || m.ToLocationType == LocationType.External);
}
var moves = db.StockMovements.AsNoTracking().Where(m => ids.Contains(m.FK_ItemId) && m.CreatedAt < toExclusive);
var before = moves.Where(m => m.CreatedAt < from);
var inRange = moves.Where(m => m.CreatedAt >= from);
var openingIn = await SumByItem(before, isIn);
var openingOut = await SumByItem(before, isOut);
var rangeIn = await SumByItem(inRange, isIn);
var rangeOut = await SumByItem(inRange, isOut);
var rows = pageRows.Select(r => new LedgerAggregate(
r.Id, r.Sku, r.Name, r.Brand, r.Category, r.Uom, r.CostPrice, r.ReorderLevel,
openingIn.GetValueOrDefault(r.Id) - openingOut.GetValueOrDefault(r.Id),
rangeIn.GetValueOrDefault(r.Id), rangeOut.GetValueOrDefault(r.Id),
r.Qty, r.Wh)).ToList();
return (rows, total);
}
private static async Task<Dictionary<Guid, decimal>> SumByItem(
IQueryable<StockMovement> q, Expression<Func<StockMovement, bool>> predicate) =>
await q.Where(predicate)
.GroupBy(m => m.FK_ItemId)
.Select(g => new { ItemId = g.Key, Qty = g.Sum(m => m.Quantity) })
.ToDictionaryAsync(x => x.ItemId, x => x.Qty);
public async Task<(List<ExpiryAggregate> Rows, int Total)> GetExpiryAsync(
Guid? locationId, string? search, DateTime cutoffExclusive, int page, int pageSize)
{
var q = ScopedBalances(locationId)
.Where(b => b.FK_ItemBatchId != null && b.Quantity > 0
&& b.ItemBatch!.ExpiryDate != null && b.ItemBatch.ExpiryDate < cutoffExclusive);
if (!string.IsNullOrWhiteSpace(search))
{
var s = search.Trim();
q = q.Where(b => b.Item.Name.Contains(s) || b.Item.Sku.Contains(s));
}
var total = await q.CountAsync();
var page_ = await q
.OrderBy(b => b.ItemBatch!.ExpiryDate).ThenBy(b => b.Item.Name)
.Skip((page - 1) * pageSize).Take(pageSize)
.Select(b => new
{
b.FK_ItemId, b.Item.Sku, b.Item.Name,
BatchId = b.FK_ItemBatchId!.Value,
b.ItemBatch!.BatchNumber,
ExpiryDate = b.ItemBatch.ExpiryDate!.Value,
b.Quantity, b.Item.CostPrice, b.LocationId, b.LocationType,
})
.ToListAsync();
var locIds = page_.Select(r => r.LocationId).Distinct().ToList();
var names = await db.Warehouses.AsNoTracking().Where(w => locIds.Contains(w.Id))
.Select(w => new { w.Id, w.Name }).ToDictionaryAsync(w => w.Id, w => w.Name);
var plates = await db.Vehicles.AsNoTracking().Where(v => locIds.Contains(v.Id))
.Select(v => new { v.Id, v.NumberPlate }).ToDictionaryAsync(v => v.Id, v => v.NumberPlate);
var rows = page_.Select(r => new ExpiryAggregate(
r.FK_ItemId, r.Sku, r.Name, r.BatchId, r.BatchNumber, r.ExpiryDate,
r.Quantity, r.CostPrice, r.LocationId, r.LocationType.ToString(),
names.GetValueOrDefault(r.LocationId) ?? plates.GetValueOrDefault(r.LocationId) ?? "—")).ToList();
return (rows, total);
}
}
@@ -0,0 +1,65 @@
using Distribution.Features.Stock;
namespace Distribution.Features.StockReport;
public class StockReportService(IStockReportRepository repo)
{
public const int MaxRangeDays = 366;
public const int MaxExpiryDays = 3650;
public static string? Validate(StockReportFilter f, int page, int pageSize)
{
if (page < 1 || pageSize < 1 || pageSize > 200) return "page must be >= 1 and pageSize between 1 and 200.";
if (f.To < f.From) return "'to' must not be before 'from'.";
if ((f.To - f.From).TotalDays > MaxRangeDays) return $"Date range cannot exceed {MaxRangeDays} days.";
if (f.Status is not (null or "low" or "out")) return "status must be 'low' or 'out'.";
return null;
}
public static string? ValidateExpiry(int days, int page, int pageSize)
{
if (page < 1 || pageSize < 1 || pageSize > 200) return "page must be >= 1 and pageSize between 1 and 200.";
if (days < 0 || days > MaxExpiryDays) return $"days must be between 0 and {MaxExpiryDays}.";
return null;
}
// Low-stock is judged on warehouse quantity, so it only applies when the scope includes warehouses.
private async Task<bool> IncludesWarehouses(Guid? locationId) =>
!locationId.HasValue || await repo.IsWarehouseAsync(locationId.Value);
public async Task<StockOverview> GetOverviewAsync(Guid? locationId, int expiryDays)
{
var agg = await repo.GetOverviewAsync(locationId, await IncludesWarehouses(locationId), DateTime.Now.Date, expiryDays);
return new StockOverview(agg.InStockItems, agg.TotalQty, agg.TotalValue, agg.LowStockItems,
agg.ActiveItems - agg.InStockItems, agg.ExpiringSoonBatches, agg.ExpiredBatches);
}
public async Task<PagedResponse<StockLedgerRow>> GetLedgerAsync(StockReportFilter f, int page, int pageSize)
{
var includeLow = await IncludesWarehouses(f.LocationId);
var (rows, total) = await repo.GetLedgerAsync(f, includeLow, page, pageSize);
var mapped = rows.Select(r =>
{
var status = r.Current <= 0 ? StockStatus.Out
: includeLow && r.ReorderLevel != null && r.WarehouseQty <= r.ReorderLevel ? StockStatus.Low
: StockStatus.Ok;
return new StockLedgerRow(r.ItemId, r.Sku, r.Name, r.Brand, r.Category, r.Uom,
r.Opening, r.In, r.Out, r.Opening + r.In - r.Out, r.Current,
r.CostPrice, r.Current * (r.CostPrice ?? 0m), r.ReorderLevel, status);
}).ToList();
return new PagedResponse<StockLedgerRow>(page, pageSize, total, mapped);
}
public async Task<PagedResponse<StockExpiryRow>> GetExpiryAsync(
Guid? locationId, string? search, int days, int page, int pageSize)
{
var today = DateTime.Now.Date;
var (rows, total) = await repo.GetExpiryAsync(locationId, search, today.AddDays(days + 1), page, pageSize);
var mapped = rows.Select(r => new StockExpiryRow(
r.ItemId, r.Sku, r.Name, r.BatchId, r.BatchNumber, r.ExpiryDate,
(int)(r.ExpiryDate.Date - today).TotalDays,
r.Quantity, r.CostPrice, r.Quantity * (r.CostPrice ?? 0m),
r.LocationId, r.LocationType, r.LocationName)).ToList();
return new PagedResponse<StockExpiryRow>(page, pageSize, total, mapped);
}
}
@@ -13,4 +13,5 @@ public interface IStoreRepository
void Update(Models.Store entity);
void Remove(Models.Store entity);
Task<List<Models.ShopPO>> GetOrdersForDashboardAsync(Guid storeId, DateTime periodStart, DateTime periodEndExclusive);
Task<List<Models.Invoice>> GetInvoicesForAnalyticsAsync(Guid storeId);
}
@@ -26,8 +26,8 @@ public class StoreController(
public async Task<IActionResult> GetById(Guid id)
{
var userId = currentUser.UserId;
if (userId.HasValue && !await scopeAccess.HasStoreScopeAsync(userId.Value, id))
return StatusCode(403);
//if (userId.HasValue && !await scopeAccess.HasStoreScopeAsync(userId.Value, id))
// return StatusCode(403);
var result = await service.GetByIdAsync(id);
return result is null ? NotFound() : Ok(result);
}
@@ -112,6 +112,13 @@ public class StoreController(
return result is null ? NotFound() : Ok(result);
}
[HttpGet("{id:guid}/analytics")]
public async Task<IActionResult> GetAnalytics(Guid id, [FromQuery] int months = 12, [FromQuery] int overdueDays = 30)
{
var result = await service.GetAnalyticsAsync(id, months, overdueDays);
return result is null ? NotFound() : Ok(result);
}
[HttpPatch("{id:guid}/status")]
[Authorize("store.manage")]
public async Task<IActionResult> PatchStatus(Guid id, PatchStoreStatusRequest request, CancellationToken ct)
+37
View File
@@ -59,3 +59,40 @@ public record SchemeEligibilityResponse(bool Eligible, string? Reason);
public record PatchStoreStatusRequest(bool IsActive);
public record StoreDashboardResponse(Guid StoreId, string Period, int OrderCount, decimal TotalOrderValue, DateTime? LastOrderDate);
public record StoreAnalyticsSummary(
decimal TotalSales,
decimal TotalCollected,
decimal Outstanding,
decimal OverdueAmount,
int OverdueInvoiceCount,
int InvoiceCount,
decimal AverageOrderValue,
DateTime? LastSaleDate,
decimal CreditLimit,
decimal CreditUtilizationPercent);
public record StoreMonthlySales(string Month, decimal Invoiced, decimal Collected, decimal Outstanding);
public record StoreTopProduct(Guid ItemId, string Name, int Quantity, decimal Revenue, DateTime LastSoldAt);
public record StoreAgeingBucket(string Label, decimal Amount, int InvoiceCount);
public record StoreAnalyticsInvoice(
Guid InvoiceId,
DateTime InvoiceDate,
DateTime DueDate,
decimal TotalAmount,
decimal PaidAmount,
decimal Balance,
string PaymentStatus,
int DaysOverdue);
public record StoreAnalyticsResponse(
Guid StoreId,
int OverdueDays,
StoreAnalyticsSummary Summary,
List<StoreMonthlySales> MonthlySales,
List<StoreTopProduct> TopProducts,
List<StoreAgeingBucket> Ageing,
List<StoreAnalyticsInvoice> Invoices);
@@ -51,4 +51,12 @@ public class StoreRepository(AppDBContext context) : IStoreRepository
await context.ShopPOs
.Where(o => o.FK_StoreId == storeId && o.IsActive && o.CreatedAt >= periodStart && o.CreatedAt < periodEndExclusive)
.ToListAsync();
public async Task<List<Models.Invoice>> GetInvoicesForAnalyticsAsync(Guid storeId) =>
await context.Invoices
.AsNoTracking()
.Include(i => i.Payments)
.Include(i => i.ShopPO).ThenInclude(p => p.Items).ThenInclude(x => x.Item)
.Where(i => i.IsActive && i.Status != InvoiceStatus.Draft && i.ShopPO.FK_StoreId == storeId)
.ToListAsync();
}
+92 -2
View File
@@ -1,12 +1,13 @@
using Distribution.Features.Scheme;
using Distribution.Features.Stock;
using Distribution.Features.StoreCredit;
using Distribution.Infra.Authorization;
using Distribution.Infra.UoW;
using Distribution.Models;
namespace Distribution.Features.Store;
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService, Route.IRouteRepository routeRepo)
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService, Route.IRouteRepository routeRepo, IStoreCreditMovementRepository creditRepo)
{
private const int GlobalScopeRoleLevel = 50;
private const int CreditApproverRoleLevel = 20;
@@ -167,7 +168,8 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
var store = await repo.GetByIdAsync(id);
if (store is null || !store.IsActive) return null;
if (!await HasStoreScopeAsync(id)) return null;
return new OutstandingBalanceResponse(store.Id, store.CreditLimit, store.OutstandingBalance, store.CreditLimit - store.OutstandingBalance);
var outstanding = await creditRepo.GetOutstandingForStoreAsync(id);
return new OutstandingBalanceResponse(store.Id, store.CreditLimit, outstanding, store.CreditLimit - outstanding);
}
public async Task<StoreResponse?> ClassifySegmentAsync(Guid id, ClassifySegmentRequest request, CancellationToken ct = default)
{
@@ -253,4 +255,92 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
orders.Sum(o => o.TotalAmount),
orders.Count == 0 ? null : orders.Max(o => o.CreatedAt));
}
public async Task<StoreAnalyticsResponse?> GetAnalyticsAsync(Guid id, int months, int overdueDays)
{
var store = await repo.GetByIdAsync(id);
if (store is null) return null;
if (!await HasStoreScopeAsync(id)) return null;
months = Math.Clamp(months, 1, 36);
overdueDays = Math.Clamp(overdueDays, 0, 365);
var now = DateTime.Now;
var from = new DateTime(now.Year, now.Month, 1).AddMonths(-(months - 1));
var invoices = (await repo.GetInvoicesForAnalyticsAsync(id))
.Select(i =>
{
var date = i.FinalizedAt ?? i.CreatedAt;
var paid = i.Payments.Where(p => p.IsActive).Sum(p => p.Amount);
var balance = Math.Max(0, i.TotalAmount - paid);
var due = date.AddDays(overdueDays);
var daysOverdue = balance > 0 && now > due ? (int)(now - due).TotalDays : 0;
return (Invoice: i, Date: date, Due: due, Paid: paid, Balance: balance, DaysOverdue: daysOverdue);
})
.ToList();
var inRange = invoices.Where(x => x.Date >= from).ToList();
var totalSales = inRange.Sum(x => x.Invoice.TotalAmount);
var overdue = invoices.Where(x => x.DaysOverdue > 0).ToList();
var summary = new StoreAnalyticsSummary(
totalSales,
inRange.Sum(x => x.Paid),
invoices.Sum(x => x.Balance),
overdue.Sum(x => x.Balance),
overdue.Count,
inRange.Count,
inRange.Count == 0 ? 0 : Math.Round(totalSales / inRange.Count, 2),
invoices.Count == 0 ? null : invoices.Max(x => x.Date),
store.CreditLimit,
store.CreditLimit > 0 ? Math.Round(store.OutstandingBalance / store.CreditLimit * 100, 1) : 0);
var monthly = Enumerable.Range(0, months)
.Select(n => from.AddMonths(n))
.Select(m => new StoreMonthlySales(
m.ToString("yyyy-MM"),
inRange.Where(x => x.Date.Year == m.Year && x.Date.Month == m.Month).Sum(x => x.Invoice.TotalAmount),
inRange.Where(x => x.Date.Year == m.Year && x.Date.Month == m.Month).Sum(x => x.Paid),
invoices
.Where(x => x.Date < m.AddMonths(1))
.Sum(x => Math.Max(0, x.Invoice.TotalAmount - x.Invoice.Payments.Where(p => p.IsActive && p.CollectedAt < m.AddMonths(1)).Sum(p => p.Amount)))))
.ToList();
var topProducts = inRange
.SelectMany(x => x.Invoice.ShopPO.Items.Select(it => (it, x.Date)))
.GroupBy(t => t.it.FK_ItemId)
.Select(g => new StoreTopProduct(
g.Key,
g.First().it.Item?.Name ?? "Unknown",
g.Sum(t => t.it.QuantityFulfilled > 0 ? t.it.QuantityFulfilled : t.it.Quantity),
g.Sum(t => (t.it.QuantityFulfilled > 0 ? t.it.QuantityFulfilled : t.it.Quantity) * t.it.UnitPrice),
g.Max(t => t.Date)))
.OrderByDescending(p => p.Revenue)
.Take(10)
.ToList();
var unpaid = invoices.Where(x => x.Balance > 0).ToList();
StoreAgeingBucket Bucket(string label, Func<int, bool> match)
{
var items = unpaid.Where(x => match(x.DaysOverdue)).ToList();
return new StoreAgeingBucket(label, items.Sum(x => x.Balance), items.Count);
}
var ageing = new List<StoreAgeingBucket>
{
Bucket("Current", d => d <= 0),
Bucket("1-30", d => d is >= 1 and <= 30),
Bucket("31-60", d => d is >= 31 and <= 60),
Bucket("61-90", d => d is >= 61 and <= 90),
Bucket("90+", d => d > 90)
};
var list = invoices
.OrderByDescending(x => x.Date)
.Select(x => new StoreAnalyticsInvoice(
x.Invoice.Id, x.Date, x.Due, x.Invoice.TotalAmount, x.Paid, x.Balance,
x.Invoice.PaymentStatus.ToString(), x.DaysOverdue))
.ToList();
return new StoreAnalyticsResponse(id, overdueDays, summary, monthly, topProducts, ageing, list);
}
}
@@ -0,0 +1,11 @@
using Distribution.Models;
namespace Distribution.Features.StoreCredit;
public interface IStoreCreditMovementRepository
{
Task<decimal> GetOutstandingForStoreAsync(Guid storeId);
Task<List<StoreCreditMovement>> GetByStoreAsync(Guid storeId, int page, int pageSize);
Task<int> GetCountByStoreAsync(Guid storeId);
Task AddAsync(StoreCreditMovement movement);
}
@@ -0,0 +1,36 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.StoreCredit;
public class StoreCreditMovementRepository(AppDBContext context) : IStoreCreditMovementRepository
{
public async Task<decimal> GetOutstandingForStoreAsync(Guid storeId)
{
var usage = await context.StoreCreditMovements
.Where(m => m.IsActive && m.FK_StoreId == storeId && m.Direction == StoreCreditMovementDirection.Usage)
.SumAsync(m => (decimal?)m.Amount) ?? 0m;
var settlements = await context.StoreCreditMovements
.Where(m => m.IsActive && m.FK_StoreId == storeId && m.Direction == StoreCreditMovementDirection.Settlement)
.SumAsync(m => (decimal?)m.Amount) ?? 0m;
return usage - settlements;
}
public async Task<List<StoreCreditMovement>> GetByStoreAsync(Guid storeId, int page, int pageSize) =>
await context.StoreCreditMovements
.Where(m => m.IsActive && m.FK_StoreId == storeId)
.OrderByDescending(m => m.OccurredAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
public async Task<int> GetCountByStoreAsync(Guid storeId) =>
await context.StoreCreditMovements
.CountAsync(m => m.IsActive && m.FK_StoreId == storeId);
public async Task AddAsync(StoreCreditMovement movement) =>
await context.StoreCreditMovements.AddAsync(movement);
}
@@ -0,0 +1,17 @@
using Distribution.Models;
namespace Distribution.Features.SupplierPayment;
public interface ISupplierPaymentRepository
{
Task<Models.SupplierPayment?> GetByIdAsync(Guid id);
Task<List<Models.SupplierPayment>> ListBySupplierAsync(Guid supplierId, int page, int pageSize);
Task<int> CountBySupplierAsync(Guid supplierId);
Task AddAsync(Models.SupplierPayment payment);
/// <summary>Sum of currently-allocated amounts to a GRN, across all active SupplierPayments.</summary>
Task<decimal> GetAllocatedTotalForGrnAsync(Guid goodsReceiptId);
/// <summary>Load a GRN with its items so the total can be computed.</summary>
Task<Models.GoodsReceipt?> GetGrnWithItemsAsync(Guid goodsReceiptId);
}
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.SupplierPayment;
[ApiController]
[Route("api/suppliers/{supplierId:guid}/payments")]
public class SupplierPaymentController(SupplierPaymentService service) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> List(Guid supplierId, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
{
var result = await service.ListBySupplierAsync(supplierId, page, pageSize);
return result is null ? NotFound() : Ok(result);
}
[HttpPost]
public async Task<IActionResult> Create(Guid supplierId, CreateSupplierPaymentRequest request, CancellationToken ct)
{
try
{
var result = await service.CreateAsync(supplierId, request, ct);
return result is null ? NotFound() : Ok(result);
}
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
}
}
@@ -0,0 +1,43 @@
using Distribution.Models;
namespace Distribution.Features.SupplierPayment;
public record SupplierPaymentAllocationRequest(
Guid GoodsReceiptId,
decimal AllocatedAmount);
public record CreateSupplierPaymentRequest(
decimal Amount,
PaymentMethod Method,
// One or more GRNs this payment covers. Sum of AllocatedAmount must equal Amount.
List<SupplierPaymentAllocationRequest> Allocations,
// Cash: required.
Guid? BankAccountId = null,
// Cheque: required — an Unused page from an Active cheque book.
Guid? ChequePageId = null,
string? Notes = null,
Guid? PaidByUserId = null);
public record SupplierPaymentAllocationResponse(
Guid GoodsReceiptId,
decimal AllocatedAmount);
public record SupplierPaymentResponse(
Guid Id,
Guid SupplierId,
decimal Amount,
PaymentMethod Method,
Guid? BankAccountId,
Guid? ChequePageId,
string? ChequeNo,
DateTime PaidAt,
Guid? PaidByUserId,
string? Notes,
List<SupplierPaymentAllocationResponse> Allocations,
DateTime CreatedAt);
public record PagedSupplierPaymentsResponse(
List<SupplierPaymentResponse> Items,
int Page,
int PageSize,
int Total);
@@ -0,0 +1,43 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.SupplierPayment;
public class SupplierPaymentRepository(AppDBContext context) : ISupplierPaymentRepository
{
public async Task<Models.SupplierPayment?> GetByIdAsync(Guid id) =>
await context.SupplierPayments
.Include(p => p.ChequePage)
.Include(p => p.Allocations)
.FirstOrDefaultAsync(p => p.Id == id);
public async Task<List<Models.SupplierPayment>> ListBySupplierAsync(Guid supplierId, int page, int pageSize) =>
await context.SupplierPayments
.Include(p => p.ChequePage)
.Include(p => p.Allocations)
.Where(p => p.FK_SupplierId == supplierId && p.IsActive)
.OrderByDescending(p => p.PaidAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
public async Task<int> CountBySupplierAsync(Guid supplierId) =>
await context.SupplierPayments
.CountAsync(p => p.FK_SupplierId == supplierId && p.IsActive);
public async Task AddAsync(Models.SupplierPayment payment) =>
await context.SupplierPayments.AddAsync(payment);
public async Task<decimal> GetAllocatedTotalForGrnAsync(Guid goodsReceiptId) =>
await context.SupplierPaymentGRNs
.Where(a => a.FK_GoodsReceiptId == goodsReceiptId
&& a.IsActive
&& a.SupplierPayment!.IsActive)
.SumAsync(a => (decimal?)a.AllocatedAmount) ?? 0m;
public async Task<Models.GoodsReceipt?> GetGrnWithItemsAsync(Guid goodsReceiptId) =>
await context.GoodsReceipts
.Include(g => g.Items)
.FirstOrDefaultAsync(g => g.Id == goodsReceiptId && g.IsActive);
}
@@ -0,0 +1,175 @@
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;
using Distribution.Infra.UoW;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.SupplierPayment;
public class SupplierPaymentService(
AppDBContext context,
ISupplierPaymentRepository repo,
ISupplierRepository supplierRepo,
IBankAccountRepository bankAccountRepo,
IChequeBookRepository chequeBookRepo,
IUnitOfWork uow,
ICurrentUserService currentUser,
GlPostingService gl)
{
private static SupplierPaymentResponse ToResponse(Models.SupplierPayment p) =>
new(p.Id, p.FK_SupplierId, p.Amount, p.Method,
p.FK_BankAccountId, p.FK_ChequePageId, p.ChequePage?.ChequeNo,
p.PaidAt, p.PaidByUserId, p.Notes,
p.Allocations
.Where(a => a.IsActive)
.Select(a => new SupplierPaymentAllocationResponse(a.FK_GoodsReceiptId, a.AllocatedAmount))
.ToList(),
p.CreatedAt);
/// <summary>GRN total = sum per line of (gross - discount + NBT + VAT). Rejected qty never costs.</summary>
private static decimal GrnTotal(Models.GoodsReceipt grn) =>
grn.Items.Where(i => i.IsActive).Sum(i =>
GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.NbtPercent, i.VatPercent).Total);
public async Task<PagedSupplierPaymentsResponse?> ListBySupplierAsync(Guid supplierId, int page, int pageSize)
{
var supplier = await supplierRepo.GetByIdAsync(supplierId);
if (supplier is null) return null;
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
if (pageSize > 200) pageSize = 200;
var payments = await repo.ListBySupplierAsync(supplierId, page, pageSize);
var total = await repo.CountBySupplierAsync(supplierId);
return new PagedSupplierPaymentsResponse(payments.Select(ToResponse).ToList(), page, pageSize, total);
}
public async Task<SupplierPaymentResponse?> CreateAsync(Guid supplierId, CreateSupplierPaymentRequest request, CancellationToken ct = default)
{
var supplier = await supplierRepo.GetByIdAsync(supplierId);
if (supplier is null) return null;
if (request.Amount <= 0)
throw new InvalidOperationException("Amount must be greater than zero");
if (request.Method == PaymentMethod.Credit)
throw new InvalidOperationException("Credit is not a valid outgoing payment method");
if (request.Allocations is null || request.Allocations.Count == 0)
throw new InvalidOperationException("At least one GRN allocation is required");
var duplicateGrn = request.Allocations.GroupBy(a => a.GoodsReceiptId).FirstOrDefault(g => g.Count() > 1);
if (duplicateGrn is not null)
throw new InvalidOperationException($"GRN {duplicateGrn.Key} is allocated more than once");
if (request.Allocations.Any(a => a.AllocatedAmount <= 0))
throw new InvalidOperationException("Each allocation amount must be greater than zero");
var allocationSum = request.Allocations.Sum(a => a.AllocatedAmount);
if (allocationSum != request.Amount)
throw new InvalidOperationException($"Allocations sum ({allocationSum}) must equal Amount ({request.Amount})");
// Validate each GRN exists, belongs to this supplier, and the new allocation does not push cumulative paid over the GRN total.
foreach (var alloc in request.Allocations)
{
var grn = await repo.GetGrnWithItemsAsync(alloc.GoodsReceiptId)
?? throw new InvalidOperationException($"GRN {alloc.GoodsReceiptId} not found");
var grnSupplierId = grn.FK_SupplierId
?? grn.PurchaseOrder?.FK_SupplierId;
if (grnSupplierId is null)
{
// PO-based GRN without the PO loaded — re-query to confirm supplier match.
var po = await context.PurchaseOrders.FindAsync([grn.FK_PurchaseOrderId], ct);
grnSupplierId = po?.FK_SupplierId;
}
if (grnSupplierId != supplierId)
throw new InvalidOperationException($"GRN {alloc.GoodsReceiptId} does not belong to the specified supplier");
var grnTotal = GrnTotal(grn);
var alreadyAllocated = await repo.GetAllocatedTotalForGrnAsync(grn.Id);
if (alreadyAllocated + alloc.AllocatedAmount > grnTotal)
throw new InvalidOperationException(
$"Allocation to GRN {grn.Id} ({alloc.AllocatedAmount}) exceeds outstanding balance " +
$"(GRN total {grnTotal}, already paid {alreadyAllocated})");
}
var payerId = currentUser.UserId ?? request.PaidByUserId;
var payment = new Models.SupplierPayment
{
FK_SupplierId = supplierId,
Amount = request.Amount,
Method = request.Method,
PaidAt = DateTime.Now,
PaidByUserId = payerId,
Notes = request.Notes,
Allocations = request.Allocations.Select(a => new Models.SupplierPaymentGRN
{
FK_GoodsReceiptId = a.GoodsReceiptId,
AllocatedAmount = a.AllocatedAmount
}).ToList()
};
await uow.BeginAsync(ct);
try
{
if (request.Method == PaymentMethod.Cash)
{
if (request.BankAccountId is null)
throw new InvalidOperationException("BankAccountId is required when Method is Cash");
var account = await bankAccountRepo.GetByIdAsync(request.BankAccountId.Value);
if (account is null)
throw new InvalidOperationException("BankAccountId does not reference a valid account");
if (account.IsActive is false)
throw new InvalidOperationException("The selected account is inactive");
payment.FK_BankAccountId = account.Id;
}
else // Cheque
{
if (request.ChequePageId is null)
throw new InvalidOperationException("ChequePageId is required when Method is Cheque");
var page = await context.ChequePages
.Include(p => p.ChequeBook)
.FirstOrDefaultAsync(p => p.Id == request.ChequePageId.Value, ct);
if (page is null)
throw new InvalidOperationException("ChequePageId does not reference a valid cheque page");
if (page.IssueStatus != ChequePageIssueStatus.Unused)
throw new InvalidOperationException($"Cannot issue a cheque in status '{page.IssueStatus}'");
if (page.ChequeBook is null || page.ChequeBook.Status != ChequeBookStatus.Active)
throw new InvalidOperationException("The cheque book is not active");
page.IssueStatus = ChequePageIssueStatus.Issued;
page.PayeeType = PayeeType.Supplier;
page.PayeeName = supplier.Name;
page.IssueDate = DateTime.Now;
page.Amount = request.Amount;
page.CurrencyCode = "LKR";
page.PrintedAt = DateTime.Now;
chequeBookRepo.UpdatePage(page);
payment.FK_ChequePageId = page.Id;
payment.ChequePage = page;
}
await repo.AddAsync(payment);
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);
}
}
@@ -31,13 +31,6 @@ public class UserController(UserService service) : ControllerBase
return result is null ? NotFound() : Ok(result);
}
[HttpPut("{id:guid}/role")]
public async Task<IActionResult> AssignRole(Guid id, AssignUserRoleRequest request, CancellationToken ct)
{
var result = await service.AssignRoleAsync(id, request, ct);
return result is null ? NotFound() : Ok(result);
}
[HttpPut("{id:guid}/scope")]
public async Task<IActionResult> AssignScope(Guid id, AssignUserScopeRequest request, CancellationToken ct)
{
+6 -1
View File
@@ -9,7 +9,12 @@ public record CreateUserRequest(
Guid RoleId,
Guid? ReportsToUserId);
public record UpdateUserRequest(string FullName);
public record UpdateUserRequest(
string FullName,
string? Username = null,
Guid? RoleId = null,
string? CurrentPassword = null,
string? NewPassword = null);
public record AssignUserRoleRequest(Guid RoleId);
+44 -17
View File
@@ -44,7 +44,7 @@ public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient a
email = request.Username,
fullname = request.FullName,
password = request.Password,
roleId = request.RoleId,
roleId = request.RoleId.ToString(),
userTypeId = "00000001-0000-0000-0000-200000000000",
sendCredentialsEmail = false
}, reference: user.Id.ToString(), ct: ct);
@@ -69,12 +69,52 @@ public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient a
{
var user = await repo.GetByIdAsync(id);
if (user is null || !user.IsActive) return null;
user.FullName = request.FullName;
if (!string.IsNullOrWhiteSpace(request.Username))
user.Username = request.Username;
if (request.RoleId.HasValue)
user.FK_RoleId = request.RoleId.Value;
user.UpdatedAt = DateTime.Now;
await uow.BeginAsync(ct);
try
{
repo.Update(user);
var hasUsernameChange = !string.IsNullOrWhiteSpace(request.Username);
var hasRoleChange = request.RoleId.HasValue;
if (hasUsernameChange || hasRoleChange)
{
var payload = new Dictionary<string, object> { ["userId"] = user.Id.ToString() };
if (hasUsernameChange) payload["userName"] = request.Username!;
if (hasRoleChange) payload["roleCode"] = request.RoleId!.Value.ToString();
var authResult = await authHex.CallAsync("UpdateUser", payload, reference: user.Id.ToString(), ct: ct);
if (!authResult.Success)
{
await uow.RollbackAsync(ct);
throw new InvalidOperationException($"AuthHex update failed: {authResult.Message}");
}
}
if (!string.IsNullOrWhiteSpace(request.CurrentPassword) && !string.IsNullOrWhiteSpace(request.NewPassword))
{
var authResult = await authHex.CallAsync("ChangeUserPassword", new
{
userId = user.Id.ToString(),
currentPassword = request.CurrentPassword,
newPassword = request.NewPassword
}, reference: user.Id.ToString(), ct: ct);
if (!authResult.Success)
{
await uow.RollbackAsync(ct);
throw new InvalidOperationException($"AuthHex password change failed: {authResult.Message}");
}
}
await uow.CommitAsync(ct);
}
catch
@@ -87,22 +127,9 @@ public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient a
public async Task<UserResponse?> AssignRoleAsync(Guid id, AssignUserRoleRequest request, CancellationToken ct = default)
{
var user = await repo.GetByIdAsync(id);
if (user is null || !user.IsActive) return null;
user.FK_RoleId = request.RoleId;
user.UpdatedAt = DateTime.Now;
await uow.BeginAsync(ct);
try
{
repo.Update(user);
await uow.CommitAsync(ct);
}
catch
{
await uow.RollbackAsync(ct);
throw;
}
return ToResponse(user);
return await UpdateAsync(id, new UpdateUserRequest(
FullName: (await repo.GetByIdAsync(id))?.FullName ?? string.Empty,
RoleId: request.RoleId), ct);
}
public async Task<bool> AssignScopeAsync(Guid id, AssignUserScopeRequest request, CancellationToken ct = default)
@@ -0,0 +1,21 @@
namespace Distribution.Features.Vehicle;
public interface IVehicleDashboardRepository
{
Task<List<VehicleBalanceRow>> GetStockOnBoardAsync(Guid vehicleId);
Task<List<VehicleMovementRow>> GetMovementsAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
Task<List<VehicleRideRow>> GetRidesAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
Task<VehicleRideRow?> GetOpenRideAsync(Guid vehicleId);
Task<List<VehicleTaskRow>> GetTasksAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
Task<List<VehicleCollectionRow>> GetCollectionsAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
Task<VehicleReturnTotals> GetReturnTotalsAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
Task<List<VehicleDriverRow>> GetDriversAsync(IEnumerable<Guid> driverProfileIds);
Task<VehicleNamedRef?> GetWarehouseRefAsync(Guid? warehouseId);
Task<VehicleNamedRef?> GetMobileWarehouseAsync(Guid vehicleId);
Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids);
Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids);
}
@@ -4,7 +4,7 @@ namespace Distribution.Features.Vehicle;
[ApiController]
[Route("api/vehicles")]
public class VehicleController(VehicleService service) : ControllerBase
public class VehicleController(VehicleService service, VehicleDashboardService dashboardService) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAll() =>
@@ -17,6 +17,15 @@ public class VehicleController(VehicleService service) : ControllerBase
return result is null ? NotFound() : Ok(result);
}
[HttpGet("{id:guid}/dashboard")]
public async Task<IActionResult> Dashboard(Guid id, [FromQuery] DateTime? from, [FromQuery] DateTime? to)
{
var error = VehicleDashboardService.Validate(from, to);
if (error is not null) return BadRequest(new { message = error });
var result = await dashboardService.GetAsync(id, from, to);
return result is null ? NotFound() : Ok(result);
}
[HttpPost]
public async Task<IActionResult> Create(CreateVehicleRequest request, CancellationToken ct)
{
@@ -0,0 +1,100 @@
using Distribution.Models;
namespace Distribution.Features.Vehicle;
// ── Repository projections ─────────────────────────────────────────────────
public record VehicleBalanceRow(
Guid ItemId, string Sku, string Name, string Category, string Uom,
decimal? SalePrice, decimal Quantity, DateTime UpdatedAt);
public record VehicleMovementRow(
Guid Id, Guid ItemId, string ItemName, string Sku,
Guid? FromLocationId, LocationType? FromLocationType,
Guid? ToLocationId, LocationType? ToLocationType,
decimal Quantity, MovementType Type, string? ReasonCode, Guid? ReferenceId,
DateTime CreatedAt, Guid CreatedBy);
public record VehicleRideRow(
Guid Id, RideStatus Status, Guid DriverProfileId, string DriverName,
int MeterStart, int MeterEnd, DateTime? StartedAt, DateTime? ClosedAt, DateTime CreatedAt,
CancelRequestType? CancelType, string? CancelReason,
int Tasks, int Delivered, int Failed);
public record VehicleTaskRow(
Guid RideId, DeliveryTaskStatus Status, Guid StoreId, string StoreName,
decimal DeliveredValue, DateTime Date);
public record VehicleCollectionRow(PaymentMethod Method, decimal Amount, int Count);
public record VehicleReturnTotals(int ReturnCount, decimal CreditValue, decimal Units);
public record VehicleDriverRow(
Guid Id, string Name, string LicenseNumber, DateTime LicenseExpiry, DateTime MedicalClearanceExpiry, bool IsActive);
public record VehicleNamedRef(Guid Id, string Name);
// ── Response ───────────────────────────────────────────────────────────────
public record VehicleDashboardPeriod(DateTime From, DateTime To);
public record VehicleDashboardKpis(
int Rides, int ClosedRides, int CancelledRides, decimal DistanceKm,
int Deliveries, int DeliveredCount, int FailedCount, int PendingCount, decimal SuccessRatePercent,
decimal DeliveredValue, decimal Collections,
decimal StockOnBoardUnits, decimal StockOnBoardValue,
decimal UnitsLoaded, decimal UnitsSold);
public record VehicleDashboardStockRow(
Guid ItemId, string Sku, string Name, string Category, string Uom,
decimal Quantity, decimal Value, DateTime UpdatedAt);
public record VehicleDashboardRide(
Guid Id, RideStatus Status, string DriverName, DateTime? StartedAt, DateTime? ClosedAt, DateTime CreatedAt,
decimal DistanceKm, int Tasks, int Delivered, int Failed, CancelRequestType? CancelType, string? CancelReason);
public record VehicleDashboardDailyDelivery(string Date, int Delivered, int Failed);
public record VehicleDashboardDailyMovement(string Date, decimal In, decimal Out);
public record VehicleDashboardMovementType(MovementType Type, string Direction, decimal Units, int Count);
public record VehicleDashboardLedgerRow(
Guid Id, DateTime CreatedAt, Guid ItemId, string ItemName, string Sku,
MovementType Type, string Direction, decimal Quantity,
string FromName, string ToName, Guid? ReferenceId, string? ReferenceKind,
string? ReasonCode, string CreatedByName);
public record VehicleDashboardCancelReason(CancelRequestType Type, int Count);
public record VehicleDashboardCollection(PaymentMethod Method, decimal Amount, int Count);
public record VehicleDashboardReturns(int ReturnCount, decimal CreditValue, decimal ReturnedUnits, decimal CustomerReturnUnitsOnVehicle);
public record VehicleDashboardTopStore(Guid StoreId, string StoreName, int Deliveries, decimal Value);
public record VehicleDashboardDriver(
Guid Id, string Name, string LicenseNumber, DateTime LicenseExpiry, DateTime MedicalClearanceExpiry,
bool IsActive, int Rides, decimal DistanceKm, DateTime? LastRideAt);
public record VehicleDashboardCurrentRide(
Guid Id, string DriverName, DateTime? StartedAt, int MeterStart, int Tasks, int Delivered, int Failed);
public record VehicleDashboardResponse(
VehicleResponse Vehicle,
VehicleNamedRef? DefaultWarehouse,
VehicleNamedRef? MobileWarehouse,
VehicleDashboardCurrentRide? CurrentRide,
VehicleDashboardPeriod Period,
VehicleDashboardKpis Kpis,
List<VehicleDashboardStockRow> StockOnBoard,
List<VehicleDashboardRide> Rides,
List<VehicleDashboardDailyDelivery> DailyDeliveries,
List<VehicleDashboardDailyMovement> DailyMovement,
List<VehicleDashboardMovementType> MovementByType,
List<VehicleDashboardLedgerRow> Ledger,
List<VehicleDashboardCancelReason> CancelReasons,
List<VehicleDashboardCollection> CollectionsByMethod,
VehicleDashboardReturns Returns,
List<VehicleDashboardTopStore> TopStores,
List<VehicleDashboardDriver> Drivers);
@@ -0,0 +1,130 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Vehicle;
public class VehicleDashboardRepository(AppDBContext db) : IVehicleDashboardRepository
{
public Task<List<VehicleBalanceRow>> GetStockOnBoardAsync(Guid vehicleId) =>
db.StockBalances.AsNoTracking()
.Where(b => b.LocationType == LocationType.Vehicle && b.LocationId == vehicleId && b.Quantity > 0)
.Select(b => new VehicleBalanceRow(
b.FK_ItemId, b.Item.Sku, b.Item.Name, b.Item.Category.Name, b.Item.BaseUom.Name,
b.Item.SalePrice, b.Quantity, b.UpdatedAt))
.ToListAsync();
public Task<List<VehicleMovementRow>> GetMovementsAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
db.StockMovements.AsNoTracking()
.Where(m => (m.FromLocationId == vehicleId || m.ToLocationId == vehicleId)
&& m.CreatedAt >= from && m.CreatedAt < toExclusive)
.OrderByDescending(m => m.CreatedAt)
.Select(m => new VehicleMovementRow(
m.Id, m.FK_ItemId, m.Item.Name, m.Item.Sku,
m.FromLocationId, m.FromLocationType, m.ToLocationId, m.ToLocationType,
m.Quantity, m.Type, m.ReasonCode, m.ReferenceId, m.CreatedAt, m.CreatedBy))
.ToListAsync();
public Task<List<VehicleRideRow>> GetRidesAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
db.DeliveryRuns.AsNoTracking()
.Where(r => r.FK_VehicleId == vehicleId
&& (r.StartedAt ?? r.CreatedAt) >= from && (r.StartedAt ?? r.CreatedAt) < toExclusive)
.OrderByDescending(r => r.StartedAt ?? r.CreatedAt)
.Select(r => new VehicleRideRow(
r.Id, r.Status, r.FK_DriverProfileId, r.DriverProfile.User.FullName,
r.Meterstart, r.Meterend, r.StartedAt, r.ClosedAt, r.CreatedAt,
r.CancelTypeCode, r.CancelReason,
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id),
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id
&& (t.Status == DeliveryTaskStatus.Delivered || t.Status == DeliveryTaskStatus.PartiallyDelivered)),
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id && t.Status == DeliveryTaskStatus.Failed)))
.ToListAsync();
public Task<VehicleRideRow?> GetOpenRideAsync(Guid vehicleId) =>
db.DeliveryRuns.AsNoTracking()
.Where(r => r.FK_VehicleId == vehicleId && r.Status == RideStatus.Open)
.Select(r => new VehicleRideRow(
r.Id, r.Status, r.FK_DriverProfileId, r.DriverProfile.User.FullName,
r.Meterstart, r.Meterend, r.StartedAt, r.ClosedAt, r.CreatedAt,
r.CancelTypeCode, r.CancelReason,
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id),
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id
&& (t.Status == DeliveryTaskStatus.Delivered || t.Status == DeliveryTaskStatus.PartiallyDelivered)),
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id && t.Status == DeliveryTaskStatus.Failed)))
.FirstOrDefaultAsync();
public Task<List<VehicleTaskRow>> GetTasksAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
db.DeliveryTasks.AsNoTracking()
.Where(t => t.Ride.FK_VehicleId == vehicleId
&& (t.Ride.StartedAt ?? t.Ride.CreatedAt) >= from && (t.Ride.StartedAt ?? t.Ride.CreatedAt) < toExclusive)
.Select(t => new VehicleTaskRow(
t.FK_RideId, t.Status, t.ShopPO.FK_StoreId, t.ShopPO.Store.StoreName,
t.ShopPO.Items.Sum(i => i.QuantityFulfilled * i.UnitPrice),
t.Ride.StartedAt ?? t.Ride.CreatedAt))
.ToListAsync();
public Task<List<VehicleCollectionRow>> GetCollectionsAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
db.Payments.AsNoTracking()
.Where(p => p.CollectedAt >= from && p.CollectedAt < toExclusive
&& db.DeliveryTasks.Any(t => t.Ride.FK_VehicleId == vehicleId && t.FK_ShopPOId == p.Invoice.FK_ShopPOId))
.GroupBy(p => p.Method)
.Select(g => new VehicleCollectionRow(g.Key, g.Sum(p => p.Amount), g.Count()))
.ToListAsync();
public async Task<VehicleReturnTotals> GetReturnTotalsAsync(Guid vehicleId, DateTime from, DateTime toExclusive)
{
var returns = db.SaleReturns.AsNoTracking()
.Where(r => r.CreatedAt >= from && r.CreatedAt < toExclusive
&& db.DeliveryTasks.Any(t => t.Ride.FK_VehicleId == vehicleId && t.FK_ShopPOId == r.FK_ShopPOId));
var count = await returns.CountAsync();
var credit = await returns.SumAsync(r => (decimal?)r.TotalCreditAmount) ?? 0m;
var units = await returns.SelectMany(r => r.Items).SumAsync(i => (decimal?)i.Quantity) ?? 0m;
return new VehicleReturnTotals(count, credit, units);
}
public Task<List<VehicleDriverRow>> GetDriversAsync(IEnumerable<Guid> driverProfileIds)
{
var ids = driverProfileIds.Distinct().ToList();
return db.DriverProfiles.AsNoTracking()
.Where(d => ids.Contains(d.Id))
.Select(d => new VehicleDriverRow(
d.Id, d.User.FullName, d.LicenseNumber, d.LicenseExpiry, d.MedicalClearanceExpiry, d.IsActive))
.ToListAsync();
}
public Task<VehicleNamedRef?> GetWarehouseRefAsync(Guid? warehouseId) =>
warehouseId is null
? Task.FromResult<VehicleNamedRef?>(null)
: db.Warehouses.AsNoTracking()
.Where(w => w.Id == warehouseId)
.Select(w => new VehicleNamedRef(w.Id, w.Name))
.FirstOrDefaultAsync();
public Task<VehicleNamedRef?> GetMobileWarehouseAsync(Guid vehicleId) =>
db.Warehouses.AsNoTracking()
.Where(w => w.VehicleId == vehicleId)
.Select(w => new VehicleNamedRef(w.Id, w.Name))
.FirstOrDefaultAsync();
public async Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids)
{
var idList = ids.Distinct().ToList();
var names = new Dictionary<Guid, string>();
if (idList.Count == 0) return names;
foreach (var w in await db.Warehouses.AsNoTracking().Where(w => idList.Contains(w.Id)).Select(w => new { w.Id, w.Name }).ToListAsync())
names[w.Id] = w.Name;
foreach (var v in await db.Vehicles.AsNoTracking().Where(v => idList.Contains(v.Id)).Select(v => new { v.Id, v.RegNumber }).ToListAsync())
names[v.Id] = v.RegNumber;
return names;
}
public async Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids)
{
var idList = ids.Distinct().ToList();
if (idList.Count == 0) return [];
return await db.Users.AsNoTracking()
.Where(u => idList.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName);
}
}
@@ -0,0 +1,206 @@
using Distribution.Models;
namespace Distribution.Features.Vehicle;
public class VehicleDashboardService(IVehicleRepository vehicleRepo, IVehicleDashboardRepository repo)
{
private const int LedgerSize = 50;
private const int RideListSize = 15;
private const int TopListSize = 5;
private const int MaxPeriodDays = 366;
public static string? Validate(DateTime? from, DateTime? to)
{
if (from.HasValue && to.HasValue)
{
if (from.Value.Date > to.Value.Date) return "'from' must not be after 'to'.";
if ((to.Value.Date - from.Value.Date).TotalDays >= MaxPeriodDays) return $"The period cannot exceed {MaxPeriodDays} days.";
}
return null;
}
/// <summary>Unlike the standard vehicle lookup, this also serves inactive vehicles so their history stays viewable.</summary>
public async Task<VehicleDashboardResponse?> GetAsync(Guid id, DateTime? from, DateTime? to)
{
var vehicle = await vehicleRepo.GetByIdAsync(id);
if (vehicle is null) return null;
var toDate = (to ?? DateTime.Now).Date;
var fromDate = (from ?? toDate.AddDays(-29)).Date;
var toExclusive = toDate.AddDays(1);
var balances = await repo.GetStockOnBoardAsync(id);
var movements = (await repo.GetMovementsAsync(id, fromDate, toExclusive))
.Select(m => (Row: m, Direction: DirectionOf(m, id)))
.Where(x => x.Direction is not null)
.ToList();
var rides = await repo.GetRidesAsync(id, fromDate, toExclusive);
var tasks = await repo.GetTasksAsync(id, fromDate, toExclusive);
var collections = await repo.GetCollectionsAsync(id, fromDate, toExclusive);
var returnTotals = await repo.GetReturnTotalsAsync(id, fromDate, toExclusive);
var openRide = await repo.GetOpenRideAsync(id);
// ── Stock on board ─────────────────────────────────────────────────
var stock = balances
.GroupBy(b => b.ItemId)
.Select(g =>
{
var f = g.First();
var qty = g.Sum(x => x.Quantity);
return new VehicleDashboardStockRow(
f.ItemId, f.Sku, f.Name, f.Category, f.Uom, qty, qty * (f.SalePrice ?? 0m), g.Max(x => x.UpdatedAt));
})
.OrderBy(r => r.Name)
.ToList();
// ── Rides and deliveries ───────────────────────────────────────────
static decimal Km(VehicleRideRow r) =>
r.Status == RideStatus.Closed && r.MeterEnd > r.MeterStart ? r.MeterEnd - r.MeterStart : 0m;
var rideRows = rides.Take(RideListSize)
.Select(r => new VehicleDashboardRide(
r.Id, r.Status, r.DriverName, r.StartedAt, r.ClosedAt, r.CreatedAt,
Km(r), r.Tasks, r.Delivered, r.Failed, r.CancelType, r.CancelReason))
.ToList();
bool IsDelivered(VehicleTaskRow t) =>
t.Status is DeliveryTaskStatus.Delivered or DeliveryTaskStatus.PartiallyDelivered;
var delivered = tasks.Count(IsDelivered);
var failed = tasks.Count(t => t.Status == DeliveryTaskStatus.Failed);
var pending = tasks.Count(t => t.Status == DeliveryTaskStatus.Pending);
var deliveredValue = tasks.Where(IsDelivered).Sum(t => t.DeliveredValue);
var dailyDeliveries = new List<VehicleDashboardDailyDelivery>();
var dailyMovement = new List<VehicleDashboardDailyMovement>();
var tasksByDay = tasks.GroupBy(t => t.Date.Date).ToDictionary(g => g.Key);
var movesByDay = movements.GroupBy(x => x.Row.CreatedAt.Date).ToDictionary(g => g.Key);
for (var d = fromDate; d <= toDate; d = d.AddDays(1))
{
var key = d.ToString("yyyy-MM-dd");
tasksByDay.TryGetValue(d, out var dayTasks);
movesByDay.TryGetValue(d, out var dayMoves);
dailyDeliveries.Add(new VehicleDashboardDailyDelivery(
key, dayTasks?.Count(IsDelivered) ?? 0, dayTasks?.Count(t => t.Status == DeliveryTaskStatus.Failed) ?? 0));
dailyMovement.Add(new VehicleDashboardDailyMovement(
key,
dayMoves?.Where(x => x.Direction == "In").Sum(x => x.Row.Quantity) ?? 0m,
dayMoves?.Where(x => x.Direction == "Out").Sum(x => x.Row.Quantity) ?? 0m));
}
var cancelReasons = rides
.Where(r => r.Status == RideStatus.Cancelled && r.CancelType.HasValue)
.GroupBy(r => r.CancelType!.Value)
.Select(g => new VehicleDashboardCancelReason(g.Key, g.Count()))
.OrderByDescending(c => c.Count)
.ToList();
var topStores = tasks
.Where(IsDelivered)
.GroupBy(t => (t.StoreId, t.StoreName))
.Select(g => new VehicleDashboardTopStore(g.Key.StoreId, g.Key.StoreName, g.Count(), g.Sum(t => t.DeliveredValue)))
.OrderByDescending(s => s.Value)
.Take(TopListSize)
.ToList();
// ── Movements ──────────────────────────────────────────────────────
var byType = movements
.GroupBy(x => (x.Row.Type, x.Direction))
.Select(g => new VehicleDashboardMovementType(g.Key.Type, g.Key.Direction!, g.Sum(x => x.Row.Quantity), g.Count()))
.OrderByDescending(r => r.Units)
.ToList();
var ledgerSource = movements.Take(LedgerSize).ToList();
var locationIds = ledgerSource
.SelectMany(x => new[]
{
(x.Row.FromLocationId, x.Row.FromLocationType),
(x.Row.ToLocationId, x.Row.ToLocationType),
})
.Where(l => l.Item1.HasValue && l.Item2 is LocationType t && t != LocationType.External)
.Select(l => l.Item1!.Value);
var locationNames = await repo.GetLocationNamesAsync(locationIds);
var userNames = await repo.GetUserNamesAsync(ledgerSource.Select(x => x.Row.CreatedBy));
var ledger = ledgerSource.Select(x => new VehicleDashboardLedgerRow(
x.Row.Id, x.Row.CreatedAt, x.Row.ItemId, x.Row.ItemName, x.Row.Sku,
x.Row.Type, x.Direction!, x.Row.Quantity,
SideName(x.Row.Type, x.Row.FromLocationId, x.Row.FromLocationType, locationNames, isFrom: true),
SideName(x.Row.Type, x.Row.ToLocationId, x.Row.ToLocationType, locationNames, isFrom: false),
x.Row.ReferenceId, ReferenceKind(x.Row.Type),
x.Row.ReasonCode, userNames.GetValueOrDefault(x.Row.CreatedBy, "—"))).ToList();
// ── Drivers ────────────────────────────────────────────────────────
var driverProfiles = (await repo.GetDriversAsync(rides.Select(r => r.DriverProfileId)))
.ToDictionary(d => d.Id);
var drivers = rides
.GroupBy(r => r.DriverProfileId)
.Where(g => driverProfiles.ContainsKey(g.Key))
.Select(g =>
{
var d = driverProfiles[g.Key];
return new VehicleDashboardDriver(
d.Id, d.Name, d.LicenseNumber, d.LicenseExpiry, d.MedicalClearanceExpiry, d.IsActive,
g.Count(), g.Sum(Km), g.Max(r => r.StartedAt ?? r.CreatedAt));
})
.OrderByDescending(d => d.Rides)
.ToList();
// ── Assemble ───────────────────────────────────────────────────────
var settled = delivered + failed;
var kpis = new VehicleDashboardKpis(
rides.Count, rides.Count(r => r.Status == RideStatus.Closed), rides.Count(r => r.Status == RideStatus.Cancelled),
rides.Sum(Km),
tasks.Count, delivered, failed, pending,
settled == 0 ? 0m : Math.Round(delivered * 100m / settled, 1),
deliveredValue, collections.Sum(c => c.Amount),
stock.Sum(r => r.Quantity), stock.Sum(r => r.Value),
movements.Where(x => x.Row.Type == MovementType.Load && x.Direction == "In").Sum(x => x.Row.Quantity),
movements.Where(x => x.Row.Type == MovementType.Sale && x.Direction == "Out").Sum(x => x.Row.Quantity));
var customerReturnUnits = movements
.Where(x => x.Row.Type == MovementType.CustomerReturn && x.Direction == "In")
.Sum(x => x.Row.Quantity);
return new VehicleDashboardResponse(
new VehicleResponse(vehicle.Id, vehicle.RegNumber, vehicle.NumberPlate, vehicle.Type, vehicle.IsActive, vehicle.CreatedAt),
await repo.GetWarehouseRefAsync(vehicle.DefaultWarehouseId),
await repo.GetMobileWarehouseAsync(id),
openRide is null ? null : new VehicleDashboardCurrentRide(
openRide.Id, openRide.DriverName, openRide.StartedAt, openRide.MeterStart,
openRide.Tasks, openRide.Delivered, openRide.Failed),
new VehicleDashboardPeriod(fromDate, toDate),
kpis, stock, rideRows, dailyDeliveries, dailyMovement, byType, ledger, cancelReasons,
collections.Select(c => new VehicleDashboardCollection(c.Method, c.Amount, c.Count))
.OrderByDescending(c => c.Amount).ToList(),
new VehicleDashboardReturns(returnTotals.ReturnCount, returnTotals.CreditValue, returnTotals.Units, customerReturnUnits),
topStores, drivers);
}
private static string? DirectionOf(VehicleMovementRow m, Guid vehicleId) =>
m.ToLocationId == vehicleId && m.FromLocationId != vehicleId ? "In"
: m.FromLocationId == vehicleId && m.ToLocationId != vehicleId ? "Out"
: null;
private static string? ReferenceKind(MovementType type) => type switch
{
MovementType.Load => "Loading Sheet",
MovementType.Sale => "Order",
MovementType.CustomerReturn => "Return",
_ => null,
};
private static string SideName(
MovementType type, Guid? id, LocationType? locationType, Dictionary<Guid, string> names, bool isFrom)
{
if (id is not Guid locId || locationType is not LocationType lt) return "—";
if (lt != LocationType.External) return names.GetValueOrDefault(locId, "—");
return type switch
{
MovementType.Sale or MovementType.CustomerReturn => "Customer",
MovementType.PurchaseReceipt or MovementType.PurchaseReturn => "Supplier",
MovementType.Damage => "Written Off",
MovementType.Adjustment => "Stock Adjustment",
_ => isFrom ? "External Source" : "External Destination",
};
}
}
@@ -0,0 +1,23 @@
namespace Distribution.Features.Warehouse;
public interface IWarehouseDashboardRepository
{
Task<List<WarehouseBalanceRow>> GetBalanceRowsAsync(Guid warehouseId);
Task<List<WarehouseMovementRow>> GetMovementsAsync(Guid warehouseId, DateTime from, DateTime toExclusive);
Task<List<WarehouseLastOutboundRow>> GetLastOutboundAsync(Guid warehouseId);
Task<WarehouseGrnTotals> GetGrnTotalsAsync(Guid warehouseId, DateTime from, DateTime toExclusive);
Task<List<DashboardGrnRow>> GetRecentGrnsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take);
Task<List<DashboardLoadingSheetRow>> GetRecentLoadingSheetsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take);
Task<decimal> GetPendingDispatchUnitsAsync(Guid warehouseId);
Task<List<WarehouseVehicleStockRow>> GetVehicleStockAsync(Guid warehouseId, Guid? primaryVehicleId);
Task<List<DashboardLinkedVehicle>> GetDefaultVehiclesAsync(Guid warehouseId, Guid? primaryVehicleId);
Task<int> GetOpenClaimCountAsync(Guid warehouseId);
Task<List<DashboardClaimRow>> GetOpenClaimsAsync(Guid warehouseId, int take);
Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids);
Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids);
}
@@ -4,7 +4,7 @@ namespace Distribution.Features.Warehouse;
[ApiController]
[Route("api/warehouses")]
public class WarehouseController(WarehouseService service) : ControllerBase
public class WarehouseController(WarehouseService service, WarehouseDashboardService dashboardService) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAll() =>
@@ -17,6 +17,16 @@ public class WarehouseController(WarehouseService service) : ControllerBase
return result is null ? NotFound() : Ok(result);
}
[HttpGet("{id:guid}/dashboard")]
public async Task<IActionResult> Dashboard(
Guid id, [FromQuery] DateTime? from, [FromQuery] DateTime? to, [FromQuery] int deadStockDays = 30)
{
var error = WarehouseDashboardService.Validate(from, to, deadStockDays);
if (error is not null) return BadRequest(new { message = error });
var result = await dashboardService.GetAsync(id, from, to, deadStockDays);
return result is null ? NotFound() : Ok(result);
}
[HttpPost]
public async Task<IActionResult> Create(CreateWarehouseRequest request, CancellationToken ct)
{
@@ -0,0 +1,105 @@
using Distribution.Models;
namespace Distribution.Features.Warehouse;
// ── Repository projections ─────────────────────────────────────────────────
public record WarehouseBalanceRow(
Guid ItemId, string Sku, string Name, string Category, string? Brand, string Uom,
decimal? ReorderLevel, decimal? CostPrice, decimal? SalePrice,
Guid? BatchId, string? BatchNumber, DateTime? ExpiryDate,
decimal Quantity, DateTime UpdatedAt);
public record WarehouseMovementRow(
Guid Id, Guid ItemId, string ItemName, string Sku,
Guid? FromLocationId, LocationType? FromLocationType,
Guid? ToLocationId, LocationType? ToLocationType,
decimal Quantity, MovementType Type, string? ReasonCode, Guid? ReferenceId,
DateTime CreatedAt, Guid CreatedBy);
public record WarehouseLastOutboundRow(Guid ItemId, DateTime LastOutboundAt);
public record WarehouseGrnTotals(int Count, int Accepted, int Rejected);
public record WarehouseVehicleStockRow(
Guid VehicleId, string RegNumber, string NumberPlate, bool IsLinked, decimal Units, int Skus);
// ── Response ───────────────────────────────────────────────────────────────
public record DashboardPeriod(DateTime From, DateTime To);
public record DashboardKpis(
decimal StockValueCost, decimal StockValueSale, decimal TotalUnits, int SkuCount,
int LowStockCount, int OutOfStockCount, int NearExpiryCount, int ExpiredCount,
decimal UnitsIn, decimal UnitsOut);
public record DashboardBatch(Guid BatchId, string? BatchNumber, DateTime? ExpiryDate, decimal Quantity);
public record DashboardStockRow(
Guid ItemId, string Sku, string Name, string Category, string? Brand, string Uom,
decimal Quantity, decimal? ReorderLevel, string Status,
decimal CostValue, decimal SaleValue, DateTime UpdatedAt, List<DashboardBatch> Batches);
public record DashboardCategoryStock(string Category, decimal Units, decimal Value);
public record DashboardExpiringBatch(
Guid ItemId, string ItemName, string? BatchNumber, DateTime ExpiryDate, decimal Quantity, string Status);
public record DashboardDailyPoint(string Date, decimal In, decimal Out);
public record DashboardMovementTypeRow(MovementType Type, string Direction, decimal Units, int Count);
public record DashboardLedgerRow(
Guid Id, DateTime CreatedAt, Guid ItemId, string ItemName, string Sku,
MovementType Type, string Direction, decimal Quantity,
string FromName, string ToName, Guid? ReferenceId, string? ReferenceKind,
string? ReasonCode, string CreatedByName);
public record DashboardGrnRow(
Guid Id, DateTime ReceivedAt, Guid? PurchaseOrderId, string? SupplierName,
int AcceptedQuantity, int RejectedQuantity, decimal CostValue);
public record DashboardInbound(
int GrnCount, int AcceptedUnits, int RejectedUnits, decimal RejectionRatePercent, List<DashboardGrnRow> RecentGrns);
public record DashboardLoadingSheetRow(
Guid Id, Guid RideId, string VehiclePlate, LoadingSheetStatus Status,
DateTime PopulatedAt, DateTime? ConfirmedAt, int Lines, decimal Units);
public record DashboardOutbound(
decimal UnitsDispatched, decimal PendingDispatchUnits, List<DashboardLoadingSheetRow> RecentLoadingSheets);
public record DashboardVehicle(
Guid VehicleId, string RegNumber, string NumberPlate, bool IsLinked, decimal Units, int Skus);
public record DashboardClaimRow(Guid Id, DateTime CreatedAt, string? SupplierName, string Details, decimal Units);
public record DashboardLosses(
decimal PurchaseReturnUnits, decimal DamageUnits, decimal NegativeAdjustmentUnits,
decimal PositiveAdjustmentUnits, int OpenClaimCount, List<DashboardClaimRow> OpenClaims);
public record DashboardTopMover(Guid ItemId, string Sku, string Name, decimal UnitsOut);
public record DashboardDeadStock(
Guid ItemId, string Sku, string Name, decimal Quantity, decimal CostValue, DateTime? LastOutboundAt);
public record DashboardLinkedVehicle(Guid Id, string RegNumber, string NumberPlate, bool IsPrimary);
public record WarehouseDashboardResponse(
WarehouseResponse Warehouse,
List<DashboardLinkedVehicle> LinkedVehicles,
DashboardPeriod Period,
int DeadStockDays,
DashboardKpis Kpis,
List<DashboardStockRow> Stock,
List<DashboardCategoryStock> StockByCategory,
List<DashboardExpiringBatch> ExpiringBatches,
List<DashboardDailyPoint> DailyMovement,
List<DashboardMovementTypeRow> MovementByType,
List<DashboardLedgerRow> Ledger,
DashboardInbound Inbound,
DashboardOutbound Outbound,
List<DashboardVehicle> Vehicles,
DashboardLosses Losses,
List<DashboardTopMover> TopMovers,
List<DashboardDeadStock> DeadStock);
@@ -0,0 +1,160 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Warehouse;
public class WarehouseDashboardRepository(AppDBContext db) : IWarehouseDashboardRepository
{
public Task<List<WarehouseBalanceRow>> GetBalanceRowsAsync(Guid warehouseId) =>
db.StockBalances.AsNoTracking()
.Where(b => b.LocationType == LocationType.Warehouse && b.LocationId == warehouseId)
.Select(b => new WarehouseBalanceRow(
b.FK_ItemId, b.Item.Sku, b.Item.Name, b.Item.Category.Name,
b.Item.Brand != null ? b.Item.Brand.Name : null, b.Item.BaseUom.Name,
b.Item.ReorderLevel, b.Item.CostPrice, b.Item.SalePrice,
b.FK_ItemBatchId,
b.ItemBatch != null ? b.ItemBatch.BatchNumber : null,
b.ItemBatch != null ? b.ItemBatch.ExpiryDate : null,
b.Quantity, b.UpdatedAt))
.ToListAsync();
public Task<List<WarehouseMovementRow>> GetMovementsAsync(Guid warehouseId, DateTime from, DateTime toExclusive) =>
db.StockMovements.AsNoTracking()
.Where(m => (m.FromLocationId == warehouseId || m.ToLocationId == warehouseId)
&& m.CreatedAt >= from && m.CreatedAt < toExclusive)
.OrderByDescending(m => m.CreatedAt)
.Select(m => new WarehouseMovementRow(
m.Id, m.FK_ItemId, m.Item.Name, m.Item.Sku,
m.FromLocationId, m.FromLocationType, m.ToLocationId, m.ToLocationType,
m.Quantity, m.Type, m.ReasonCode, m.ReferenceId, m.CreatedAt, m.CreatedBy))
.ToListAsync();
public Task<List<WarehouseLastOutboundRow>> GetLastOutboundAsync(Guid warehouseId) =>
db.StockMovements.AsNoTracking()
.Where(m => m.FromLocationId == warehouseId && m.ToLocationId != warehouseId)
.GroupBy(m => m.FK_ItemId)
.Select(g => new WarehouseLastOutboundRow(g.Key, g.Max(x => x.CreatedAt)))
.ToListAsync();
public async Task<WarehouseGrnTotals> GetGrnTotalsAsync(Guid warehouseId, DateTime from, DateTime toExclusive)
{
var receipts = db.GoodsReceipts.AsNoTracking()
.Where(g => g.FK_WarehouseId == warehouseId && g.ReceivedAt >= from && g.ReceivedAt < toExclusive);
var count = await receipts.CountAsync();
var accepted = await receipts.SelectMany(g => g.Items).SumAsync(i => (int?)i.AcceptedQuantity) ?? 0;
var rejected = await receipts.SelectMany(g => g.Items).SumAsync(i => (int?)i.RejectedQuantity) ?? 0;
return new WarehouseGrnTotals(count, accepted, rejected);
}
public Task<List<DashboardGrnRow>> GetRecentGrnsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take) =>
db.GoodsReceipts.AsNoTracking()
.Where(g => g.FK_WarehouseId == warehouseId && g.ReceivedAt >= from && g.ReceivedAt < toExclusive)
.OrderByDescending(g => g.ReceivedAt)
.Take(take)
.Select(g => new DashboardGrnRow(
g.Id, g.ReceivedAt, g.FK_PurchaseOrderId,
g.PurchaseOrder != null ? g.PurchaseOrder.Supplier.Name : (g.Supplier != null ? g.Supplier.Name : null),
g.Items.Sum(i => i.AcceptedQuantity),
g.Items.Sum(i => i.RejectedQuantity),
g.Items.Sum(i => i.AcceptedQuantity * i.UnitCost)))
.ToListAsync();
public Task<List<DashboardLoadingSheetRow>> GetRecentLoadingSheetsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take) =>
db.LoadingSheets.AsNoTracking()
.Where(s => s.PopulatedAt >= from && s.PopulatedAt < toExclusive
&& s.Items.Any(i => i.FK_WarehouseId == warehouseId))
.OrderByDescending(s => s.PopulatedAt)
.Take(take)
.Select(s => new DashboardLoadingSheetRow(
s.Id, s.FK_RideId, s.Ride.Vehicle.NumberPlate, s.Status, s.PopulatedAt, s.ConfirmedAt,
s.Items.Count(i => i.FK_WarehouseId == warehouseId),
s.Items.Where(i => i.FK_WarehouseId == warehouseId).Sum(i => i.QuantityDispatched)))
.ToListAsync();
public async Task<decimal> GetPendingDispatchUnitsAsync(Guid warehouseId) =>
await db.LoadingSheetItems.AsNoTracking()
.Where(i => i.FK_WarehouseId == warehouseId && i.LoadingSheet.Status == LoadingSheetStatus.Planned)
.SumAsync(i => (decimal?)i.QuantityDispatched) ?? 0m;
public async Task<List<WarehouseVehicleStockRow>> GetVehicleStockAsync(Guid warehouseId, Guid? primaryVehicleId)
{
var loadedFromHere = db.LoadingSheetItems
.Where(i => i.FK_WarehouseId == warehouseId)
.Select(i => i.LoadingSheet.Ride.FK_VehicleId);
var vehicles = await db.Vehicles.AsNoTracking()
.Where(v => v.IsActive
&& (v.DefaultWarehouseId == warehouseId
|| (primaryVehicleId != null && v.Id == primaryVehicleId)
|| loadedFromHere.Contains(v.Id)))
.Select(v => new
{
v.Id, v.RegNumber, v.NumberPlate,
IsLinked = v.DefaultWarehouseId == warehouseId || (primaryVehicleId != null && v.Id == primaryVehicleId),
})
.ToListAsync();
var ids = vehicles.Select(v => v.Id).ToList();
var balances = await db.StockBalances.AsNoTracking()
.Where(b => b.LocationType == LocationType.Vehicle && ids.Contains(b.LocationId) && b.Quantity > 0)
.GroupBy(b => b.LocationId)
.Select(g => new { VehicleId = g.Key, Units = g.Sum(x => x.Quantity), Skus = g.Select(x => x.FK_ItemId).Distinct().Count() })
.ToListAsync();
return vehicles
.Select(v =>
{
var bal = balances.FirstOrDefault(b => b.VehicleId == v.Id);
return new WarehouseVehicleStockRow(v.Id, v.RegNumber, v.NumberPlate, v.IsLinked, bal?.Units ?? 0m, bal?.Skus ?? 0);
})
.OrderByDescending(v => v.Units)
.ToList();
}
public Task<List<DashboardLinkedVehicle>> GetDefaultVehiclesAsync(Guid warehouseId, Guid? primaryVehicleId) =>
db.Vehicles.AsNoTracking()
.Where(v => v.IsActive
&& (v.DefaultWarehouseId == warehouseId || (primaryVehicleId != null && v.Id == primaryVehicleId)))
.Select(v => new DashboardLinkedVehicle(
v.Id, v.RegNumber, v.NumberPlate, primaryVehicleId != null && v.Id == primaryVehicleId))
.ToListAsync();
public Task<int> GetOpenClaimCountAsync(Guid warehouseId) =>
db.SupplierClaims.AsNoTracking()
.CountAsync(c => c.Status == SupplierClaimStatus.Open
&& c.GoodsReceipt != null && c.GoodsReceipt.FK_WarehouseId == warehouseId);
public Task<List<DashboardClaimRow>> GetOpenClaimsAsync(Guid warehouseId, int take) =>
db.SupplierClaims.AsNoTracking()
.Where(c => c.Status == SupplierClaimStatus.Open
&& c.GoodsReceipt != null && c.GoodsReceipt.FK_WarehouseId == warehouseId)
.OrderByDescending(c => c.CreatedAt)
.Take(take)
.Select(c => new DashboardClaimRow(
c.Id, c.CreatedAt, c.PurchaseOrder.Supplier.Name, c.Details,
c.Items.Sum(i => i.Quantity)))
.ToListAsync();
public async Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids)
{
var idList = ids.Distinct().ToList();
var names = new Dictionary<Guid, string>();
if (idList.Count == 0) return names;
foreach (var w in await db.Warehouses.AsNoTracking().Where(w => idList.Contains(w.Id)).Select(w => new { w.Id, w.Name }).ToListAsync())
names[w.Id] = w.Name;
foreach (var v in await db.Vehicles.AsNoTracking().Where(v => idList.Contains(v.Id)).Select(v => new { v.Id, v.RegNumber }).ToListAsync())
names[v.Id] = v.RegNumber;
return names;
}
public async Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids)
{
var idList = ids.Distinct().ToList();
if (idList.Count == 0) return [];
return await db.Users.AsNoTracking()
.Where(u => idList.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName);
}
}
@@ -0,0 +1,229 @@
using Distribution.Models;
namespace Distribution.Features.Warehouse;
public class WarehouseDashboardService(IWarehouseRepository warehouseRepo, IWarehouseDashboardRepository repo)
{
private const int NearExpiryDays = 30;
private const int LedgerSize = 50;
private const int RecentDocumentCount = 8;
private const int TopListSize = 5;
private const int DeadStockListSize = 10;
private const int MaxPeriodDays = 366;
public static string? Validate(DateTime? from, DateTime? to, int deadStockDays)
{
if (deadStockDays < 1 || deadStockDays > 365) return "deadStockDays must be between 1 and 365.";
if (from.HasValue && to.HasValue)
{
if (from.Value.Date > to.Value.Date) return "'from' must not be after 'to'.";
if ((to.Value.Date - from.Value.Date).TotalDays >= MaxPeriodDays) return $"The period cannot exceed {MaxPeriodDays} days.";
}
return null;
}
/// <summary>Unlike the standard warehouse lookup, this also serves inactive warehouses so their history stays viewable.</summary>
public async Task<WarehouseDashboardResponse?> GetAsync(Guid id, DateTime? from, DateTime? to, int deadStockDays)
{
var warehouse = await warehouseRepo.GetByIdAsync(id);
if (warehouse is null) return null;
var now = DateTime.Now;
var today = now.Date;
var toDate = (to ?? today).Date;
var fromDate = (from ?? toDate.AddDays(-29)).Date;
var toExclusive = toDate.AddDays(1);
var balances = await repo.GetBalanceRowsAsync(id);
var movements = (await repo.GetMovementsAsync(id, fromDate, toExclusive))
.Select(m => (Row: m, Direction: DirectionOf(m, id)))
.Where(x => x.Direction is not null)
.ToList();
var lastOutbound = (await repo.GetLastOutboundAsync(id)).ToDictionary(r => r.ItemId, r => r.LastOutboundAt);
// ── Stock on hand ──────────────────────────────────────────────────
var stock = balances
.GroupBy(b => b.ItemId)
.Select(g => BuildStockRow(g.ToList()))
.OrderBy(r => r.Name)
.ToList();
var stockByCategory = stock
.Where(r => r.Quantity > 0)
.GroupBy(r => r.Category)
.Select(g => new DashboardCategoryStock(g.Key, g.Sum(r => r.Quantity), g.Sum(r => r.CostValue)))
.OrderByDescending(c => c.Value)
.ToList();
var itemNames = stock.ToDictionary(r => r.ItemId, r => r.Name);
var expiring = balances
.Where(b => b.Quantity > 0 && b.ExpiryDate.HasValue && b.BatchId.HasValue
&& b.ExpiryDate.Value.Date <= today.AddDays(NearExpiryDays))
.GroupBy(b => (b.ItemId, b.BatchId))
.Select(g =>
{
var first = g.First();
var expiry = first.ExpiryDate!.Value;
return new DashboardExpiringBatch(
first.ItemId, itemNames.GetValueOrDefault(first.ItemId, first.Name), first.BatchNumber,
expiry, g.Sum(x => x.Quantity), expiry.Date < today ? "Expired" : "NearExpiry");
})
.OrderBy(b => b.ExpiryDate)
.ToList();
// ── Movements ──────────────────────────────────────────────────────
var unitsIn = movements.Where(x => x.Direction == "In").Sum(x => x.Row.Quantity);
var unitsOut = movements.Where(x => x.Direction == "Out").Sum(x => x.Row.Quantity);
var perDay = movements
.GroupBy(x => x.Row.CreatedAt.Date)
.ToDictionary(g => g.Key, g => (
In: g.Where(x => x.Direction == "In").Sum(x => x.Row.Quantity),
Out: g.Where(x => x.Direction == "Out").Sum(x => x.Row.Quantity)));
var daily = new List<DashboardDailyPoint>();
for (var d = fromDate; d <= toDate; d = d.AddDays(1))
{
perDay.TryGetValue(d, out var p);
daily.Add(new DashboardDailyPoint(d.ToString("yyyy-MM-dd"), p.In, p.Out));
}
var byType = movements
.GroupBy(x => (x.Row.Type, x.Direction))
.Select(g => new DashboardMovementTypeRow(g.Key.Type, g.Key.Direction!, g.Sum(x => x.Row.Quantity), g.Count()))
.OrderByDescending(r => r.Units)
.ToList();
var ledgerSource = movements.Take(LedgerSize).ToList();
var locationIds = ledgerSource
.SelectMany(x => new[]
{
(x.Row.FromLocationId, x.Row.FromLocationType),
(x.Row.ToLocationId, x.Row.ToLocationType),
})
.Where(l => l.Item1.HasValue && l.Item2 is LocationType t && t != LocationType.External)
.Select(l => l.Item1!.Value);
var locationNames = await repo.GetLocationNamesAsync(locationIds);
var userNames = await repo.GetUserNamesAsync(ledgerSource.Select(x => x.Row.CreatedBy));
var ledger = ledgerSource.Select(x => new DashboardLedgerRow(
x.Row.Id, x.Row.CreatedAt, x.Row.ItemId, x.Row.ItemName, x.Row.Sku,
x.Row.Type, x.Direction!, x.Row.Quantity,
SideName(x.Row.Type, x.Row.FromLocationId, x.Row.FromLocationType, locationNames, isFrom: true),
SideName(x.Row.Type, x.Row.ToLocationId, x.Row.ToLocationType, locationNames, isFrom: false),
x.Row.ReferenceId, ReferenceKind(x.Row.Type),
x.Row.ReasonCode, userNames.GetValueOrDefault(x.Row.CreatedBy, "—"))).ToList();
// ── Inbound (GRN) ──────────────────────────────────────────────────
var grnTotals = await repo.GetGrnTotalsAsync(id, fromDate, toExclusive);
var received = grnTotals.Accepted + grnTotals.Rejected;
var inbound = new DashboardInbound(
grnTotals.Count, grnTotals.Accepted, grnTotals.Rejected,
received == 0 ? 0m : Math.Round(grnTotals.Rejected * 100m / received, 1),
await repo.GetRecentGrnsAsync(id, fromDate, toExclusive, RecentDocumentCount));
// ── Outbound (loading sheets) ──────────────────────────────────────
var outbound = new DashboardOutbound(
movements.Where(x => x.Row.Type == MovementType.Load && x.Direction == "Out").Sum(x => x.Row.Quantity),
await repo.GetPendingDispatchUnitsAsync(id),
await repo.GetRecentLoadingSheetsAsync(id, fromDate, toExclusive, RecentDocumentCount));
// ── Vehicles ───────────────────────────────────────────────────────
var vehicleStock = (await repo.GetVehicleStockAsync(id, warehouse.VehicleId))
.Select(v => new DashboardVehicle(v.VehicleId, v.RegNumber, v.NumberPlate, v.IsLinked, v.Units, v.Skus))
.ToList();
var linkedVehicles = await repo.GetDefaultVehiclesAsync(id, warehouse.VehicleId);
// ── Returns and losses ─────────────────────────────────────────────
decimal Units(MovementType type, string direction) =>
movements.Where(x => x.Row.Type == type && x.Direction == direction).Sum(x => x.Row.Quantity);
var losses = new DashboardLosses(
Units(MovementType.PurchaseReturn, "Out"), Units(MovementType.Damage, "Out"),
Units(MovementType.Adjustment, "Out"), Units(MovementType.Adjustment, "In"),
await repo.GetOpenClaimCountAsync(id),
await repo.GetOpenClaimsAsync(id, RecentDocumentCount));
// ── Top movers and dead stock ──────────────────────────────────────
var topMovers = movements
.Where(x => x.Row.Type == MovementType.Load && x.Direction == "Out")
.GroupBy(x => x.Row.ItemId)
.Select(g => new DashboardTopMover(g.Key, g.First().Row.Sku, g.First().Row.ItemName, g.Sum(x => x.Row.Quantity)))
.OrderByDescending(m => m.UnitsOut)
.Take(TopListSize)
.ToList();
var deadCutoff = now.AddDays(-deadStockDays);
var deadStock = stock
.Where(r => r.Quantity > 0 && r.UpdatedAt < deadCutoff
&& (!lastOutbound.TryGetValue(r.ItemId, out var last) || last < deadCutoff))
.OrderByDescending(r => r.CostValue)
.Take(DeadStockListSize)
.Select(r => new DashboardDeadStock(
r.ItemId, r.Sku, r.Name, r.Quantity, r.CostValue,
lastOutbound.TryGetValue(r.ItemId, out var last) ? last : null))
.ToList();
var kpis = new DashboardKpis(
stock.Sum(r => r.CostValue), stock.Sum(r => r.SaleValue),
stock.Where(r => r.Quantity > 0).Sum(r => r.Quantity), stock.Count(r => r.Quantity > 0),
stock.Count(r => r.Status == "Low"), stock.Count(r => r.Status == "Out"),
expiring.Count(b => b.Status == "NearExpiry"), expiring.Count(b => b.Status == "Expired"),
unitsIn, unitsOut);
return new WarehouseDashboardResponse(
new WarehouseResponse(warehouse.Id, warehouse.Name, warehouse.Location, warehouse.Type,
warehouse.VehicleId, warehouse.IsActive, warehouse.CreatedAt),
linkedVehicles, new DashboardPeriod(fromDate, toDate), deadStockDays, kpis,
stock, stockByCategory, expiring, daily, byType, ledger,
inbound, outbound, vehicleStock, losses, topMovers, deadStock);
}
private static DashboardStockRow BuildStockRow(List<WarehouseBalanceRow> rows)
{
var first = rows[0];
var quantity = rows.Sum(r => r.Quantity);
var billable = Math.Max(quantity, 0m);
var status = quantity <= 0 ? "Out"
: first.ReorderLevel.HasValue && quantity < first.ReorderLevel.Value ? "Low"
: "OK";
var batches = rows
.Where(r => r.BatchId.HasValue && r.Quantity > 0)
.GroupBy(r => r.BatchId!.Value)
.Select(g => new DashboardBatch(g.Key, g.First().BatchNumber, g.First().ExpiryDate, g.Sum(r => r.Quantity)))
.OrderBy(b => b.ExpiryDate ?? DateTime.MaxValue)
.ToList();
return new DashboardStockRow(
first.ItemId, first.Sku, first.Name, first.Category, first.Brand, first.Uom,
quantity, first.ReorderLevel, status,
billable * (first.CostPrice ?? 0m), billable * (first.SalePrice ?? 0m),
rows.Max(r => r.UpdatedAt), batches);
}
private static string? DirectionOf(WarehouseMovementRow m, Guid warehouseId) =>
m.ToLocationId == warehouseId && m.FromLocationId != warehouseId ? "In"
: m.FromLocationId == warehouseId && m.ToLocationId != warehouseId ? "Out"
: null;
private static string? ReferenceKind(MovementType type) => type switch
{
MovementType.PurchaseReceipt => "GRN",
MovementType.Load => "Loading Sheet",
MovementType.PurchaseReturn => "Purchase Return",
_ => null,
};
private static string SideName(
MovementType type, Guid? id, LocationType? locationType, Dictionary<Guid, string> names, bool isFrom)
{
if (id is not Guid locId || locationType is not LocationType lt) return "—";
if (lt != LocationType.External) return names.GetValueOrDefault(locId, "—");
return type switch
{
MovementType.Sale or MovementType.CustomerReturn => "Customer",
MovementType.PurchaseReceipt or MovementType.PurchaseReturn => "Supplier",
MovementType.Damage => "Written Off",
MovementType.Adjustment => "Stock Adjustment",
_ => isFrom ? "External Source" : "External Destination",
};
}
}
@@ -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",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,926 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class AddPaymentServiceModule : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BankAccounts",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
AccountType = table.Column<int>(type: "int", nullable: false),
AccountName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
BankName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CashAccountTypeName = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
AccountNumber = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CurrencyCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
IsDefault = table.Column<bool>(type: "tinyint(1)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BankAccounts", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "ChequeBooks",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ChequeBookNo = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
BranchId = table.Column<int>(type: "int", nullable: false),
FK_BankAccountId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
StartChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
EndChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TotalLeaves = table.Column<int>(type: "int", nullable: false),
ReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
Description = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<int>(type: "int", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChequeBooks", x => x.Id);
table.ForeignKey(
name: "FK_ChequeBooks_BankAccounts_FK_BankAccountId",
column: x => x.FK_BankAccountId,
principalTable: "BankAccounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "ReceivedCheques",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
CompanyId = table.Column<int>(type: "int", nullable: false),
BranchId = table.Column<int>(type: "int", nullable: true),
ReceivedFromType = table.Column<int>(type: "int", nullable: false),
ReceivedFromId = table.Column<int>(type: "int", nullable: true),
ReceivedFromName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
DrawerBankName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
DrawerBankBranch = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
AccountHolderName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ChequeDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
ReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
ReferenceType = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ReferenceId = table.Column<int>(type: "int", nullable: true),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<int>(type: "int", nullable: false),
FK_DepositBankAccountId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
DepositDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ReceivedCheques", x => x.Id);
table.ForeignKey(
name: "FK_ReceivedCheques_BankAccounts_FK_DepositBankAccountId",
column: x => x.FK_DepositBankAccountId,
principalTable: "BankAccounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "ChequePages",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FK_ChequeBookId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
IssueStatus = table.Column<int>(type: "int", nullable: false),
PayeeType = table.Column<int>(type: "int", nullable: true),
PayeeId = table.Column<int>(type: "int", nullable: true),
PayeeName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
IssueDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: true),
CurrencyCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
VoucherId = table.Column<int>(type: "int", nullable: true),
ReferenceNo = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Purpose = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
IsCrossCheque = table.Column<bool>(type: "tinyint(1)", nullable: true),
IsAccountPayee = table.Column<bool>(type: "tinyint(1)", nullable: true),
IsPostDated = table.Column<bool>(type: "tinyint(1)", nullable: true),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PrintedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PrintedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ClearedDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ClearedByBank = table.Column<bool>(type: "tinyint(1)", nullable: true),
CancelReason = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChequePages", x => x.Id);
table.ForeignKey(
name: "FK_ChequePages_ChequeBooks_FK_ChequeBookId",
column: x => x.FK_ChequeBookId,
principalTable: "ChequeBooks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8c43c584-0f65-496d-a77c-a7df46bb175d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("25576a9d-38cb-4ff5-b8ca-08df43d8a5a6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e47016c1-a014-476f-ba87-4b3d438de62a"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a24c0e9e-0194-4f96-823c-bdbdcd122d85"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e76ef6e2-67ba-462f-a954-b1dbc5a95d8c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bba4fdaa-fd0e-43dc-aedf-331212bd06cd"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bc66d450-af6b-426e-9d05-266ceba21fac"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("9ee2196c-1e4b-452d-aaf6-7c55e3d2be2b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("63508590-7a30-4640-9fc9-d3d54978f581"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("82802a3f-23bc-45db-a531-d7e880dd7970"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("65ccf5b6-754a-431a-81bf-d8e0587c956e"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6c713413-362b-475a-846b-9f3382cf7a35"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6dfe68ff-f5a3-41a8-981b-ac108a81408e"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("09c6b812-dc14-44a2-9362-429dfef3942d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bf29a1bb-234e-47a3-922f-f0fb3a1daba0"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("0d2f3497-4852-453e-a451-275e945b6c1d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("41c13dbc-4f51-4134-891c-b6955f728d65"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("29bc0291-7a91-4fc5-840b-9420b64b86fc"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8ae0c386-d206-4580-890f-c1673c1dfeb2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5db4bd09-9481-4608-9dec-3412ba7f3f69"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c1840bd7-88fd-496a-8d88-aef3d886e1ec"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("af97d659-c040-4fdc-beeb-62fc8334f95c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a6443517-3c2a-4407-a3bd-5497e053b0d6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("2f540d56-bff4-41fe-9181-4a423c1da51c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db18cfb8-a56c-46bc-baff-298e6204c455"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fe209c12-1189-485b-920e-ce6db3c132b9"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fedef395-394d-4237-b5aa-8254603ed304"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7cc47626-65b8-44b0-83fb-a5dff96c39f6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("cf04aea5-3f47-4717-95aa-8dab547f9cfb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c2906d62-9eea-4d08-b904-86a3f8b04a20"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7a546bf9-c2f3-4d23-a38e-a068da53dfeb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("19c916c3-7b17-42e0-b4b6-89b8cb6475f4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e6805dbe-a931-4e2a-b08f-83e8760d4968"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7d991f5c-16db-46c5-863c-93063c2e7382"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("513c50d0-5d7b-426d-beef-f14bdea626bc"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("638a6a60-25da-425c-bdec-683431c26d62"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b00bfae6-92bb-48cb-99d9-f2814baae47d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6562c9b4-0673-4ce0-8805-03734bc889ce"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6601e0fe-83dd-4307-be91-38dae998cc9c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c4848449-9678-4fa6-8a00-db4e504b57d6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("91c02c24-12a3-48d5-9d19-174c8e764152"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("0f00477f-5120-40ba-a0e6-0406e47d1c80"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db135a10-6335-478d-b2f8-3b5934cd142b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5c219dd9-9ff3-4541-a708-cc7e4bd4dd25"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("086e7b93-079d-49d0-90e9-95899b53be52"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("f0b3d172-cb32-4606-8272-00feaf4067a2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("75a882c0-b2e9-4efa-a64c-b5d530d42194"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("03540f63-d811-4436-869f-2f76995bbb19"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("73894d9f-a823-4b5b-b809-5cc0aa716a70"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("1ad4bfb8-cde6-4623-a21d-9a78af0026d6"));
migrationBuilder.CreateIndex(
name: "IX_BankAccounts_AccountType_AccountName",
table: "BankAccounts",
columns: new[] { "AccountType", "AccountName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ChequeBooks_ChequeBookNo",
table: "ChequeBooks",
column: "ChequeBookNo",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ChequeBooks_FK_BankAccountId",
table: "ChequeBooks",
column: "FK_BankAccountId");
migrationBuilder.CreateIndex(
name: "IX_ChequePages_ChequeNo",
table: "ChequePages",
column: "ChequeNo",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ChequePages_FK_ChequeBookId",
table: "ChequePages",
column: "FK_ChequeBookId");
migrationBuilder.CreateIndex(
name: "IX_ReceivedCheques_ChequeNo",
table: "ReceivedCheques",
column: "ChequeNo");
migrationBuilder.CreateIndex(
name: "IX_ReceivedCheques_FK_DepositBankAccountId",
table: "ReceivedCheques",
column: "FK_DepositBankAccountId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ChequePages");
migrationBuilder.DropTable(
name: "ReceivedCheques");
migrationBuilder.DropTable(
name: "ChequeBooks");
migrationBuilder.DropTable(
name: "BankAccounts");
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db785a23-5337-4fc8-b460-80c30b707d79"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b07b73f9-2c8b-4dff-af5c-42622b026321"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("2e867ba5-2739-4ce4-b8d7-25b14ed392c1"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("203d855d-f2d0-43d0-9d4f-9c4d0e8e7566"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("edd2a0ee-1ca9-47ff-9df6-7254929aef80"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("644b5ee5-f2c5-4187-a3d4-322cdd5c95d7"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("70fdf921-f3fd-4e66-bfd2-cb7bb156fa34"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e2de5341-4146-4c84-a584-8788774c14df"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ece07436-4a0b-435c-888b-a22c14612dc4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b72f8737-7a21-48c0-809a-bc0d3fdd7063"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fe1efed0-271f-45e3-a58b-244f1115246f"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("1b58e809-e4a0-4108-af6e-fd0dac7b3163"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("881e8e67-e2ee-47be-94f7-77da73a05365"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e5d0baa8-9def-4b81-8727-5886b2906eaf"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a46ec771-5e79-4595-bb40-91e8a954a792"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ef8b1808-3b14-43a8-ba75-30551e643cc5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("50142904-8d40-424b-aeb3-53e8cb110445"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("4bd67f53-7f84-492c-83f9-fd6488fa88b7"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("d9a0eb59-600e-47e9-8e07-8b7bffc93f8b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e5a9d4da-9497-4e7e-8fb9-796927cbb510"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ffa29833-505e-4f56-9a4d-58f5df844af2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("4e54994b-7ce1-4b99-aa98-de00c1d205fa"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("f45b7352-f42a-4bb3-9d1e-d7cbe2665c0c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("27744965-334b-4da2-8a2d-5d7433d11b9d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("cec1991b-ea93-45b0-b380-01cf0d36fc17"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("25675bb4-2ab5-437c-bee8-1b1ec1741736"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5fc66d40-1960-4028-af48-cf51a80bff04"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bd4e1740-a8b6-4a53-8633-a68c9858b3f5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("9de97081-77d7-43c3-90b9-af3bc9d4bbef"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6ae05e16-95e5-4d43-bfa0-56991393d76f"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("eb096aea-92c9-4556-ba4f-3084da223c7d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6c9dac72-200a-46eb-b49e-699b1ca775b5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8c8d0c74-40f6-4164-a6a7-6d2374ef162a"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("4a0d8cea-c33d-45a6-bd42-d8a51ce321c8"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a0ea0280-8e40-45bc-88e4-c3feb92e36d4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("61da4c55-4f19-4ce4-bfc9-3fa9073365a2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fbab8f2e-f224-42a0-be16-b7ea4e24eec5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bcf7f883-e6c8-4a97-8a27-6814b0ed9144"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("13c01d81-198b-45cb-9de9-f25130e16313"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("667c3022-ea0d-4d1c-8ae4-b64e2f104295"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("78607299-2f5c-4edb-9143-c4a83395f731"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e53313fd-c6d4-4d52-827f-14cfd55401eb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c2521113-60f4-41fa-8ded-6b6a6b91a584"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("14532efd-d9f9-4a46-a7c3-fbfbe85a6f13"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("438bbd52-ee61-4afc-9d59-7d0407da4f1d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("3e3d0cd9-f95e-4c1c-b829-cd02f1bad987"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("84daf87e-d3e1-4f4e-9a57-ec9996fff41d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("ddcb8caf-106e-4072-b48e-a2542de51211"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("95d39d28-c5c7-4e82-87eb-40570db1c1f5"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b8e7b327-33c9-4420-b4d1-0071c58a5c95"));
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,729 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class SeedDefaultCashAccount : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "BankAccounts",
columns: new[] { "Id", "AccountName", "AccountNumber", "AccountType", "BankName", "CashAccountTypeName", "CreatedAt", "CurrencyCode", "IsActive", "IsDefault" },
values: new object[] { new Guid("11111111-1111-1111-1111-000000000001"), "Petty Cash", null, 0, null, "Petty Cash", new DateTime(2026, 10, 19, 0, 0, 0, 0, DateTimeKind.Unspecified), "LKR", true, 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("ec13fdfa-d848-4004-b749-4b455ba0a77e"));
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("66f41359-cf33-4bd9-a936-816f03b23da7"));
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("369efbe7-f200-4b2e-bc15-858b6d2ca882"));
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("adc14999-08af-4b4c-937d-3ecd7c35b709"));
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("f1bf4ccb-45c6-4915-b941-6b507eb1e44f"));
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("1cbd132a-ea3d-46b2-8150-c5a077b6b36a"));
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("362118bd-1d4c-4576-95f6-7da899bf7368"));
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("15e94675-d2ae-4f5a-8198-6297a26d7a2c"));
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("4af2ab93-d3ba-4bda-b440-167f672cc166"));
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("bc5ba5f8-f20a-432a-985b-95b9500ce693"));
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("4f43f82b-8e6a-4729-9ded-c78c7dde24a7"));
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("7ac3df8c-f5c3-4d57-b537-5d5ca4ec1e11"));
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("44ec8d0f-f540-4786-9d9a-9194fa091286"));
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("143c4280-21be-4852-adc5-7cd17ab595fd"));
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("d00b144e-bf2d-4a04-9c85-0c14544edf48"));
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("9a72b7e4-1197-4032-8ec6-ebb3442a267f"));
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("5ec2af6e-d1db-4811-9c37-94a260e6ef9b"));
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("a71aceba-5199-4824-8b4e-998b108c082c"));
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("4bd840bf-da52-46fd-b065-40253f915c7c"));
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("8b4d8558-dba9-45bb-b263-f066580a55e9"));
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("0ee1a2e0-e4d2-4af3-9cca-ffad05bc1e20"));
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("700be5ec-0c11-497b-808a-80e7a854d2b4"));
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("b9978e5a-14b1-462a-9b7b-c9c32c4decb9"));
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("1e7cc9d4-f65e-434b-90e5-f78b4fda39b8"));
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("bfb3f80b-32c3-43b0-b216-f05e55f09360"));
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("fcfe2242-93ad-4c6d-8eec-3ec1f1270d0a"));
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("8e6eeb90-966f-4417-a1ae-148cdaa64904"));
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("6df60aea-63f3-465e-96fb-9a7304ead3c2"));
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("c0e21519-a095-4c7f-af42-e26efdcdd31e"));
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("496bbf8d-d84d-4c21-b190-bf95a06b4113"));
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("36f55476-d749-459b-92d9-839919499460"));
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("d90301fa-8990-49cf-8fa0-228ea7e684bc"));
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("8da4cd2a-1fd2-4417-bb6d-9e91174193f5"));
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("cc9bed46-a230-4611-88b4-d568abca857e"));
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("4d791413-ac44-4165-87b8-c28dbfa49230"));
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("2bb28028-8b12-4937-9dc6-68e35b100007"));
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("fdc8d3d9-a32d-4322-8144-93ff677beea3"));
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("b1ea0901-08e8-4dea-8ac3-15b522a2f274"));
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("116ac7bf-741e-4d4f-a313-2e7946a50044"));
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("7fd97ca6-a14b-4e42-8474-5471c0ef79a9"));
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("e58f35b2-8308-4fbb-b3ca-db9d037347cd"));
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("dc6b2750-083b-4653-a986-7af7aaec8136"));
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("6629888a-eb6b-433f-bbfe-ee5d947fb5b2"));
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("94b97271-7593-4cec-9290-802e9a26f0d8"));
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("0dc88aca-4e86-4712-988f-8db6a466531d"));
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("c8839187-4358-4218-bf3d-5ea4ed758661"));
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("5c731906-8d81-4f85-abad-230050af0bd0"));
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("d6e3db40-ca38-4caf-88d8-ce87475b4d87"));
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("398f1c4d-5d7d-4af4-9aa7-a88d7abf9a33"));
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("0f6c66e6-833c-4b52-ba2c-09da8dbead6f"));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "BankAccounts",
keyColumn: "Id",
keyValue: new Guid("11111111-1111-1111-1111-000000000001"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8c43c584-0f65-496d-a77c-a7df46bb175d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("25576a9d-38cb-4ff5-b8ca-08df43d8a5a6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e47016c1-a014-476f-ba87-4b3d438de62a"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a24c0e9e-0194-4f96-823c-bdbdcd122d85"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e76ef6e2-67ba-462f-a954-b1dbc5a95d8c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bba4fdaa-fd0e-43dc-aedf-331212bd06cd"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bc66d450-af6b-426e-9d05-266ceba21fac"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("9ee2196c-1e4b-452d-aaf6-7c55e3d2be2b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("63508590-7a30-4640-9fc9-d3d54978f581"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("82802a3f-23bc-45db-a531-d7e880dd7970"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("65ccf5b6-754a-431a-81bf-d8e0587c956e"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6c713413-362b-475a-846b-9f3382cf7a35"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6dfe68ff-f5a3-41a8-981b-ac108a81408e"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("09c6b812-dc14-44a2-9362-429dfef3942d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("bf29a1bb-234e-47a3-922f-f0fb3a1daba0"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("0d2f3497-4852-453e-a451-275e945b6c1d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("41c13dbc-4f51-4134-891c-b6955f728d65"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("29bc0291-7a91-4fc5-840b-9420b64b86fc"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("8ae0c386-d206-4580-890f-c1673c1dfeb2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5db4bd09-9481-4608-9dec-3412ba7f3f69"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c1840bd7-88fd-496a-8d88-aef3d886e1ec"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("af97d659-c040-4fdc-beeb-62fc8334f95c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("a6443517-3c2a-4407-a3bd-5497e053b0d6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("2f540d56-bff4-41fe-9181-4a423c1da51c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db18cfb8-a56c-46bc-baff-298e6204c455"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fe209c12-1189-485b-920e-ce6db3c132b9"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("fedef395-394d-4237-b5aa-8254603ed304"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7cc47626-65b8-44b0-83fb-a5dff96c39f6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("cf04aea5-3f47-4717-95aa-8dab547f9cfb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c2906d62-9eea-4d08-b904-86a3f8b04a20"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7a546bf9-c2f3-4d23-a38e-a068da53dfeb"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("19c916c3-7b17-42e0-b4b6-89b8cb6475f4"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("e6805dbe-a931-4e2a-b08f-83e8760d4968"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("7d991f5c-16db-46c5-863c-93063c2e7382"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("513c50d0-5d7b-426d-beef-f14bdea626bc"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("638a6a60-25da-425c-bdec-683431c26d62"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("b00bfae6-92bb-48cb-99d9-f2814baae47d"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6562c9b4-0673-4ce0-8805-03734bc889ce"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("6601e0fe-83dd-4307-be91-38dae998cc9c"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("c4848449-9678-4fa6-8a00-db4e504b57d6"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("91c02c24-12a3-48d5-9d19-174c8e764152"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("0f00477f-5120-40ba-a0e6-0406e47d1c80"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("db135a10-6335-478d-b2f8-3b5934cd142b"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("5c219dd9-9ff3-4541-a708-cc7e4bd4dd25"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("086e7b93-079d-49d0-90e9-95899b53be52"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("f0b3d172-cb32-4606-8272-00feaf4067a2"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("75a882c0-b2e9-4efa-a64c-b5d530d42194"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("03540f63-d811-4436-869f-2f76995bbb19"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("73894d9f-a823-4b5b-b809-5cc0aa716a70"));
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("1ad4bfb8-cde6-4623-a21d-9a78af0026d6"));
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,928 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class AddSupplierPayments : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "CompanyId",
table: "ReceivedCheques",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddColumn<Guid>(
name: "FK_BankAccountId",
table: "Payments",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.AddColumn<Guid>(
name: "FK_ReceivedChequeId",
table: "Payments",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.CreateTable(
name: "SupplierPayments",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
FK_SupplierId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Amount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
Method = table.Column<int>(type: "int", nullable: false),
FK_BankAccountId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
FK_ChequePageId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
PaidAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
PaidByUserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SupplierPayments", x => x.Id);
table.ForeignKey(
name: "FK_SupplierPayments_BankAccounts_FK_BankAccountId",
column: x => x.FK_BankAccountId,
principalTable: "BankAccounts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_SupplierPayments_ChequePages_FK_ChequePageId",
column: x => x.FK_ChequePageId,
principalTable: "ChequePages",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_SupplierPayments_Suppliers_FK_SupplierId",
column: x => x.FK_SupplierId,
principalTable: "Suppliers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "SupplierPaymentGRNs",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
FK_SupplierPaymentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
FK_GoodsReceiptId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
AllocatedAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SupplierPaymentGRNs", x => x.Id);
table.ForeignKey(
name: "FK_SupplierPaymentGRNs_GoodsReceipts_FK_GoodsReceiptId",
column: x => x.FK_GoodsReceiptId,
principalTable: "GoodsReceipts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_SupplierPaymentGRNs_SupplierPayments_FK_SupplierPaymentId",
column: x => x.FK_SupplierPaymentId,
principalTable: "SupplierPayments",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000001"),
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
values: new object[] { null, null });
migrationBuilder.UpdateData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000002"),
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
values: new object[] { null, null });
migrationBuilder.UpdateData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000003"),
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
values: new object[] { null, null });
migrationBuilder.UpdateData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000004"),
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
values: new object[] { null, null });
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("30f658ac-2c92-4cd8-ad47-10b0def2699c"));
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("42ab2c52-eaf8-4a17-b67a-ccd66569e56a"));
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("8afec825-27b3-4153-ad06-f8bcdbf93da8"));
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("4b427620-e99d-4068-a416-c3788d5bd9b5"));
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("361f44ef-1cd1-48a9-be4d-4c068aacef1e"));
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("11fc03ae-92ee-4336-ae57-29aac731ad51"));
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("0b1f2b73-ef60-4634-864f-22a6c0cd1373"));
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("2380abf3-2bcd-4a9f-b447-5f1685df2fc8"));
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("240e4d6a-8306-40ba-aaec-8e05cd0347db"));
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("cfde0aa6-e859-4f9c-8291-b0d886fcd4c9"));
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("dae772c8-26ec-4b5c-8d7a-f3196510bf2c"));
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("3512c0a4-a5ae-4002-a779-7d3cc600ab61"));
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("f9e2a348-159e-47a2-b7be-a60f0254873f"));
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("c8b4c181-5b08-45d2-818b-74bceeab78a8"));
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("8fb4ff4c-c5bd-4afd-a08e-02861d63cb7b"));
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("edb3b711-d623-439d-811e-afaef8d0680d"));
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("f33e4c62-d69f-4a91-8b62-acbd5351ea2a"));
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("c7c9d16c-e343-43e9-9e27-f38f903a5d00"));
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("f5f4760a-8a45-46ca-8093-743c95b0313f"));
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("c5434801-146a-4fd9-9c29-a1145e7f7f00"));
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("6c085135-78a5-4e65-a380-ee090b07e2bb"));
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("bbde2785-a3df-4f81-bad1-0bc5afe5de57"));
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("4421a0fb-bdd4-45a9-8dc0-d9b70f775e97"));
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("dba9dcb4-3b94-4ddc-89f3-552cfc109478"));
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("d8f74160-c150-4d9c-aba6-e45e628c684e"));
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("495fb68b-9f01-4538-9561-fefce0d39558"));
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("3bdece9b-a798-4223-a23c-9c9dd10d042d"));
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("6fa616eb-99fe-46b9-8305-908765d037fd"));
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("7a07148a-1327-4e97-befc-b2045eacad21"));
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("852e86d7-aa57-4a77-81c0-054f3fb4d7f7"));
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("3b767929-c973-41fb-a340-4ee5feb65619"));
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("63619bbd-0352-41bd-9e1c-a3f7908fb5d2"));
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("f6b4c8bf-a9b3-4179-9f57-a8584678c0d1"));
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("bb47b8ca-f7cf-40fe-8945-fc532a439b53"));
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("52126271-ae3b-4b58-a9d2-50e2dd894087"));
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("981e3358-85bb-47fe-a1f5-3a3a02cf47b1"));
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("749408da-fcee-458d-9144-8d8dee928ba0"));
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("c157857c-fb0e-41fd-a414-c0f57a44b120"));
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("3217cdbc-76c9-4505-b9f3-23de91a6212b"));
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("63526c52-18ba-40e0-9889-3bd3b7fc8161"));
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("260cf2fa-1ca6-4cd5-92ae-8c479a80e2f0"));
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("130ab09c-8a36-4705-8235-9d00f75dd684"));
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("41794774-e919-4f4e-a42a-5ae408d3407f"));
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("2ff23d04-d1f6-4aa3-a48e-1c82bb9ad12a"));
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("d36c81c0-8e9b-41ea-8e09-464a327a16d2"));
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("6c51dfc1-63e0-46a6-b2bb-27c21c708b51"));
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("076333d5-b1f3-4b89-86fd-5a61a4451c88"));
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("7e834f3e-1424-4aaa-816e-d62da6df672b"));
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("699fa06b-a7ff-4dbd-98a7-a29c2c361665"));
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("00df5735-54c6-4cdf-8e2c-a64cd90ede15"));
migrationBuilder.CreateIndex(
name: "IX_Payments_FK_BankAccountId",
table: "Payments",
column: "FK_BankAccountId");
migrationBuilder.CreateIndex(
name: "IX_Payments_FK_ReceivedChequeId",
table: "Payments",
column: "FK_ReceivedChequeId");
migrationBuilder.CreateIndex(
name: "IX_SupplierPaymentGRNs_FK_GoodsReceiptId",
table: "SupplierPaymentGRNs",
column: "FK_GoodsReceiptId");
migrationBuilder.CreateIndex(
name: "IX_SupplierPaymentGRNs_FK_SupplierPaymentId",
table: "SupplierPaymentGRNs",
column: "FK_SupplierPaymentId");
migrationBuilder.CreateIndex(
name: "IX_SupplierPayments_FK_BankAccountId",
table: "SupplierPayments",
column: "FK_BankAccountId");
migrationBuilder.CreateIndex(
name: "IX_SupplierPayments_FK_ChequePageId",
table: "SupplierPayments",
column: "FK_ChequePageId");
migrationBuilder.CreateIndex(
name: "IX_SupplierPayments_FK_SupplierId",
table: "SupplierPayments",
column: "FK_SupplierId");
migrationBuilder.AddForeignKey(
name: "FK_Payments_BankAccounts_FK_BankAccountId",
table: "Payments",
column: "FK_BankAccountId",
principalTable: "BankAccounts",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Payments_ReceivedCheques_FK_ReceivedChequeId",
table: "Payments",
column: "FK_ReceivedChequeId",
principalTable: "ReceivedCheques",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Payments_BankAccounts_FK_BankAccountId",
table: "Payments");
migrationBuilder.DropForeignKey(
name: "FK_Payments_ReceivedCheques_FK_ReceivedChequeId",
table: "Payments");
migrationBuilder.DropTable(
name: "SupplierPaymentGRNs");
migrationBuilder.DropTable(
name: "SupplierPayments");
migrationBuilder.DropIndex(
name: "IX_Payments_FK_BankAccountId",
table: "Payments");
migrationBuilder.DropIndex(
name: "IX_Payments_FK_ReceivedChequeId",
table: "Payments");
migrationBuilder.DropColumn(
name: "FK_BankAccountId",
table: "Payments");
migrationBuilder.DropColumn(
name: "FK_ReceivedChequeId",
table: "Payments");
migrationBuilder.AlterColumn<int>(
name: "CompanyId",
table: "ReceivedCheques",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: 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("ec13fdfa-d848-4004-b749-4b455ba0a77e"));
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("66f41359-cf33-4bd9-a936-816f03b23da7"));
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("369efbe7-f200-4b2e-bc15-858b6d2ca882"));
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("adc14999-08af-4b4c-937d-3ecd7c35b709"));
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("f1bf4ccb-45c6-4915-b941-6b507eb1e44f"));
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("1cbd132a-ea3d-46b2-8150-c5a077b6b36a"));
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("362118bd-1d4c-4576-95f6-7da899bf7368"));
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("15e94675-d2ae-4f5a-8198-6297a26d7a2c"));
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("4af2ab93-d3ba-4bda-b440-167f672cc166"));
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("bc5ba5f8-f20a-432a-985b-95b9500ce693"));
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("4f43f82b-8e6a-4729-9ded-c78c7dde24a7"));
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("7ac3df8c-f5c3-4d57-b537-5d5ca4ec1e11"));
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("44ec8d0f-f540-4786-9d9a-9194fa091286"));
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("143c4280-21be-4852-adc5-7cd17ab595fd"));
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("d00b144e-bf2d-4a04-9c85-0c14544edf48"));
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("9a72b7e4-1197-4032-8ec6-ebb3442a267f"));
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("5ec2af6e-d1db-4811-9c37-94a260e6ef9b"));
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("a71aceba-5199-4824-8b4e-998b108c082c"));
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("4bd840bf-da52-46fd-b065-40253f915c7c"));
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("8b4d8558-dba9-45bb-b263-f066580a55e9"));
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("0ee1a2e0-e4d2-4af3-9cca-ffad05bc1e20"));
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("700be5ec-0c11-497b-808a-80e7a854d2b4"));
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("b9978e5a-14b1-462a-9b7b-c9c32c4decb9"));
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("1e7cc9d4-f65e-434b-90e5-f78b4fda39b8"));
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("bfb3f80b-32c3-43b0-b216-f05e55f09360"));
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("fcfe2242-93ad-4c6d-8eec-3ec1f1270d0a"));
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("8e6eeb90-966f-4417-a1ae-148cdaa64904"));
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("6df60aea-63f3-465e-96fb-9a7304ead3c2"));
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("c0e21519-a095-4c7f-af42-e26efdcdd31e"));
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("496bbf8d-d84d-4c21-b190-bf95a06b4113"));
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("36f55476-d749-459b-92d9-839919499460"));
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("d90301fa-8990-49cf-8fa0-228ea7e684bc"));
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("8da4cd2a-1fd2-4417-bb6d-9e91174193f5"));
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("cc9bed46-a230-4611-88b4-d568abca857e"));
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("4d791413-ac44-4165-87b8-c28dbfa49230"));
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("2bb28028-8b12-4937-9dc6-68e35b100007"));
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("fdc8d3d9-a32d-4322-8144-93ff677beea3"));
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("b1ea0901-08e8-4dea-8ac3-15b522a2f274"));
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("116ac7bf-741e-4d4f-a313-2e7946a50044"));
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("7fd97ca6-a14b-4e42-8474-5471c0ef79a9"));
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("e58f35b2-8308-4fbb-b3ca-db9d037347cd"));
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("dc6b2750-083b-4653-a986-7af7aaec8136"));
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("6629888a-eb6b-433f-bbfe-ee5d947fb5b2"));
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("94b97271-7593-4cec-9290-802e9a26f0d8"));
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("0dc88aca-4e86-4712-988f-8db6a466531d"));
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("c8839187-4358-4218-bf3d-5ea4ed758661"));
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("5c731906-8d81-4f85-abad-230050af0bd0"));
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("d6e3db40-ca38-4caf-88d8-ce87475b4d87"));
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("398f1c4d-5d7d-4af4-9aa7-a88d7abf9a33"));
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("0f6c66e6-833c-4b52-ba2c-09da8dbead6f"));
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,778 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class credittable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "StoreCreditMovements",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
FK_StoreId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Direction = table.Column<int>(type: "int", nullable: false),
Amount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
FK_InvoiceId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
FK_PaymentId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
OccurredAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
RecordedBy = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StoreCreditMovements", x => x.Id);
table.ForeignKey(
name: "FK_StoreCreditMovements_Invoices_FK_InvoiceId",
column: x => x.FK_InvoiceId,
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_StoreCreditMovements_Payments_FK_PaymentId",
column: x => x.FK_PaymentId,
principalTable: "Payments",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_StoreCreditMovements_Stores_FK_StoreId",
column: x => x.FK_StoreId,
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "RolePermissions",
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
column: "Id",
value: new Guid("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"));
migrationBuilder.CreateIndex(
name: "IX_StoreCreditMovements_FK_InvoiceId",
table: "StoreCreditMovements",
column: "FK_InvoiceId");
migrationBuilder.CreateIndex(
name: "IX_StoreCreditMovements_FK_PaymentId",
table: "StoreCreditMovements",
column: "FK_PaymentId");
migrationBuilder.CreateIndex(
name: "IX_StoreCreditMovements_FK_StoreId_OccurredAt",
table: "StoreCreditMovements",
columns: new[] { "FK_StoreId", "OccurredAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StoreCreditMovements");
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("30f658ac-2c92-4cd8-ad47-10b0def2699c"));
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("42ab2c52-eaf8-4a17-b67a-ccd66569e56a"));
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("8afec825-27b3-4153-ad06-f8bcdbf93da8"));
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("4b427620-e99d-4068-a416-c3788d5bd9b5"));
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("361f44ef-1cd1-48a9-be4d-4c068aacef1e"));
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("11fc03ae-92ee-4336-ae57-29aac731ad51"));
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("0b1f2b73-ef60-4634-864f-22a6c0cd1373"));
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("2380abf3-2bcd-4a9f-b447-5f1685df2fc8"));
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("240e4d6a-8306-40ba-aaec-8e05cd0347db"));
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("cfde0aa6-e859-4f9c-8291-b0d886fcd4c9"));
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("dae772c8-26ec-4b5c-8d7a-f3196510bf2c"));
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("3512c0a4-a5ae-4002-a779-7d3cc600ab61"));
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("f9e2a348-159e-47a2-b7be-a60f0254873f"));
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("c8b4c181-5b08-45d2-818b-74bceeab78a8"));
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("8fb4ff4c-c5bd-4afd-a08e-02861d63cb7b"));
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("edb3b711-d623-439d-811e-afaef8d0680d"));
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("f33e4c62-d69f-4a91-8b62-acbd5351ea2a"));
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("c7c9d16c-e343-43e9-9e27-f38f903a5d00"));
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("f5f4760a-8a45-46ca-8093-743c95b0313f"));
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("c5434801-146a-4fd9-9c29-a1145e7f7f00"));
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("6c085135-78a5-4e65-a380-ee090b07e2bb"));
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("bbde2785-a3df-4f81-bad1-0bc5afe5de57"));
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("4421a0fb-bdd4-45a9-8dc0-d9b70f775e97"));
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("dba9dcb4-3b94-4ddc-89f3-552cfc109478"));
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("d8f74160-c150-4d9c-aba6-e45e628c684e"));
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("495fb68b-9f01-4538-9561-fefce0d39558"));
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("3bdece9b-a798-4223-a23c-9c9dd10d042d"));
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("6fa616eb-99fe-46b9-8305-908765d037fd"));
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("7a07148a-1327-4e97-befc-b2045eacad21"));
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("852e86d7-aa57-4a77-81c0-054f3fb4d7f7"));
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("3b767929-c973-41fb-a340-4ee5feb65619"));
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("63619bbd-0352-41bd-9e1c-a3f7908fb5d2"));
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("f6b4c8bf-a9b3-4179-9f57-a8584678c0d1"));
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("bb47b8ca-f7cf-40fe-8945-fc532a439b53"));
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("52126271-ae3b-4b58-a9d2-50e2dd894087"));
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("981e3358-85bb-47fe-a1f5-3a3a02cf47b1"));
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("749408da-fcee-458d-9144-8d8dee928ba0"));
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("c157857c-fb0e-41fd-a414-c0f57a44b120"));
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("3217cdbc-76c9-4505-b9f3-23de91a6212b"));
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("63526c52-18ba-40e0-9889-3bd3b7fc8161"));
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("260cf2fa-1ca6-4cd5-92ae-8c479a80e2f0"));
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("130ab09c-8a36-4705-8235-9d00f75dd684"));
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("41794774-e919-4f4e-a42a-5ae408d3407f"));
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("2ff23d04-d1f6-4aa3-a48e-1c82bb9ad12a"));
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("d36c81c0-8e9b-41ea-8e09-464a327a16d2"));
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("6c51dfc1-63e0-46a6-b2bb-27c21c708b51"));
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("076333d5-b1f3-4b89-86fd-5a61a4451c88"));
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("7e834f3e-1424-4aaa-816e-d62da6df672b"));
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("699fa06b-a7ff-4dbd-98a7-a29c2c361665"));
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("00df5735-54c6-4cdf-8e2c-a64cd90ede15"));
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,814 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class GlIntegration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "GlAccountCode",
table: "BankAccounts",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "GlPostingLogs",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Event = table.Column<string>(type: "varchar(60)", maxLength: 60, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
SourceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Reference = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<int>(type: "int", nullable: false),
PayloadJson = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
GlJournalNo = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Error = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Attempts = table.Column<int>(type: "int", nullable: false),
LastAttemptAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_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);
}
/// <inheritdoc />
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"));
}
}
}
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
namespace Distribution.Models
{
public class BankAccount : Template
{
public BankAccountType AccountType { get; set; }
public string AccountName { get; set; } = string.Empty;
public string? BankName { get; set; }
public string? CashAccountTypeName { get; set; }
public string? AccountNumber { get; set; }
public string CurrencyCode { get; set; } = "LKR";
public bool IsDefault { get; set; }
/// <summary>Code of the mirrored General Ledger account (child of GL Cash in Hand / Bank Accounts). Null until synced.</summary>
public string? GlAccountCode { get; set; }
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class ChequeBook : Template
{
public string ChequeBookNo { get; set; } = string.Empty;
public int BranchId { get; set; }
public Guid FK_BankAccountId { get; set; }
[ForeignKey("FK_BankAccountId")]
public BankAccount? BankAccount { get; set; }
public string StartChequeNo { get; set; } = string.Empty;
public string EndChequeNo { get; set; } = string.Empty;
public int TotalLeaves { get; set; }
public DateTime ReceivedDate { get; set; }
public string? Description { get; set; }
public string? CreatedBy { get; set; }
public ChequeBookStatus Status { get; set; } = ChequeBookStatus.Active;
public List<ChequePage> Pages { get; set; } = new();
}
}
+38
View File
@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class ChequePage : Template
{
public string ChequeNo { get; set; } = string.Empty;
public Guid FK_ChequeBookId { get; set; }
[ForeignKey("FK_ChequeBookId")]
public ChequeBook? ChequeBook { get; set; }
public ChequePageIssueStatus IssueStatus { get; set; } = ChequePageIssueStatus.Unused;
public PayeeType? PayeeType { get; set; }
public int? PayeeId { get; set; }
public string? PayeeName { get; set; }
public DateTime? IssueDate { get; set; }
public decimal? Amount { get; set; }
public string? CurrencyCode { get; set; }
public int? VoucherId { get; set; }
public string? ReferenceNo { get; set; }
public string? Purpose { get; set; }
public bool? IsCrossCheque { get; set; }
public bool? IsAccountPayee { get; set; }
public bool? IsPostDated { get; set; }
public string? Notes { get; set; }
public string? PrintedBy { get; set; }
public DateTime? PrintedAt { get; set; }
public DateTime? ClearedDate { get; set; }
public bool? ClearedByBank { get; set; }
public string? CancelReason { get; set; }
}
}
+18
View File
@@ -23,6 +23,8 @@ namespace Distribution.Models
public enum PaymentMethod { Cash, Cheque, Credit }
public enum StoreCreditMovementDirection { Usage, Settlement }
public enum EntryMode { RealTime, DayEndManual }
public enum DeliveryTaskStatus { Pending, Delivered, PartiallyDelivered, Failed }
@@ -49,4 +51,20 @@ namespace Distribution.Models
public enum TaxMode { Disabled, Optional, Mandatory }
public enum SerialStatus { InStock, Sold, Returned, Damaged }
public enum BankAccountType { Cash, Bank }
public enum ChequeBookStatus { Active, Completed, Cancelled }
public enum ChequePageIssueStatus { Unused, Issued, Cleared, Bounced, Cancelled, Void }
public enum ChequePageStatusAction { Clear, Bounce, Cancel, Void }
public enum PayeeType { Supplier, Customer, Employee, Other }
public enum ReceivedChequeStatus { Received, Deposited, Cleared, Returned, Cancelled }
public enum ReceivedChequeStatusAction { Deposit, Clear, Return, Cancel }
public enum ReceivedFromType { Customer, Supplier, Other }
}
+24
View File
@@ -0,0 +1,24 @@
namespace Distribution.Models
{
public enum GlPostingStatus { Pending, Posted, Failed }
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>The journal entry body (without periodId, which is resolved from EntryDate at send time).</summary>
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; }
}
}
+11
View File
@@ -16,5 +16,16 @@ namespace Distribution.Models
public DateTime CollectedAt { get; set; }
public Guid CollectedBy { get; set; }
public Guid? RecordedBy { get; set; }
// Cash/Bank account that received the funds. Required when Method = Cash;
// for Cheque, the FK_ReceivedCheque below carries the deposit account once deposited.
public Guid? FK_BankAccountId { get; set; }
[ForeignKey("FK_BankAccountId")]
public BankAccount? BankAccount { get; set; }
// Cheque record auto-created when Method = Cheque. Null for Cash/Credit payments.
public Guid? FK_ReceivedChequeId { get; set; }
[ForeignKey("FK_ReceivedChequeId")]
public ReceivedCheque? ReceivedCheque { get; set; }
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class ReceivedCheque : Template
{
public int? CompanyId { get; set; }
public int? BranchId { get; set; }
public ReceivedFromType ReceivedFromType { get; set; }
public int? ReceivedFromId { get; set; }
public string ReceivedFromName { get; set; } = string.Empty;
public string? DrawerBankName { get; set; }
public string? DrawerBankBranch { get; set; }
public string? AccountHolderName { get; set; }
public string ChequeNo { get; set; } = string.Empty;
public DateTime ChequeDate { get; set; }
public decimal Amount { get; set; }
public DateTime ReceivedDate { get; set; }
public string? ReferenceType { get; set; }
public int? ReferenceId { get; set; }
public string? Notes { get; set; }
public string? CreatedBy { get; set; }
public ReceivedChequeStatus Status { get; set; } = ReceivedChequeStatus.Received;
public Guid? FK_DepositBankAccountId { get; set; }
[ForeignKey("FK_DepositBankAccountId")]
public BankAccount? DepositBankAccount { get; set; }
public DateTime? DepositDate { get; set; }
}
}
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class StoreCreditMovement : Template
{
public Guid FK_StoreId { get; set; }
[ForeignKey("FK_StoreId")]
public Store? Store { get; set; }
public StoreCreditMovementDirection Direction { get; set; }
public decimal Amount { get; set; }
public Guid? FK_InvoiceId { get; set; }
[ForeignKey("FK_InvoiceId")]
public Invoice? Invoice { get; set; }
public Guid? FK_PaymentId { get; set; }
[ForeignKey("FK_PaymentId")]
public Payment? Payment { get; set; }
public DateTime OccurredAt { get; set; } = DateTime.Now;
public Guid? RecordedBy { get; set; }
public string? Notes { get; set; }
}
}
+32
View File
@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class SupplierPayment : Template
{
public Guid FK_SupplierId { get; set; }
[ForeignKey("FK_SupplierId")]
public Supplier? Supplier { get; set; }
public decimal Amount { get; set; }
// Only Cash or Cheque are valid for outgoing payments. Credit is rejected at the service layer.
public PaymentMethod Method { get; set; }
// Cash: required. Null for Cheque.
public Guid? FK_BankAccountId { get; set; }
[ForeignKey("FK_BankAccountId")]
public BankAccount? BankAccount { get; set; }
// Cheque: required — the ChequePage this payment issued. Null for Cash.
public Guid? FK_ChequePageId { get; set; }
[ForeignKey("FK_ChequePageId")]
public ChequePage? ChequePage { get; set; }
public DateTime PaidAt { get; set; }
public Guid? PaidByUserId { get; set; }
public string? Notes { get; set; }
public ICollection<SupplierPaymentGRN> Allocations { get; set; } = [];
}
}

Some files were not shown because too many files have changed in this diff Show More