From e7b1f270a53b45f5ce9bd0aa20a80f56058d53ae Mon Sep 17 00:00:00 2001 From: Dhananjaya99 <152056742+ashanruu@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:46:31 +0530 Subject: [PATCH] feat: add vendor optional fields and configuration hooks - Implemented VendorOptionalFields component to handle optional vendor details (mobile, email, address) with validation. - Created useCustomerConfig hook to load optional customer field settings from Sales configuration. - Developed useProcurementConfig hook to manage Procurement configuration flags. - Introduced accessConfigApi for managing access configuration settings. - Added openingBalancesApi for handling opening balances operations. - Created procurementConfigApi for managing procurement configuration settings. - Implemented nav-routes for permission-based routing. - Developed PDF generation for purchase orders with dynamic vendor and item details. - Added purchase-order tax helpers for tax calculations and formatting. - Defined types for opening balances and related operations. --- Backend/ERPCore.Tests/AccessControlTests.cs | 512 + .../ERPCore.Tests/CustomerFieldSwitchTests.cs | 158 + Backend/ERPCore.Tests/DualApprovalTests.cs | 22 +- .../ERPCore.Tests/PermissionCoverageTests.cs | 420 + .../ProcurementAndOpeningBalanceTests.cs | 840 ++ .../NotificationConfigTestSupport.cs | 1 + .../Controllers/AccessConfigController.cs | 40 + .../Controllers/AuditLogsController.cs | 3 + Backend/ERPCore/Controllers/AuthController.cs | 3 +- Backend/ERPCore/Controllers/GrnsController.cs | 5 +- .../Hrm/AttendanceBatchesController.cs | 8 + .../Controllers/Hrm/BranchesController.cs | 6 + .../Controllers/Hrm/DepartmentsController.cs | 7 + .../Controllers/Hrm/DesignationsController.cs | 7 + .../Controllers/Hrm/EmployeesController.cs | 24 + .../Hrm/EmploymentTypesController.cs | 7 + .../Hrm/HrDocumentTypesController.cs | 7 + .../Controllers/Hrm/HrReportsController.cs | 5 + .../Hrm/LeaveRequestsController.cs | 7 + .../Controllers/Hrm/LeaveTypesController.cs | 7 + .../Controllers/Hrm/PayrollRunsController.cs | 7 + .../Hrm/PayrollStatutorySettingsController.cs | 3 + .../Controllers/Hrm/PayslipsController.cs | 3 + .../Hrm/SalaryComponentsController.cs | 7 + .../Controllers/Hrm/TaxSlabsController.cs | 4 + .../Controllers/Hrm/WorkShiftsController.cs | 7 + .../Controllers/OpeningBalancesController.cs | 71 + .../ProcurementConfigController.cs | 46 + .../Controllers/PurchaseOrdersController.cs | 5 +- .../Controllers/ReasonCodesController.cs | 3 + .../Controllers/SalesReturnsController.cs | 6 +- .../ERPCore/Controllers/UsersController.cs | 20 +- .../ERPCore/Controllers/VendorsController.cs | 11 +- .../ERPCore/Domain/Entities/AccessConfig.cs | 26 + .../Domain/Entities/DocumentApproval.cs | 5 +- .../Domain/Entities/OpeningBalanceRun.cs | 74 + Backend/ERPCore/Domain/Entities/PoLine.cs | 5 + .../Domain/Entities/ProcurementConfig.cs | 42 + Backend/ERPCore/Domain/Entities/Role.cs | 21 +- .../ERPCore/Domain/Entities/SalesConfig.cs | 25 + Backend/ERPCore/Domain/Entities/Vendor.cs | 7 + .../Domain/Enums/OpeningBalanceKind.cs | 12 + .../Domain/Enums/PurchaseOrderStatus.cs | 9 +- .../ERPCore/Domain/Enums/VendorContactMode.cs | 12 + .../ERPCore/Dtos/Approvals/ApprovalDtos.cs | 5 +- Backend/ERPCore/Dtos/Auth/AuthRoleDtos.cs | 28 - Backend/ERPCore/Dtos/Auth/AuthUserDtos.cs | 4 +- .../ERPCore/Dtos/Config/AccessConfigDtos.cs | 12 + .../Dtos/Config/ProcurementConfigDtos.cs | 24 + .../ERPCore/Dtos/Config/SalesConfigDtos.cs | 12 +- Backend/ERPCore/Dtos/Grn/GrnDtos.cs | 6 +- .../OpeningBalances/OpeningBalanceDtos.cs | 71 + .../Dtos/Procurement/PurchaseOrderDtos.cs | 12 +- Backend/ERPCore/Dtos/Rbac/MeDtos.cs | 4 +- Backend/ERPCore/Dtos/Rbac/RoleDtos.cs | 2 +- Backend/ERPCore/Dtos/Users/UserDtos.cs | 7 + Backend/ERPCore/Dtos/Vendors/VendorDtos.cs | 15 +- .../Infra/Auth/AuthHex/AuthHexClient.cs | 24 +- .../Infra/Auth/AuthHex/IAuthHexClient.cs | 8 - Backend/ERPCore/Infra/Auth/AuthHexClaims.cs | 20 + Backend/ERPCore/Infra/Auth/CurrentUser.cs | 23 +- .../Infra/Auth/ICurrentBranchContext.cs | 26 +- Backend/ERPCore/Infra/Auth/ICurrentUser.cs | 27 +- .../ERPCore/Infra/Auth/JwtAuthExtensions.cs | 10 +- .../ERPCore/Infra/Auth/NavAuthorization.cs | 10 +- .../Auth/ShadowUserClaimsTransformation.cs | 154 +- .../Infra/Auth/ValidateCsrfAttribute.cs | 48 +- .../AccessConfigConfiguration.cs | 31 + .../Configurations/ApprovalConfiguration.cs | 2 +- .../Configurations/NavItemConfiguration.cs | 6 +- .../OpeningBalanceConfiguration.cs | 67 + .../Configurations/PermissionConfiguration.cs | 15 +- .../ProcurementConfigConfiguration.cs | 42 + .../PurchaseOrderConfiguration.cs | 1 + .../Configurations/RoleConfiguration.cs | 3 +- .../SalesConfigConfiguration.cs | 7 + .../Configurations/SubNavItemConfiguration.cs | 14 +- .../Configurations/VendorConfiguration.cs | 3 + .../ERPCore/Infra/Persistence/DataSeeder.cs | 65 + .../ERPCore/Infra/Persistence/ErpDbContext.cs | 6 + ...curementConfigAndVendorContact.Designer.cs | 10917 +++++++++++++++ ...13_AddProcurementConfigAndVendorContact.cs | 88 + ...61017194128_AddPoLineTaxAmount.Designer.cs | 10923 +++++++++++++++ .../20261017194128_AddPoLineTaxAmount.cs | 35 + ...261017194653_AddPoApprovalsNav.Designer.cs | 10939 +++++++++++++++ .../20261017194653_AddPoApprovalsNav.cs | 52 + ...61017200125_AddOpeningBalances.Designer.cs | 11121 +++++++++++++++ .../20261017200125_AddOpeningBalances.cs | 170 + ...7201933_AddVendorFieldSwitches.Designer.cs | 11131 +++++++++++++++ .../20261017201933_AddVendorFieldSwitches.cs | 40 + ...20261017214857_RolesOwnedByErp.Designer.cs | 11183 +++++++++++++++ .../20261017214857_RolesOwnedByErp.cs | 129 + ...61017235430_PermissionCoverage.Designer.cs | 11331 +++++++++++++++ .../20261017235430_PermissionCoverage.cs | 195 + ...70404_AddCustomerFieldSwitches.Designer.cs | 11366 ++++++++++++++++ ...20261018070404_AddCustomerFieldSwitches.cs | 95 + .../Migrations/ErpDbContextModelSnapshot.cs | 519 +- Backend/ERPCore/Program.cs | 11 +- .../ERPCore/Services/AccessConfigService.cs | 79 + .../ERPCore/Services/ApprovalInboxService.cs | 2 +- Backend/ERPCore/Services/ApprovalService.cs | 48 +- .../ERPCore/Services/Auth/AuthUserService.cs | 3 + .../ERPCore/Services/BankDepositService.cs | 5 + Backend/ERPCore/Services/CustomerService.cs | 69 +- .../ERPCore/Services/GeneralLedgerService.cs | 48 +- Backend/ERPCore/Services/Gl/GlModels.cs | 2 + .../ERPCore/Services/GlobalSearchService.cs | 24 +- Backend/ERPCore/Services/GrnService.cs | 97 +- .../Interfaces/IAccessConfigService.cs | 12 + .../Interfaces/IFifoCostingService.cs | 8 +- .../Services/Interfaces/IGrnService.cs | 3 +- .../Interfaces/INavPermissionService.cs | 32 +- .../Interfaces/IOpeningBalanceService.cs | 17 + .../Interfaces/IOpeningStockService.cs | 17 + .../Interfaces/IProcurementConfigService.cs | 16 + .../Interfaces/IPurchaseOrderService.cs | 7 +- .../Services/Interfaces/IRoleService.cs | 3 +- .../Interfaces/IUserManagementService.cs | 10 +- .../ERPCore/Services/NavPermissionService.cs | 99 +- .../ERPCore/Services/OpeningBalanceService.cs | 178 + .../ERPCore/Services/OpeningEquityAccount.cs | 61 + .../ERPCore/Services/OpeningStockService.cs | 238 + .../Services/ProcurementConfigService.cs | 78 + .../ERPCore/Services/PurchaseOrderService.cs | 130 +- .../ERPCore/Services/PurchaseReturnService.cs | 3 +- Backend/ERPCore/Services/RoleService.cs | 125 +- .../ERPCore/Services/SalesConfigService.cs | 11 +- .../ERPCore/Services/SalesPostingService.cs | 3 +- .../Services/Stock/FifoCostingService.cs | 4 +- .../ERPCore/Services/Stock/StockMutator.cs | 6 +- .../ERPCore/Services/UserManagementService.cs | 101 +- Backend/ERPCore/Services/VendorService.cs | 81 +- Backend/ERPCore/System/Errors/ErrorCodes.cs | 3 + Backend/ERPCore/System/Time/BusinessClock.cs | 4 + Backend/PROGRESS.md | 52 + Backend/smoke/smoke_common.py | 3 + .../accounts/opening-balances/page.tsx | 49 + .../app/dashboard/accounts/page.tsx | 8 +- .../app/dashboard/customers/[id]/page.tsx | 83 +- .../app/dashboard/customers/page.tsx | 148 +- .../app/dashboard/hrm/employees/[id]/page.tsx | 7 +- Frontend/erp-system/app/dashboard/layout.tsx | 3 +- .../procurement/purchase-orders/[id]/page.tsx | 194 +- .../purchase-orders/approvals/page.tsx | 215 +- .../procurement/purchase-orders/new/page.tsx | 272 +- .../procurement/purchase-orders/page.tsx | 511 +- .../app/dashboard/products/[id]/edit/page.tsx | 7 + .../app/dashboard/products/[id]/page.tsx | 504 +- .../app/dashboard/products/new/page.tsx | 2 +- .../app/dashboard/products/page.tsx | 4 +- .../products/price-adjustments/new/page.tsx | 24 +- .../app/dashboard/receiving/grn/new/page.tsx | 60 +- .../app/dashboard/receiving/grn/page.tsx | 525 +- .../app/dashboard/sales/day-end/page.tsx | 18 +- .../app/dashboard/sales/invoices/page.tsx | 202 +- .../app/dashboard/settings/access/page.tsx | 123 + .../app/dashboard/settings/approvals/page.tsx | 43 +- .../dashboard/settings/audit-logs/page.tsx | 139 + .../dashboard/settings/configuration/page.tsx | 14 +- .../procurement-configuration/page.tsx | 213 + .../settings/sales-configuration/page.tsx | 48 + .../dashboard/settings/users/[id]/page.tsx | 62 +- .../app/dashboard/settings/users/page.tsx | 2 +- .../app/dashboard/vendors/[id]/page.tsx | 45 +- .../erp-system/app/dashboard/vendors/page.tsx | 62 +- .../components/Layouts/AppSidebar.tsx | 25 +- .../erp-system/components/Layouts/Header.tsx | 1 + .../accounts/OpeningCashBankPanel.tsx | 278 + .../components/accounts/OpeningStockPanel.tsx | 449 + .../erp-system/components/auth/AccessGate.tsx | 79 + .../components/auth/AuthProvider.tsx | 111 +- .../customers/EditCustomerDialog.tsx | 101 +- .../procurement/PurchaseOrderListView.tsx | 395 + .../components/procurement/status-badges.tsx | 15 +- .../components/products/WarrantyFields.tsx | 183 + .../components/receiving/GrnReceiptPrint.tsx | 301 + .../components/ui/date-range-filter.tsx | 83 + .../components/vendors/EditVendorDialog.tsx | 45 +- .../vendors/VendorOptionalFields.tsx | 117 + .../erp-system/hooks/use-customer-config.ts | 63 + .../hooks/use-procurement-config.ts | 43 + Frontend/erp-system/lib/api-client.ts | 22 +- Frontend/erp-system/lib/api/access-config.ts | 21 + Frontend/erp-system/lib/api/grns.ts | 5 + .../erp-system/lib/api/opening-balances.ts | 40 + .../erp-system/lib/api/procurement-config.ts | 22 + .../erp-system/lib/api/purchase-orders.ts | 9 +- Frontend/erp-system/lib/api/users.ts | 2 + Frontend/erp-system/lib/nav-routes.ts | 42 + .../lib/pdf/purchase-order-download.ts | 36 + .../erp-system/lib/pdf/purchase-order-pdf.ts | 259 + Frontend/erp-system/lib/po-tax.ts | 33 + .../erp-system/lib/validations/procurement.ts | 1 + Frontend/erp-system/types/approvals.ts | 4 +- Frontend/erp-system/types/grn.ts | 7 + Frontend/erp-system/types/master-data.ts | 8 + Frontend/erp-system/types/opening-balances.ts | 78 + Frontend/erp-system/types/procurement.ts | 40 + Frontend/erp-system/types/rbac.ts | 8 + Frontend/erp-system/types/sales.ts | 15 + Testing/e2e/specs/global.setup.ts | 3 + docs/02-SECURITY.md | 8 +- 202 files changed, 99906 insertions(+), 1894 deletions(-) create mode 100644 Backend/ERPCore.Tests/AccessControlTests.cs create mode 100644 Backend/ERPCore.Tests/CustomerFieldSwitchTests.cs create mode 100644 Backend/ERPCore.Tests/PermissionCoverageTests.cs create mode 100644 Backend/ERPCore.Tests/ProcurementAndOpeningBalanceTests.cs create mode 100644 Backend/ERPCore/Controllers/AccessConfigController.cs create mode 100644 Backend/ERPCore/Controllers/OpeningBalancesController.cs create mode 100644 Backend/ERPCore/Controllers/ProcurementConfigController.cs create mode 100644 Backend/ERPCore/Domain/Entities/AccessConfig.cs create mode 100644 Backend/ERPCore/Domain/Entities/OpeningBalanceRun.cs create mode 100644 Backend/ERPCore/Domain/Entities/ProcurementConfig.cs create mode 100644 Backend/ERPCore/Domain/Enums/OpeningBalanceKind.cs create mode 100644 Backend/ERPCore/Domain/Enums/VendorContactMode.cs delete mode 100644 Backend/ERPCore/Dtos/Auth/AuthRoleDtos.cs create mode 100644 Backend/ERPCore/Dtos/Config/AccessConfigDtos.cs create mode 100644 Backend/ERPCore/Dtos/Config/ProcurementConfigDtos.cs create mode 100644 Backend/ERPCore/Dtos/OpeningBalances/OpeningBalanceDtos.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/AccessConfigConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/OpeningBalanceConfiguration.cs create mode 100644 Backend/ERPCore/Infra/Persistence/Configurations/ProcurementConfigConfiguration.cs create mode 100644 Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.cs create mode 100644 Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.cs create mode 100644 Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.cs create mode 100644 Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.cs create mode 100644 Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.cs create mode 100644 Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.cs create mode 100644 Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.cs create mode 100644 Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.cs create mode 100644 Backend/ERPCore/Services/AccessConfigService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IAccessConfigService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IOpeningBalanceService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IOpeningStockService.cs create mode 100644 Backend/ERPCore/Services/Interfaces/IProcurementConfigService.cs create mode 100644 Backend/ERPCore/Services/OpeningBalanceService.cs create mode 100644 Backend/ERPCore/Services/OpeningEquityAccount.cs create mode 100644 Backend/ERPCore/Services/OpeningStockService.cs create mode 100644 Backend/ERPCore/Services/ProcurementConfigService.cs create mode 100644 Frontend/erp-system/app/dashboard/accounts/opening-balances/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/products/[id]/edit/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/settings/access/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/settings/audit-logs/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/settings/procurement-configuration/page.tsx create mode 100644 Frontend/erp-system/components/accounts/OpeningCashBankPanel.tsx create mode 100644 Frontend/erp-system/components/accounts/OpeningStockPanel.tsx create mode 100644 Frontend/erp-system/components/auth/AccessGate.tsx create mode 100644 Frontend/erp-system/components/procurement/PurchaseOrderListView.tsx create mode 100644 Frontend/erp-system/components/products/WarrantyFields.tsx create mode 100644 Frontend/erp-system/components/receiving/GrnReceiptPrint.tsx create mode 100644 Frontend/erp-system/components/ui/date-range-filter.tsx create mode 100644 Frontend/erp-system/components/vendors/VendorOptionalFields.tsx create mode 100644 Frontend/erp-system/hooks/use-customer-config.ts create mode 100644 Frontend/erp-system/hooks/use-procurement-config.ts create mode 100644 Frontend/erp-system/lib/api/access-config.ts create mode 100644 Frontend/erp-system/lib/api/opening-balances.ts create mode 100644 Frontend/erp-system/lib/api/procurement-config.ts create mode 100644 Frontend/erp-system/lib/nav-routes.ts create mode 100644 Frontend/erp-system/lib/pdf/purchase-order-download.ts create mode 100644 Frontend/erp-system/lib/pdf/purchase-order-pdf.ts create mode 100644 Frontend/erp-system/lib/po-tax.ts create mode 100644 Frontend/erp-system/types/opening-balances.ts diff --git a/Backend/ERPCore.Tests/AccessControlTests.cs b/Backend/ERPCore.Tests/AccessControlTests.cs new file mode 100644 index 0000000..9479874 --- /dev/null +++ b/Backend/ERPCore.Tests/AccessControlTests.cs @@ -0,0 +1,512 @@ +using System.Security.Claims; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Config; +using ERPCore.Dtos.Users; +using ERPCore.Infra.Auth; +using ERPCore.Infra.Persistence; +using ERPCore.Repositories; +using ERPCore.Services; +using ERPCore.System.Errors; +using ERPCore.Tests.TestSupport; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Configuration; +using static ERPCore.Tests.TestSupport.BankDepositTestSupport; + +namespace ERPCore.Tests; + +/// +/// The ERP-owned role model: AuthHex proves who someone is; the role, its permissions and "is admin" come from the +/// ERP's own users/roles tables, are read on every request, and a change applies on the very next one. +/// +public class AccessControlTests +{ + private const int AdminRole = 1, ApproverRole = 2, ViewerRole = 3, NoAccessRole = 4, InactiveRole = 5; + + // ---------------------------------------------------------------- helpers + + private sealed class FakeUser(int userId = 10, int? roleId = null, bool isAdmin = false) : ICurrentUser + { + public int AuditUserId { get; set; } = userId; + public int? RoleId { get; set; } = roleId; + public string? RoleCode { get; set; } + public bool IsAdmin { get; set; } = isAdmin; + public string UserId => AuditUserId.ToString(); + public bool IsAuthenticated => true; + public int? BranchId => null; + } + + private static async Task SeedAsync(bool withDefault = true) + { + var db = CreateContext(); + db.Roles.AddRange( + new Role { RoleId = AdminRole, Code = "Admin", Name = "Administrator", IsAdmin = true, IsSystemRole = true, CreatedAt = DateTime.UtcNow }, + new Role { RoleId = ApproverRole, Code = "Approver", Name = "Approver", CreatedAt = DateTime.UtcNow }, + new Role { RoleId = ViewerRole, Code = "Viewer", Name = "Viewer", CreatedAt = DateTime.UtcNow }, + new Role { RoleId = NoAccessRole, Code = "NoAccess", Name = "No access", IsSystemRole = true, CreatedAt = DateTime.UtcNow }, + new Role { RoleId = InactiveRole, Code = "Retired", Name = "Retired", Status = EntityStatus.Inactive, CreatedAt = DateTime.UtcNow }); + + db.NavItems.Add(new NavItem { NavItemId = 100, Code = "procurement", Label = "Procurement" }); + db.SubNavItems.Add(new SubNavItem { SubNavItemId = 101, NavItemId = 100, Code = "procurement.vendors", Label = "Vendors" }); + db.NavItems.Add(new NavItem { NavItemId = 102, Code = "stock", Label = "Stock" }); + db.Permissions.AddRange( + new Permission { PermissionId = 200, Code = "NAV:procurement", NavItemId = 100 }, + new Permission { PermissionId = 201, Code = "NAV:procurement.vendors", SubNavItemId = 101 }, + new Permission { PermissionId = 202, Code = "NAV:stock", NavItemId = 102 }); + db.RolePermissions.AddRange( + new RolePermission { RoleId = ApproverRole, PermissionId = 200, Actions = RolePermissionAction.View | RolePermissionAction.Approve }, + new RolePermission { RoleId = ViewerRole, PermissionId = 201, Actions = RolePermissionAction.View }, + new RolePermission { RoleId = InactiveRole, PermissionId = 202, Actions = RolePermissionAction.All }); + + if (withDefault) db.AccessConfig.Add(new AccessConfig { ConfigId = AccessConfig.SingletonId, DefaultRoleId = NoAccessRole }); + await db.SaveChangesAsync(); + return db; + } + + private static ClaimsPrincipal Principal(Guid authUserId, string? tokenRoleCode = null) + { + var claims = new List { new("UserId", authUserId.ToString()), new("NIC", "N-1") }; + if (tokenRoleCode is not null) claims.Add(new Claim("RoleCode", tokenRoleCode)); + return new ClaimsPrincipal(new ClaimsIdentity(claims, "test")); + } + + private static ShadowUserClaimsTransformation Transformer(ErpDbContext db, bool legacyFallback = true) => + new(db, new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["Auth:LegacyRoleClaimFallback"] = legacyFallback.ToString() }) + .Build()); + + private static string? Claim(ClaimsPrincipal p, string type) => p.FindFirstValue(type); + + private static User NewUser(Guid authId, int? roleId, EntityStatus status = EntityStatus.Active, int id = 0) => new() + { + UserId = id == 0 ? Random.Shared.Next(100, 100000) : id, + AuthUserId = authId, Username = $"u-{authId:N}", DisplayName = "Someone", RoleId = roleId, Status = status + }; + + private static NavPermissionService NavService(ErpDbContext db) => + new(new Repository(db), new Repository(db), new MemoryCache(new MemoryCacheOptions())); + + private static RoleService RoleSvc(ErpDbContext db, ICurrentUser user, NavPermissionService nav) => + new(new Repository(db), new Repository(db), new Repository(db), new Repository(db), + new Repository(db), new Repository(db), user, nav, new FakeBranchConfig(), new PassThroughUnitOfWork(db)); + + // The AuthHex client, link and employee services are not touched by role/status changes (users here have no AuthUserId). + private static UserManagementService UserSvc(ErpDbContext db, ICurrentUser user) => + new(new Repository(db), new Repository(db), new Repository(db), null!, null!, null!, user, new PassThroughUnitOfWork(db)); + + // ---------------------------------------------------------------- claims: the role comes from the ERP + + [Fact] + public async Task Role_IsReadFromTheErpTable_NotFromTheTokensRoleClaim() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, ApproverRole)); + await db.SaveChangesAsync(); + + // The token still says "Admin" (an old AuthHex token). The ERP says Approver, and the ERP wins. + var principal = await Transformer(db).TransformAsync(Principal(auth, tokenRoleCode: "Admin")); + + Assert.Equal(ApproverRole.ToString(), Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Equal("Approver", Claim(principal, AuthHexClaims.ErpRoleCode)); + Assert.Equal("false", Claim(principal, AuthHexClaims.ErpIsAdmin)); + } + + [Fact] + public async Task Admin_IsTheRoleFlag_NotTheRoleName() + { + await using var db = await SeedAsync(); + var boss = Guid.NewGuid(); + var impostor = Guid.NewGuid(); + db.Roles.Add(new Role { RoleId = 20, Code = "Boss", Name = "The boss", IsAdmin = true, CreatedAt = DateTime.UtcNow }); + db.Roles.Add(new Role { RoleId = 21, Code = "ADMIN2", Name = "admin", CreatedAt = DateTime.UtcNow }); // named like an admin, is not + db.Users.AddRange(NewUser(boss, 20), NewUser(impostor, 21)); + await db.SaveChangesAsync(); + + Assert.Equal("true", Claim(await Transformer(db).TransformAsync(Principal(boss)), AuthHexClaims.ErpIsAdmin)); + Assert.Equal("false", Claim(await Transformer(db).TransformAsync(Principal(impostor)), AuthHexClaims.ErpIsAdmin)); + } + + [Fact] + public async Task RenamingTheAdminRole_DoesNotCostAnyoneTheirAdminRights() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, AdminRole)); + await db.SaveChangesAsync(); + + db.Roles.Single(r => r.RoleId == AdminRole).Code = "Owners"; + await db.SaveChangesAsync(); + + var principal = await Transformer(db).TransformAsync(Principal(auth)); + Assert.Equal("true", Claim(principal, AuthHexClaims.ErpIsAdmin)); + Assert.Equal("Owners", Claim(principal, AuthHexClaims.ErpRoleCode)); + } + + [Fact] + public async Task FirstSignIn_CreatesTheUser_AndGivesItTheDefaultRole() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + + var principal = await Transformer(db).TransformAsync(Principal(auth)); + + var user = Assert.Single(db.Users.Where(u => u.AuthUserId == auth)); + Assert.Equal(NoAccessRole, user.RoleId); // saved, so the Users screen shows it + Assert.Equal(NoAccessRole.ToString(), Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Equal("false", Claim(principal, AuthHexClaims.ErpIsAdmin)); + Assert.Equal(user.UserId.ToString(), Claim(principal, ClaimTypes.NameIdentifier)); + } + + [Fact] + public async Task WithNoDefaultRole_APersonWithoutARoleHasNoRoleClaims() + { + await using var db = await SeedAsync(withDefault: false); + var principal = await Transformer(db).TransformAsync(Principal(Guid.NewGuid())); + + Assert.Null(Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Null(Claim(principal, AuthHexClaims.ErpIsAdmin)); + Assert.NotNull(Claim(principal, ClaimTypes.NameIdentifier)); // still a known user + } + + [Fact] + public async Task LegacyFallback_UsesTheTokensRole_ForARolelessUser_AndSavesIt() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, roleId: null)); + await db.SaveChangesAsync(); + + var principal = await Transformer(db, legacyFallback: true).TransformAsync(Principal(auth, "Approver")); + + Assert.Equal(ApproverRole.ToString(), Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Equal(ApproverRole, db.Users.Single(u => u.AuthUserId == auth).RoleId); + } + + [Fact] + public async Task LegacyFallbackOff_IgnoresTheTokenAndUsesTheDefault() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, roleId: null)); + await db.SaveChangesAsync(); + + var principal = await Transformer(db, legacyFallback: false).TransformAsync(Principal(auth, "Admin")); + + Assert.Equal(NoAccessRole.ToString(), Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Equal("false", Claim(principal, AuthHexClaims.ErpIsAdmin)); + } + + [Fact] + public async Task AnAssignedRole_IsNeverOverwrittenByTheFallbackOrTheDefault() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, ViewerRole)); + await db.SaveChangesAsync(); + + var principal = await Transformer(db).TransformAsync(Principal(auth, "Admin")); + + Assert.Equal(ViewerRole.ToString(), Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Equal(ViewerRole, db.Users.Single(u => u.AuthUserId == auth).RoleId); + } + + [Fact] + public async Task InactiveUser_IsBlocked_AndGetsNoRole() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, AdminRole, EntityStatus.Inactive)); + await db.SaveChangesAsync(); + + var principal = await Transformer(db).TransformAsync(Principal(auth)); + + Assert.Equal("1", Claim(principal, AuthHexClaims.ErpBlocked)); + Assert.Null(Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Null(Claim(principal, AuthHexClaims.ErpIsAdmin)); + } + + [Fact] + public async Task InactiveRole_GrantsNothing() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + db.Users.Add(NewUser(auth, InactiveRole)); + await db.SaveChangesAsync(); + + var principal = await Transformer(db).TransformAsync(Principal(auth)); + + Assert.Null(Claim(principal, AuthHexClaims.ErpRoleId)); + Assert.Null(Claim(principal, AuthHexClaims.ErpBlocked)); // not blocked — just no permissions + } + + [Fact] + public async Task Branch_ComesFromTheLinkedEmployee_AndTransformingTwiceAddsNothingNew() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + var user = NewUser(auth, ViewerRole, id: 500); + db.Users.Add(user); + db.Employees.Add(new Employee { EmployeeId = 1, EmployeeCode = "E1", FullName = "Emp", UserId = 500, BranchId = Branch2 }); + await db.SaveChangesAsync(); + + var transformer = Transformer(db); + var principal = await transformer.TransformAsync(Principal(auth)); + var claimsAfterFirst = principal.Claims.Count(); + principal = await transformer.TransformAsync(principal); + + Assert.Equal(Branch2.ToString(), Claim(principal, AuthHexClaims.BranchId)); + Assert.Equal(claimsAfterFirst, principal.Claims.Count()); + } + + // ---------------------------------------------------------------- permissions by role id + + [Fact] + public async Task Nav_AccessAndActions_AreDecidedByTheRolesPermissions() + { + await using var db = await SeedAsync(); + var nav = NavService(db); + + Assert.True(await nav.HasAccessAsync(ApproverRole, "procurement")); + Assert.True(await nav.HasActionAsync(ApproverRole, "procurement", RolePermissionAction.Approve)); + Assert.False(await nav.HasActionAsync(ApproverRole, "procurement", RolePermissionAction.Delete)); + Assert.False(await nav.HasAccessAsync(ApproverRole, "stock")); + + // A parent module is reachable through a child grant; an action is exact. + Assert.True(await nav.HasAccessAsync(ViewerRole, "procurement")); + Assert.False(await nav.HasActionAsync(ViewerRole, "procurement", RolePermissionAction.View)); + Assert.True(await nav.HasActionAsync(ViewerRole, "procurement.vendors", RolePermissionAction.View)); + + Assert.False(await nav.HasAccessAsync(null, "procurement")); + Assert.False(await nav.HasAccessAsync(999, "procurement")); + } + + [Fact] + public async Task Nav_InactiveRole_HasNoAccess() + { + await using var db = await SeedAsync(); + Assert.False(await NavService(db).HasAccessAsync(InactiveRole, "stock")); + } + + [Fact] + public async Task EditingARolesPermissions_AppliesImmediately_NotAfterACacheTimeout() + { + await using var db = await SeedAsync(); + var nav = NavService(db); + var roles = RoleSvc(db, new FakeUser(roleId: AdminRole, isAdmin: true), nav); + + Assert.False(await nav.HasAccessAsync(ViewerRole, "stock")); // cached as "no" + await roles.AssignPermissionsAsync(ViewerRole, new Dtos.Rbac.AssignRolePermissionsRequest { NavItemIds = [102] }); + + Assert.True(await nav.HasAccessAsync(ViewerRole, "stock")); // visible straight away + } + + // ---------------------------------------------------------------- /auth/me + + [Fact] + public async Task Me_ForAnAdministrator_ListsEveryPermission_EvenOnesNeverGranted() + { + await using var db = await SeedAsync(); + var me = await RoleSvc(db, new FakeUser(roleId: AdminRole, isAdmin: true), NavService(db)).GetMeAsync(); + + Assert.True(me.HasRole); + Assert.True(me.IsAdmin); + Assert.Equal(AdminRole, me.RoleId); + Assert.Contains("procurement.vendors", me.NavCodes); + Assert.Contains("stock", me.NavCodes); + Assert.Equal((int)RolePermissionAction.All, me.Permissions!["stock"]); + } + + [Fact] + public async Task Me_ForAnOrdinaryRole_ListsOnlyItsGrants_WithTheirActions() + { + await using var db = await SeedAsync(); + var me = await RoleSvc(db, new FakeUser(roleId: ApproverRole), NavService(db)).GetMeAsync(); + + Assert.True(me.HasRole); + Assert.False(me.IsAdmin); + Assert.Equal(["procurement"], me.NavCodes); + Assert.Equal((int)(RolePermissionAction.View | RolePermissionAction.Approve), me.Permissions!["procurement"]); + } + + [Fact] + public async Task Me_WithoutARole_SaysSo_InsteadOfReturningAnEmptyMenu() + { + await using var db = await SeedAsync(); + var me = await RoleSvc(db, new FakeUser(roleId: null), NavService(db)).GetMeAsync(); + + Assert.False(me.HasRole); + Assert.Empty(me.NavCodes); + Assert.Empty(me.Permissions!); + } + + // ---------------------------------------------------------------- role management guards + + [Fact] + public async Task TheAdministratorSystemAndDefaultRoles_CannotBeDeactivatedOrDeleted() + { + await using var db = await SeedAsync(); + var roles = RoleSvc(db, new FakeUser(roleId: AdminRole, isAdmin: true), NavService(db)); + + await Assert.ThrowsAsync(() => roles.SetStatusAsync(AdminRole, EntityStatus.Inactive)); + await Assert.ThrowsAsync(() => roles.DeleteAsync(AdminRole)); + await Assert.ThrowsAsync(() => roles.DeleteAsync(NoAccessRole)); // system role and the default + await Assert.ThrowsAsync(() => roles.SetStatusAsync(NoAccessRole, EntityStatus.Inactive)); + } + + [Fact] + public async Task ARoleInUse_CannotBeDeactivated_ButAnUnusedOneCanBeDeleted() + { + await using var db = await SeedAsync(); + db.Users.Add(NewUser(Guid.NewGuid(), ViewerRole)); + await db.SaveChangesAsync(); + var roles = RoleSvc(db, new FakeUser(roleId: AdminRole, isAdmin: true), NavService(db)); + + await Assert.ThrowsAsync(() => roles.SetStatusAsync(ViewerRole, EntityStatus.Inactive)); + await roles.DeleteAsync(ApproverRole); + Assert.False(db.Roles.Any(r => r.RoleId == ApproverRole)); + } + + [Fact] + public async Task CreatedRoles_NeverCarryTheAdminFlag() + { + await using var db = await SeedAsync(); + var roles = RoleSvc(db, new FakeUser(roleId: AdminRole, isAdmin: true), NavService(db)); + + var created = (await roles.CreateAsync(new Dtos.Rbac.CreateRoleRequest { Code = "Clerk", Name = "Clerk" })).Value; + + Assert.False(created.IsAdmin); + Assert.False(created.IsSystemRole); + Assert.Null(db.Roles.Single(r => r.RoleId == created.RoleId).AuthRoleId); + } + + // ---------------------------------------------------------------- user management guards + + [Fact] + public async Task ChangingAUsersRole_TakesEffectOnTheirNextRequest_WithNoRelogin() + { + await using var db = await SeedAsync(); + var auth = Guid.NewGuid(); + var user = NewUser(auth, ViewerRole, id: 700); + db.Users.Add(user); + await db.SaveChangesAsync(); + Assert.Equal(ViewerRole.ToString(), Claim(await Transformer(db).TransformAsync(Principal(auth)), AuthHexClaims.ErpRoleId)); + + await UserSvc(db, new FakeUser(1, AdminRole, true)).UpdateRoleAsync(700, new UpdateUserRoleRequest { RoleId = ApproverRole }); + + // A fresh request for the same login now carries the new role. + Assert.Equal(ApproverRole.ToString(), Claim(await Transformer(db).TransformAsync(Principal(auth)), AuthHexClaims.ErpRoleId)); + } + + [Fact] + public async Task TheLastActiveAdministrator_CannotBeDemotedOrDeactivated() + { + await using var db = await SeedAsync(); + db.Users.Add(NewUser(Guid.NewGuid(), AdminRole, id: 801)); + await db.SaveChangesAsync(); + var svc = UserSvc(db, new FakeUser(userId: 1, roleId: AdminRole, isAdmin: true)); + + var demote = await Assert.ThrowsAsync(() => svc.UpdateRoleAsync(801, new UpdateUserRoleRequest { RoleId = ViewerRole })); + var deactivate = await Assert.ThrowsAsync(() => svc.SetStatusAsync(801, EntityStatus.Inactive)); + + Assert.Equal(409, demote.StatusCode); + Assert.Equal(409, deactivate.StatusCode); + Assert.Equal(AdminRole, db.Users.Single(u => u.UserId == 801).RoleId); + } + + [Fact] + public async Task AnAdministrator_CanBeDemoted_WhenAnotherActiveAdminRemains() + { + await using var db = await SeedAsync(); + db.Users.AddRange(NewUser(Guid.NewGuid(), AdminRole, id: 801), NewUser(Guid.NewGuid(), AdminRole, id: 802)); + await db.SaveChangesAsync(); + + await UserSvc(db, new FakeUser(userId: 802, roleId: AdminRole, isAdmin: true)) + .UpdateRoleAsync(801, new UpdateUserRoleRequest { RoleId = ViewerRole }); + + Assert.Equal(ViewerRole, db.Users.Single(u => u.UserId == 801).RoleId); + } + + [Fact] + public async Task Nobody_CanRemoveTheirOwnAdminAccess_OrDeactivateThemselves() + { + await using var db = await SeedAsync(); + db.Users.AddRange(NewUser(Guid.NewGuid(), AdminRole, id: 801), NewUser(Guid.NewGuid(), AdminRole, id: 802)); + await db.SaveChangesAsync(); + var svc = UserSvc(db, new FakeUser(userId: 801, roleId: AdminRole, isAdmin: true)); + + await Assert.ThrowsAsync(() => svc.UpdateRoleAsync(801, new UpdateUserRoleRequest { RoleId = ViewerRole })); + await Assert.ThrowsAsync(() => svc.SetStatusAsync(801, EntityStatus.Inactive)); + } + + [Fact] + public async Task AUser_CannotBeGivenAnInactiveRole() + { + await using var db = await SeedAsync(); + db.Users.Add(NewUser(Guid.NewGuid(), ViewerRole, id: 900)); + await db.SaveChangesAsync(); + + await Assert.ThrowsAsync( + () => UserSvc(db, new FakeUser(1, AdminRole, true)).UpdateRoleAsync(900, new UpdateUserRoleRequest { RoleId = InactiveRole })); + } + + // ---------------------------------------------------------------- access config (default role) + + private static AccessConfigService AccessSvc(ErpDbContext db) => + new(new Repository(db), new Repository(db), new FakeUser(), new PassThroughUnitOfWork(db)); + + [Fact] + public async Task TheDefaultRole_CanNeverBeAnAdministratorOrAnInactiveRole() + { + await using var db = await SeedAsync(); + var svc = AccessSvc(db); + var etag = db.AccessConfig.Single().RowVersion; + + await Assert.ThrowsAsync(() => svc.UpdateAsync(new UpdateAccessConfigRequest { DefaultRoleId = AdminRole }, etag)); + await Assert.ThrowsAsync(() => svc.UpdateAsync(new UpdateAccessConfigRequest { DefaultRoleId = InactiveRole }, etag)); + await Assert.ThrowsAsync(() => svc.UpdateAsync(new UpdateAccessConfigRequest { DefaultRoleId = 999 }, etag)); + Assert.Equal(NoAccessRole, db.AccessConfig.Single().DefaultRoleId); + + var changed = (await svc.UpdateAsync(new UpdateAccessConfigRequest { DefaultRoleId = ViewerRole }, etag)).Value; + Assert.Equal(("Viewer", ViewerRole), (changed.DefaultRoleName, changed.DefaultRoleId)); + } + + // ---------------------------------------------------------------- seeding + + [Fact] + public async Task Seeder_EnsuresAnAdminRoleAndALeastPrivilegeDefault_AndIsIdempotent() + { + await using var db = CreateContext(); + db.Database.EnsureCreated(); + + await DataSeeder.SeedAsync(db); + await DataSeeder.SeedAsync(db); + + var admin = Assert.Single(db.Roles.Where(r => r.IsAdmin)); + Assert.True(admin.IsSystemRole); + Assert.Equal(db.Permissions.Count(), db.RolePermissions.Count(rp => rp.RoleId == admin.RoleId)); + var noAccess = Assert.Single(db.Roles.Where(r => r.Code == DataSeeder.DefaultNoAccessRoleCode)); + Assert.Empty(db.RolePermissions.Where(rp => rp.RoleId == noAccess.RoleId)); + Assert.Equal(noAccess.RoleId, Assert.Single(db.AccessConfig).DefaultRoleId); + } + + [Fact] + public async Task Seeder_FlagsAnExistingRoleCalledAdmin_InsteadOfAddingASecond_AndKeepsAChosenDefault() + { + await using var db = CreateContext(); + db.Database.EnsureCreated(); + db.Roles.Add(new Role { RoleId = 50, Code = "ADMIN", Name = "Old admin", CreatedAt = DateTime.UtcNow }); + db.Roles.Add(new Role { RoleId = 51, Code = "Reader", Name = "Reader", CreatedAt = DateTime.UtcNow }); + await db.SaveChangesAsync(); + + await DataSeeder.SeedAsync(db); + db.AccessConfig.Single().DefaultRoleId = 51; // an administrator's own choice + await db.SaveChangesAsync(); + await DataSeeder.SeedAsync(db); + + Assert.True(db.Roles.Single(r => r.RoleId == 50).IsAdmin); + Assert.Equal(1, db.Roles.Count(r => r.IsAdmin)); + Assert.Equal(51, db.AccessConfig.Single().DefaultRoleId); + } +} diff --git a/Backend/ERPCore.Tests/CustomerFieldSwitchTests.cs b/Backend/ERPCore.Tests/CustomerFieldSwitchTests.cs new file mode 100644 index 0000000..17b9e48 --- /dev/null +++ b/Backend/ERPCore.Tests/CustomerFieldSwitchTests.cs @@ -0,0 +1,158 @@ +using ERPCore.Domain.Entities; +using ERPCore.Dtos.Customers; +using ERPCore.Infra.Persistence; +using ERPCore.Repositories; +using ERPCore.Services; +using ERPCore.System.Errors; +using ERPCore.Tests.TestSupport; +using static ERPCore.Tests.TestSupport.BankDepositTestSupport; + +namespace ERPCore.Tests; + +/// +/// Sales Configuration decides which optional customer fields exist (phone, email, address, city, country, credit limit, +/// credit days). The forms hide a switched-off field; the server refuses a new value for it and keeps what is stored. +/// +public class CustomerFieldSwitchTests +{ + private static CustomerService Svc(ErpDbContext db) => + new(new Repository(db), new Repository(db), new FakeBranchConfig(), + new Repository(db), new Repository(db), new Repository(db), + new PassThroughUnitOfWork(db)); + + private static async Task SwitchesAsync(ErpDbContext db, Action tweak) + { + var config = new SalesConfig { ConfigId = SalesConfig.SingletonId }; + tweak(config); + db.SalesConfig.Add(config); + await db.SaveChangesAsync(); + } + + private static CreateCustomerRequest NewCustomer(Action? tweak = null) + { + var request = new CreateCustomerRequest { CustomerCode = "C-1", Name = "Nimal Perera", BranchId = Branch1 }; + tweak?.Invoke(request); + return request; + } + + private static UpdateCustomerRequest Same(Customer c, Action? tweak = null) + { + var request = new UpdateCustomerRequest + { + CustomerCode = c.CustomerCode, CustomerType = c.CustomerType, Name = c.Name, DisplayName = c.DisplayName, + Phone = c.Phone, Email = c.Email, AddressLine1 = c.AddressLine1, AddressLine2 = c.AddressLine2, + City = c.City, Country = c.Country, TaxRegistrationNo = c.TaxRegistrationNo, + CreditLimit = c.CreditLimit, CreditDays = c.CreditDays, BranchId = c.BranchId + }; + tweak?.Invoke(request); + return request; + } + + [Fact] + public async Task EveryFieldIsOn_WhenNothingIsConfigured_SoExistingBehaviourIsUnchanged() + { + await using var db = CreateContext(); // no sales_config row at all + + var created = (await Svc(db).CreateAsync(NewCustomer(r => + { + r.Phone = "0771234567"; r.Email = "a@b.lk"; r.AddressLine1 = "No. 12"; r.City = "Colombo"; + r.Country = "Sri Lanka"; r.CreditLimit = 5000m; r.CreditDays = 30; + }))).Value; + + Assert.Equal(("0771234567", "a@b.lk", "No. 12", "Colombo", "Sri Lanka"), (created.Phone, created.Email, created.AddressLine1, created.City, created.Country)); + Assert.Equal((5000m, 30), (created.CreditLimit, created.CreditDays)); + } + + [Theory] + [InlineData("phone")] + [InlineData("email")] + [InlineData("address")] + [InlineData("city")] + [InlineData("country")] + [InlineData("creditLimit")] + [InlineData("creditDays")] + public async Task Create_ANewValueForASwitchedOffField_IsRefused(string field) + { + await using var db = CreateContext(); + await SwitchesAsync(db, c => + { + c.CustomerPhoneEnabled = field != "phone"; + c.CustomerEmailEnabled = field != "email"; + c.CustomerAddressEnabled = field != "address"; + c.CustomerCityEnabled = field != "city"; + c.CustomerCountryEnabled = field != "country"; + c.CustomerCreditLimitEnabled = field != "creditLimit"; + c.CustomerCreditDaysEnabled = field != "creditDays"; + }); + + var ex = await Assert.ThrowsAsync(() => Svc(db).CreateAsync(NewCustomer(r => + { + switch (field) + { + case "phone": r.Phone = "0771234567"; break; + case "email": r.Email = "a@b.lk"; break; + case "address": r.AddressLine2 = "Wellawatte"; break; + case "city": r.City = "Colombo"; break; + case "country": r.Country = "Sri Lanka"; break; + case "creditLimit": r.CreditLimit = 100m; break; + default: r.CreditDays = 7; break; + } + }))); + + Assert.Equal(ErrorCodes.ConfigDisabled, ex.Code); + Assert.Equal(422, ex.StatusCode); + Assert.Empty(db.Customers.Where(c => c.CustomerCode == "C-1")); + } + + [Fact] + public async Task Create_WithTheSwitchedOffFieldsLeftBlank_Works() + { + await using var db = CreateContext(); + await SwitchesAsync(db, c => + { + c.CustomerPhoneEnabled = c.CustomerEmailEnabled = c.CustomerAddressEnabled = false; + c.CustomerCityEnabled = c.CustomerCountryEnabled = false; + c.CustomerCreditLimitEnabled = c.CustomerCreditDaysEnabled = false; + }); + + var created = (await Svc(db).CreateAsync(NewCustomer())).Value; + + Assert.Equal("Nimal Perera", created.Name); + Assert.Null(created.Phone); + Assert.Equal((0m, 0), (created.CreditLimit, created.CreditDays)); + } + + [Fact] + public async Task Update_KeepsAStoredValueWhoseFieldWasSwitchedOff_EvenWhenTheFormSendsItBlank() + { + await using var db = CreateContext(); + var svc = Svc(db); + var created = (await svc.CreateAsync(NewCustomer(r => { r.Phone = "0771234567"; r.CreditLimit = 5000m; r.CreditDays = 30; }))).Value; + + await SwitchesAsync(db, c => { c.CustomerPhoneEnabled = false; c.CustomerCreditLimitEnabled = false; c.CustomerCreditDaysEnabled = false; }); + var stored = db.Customers.Single(c => c.CustomerId == created.CustomerId); + + // The edit form no longer has the fields, so it sends blanks / zeros. + var updated = (await svc.UpdateAsync(created.CustomerId, + Same(stored, r => { r.Name = "Nimal P."; r.Phone = null; r.CreditLimit = 0m; r.CreditDays = 0; }), stored.RowVersion)).Value; + + Assert.Equal("Nimal P.", updated.Name); + Assert.Equal("0771234567", updated.Phone); + Assert.Equal((5000m, 30), (updated.CreditLimit, updated.CreditDays)); + } + + [Fact] + public async Task Update_ADifferentValueForASwitchedOffField_IsRefused() + { + await using var db = CreateContext(); + var svc = Svc(db); + var created = (await svc.CreateAsync(NewCustomer(r => r.CreditLimit = 5000m))).Value; + await SwitchesAsync(db, c => c.CustomerCreditLimitEnabled = false); + var stored = db.Customers.Single(c => c.CustomerId == created.CustomerId); + + var ex = await Assert.ThrowsAsync( + () => svc.UpdateAsync(created.CustomerId, Same(stored, r => r.CreditLimit = 9999m), stored.RowVersion)); + + Assert.Equal(ErrorCodes.ConfigDisabled, ex.Code); + } +} diff --git a/Backend/ERPCore.Tests/DualApprovalTests.cs b/Backend/ERPCore.Tests/DualApprovalTests.cs index 638f4f0..209a274 100644 --- a/Backend/ERPCore.Tests/DualApprovalTests.cs +++ b/Backend/ERPCore.Tests/DualApprovalTests.cs @@ -141,7 +141,7 @@ public class DualApprovalTests [Fact] public async Task SecondApproval_CanBeLimitedToRoles() { - var h = await Harness.CreateAsync(level2Roles: "Approver"); + var h = await Harness.CreateAsync(level2RoleIds: ApproverRoleId.ToString()); var id = (await h.Stock.CreateAsync(Request(-1))).AdjustmentId; h.User.Be(ApproverA); await h.Stock.ApproveAsync(id, null); @@ -250,7 +250,7 @@ public class DualApprovalTests public required AdjustmentService Stock { get; init; } public required PriceAdjustmentService Prices { get; init; } - public static async Task CreateAsync(int stockLevels = 2, decimal stockThreshold = 0, string? level2Roles = null) + public static async Task CreateAsync(int stockLevels = 2, decimal stockThreshold = 0, string? level2RoleIds = null) { var db = new ErpDbContext( new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString("N"), Root).Options, @@ -283,7 +283,7 @@ public class DualApprovalTests db.ApprovalConfig.Add(new ApprovalConfig { ApprovalConfigId = ApprovalConfig.SingletonId, StockAdjustmentLevels = stockLevels, - StockAdjustmentLevel2ThresholdValue = stockThreshold, PriceAdjustmentLevels = 2, Level2RoleCodes = level2Roles + StockAdjustmentLevel2ThresholdValue = stockThreshold, PriceAdjustmentLevels = 2, Level2RoleIds = level2RoleIds }); await db.SaveChangesAsync(); @@ -292,7 +292,7 @@ public class DualApprovalTests var notifications = new RecordingNotifications(); var approvals = new ApprovalService( new Repository(db), new Repository(db), new Repository(db), - new Repository(db), user, notifications, uow); + new Repository(db), new Repository(db), user, notifications, uow); var gl = new FakeGeneralLedger(); var mutator = new RecordingMutator(); var numbers = new FakeNumberSequence(); @@ -308,8 +308,9 @@ public class DualApprovalTests new SerialTrackingService(new Repository(db), new Repository(db), new Repository(db), new Repository(db), user)); var prices = new PriceAdjustmentService( - new Repository(db), new Repository(db), new Repository(db), - new Repository(db), new Repository(db), approvals, notifications, numbers, user, uow); + new Repository(db), new Repository(db), new Repository(db), + new Repository(db), new Repository(db), new Repository(db), + approvals, notifications, numbers, user, uow); return new Harness { Db = db, User = user, Mutator = mutator, Gl = gl, Notifications = notifications, Stock = stock, Prices = prices }; } @@ -323,7 +324,13 @@ public class DualApprovalTests [Requester] = "Requester", [ApproverA] = "Approver", [ApproverB] = "Approver", [Clerk] = "Clerk" }; + private static readonly Dictionary RoleIds = new() + { + [Requester] = 1, [ApproverA] = ApproverRoleId, [ApproverB] = ApproverRoleId, [Clerk] = ClerkRoleId + }; + public int AuditUserId { get; private set; } = userId; + public int? RoleId { get; private set; } = RoleIds[userId]; public string? RoleCode { get; private set; } = roleCode; public string UserId => AuditUserId.ToString(); public bool IsAuthenticated => true; @@ -333,6 +340,7 @@ public class DualApprovalTests public void Be(int userId) { AuditUserId = userId; + RoleId = RoleIds[userId]; RoleCode = Roles[userId]; } } @@ -368,7 +376,7 @@ public class DualApprovalTests => throw new NotSupportedException(); public Task PostLedgerAsync(int itemId, int warehouseId, int? binId, int? batchId, int? serialId, int userId, Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, string sourceDocType, int sourceDocId, - DateTime createdAt, CancellationToken ct = default, decimal? valueOverride = null) => throw new NotSupportedException(); + DateTime createdAt, CancellationToken ct = default, decimal? valueOverride = null, bool postGlJournal = true) => throw new NotSupportedException(); public Task GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default) => Task.FromResult(10m); } diff --git a/Backend/ERPCore.Tests/PermissionCoverageTests.cs b/Backend/ERPCore.Tests/PermissionCoverageTests.cs new file mode 100644 index 0000000..0477b6f --- /dev/null +++ b/Backend/ERPCore.Tests/PermissionCoverageTests.cs @@ -0,0 +1,420 @@ +using System.Reflection; +using System.Security.Claims; +using ERPCore.Controllers; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Search; +using ERPCore.Infra.Auth; +using ERPCore.Infra.Persistence; +using ERPCore.Repositories; +using ERPCore.Services; +using ERPCore.System.Errors; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.Caching.Memory; +using static ERPCore.Tests.TestSupport.BankDepositTestSupport; + +namespace ERPCore.Tests; + +/// +/// Permission coverage (Phase B): every API endpoint that changes data sits behind a permission, every permission the +/// controllers name actually exists in the seeded catalogue, HR data is gated, global search respects the role, +/// state-changing cookie requests need the CSRF token, and a branch-less person can't read every branch. +/// +public class PermissionCoverageTests +{ + // The seeded (HasData) rows live in the design-time model; the runtime model drops them. + private static IEnumerable> Seed(ErpDbContext db) => + Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService(db) + .Model.FindEntityType(typeof(T))!.GetSeedData(); + + // ---------------------------------------------------------------- controllers <-> permissions + + private static IEnumerable ApiControllers() => + typeof(ApiControllerBase).Assembly.GetTypes() + .Where(t => !t.IsAbstract && typeof(ApiControllerBase).IsAssignableFrom(t)); + + private static IEnumerable NavPolicyNames(MemberInfo member) => + member.GetCustomAttributes(inherit: true) + .Select(a => a.Policy) + .Where(p => p is not null && p.StartsWith(NavPolicies.Prefix, StringComparison.Ordinal))!; + + private static bool IsReadOnly(MethodInfo m) => + m.GetCustomAttributes().Any() && !m.GetCustomAttributes().OfType().Any(a => a is not HttpGetAttribute); + + /// Endpoints that deliberately sit behind the login door only: personal, or checked inside the service. + private static readonly HashSet OpenByDesign = new() + { + nameof(AuthController), // sign-in, sessions, password: the person's own + nameof(NavController), // "what may I see": the person's own permissions + nameof(NotificationsController), // the person's own inbox + nameof(SupportTicketsController), // anyone may raise a ticket; the service scopes by owner/admin + nameof(TrackingController), // read-only serial/batch pickers used inside sales and stock screens + nameof(SearchController), // gated per result type inside GlobalSearchService + nameof(MetaController), + }; + + [Fact] + public void EveryDataChangingEndpoint_IsBehindAPermission() + { + var uncovered = new List(); + foreach (var controller in ApiControllers().Where(c => !OpenByDesign.Contains(c.Name))) + { + var classCovered = NavPolicyNames(controller).Any(); + foreach (var action in controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.GetCustomAttributes().OfType().Any())) + { + if (IsReadOnly(action)) continue; + if (action.GetCustomAttributes().Any()) continue; + if (!classCovered && !NavPolicyNames(action).Any()) uncovered.Add($"{controller.Name}.{action.Name}"); + } + } + + Assert.True(uncovered.Count == 0, "Data-changing endpoints with no Nav permission: " + string.Join(", ", uncovered)); + } + + [Fact] + public void EveryControllerAboutSensitiveData_IsGatedAsAWhole() + { + // HR (salaries, bank accounts, loans, payslips) and the audit trail used to be open to every signed-in person. + foreach (var name in new[] + { + "AttendanceBatchesController", "LeaveRequestsController", "PayrollRunsController", "PayslipsController", + "HrReportsController", "EmployeesController", "SalaryComponentsController", "TaxSlabsController", + "PayrollStatutorySettingsController", "AuditLogsController", + }) + { + var type = typeof(ApiControllerBase).Assembly.GetTypes().Single(t => t.Name == name); + Assert.True(NavPolicyNames(type).Any(), $"{name} has no class-level Nav permission."); + } + } + + [Fact] + public void PayData_NeedsThePayrollPermission_OnTopOfEmployees() + { + var employees = typeof(ApiControllerBase).Assembly.GetTypes().Single(t => t.Name == "EmployeesController"); + foreach (var action in new[] { "ListBankDetails", "GetSalaryStructureHistory", "ListLoans", "GetLoan", "CreateLoan", "CreateSalaryStructure", "ReplaceBankDetails" }) + { + var method = employees.GetMethod(action)!; + Assert.Contains(NavPolicyNames(method), p => p.StartsWith(NavPolicies.Prefix + "hrm.payroll", StringComparison.Ordinal)); + } + + var reports = typeof(ApiControllerBase).Assembly.GetTypes().Single(t => t.Name == "HrReportsController"); + foreach (var action in new[] { "PayrollRegister", "SalaryHistory" }) + Assert.Contains(NavPolicyNames(reports.GetMethod(action)!), p => p == NavPolicies.Prefix + "hrm.payroll"); + } + + [Fact] + public void EveryNavCodeTheControllersName_ExistsInTheSeededCatalogue() + { + // The old Nav:vendors policy named a code that no longer existed, so only administrators got through. + using var db = CreateContext(); + var seeded = Seed(db) + .Select(d => ((string)d[nameof(Permission.Code)]!).Substring("NAV:".Length)) + .ToHashSet(StringComparer.Ordinal); + + var missing = new SortedSet(); + foreach (var controller in ApiControllers()) + { + var names = NavPolicyNames(controller).Concat( + controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).SelectMany(NavPolicyNames)); + foreach (var name in names) + { + var rest = name[NavPolicies.Prefix.Length..]; + var sep = rest.LastIndexOf(NavPolicies.ActionSeparator, StringComparison.Ordinal); + var code = sep >= 0 && Enum.TryParse(rest[(sep + 1)..], out _) ? rest[..sep] : rest; + // A module code is satisfied by the module itself or any screen inside it (HasAccessAsync does the same). + if (!seeded.Contains(code) && !seeded.Any(s => s.StartsWith(code + ".", StringComparison.Ordinal))) missing.Add($"{controller.Name}: {code}"); + } + } + + Assert.True(missing.Count == 0, "Controllers name permissions that are not seeded: " + string.Join("; ", missing)); + } + + [Fact] + public void SeededCatalogue_HasNoDuplicateCodes_AndEverySubItemHasAPermission() + { + using var db = CreateContext(); + var permissions = Seed(db).ToList(); + var codes = permissions.Select(p => (string)p[nameof(Permission.Code)]!).ToList(); + Assert.Equal(codes.Count, codes.Distinct().Count()); + + var subItems = Seed(db); + var granted = permissions.Where(p => p[nameof(Permission.SubNavItemId)] is not null).Select(p => (int)p[nameof(Permission.SubNavItemId)]!).ToHashSet(); + foreach (var sub in subItems) + Assert.True(granted.Contains((int)sub[nameof(SubNavItem.SubNavItemId)]!), $"No permission row for sub item {sub[nameof(SubNavItem.Code)]}."); + } + + [Theory] + [InlineData("hrm")] + [InlineData("hrm.employees")] + [InlineData("hrm.payroll")] + [InlineData("sales-returns")] + [InlineData("settings.audit-logs")] + public void NewPermissions_AreSeeded(string code) + { + using var db = CreateContext(); + var codes = Seed(db).Select(d => (string)d[nameof(Permission.Code)]!); + Assert.Contains("NAV:" + code, codes); + } + + // ---------------------------------------------------------------- the policies actually decide + + private sealed class FakeUser(int? roleId, bool isAdmin = false, int? branchId = null, bool branching = true) : ICurrentUser + { + public int AuditUserId => 10; + public int? RoleId => roleId; + public string? RoleCode => null; + public bool IsAdmin => isAdmin; + public string UserId => "10"; + public bool IsAuthenticated => true; + public int? BranchId => branchId; + public bool BranchingEnabled => branching; + } + + private static async Task<(ErpDbContext Db, NavPermissionService Nav)> RolesAsync() + { + var db = CreateContext(); + db.Roles.AddRange( + new Role { RoleId = 1, Code = "Hr", Name = "HR officer", CreatedAt = DateTime.UtcNow }, + new Role { RoleId = 2, Code = "Clerk", Name = "Clerk", CreatedAt = DateTime.UtcNow }); + db.NavItems.Add(new NavItem { NavItemId = 100, Code = "hrm", Label = "HR" }); + db.SubNavItems.AddRange( + new SubNavItem { SubNavItemId = 101, NavItemId = 100, Code = "hrm.employees", Label = "Employees" }, + new SubNavItem { SubNavItemId = 102, NavItemId = 100, Code = "hrm.payroll", Label = "Payroll" }); + db.Permissions.AddRange( + new Permission { PermissionId = 200, Code = "NAV:hrm.employees", SubNavItemId = 101 }, + new Permission { PermissionId = 201, Code = "NAV:hrm.payroll", SubNavItemId = 102 }); + db.RolePermissions.Add(new RolePermission { RoleId = 1, PermissionId = 200, Actions = RolePermissionAction.View | RolePermissionAction.Edit }); + await db.SaveChangesAsync(); + return (db, new NavPermissionService(new Repository(db), new Repository(db), new MemoryCache(new MemoryCacheOptions()))); + } + + private static async Task PassesAsync(NavPermissionService nav, ICurrentUser user, string policyName) + { + var provider = new NavPolicyProvider(Microsoft.Extensions.Options.Options.Create(new AuthorizationOptions())); + var policy = (await provider.GetPolicyAsync(policyName))!; + var requirement = policy.Requirements.OfType().Single(); + var context = new AuthorizationHandlerContext(new[] { requirement }, new ClaimsPrincipal(new ClaimsIdentity("test")), null); + await new NavAuthorizationHandler(user, nav).HandleAsync(context); + return context.HasSucceeded; + } + + [Fact] + public async Task HrOfficer_ReachesEmployees_ButNotPayData_AndAnAdministratorReachesAll() + { + var (db, nav) = await RolesAsync(); + await using var _ = db; + var hr = new FakeUser(roleId: 1); + + Assert.True(await PassesAsync(nav, hr, "Nav:hrm")); // the module, via a screen inside it + Assert.True(await PassesAsync(nav, hr, "Nav:hrm.employees")); + Assert.True(await PassesAsync(nav, hr, "Nav:hrm.employees:Edit")); + Assert.False(await PassesAsync(nav, hr, "Nav:hrm.employees:Delete")); // not granted + Assert.False(await PassesAsync(nav, hr, "Nav:hrm.payroll")); // salaries, loans, bank accounts + Assert.False(await PassesAsync(nav, hr, "Nav:settings.audit-logs")); + + Assert.False(await PassesAsync(nav, new FakeUser(roleId: 2), "Nav:hrm")); + Assert.False(await PassesAsync(nav, new FakeUser(roleId: null), "Nav:hrm")); + + Assert.True(await PassesAsync(nav, new FakeUser(roleId: 2, isAdmin: true), "Nav:hrm.payroll:Approve")); + } + + // ---------------------------------------------------------------- global search follows the role + + [Fact] + public async Task Search_OnlyReturnsKindsTheRoleMayOpen() + { + var (db, nav) = await RolesAsync(); + await using var _ = db; + db.Employees.Add(new Employee { EmployeeId = 1, EmployeeCode = "E-1", FullName = "Alice Perera", BranchId = Branch1 }); + await db.SaveChangesAsync(); + + GlobalSearchService Search(ICurrentUser u) => new( + new Repository(db), new Repository(db), new Repository(db), new Repository(db), u, nav); + + var hr = await Search(new FakeUser(roleId: 1)).SearchAsync("Alice"); + Assert.Contains(hr, r => r.Type == GlobalSearchResultType.Employee); + + var clerk = await Search(new FakeUser(roleId: 2)).SearchAsync("Alice"); + Assert.DoesNotContain(clerk, r => r.Type == GlobalSearchResultType.Employee); + + var admin = await Search(new FakeUser(roleId: 2, isAdmin: true)).SearchAsync("Alice"); + Assert.Contains(admin, r => r.Type == GlobalSearchResultType.Employee); + } + + // ---------------------------------------------------------------- CSRF on every cookie-authenticated mutation + + private static (CsrfProtectionFilter Filter, ActionExecutingContext Context) CsrfCall( + string method, string? cookie = "token", string? header = null, bool sessionCookie = true, + bool bearer = false, bool anonymousEndpoint = false, bool hasCsrfAttribute = false) + { + var http = new DefaultHttpContext(); + http.Request.Method = method; + var cookies = new List(); + if (sessionCookie) cookies.Add("erp_at=jwt"); + if (cookie is not null) cookies.Add($"XSRF-TOKEN={cookie}"); + if (cookies.Count > 0) http.Request.Headers.Cookie = string.Join("; ", cookies); + if (header is not null) http.Request.Headers["X-XSRF-TOKEN"] = header; + if (bearer) http.Request.Headers.Authorization = "Bearer abc"; + + var metadata = new List(); + if (anonymousEndpoint) metadata.Add(new AllowAnonymousAttribute()); + if (hasCsrfAttribute) metadata.Add(new ValidateCsrfAttribute()); + http.SetEndpoint(new Endpoint(null, new EndpointMetadataCollection(metadata), "test")); + + var actionContext = new ActionContext(http, new RouteData(), new ActionDescriptor()); + var context = new ActionExecutingContext(actionContext, new List(), new Dictionary(), new object()); + return (new CsrfProtectionFilter(), context); + } + + private static async Task RunAsync(CsrfProtectionFilter filter, ActionExecutingContext context) + { + var ran = false; + await filter.OnActionExecutionAsync(context, () => + { + ran = true; + return Task.FromResult(new ActionExecutedContext(context, context.Filters, context.Controller)); + }); + return ran; + } + + [Theory] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task CookieRequest_WithoutTheCsrfHeader_IsRefused(string method) + { + var (filter, context) = CsrfCall(method, cookie: "token", header: null); + var ex = await Assert.ThrowsAsync(() => RunAsync(filter, context)); + Assert.Equal(ErrorCodes.CsrfTokenMismatch, ex.Code); + Assert.Equal(403, ex.StatusCode); + } + + [Fact] + public async Task CookieRequest_WithAWrongOrMissingCsrfCookie_IsRefused() + { + var (f1, c1) = CsrfCall("POST", cookie: "token", header: "other"); + await Assert.ThrowsAsync(() => RunAsync(f1, c1)); + + var (f2, c2) = CsrfCall("POST", cookie: null, header: "token"); + await Assert.ThrowsAsync(() => RunAsync(f2, c2)); + } + + [Fact] + public async Task CookieRequest_WithMatchingCsrfHeader_Runs() + { + var (filter, context) = CsrfCall("DELETE", cookie: "token", header: "token"); + Assert.True(await RunAsync(filter, context)); + } + + [Theory] + [InlineData("GET")] + [InlineData("HEAD")] + [InlineData("OPTIONS")] + public async Task SafeMethods_AreNotChecked(string method) + { + var (filter, context) = CsrfCall(method, cookie: null); + Assert.True(await RunAsync(filter, context)); + } + + [Fact] + public async Task BearerCallers_AnonymousEndpoints_AndRequestsWithoutASessionCookie_AreNotChecked() + { + var (f1, c1) = CsrfCall("POST", cookie: null, bearer: true); + Assert.True(await RunAsync(f1, c1)); + + var (f2, c2) = CsrfCall("POST", cookie: null, anonymousEndpoint: true); + Assert.True(await RunAsync(f2, c2)); + + var (f3, c3) = CsrfCall("POST", cookie: null, sessionCookie: false); + Assert.True(await RunAsync(f3, c3)); + } + + [Fact] + public async Task ActionsThatCarryTheirOwnCsrfAttribute_AreNotCheckedTwice() + { + var (filter, context) = CsrfCall("POST", cookie: null, hasCsrfAttribute: true); + Assert.True(await RunAsync(filter, context)); // the attribute itself enforces it + } + + [Fact] + public void TheCsrfFilter_IsRegisteredGlobally() + { + var program = File.ReadAllText(Path.Combine(FindBackendRoot(), "ERPCore", "Program.cs")); + Assert.Contains("Filters.Add()", program); + } + + private static string FindBackendRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir is not null && !Directory.Exists(Path.Combine(dir.FullName, "ERPCore"))) dir = dir.Parent; + return dir?.FullName ?? throw new DirectoryNotFoundException("Backend root not found."); + } + + // ---------------------------------------------------------------- a branch-less person can't read every branch + + [Fact] + public void BranchContext_AdministratorsAreNeverScoped() + { + var ctx = new CurrentBranchContext(new FakeUser(roleId: 1, isAdmin: true, branchId: 5)); + Assert.False(ctx.IsActive); + Assert.Null(ctx.BranchId); + } + + [Fact] + public void BranchContext_AnEmployeeIsScopedToTheirOwnBranch() + { + var ctx = new CurrentBranchContext(new FakeUser(roleId: 1, branchId: 5)); + Assert.True(ctx.IsActive); + Assert.Equal(5, ctx.BranchId); + } + + [Fact] + public void BranchContext_APersonWithNoBranch_InABranchingDeployment_SeesNoBranchAtAll() + { + var ctx = new CurrentBranchContext(new FakeUser(roleId: 1, branchId: null, branching: true)); + Assert.True(ctx.IsActive); + Assert.Equal(CurrentBranchContext.NoBranchId, ctx.BranchId); + } + + [Fact] + public void BranchContext_APersonWithNoBranch_WhenBranchingIsOff_IsNotScoped() + { + var ctx = new CurrentBranchContext(new FakeUser(roleId: 1, branchId: null, branching: false)); + Assert.False(ctx.IsActive); + Assert.Null(ctx.BranchId); + } + + [Fact] + public async Task QueryFilters_HideEveryWarehouse_FromABranchlessNonAdmin_ButNotFromAnAdminOrWhenBranchingIsOff() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString("N")).Options; + + async Task CountFor(ICurrentUser user, bool seed) + { + await using var db = new ErpDbContext(options, user, new CurrentBranchContext(user)); + if (seed) + { + db.Warehouses.AddRange( + new Warehouse { WarehouseId = 1, Code = "W1", Name = "One", BranchId = 1 }, + new Warehouse { WarehouseId = 2, Code = "W2", Name = "Two", BranchId = 2 }); + await db.SaveChangesAsync(); + } + return db.Warehouses.Count(); + } + + Assert.Equal(2, await CountFor(new FakeUser(1, isAdmin: true), seed: true)); + Assert.Equal(1, await CountFor(new FakeUser(1, branchId: 2), seed: false)); + Assert.Equal(0, await CountFor(new FakeUser(1, branchId: null, branching: true), seed: false)); + Assert.Equal(2, await CountFor(new FakeUser(1, branchId: null, branching: false), seed: false)); + } +} diff --git a/Backend/ERPCore.Tests/ProcurementAndOpeningBalanceTests.cs b/Backend/ERPCore.Tests/ProcurementAndOpeningBalanceTests.cs new file mode 100644 index 0000000..9c40f7b --- /dev/null +++ b/Backend/ERPCore.Tests/ProcurementAndOpeningBalanceTests.cs @@ -0,0 +1,840 @@ +using System.Text.Json; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Notifications; +using ERPCore.Dtos.OpeningBalances; +using ERPCore.Dtos.Procurement; +using ERPCore.Dtos.Vendors; +using ERPCore.Infra.Gl; +using ERPCore.Infra.Persistence; +using ERPCore.Repositories; +using ERPCore.Services; +using ERPCore.Services.Gl; +using ERPCore.Services.Interfaces; +using ERPCore.Services.Stock; +using ERPCore.System.Errors; +using ERPCore.Tests.TestSupport; +using Microsoft.EntityFrameworkCore; +using static ERPCore.Tests.TestSupport.BankDepositTestSupport; + +namespace ERPCore.Tests; + +/// +/// Procurement configuration (vendor contact / address, PO tax) and the one-time opening balances. +/// Every rule here is also enforced by the UI, so these tests are about the server refusing what a stale +/// or hand-built request sends. +/// +public class ProcurementAndOpeningBalanceTests +{ + // ---------------------------------------------------------------- helpers + + private static async Task SeedProcurementConfigAsync( + ErpDbContext db, VendorContactMode mode = VendorContactMode.None, bool address = false, bool tax = false, + bool terms = true, bool taxReg = true) + { + db.ProcurementConfig.Add(new ProcurementConfig + { + ConfigId = ProcurementConfig.SingletonId, + VendorContactMode = mode, + VendorAddressEnabled = address, + PoTaxEnabled = tax, + VendorPaymentTermsEnabled = terms, + VendorTaxRegEnabled = taxReg + }); + await db.SaveChangesAsync(); + } + + private static ProcurementConfigService ConfigService(ErpDbContext db) => + new(new Repository(db), new TestCurrentUser(), new PassThroughUnitOfWork(db)); + + private static VendorService VendorSvc(ErpDbContext db) => + new(new Repository(db), new FakeBranchConfig(), ConfigService(db), new PassThroughUnitOfWork(db)); + + private static CreateVendorRequest NewVendor(string code, Action? tweak = null) + { + var request = new CreateVendorRequest { Code = code, Name = $"Vendor {code}", Currency = "LKR", BranchId = Branch1 }; + tweak?.Invoke(request); + return request; + } + + private sealed class NullNotifications : INotificationService + { + public Task RaiseAsync(string moduleKey, string title, string message, string? relatedEntityType = null, int? relatedEntityId = null, CancellationToken ct = default) => Task.CompletedTask; + public Task NotifyUsersAsync(IEnumerable userIds, string moduleKey, string title, string message, string? relatedEntityType = null, int? relatedEntityId = null, CancellationToken ct = default) => Task.CompletedTask; + public Task GetFeedAsync(int userId, int take = 20, CancellationToken ct = default) => throw new NotSupportedException(); + public Task MarkAllReadAsync(int userId, CancellationToken ct = default) => Task.CompletedTask; + } + + // ---------------------------------------------------------------- vendors + + [Fact] + public async Task Vendor_Mobile_IsRejected_WhileContactModeIsNone() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, VendorContactMode.None); + + var ex = await Assert.ThrowsAsync( + () => VendorSvc(db).CreateAsync(NewVendor("V1", r => r.Mobile = "0771234567"))); + + Assert.Equal(ErrorCodes.ConfigDisabled, ex.Code); + Assert.Empty(db.Vendors); + } + + [Fact] + public async Task Vendor_Mobile_IsSaved_WhenContactModeIsMobile_AndEmailIsStillRefused() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, VendorContactMode.Mobile); + + var created = (await VendorSvc(db).CreateAsync(NewVendor("V2", r => r.Mobile = " +94 77 123 4567 "))).Value; + Assert.Equal("+94 77 123 4567", created.Mobile); + Assert.Null(created.Email); + + var ex = await Assert.ThrowsAsync( + () => VendorSvc(db).CreateAsync(NewVendor("V3", r => r.Email = "a@b.lk"))); + Assert.Equal(ErrorCodes.ConfigDisabled, ex.Code); + } + + [Theory] + [InlineData("abc")] + [InlineData("12")] + public async Task Vendor_InvalidMobile_IsRejected(string mobile) + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, VendorContactMode.Mobile); + + var ex = await Assert.ThrowsAsync( + () => VendorSvc(db).CreateAsync(NewVendor("V4", r => r.Mobile = mobile))); + + Assert.Equal(ErrorCodes.Validation, ex.Code); + } + + [Fact] + public async Task Vendor_Address_NeedsTheSwitchOn() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, address: false); + + var off = await Assert.ThrowsAsync( + () => VendorSvc(db).CreateAsync(NewVendor("V5", r => r.Address = "12 Galle Road"))); + Assert.Equal(ErrorCodes.ConfigDisabled, off.Code); + + db.ProcurementConfig.Single().VendorAddressEnabled = true; + await db.SaveChangesAsync(); + + var created = (await VendorSvc(db).CreateAsync(NewVendor("V6", r => r.Address = "12 Galle Road"))).Value; + Assert.Equal("12 Galle Road", created.Address); + } + + [Fact] + public async Task Vendor_Update_KeepsStoredContact_WhenTheSwitchIsOff() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, VendorContactMode.Email, address: true); + var created = (await VendorSvc(db).CreateAsync(NewVendor("V7", r => + { + r.Email = "accounts@supplier.lk"; + r.Address = "Colombo"; + }))).Value; + + // Contact mode switched off later: an update must not erase what was recorded. + db.ProcurementConfig.Single().VendorContactMode = VendorContactMode.None; + await db.SaveChangesAsync(); + + var tracked = db.Vendors.Single(v => v.VendorId == created.VendorId); + var updated = (await VendorSvc(db).UpdateAsync( + created.VendorId, + new UpdateVendorRequest { Code = "V7", Name = "Renamed", Currency = "LKR", BranchId = Branch1 }, + tracked.RowVersion)).Value; + + Assert.Equal("Renamed", updated.Name); + Assert.Equal("accounts@supplier.lk", db.Vendors.Single().Email); + } + + [Fact] + public async Task Vendor_TermsAndTaxReg_AreOnByDefault_AndSaved() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); // defaults: terms + tax registration on + + var created = (await VendorSvc(db).CreateAsync(NewVendor("V8", r => + { + r.Terms = " NET30 "; + r.TaxReg = "134567890-7000"; + }))).Value; + + Assert.Equal("NET30", created.Terms); + Assert.Equal("134567890-7000", created.TaxReg); + } + + [Fact] + public async Task Vendor_TermsAndTaxReg_AreRejectedWhenTheirSwitchesAreOff() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, terms: false, taxReg: false); + + var terms = await Assert.ThrowsAsync(() => VendorSvc(db).CreateAsync(NewVendor("V9", r => r.Terms = "NET30"))); + var taxReg = await Assert.ThrowsAsync(() => VendorSvc(db).CreateAsync(NewVendor("V10", r => r.TaxReg = "123"))); + + Assert.Equal(ErrorCodes.ConfigDisabled, terms.Code); + Assert.Equal(ErrorCodes.ConfigDisabled, taxReg.Code); + Assert.Empty(db.Vendors); + + // Blank is fine: the form simply doesn't send them. + var ok = (await VendorSvc(db).CreateAsync(NewVendor("V11"))).Value; + Assert.Null(ok.Terms); + } + + [Fact] + public async Task Vendor_Update_KeepsStoredTermsAndTaxReg_WhenSwitchedOff() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); + var created = (await VendorSvc(db).CreateAsync(NewVendor("V12", r => + { + r.Terms = "NET45"; + r.TaxReg = "TRN-1"; + }))).Value; + + var cfg = db.ProcurementConfig.Single(); + cfg.VendorPaymentTermsEnabled = false; + cfg.VendorTaxRegEnabled = false; + await db.SaveChangesAsync(); + + var tracked = db.Vendors.Single(v => v.VendorId == created.VendorId); + var updated = (await VendorSvc(db).UpdateAsync( + created.VendorId, + new UpdateVendorRequest { Code = "V12", Name = "Renamed", Currency = "LKR", BranchId = Branch1 }, + tracked.RowVersion)).Value; + + Assert.Equal("Renamed", updated.Name); + Assert.Equal(("NET45", "TRN-1"), (db.Vendors.Single().Terms, db.Vendors.Single().TaxReg)); + } + + // ---------------------------------------------------------------- purchase-order tax + + private static PurchaseOrderService PoSvc(ErpDbContext db) => + new(new Repository(db), new Repository(db), new Repository(db), + new Repository(db), new Repository(db), new Repository(db), + new FakeNumberSequence(), new TestCurrentUser(), new NullNotifications(), new FakeBranchConfig(), + ConfigService(db), new PassThroughUnitOfWork(db)); + + private static async Task SeedVendorAndItemAsync(ErpDbContext db) + { + db.Vendors.Add(new Vendor { VendorId = 1, Code = "V-PO", Name = "PO Vendor", BranchId = Branch1, Status = EntityStatus.Active }); + db.Items.Add(new Item { ItemId = 1, Sku = "SKU-1", Name = "Cola 500ml", CategoryId = 1, BaseUomId = 1 }); + await db.SaveChangesAsync(); + return 1; + } + + private static CreatePurchaseOrderRequest PoRequest(decimal taxRate) => new() + { + VendorId = 1, + BranchId = Branch1, + SaveAsDraft = true, + Lines = [new CreatePoLineInput { ItemId = 1, WarehouseId = Warehouse1, Qty = 10, UnitPrice = 100m, Tax = taxRate }] + }; + + [Fact] + public async Task Po_TaxOn_StoresTheLineTaxAmount_AndTotalsIncludeIt() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, tax: true); + await SeedVendorAndItemAsync(db); + + var po = (await PoSvc(db).CreateAsync(PoRequest(0.18m))).Value; + + var line = Assert.Single(po.Lines); + Assert.Equal(180m, line.TaxAmount); + Assert.Equal((1000m, 180m, 1180m), (po.Totals.SubTotal, po.Totals.Tax, po.Totals.GrandTotal)); + Assert.Equal(180m, db.PoLines.Single().TaxAmount); + } + + [Fact] + public async Task Po_TaxAmount_RoundsHalfUp_ToTwoDecimals() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, tax: true); + await SeedVendorAndItemAsync(db); + + // 3 x 33.5 = 100.5; at 5% the tax is 5.025 -> 5.03 (half away from zero, not banker's rounding). + var request = PoRequest(0.05m); + request.Lines[0].Qty = 3; + request.Lines[0].UnitPrice = 33.5m; + + var po = (await PoSvc(db).CreateAsync(request)).Value; + + Assert.Equal(5.03m, Assert.Single(po.Lines).TaxAmount); + } + + [Fact] + public async Task Po_TaxOff_RejectsANonZeroRate_ButAcceptsZero() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, tax: false); + await SeedVendorAndItemAsync(db); + + var ex = await Assert.ThrowsAsync(() => PoSvc(db).CreateAsync(PoRequest(0.18m))); + Assert.Equal(ErrorCodes.ConfigDisabled, ex.Code); + Assert.Empty(db.PurchaseOrders); + + var po = (await PoSvc(db).CreateAsync(PoRequest(0m))).Value; + Assert.Equal(0m, po.Totals.Tax); + } + + [Fact] + public async Task Po_CarriesVendorDetails_OnlyForTheEnabledFields() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db, VendorContactMode.Email, address: false); + await SeedVendorAndItemAsync(db); + var vendor = db.Vendors.Single(); + vendor.Mobile = "0771234567"; + vendor.Email = "po@vendor.lk"; + vendor.Address = "Colombo"; + await db.SaveChangesAsync(); + + var po = (await PoSvc(db).CreateAsync(PoRequest(0m))).Value; + + Assert.NotNull(po.Vendor); + Assert.Equal("PO Vendor", po.Vendor!.Name); + Assert.Equal("po@vendor.lk", po.Vendor.Email); + Assert.Null(po.Vendor.Mobile); // contact mode is Email + Assert.Null(po.Vendor.Address); // address switched off + } + + // ---------------------------------------------------------------- purchase-order list: date filter + sort + + /// Three POs from two vendors, created on known business days; returns their ids in creation order. + private static async Task SeedListAsync(ErpDbContext db) + { + db.Vendors.AddRange( + new Vendor { VendorId = 1, Code = "V-B", Name = "Beta Traders", BranchId = Branch1, Status = EntityStatus.Active }, + new Vendor { VendorId = 2, Code = "V-A", Name = "Alpha Foods", BranchId = Branch1, Status = EntityStatus.Active }); + db.Items.Add(new Item { ItemId = 1, Sku = "SKU-1", Name = "Cola", CategoryId = 1, BaseUomId = 1 }); + + PurchaseOrder Po(int id, int vendor, DateTime createdUtc, decimal qty) => new() + { + PoId = id, DocNo = $"PO-{id}", VendorId = vendor, BranchId = Branch1, Status = PurchaseOrderStatus.Draft, + CreatedAt = createdUtc, + Lines = [new PoLine { ItemId = 1, WarehouseId = Warehouse1, Qty = qty, UnitPrice = 10m }] + }; + db.PurchaseOrders.AddRange( + Po(1, 1, new DateTime(2026, 4, 1, 4, 0, 0, DateTimeKind.Utc), 5), // 1 Apr (Colombo 09:30), total 50 + Po(2, 2, new DateTime(2026, 4, 1, 20, 0, 0, DateTimeKind.Utc), 30), // 2 Apr in Colombo (01:30), total 300 + Po(3, 1, new DateTime(2026, 4, 10, 6, 0, 0, DateTimeKind.Utc), 10)); // 10 Apr, total 100 + await db.SaveChangesAsync(); + return [1, 2, 3]; + } + + private static async Task> ListIds(PurchaseOrderService svc, string? sort = null, DateOnly? from = null, DateOnly? to = null) + { + var page = await svc.ListAsync(new ERPCore.Dtos.Common.PageQuery { Sort = sort, PageSize = 50 }, null, null, from, to); + return page.Items.Select(i => i.PoId).ToList(); + } + + [Fact] + public async Task PoList_DateRange_UsesTheBusinessDayOfTheCreatedTime_AndIsInclusive() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); + await SeedListAsync(db); + var svc = PoSvc(db); + + // PO 2 was created 20:00 UTC on 1 Apr = 2 Apr in Colombo, so a "1 Apr only" filter must not include it. + Assert.Equal([1], await ListIds(svc, from: new DateOnly(2026, 4, 1), to: new DateOnly(2026, 4, 1))); + Assert.Equal([2], await ListIds(svc, from: new DateOnly(2026, 4, 2), to: new DateOnly(2026, 4, 2))); + Assert.Equal([3, 2], await ListIds(svc, from: new DateOnly(2026, 4, 2), to: new DateOnly(2026, 4, 10))); + Assert.Equal([3, 2, 1], await ListIds(svc, to: new DateOnly(2026, 4, 30))); + Assert.Equal([3], await ListIds(svc, from: new DateOnly(2026, 4, 3))); + } + + [Fact] + public async Task PoList_Sorts_ByCreatedVendorAndTotal_BothWays() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); + await SeedListAsync(db); + var svc = PoSvc(db); + + Assert.Equal([3, 2, 1], await ListIds(svc)); // default: newest first + Assert.Equal([1, 2, 3], await ListIds(svc, "created")); // oldest first + Assert.Equal([2, 3, 1], await ListIds(svc, "vendor")); // Alpha, then Beta (ties: newest first) + Assert.Equal([3, 1, 2], await ListIds(svc, "-vendor")); + Assert.Equal([2, 3, 1], await ListIds(svc, "-total")); // 300, 100, 50 + Assert.Equal([1, 3, 2], await ListIds(svc, "total")); + Assert.Equal([3, 2, 1], await ListIds(svc, "nonsense")); // unknown key falls back to the default + } + + [Fact] + public async Task PoList_Search_MatchesVendorNameAndCode() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); + await SeedListAsync(db); + var svc = PoSvc(db); + + var byName = await svc.ListAsync(new ERPCore.Dtos.Common.PageQuery { Q = "alpha", PageSize = 50 }, null, null); + var byCode = await svc.ListAsync(new ERPCore.Dtos.Common.PageQuery { Q = "V-B", PageSize = 50 }, null, null); + + Assert.Equal([2], byName.Items.Select(i => i.PoId)); + Assert.Equal([3, 1], byCode.Items.Select(i => i.PoId)); + } + + // ---------------------------------------------------------------- opening balances + + /// A GL that knows two LKR cash/bank accounts, optionally the equity account, and can create it. + private sealed class OpeningGl : IGeneralLedgerService + { + public List Posted { get; } = new(); + public int AccountCreates { get; private set; } + public bool EquityExists { get; init; } = true; + public string BankCurrency { get; init; } = "LKR"; + + public Task ForwardAsync(HttpMethod method, string path, string? queryString, string? contentType, Stream? body, CancellationToken ct) + { + AccountCreates++; + return Task.FromResult(new GeneralLedgerResponse + { + StatusCode = 201, + Body = """{"statusCode":201,"success":true,"message":"Account created successfully","data":{"accountCode":"3000"}}""" + }); + } + + public Task PostJournalEntryAsync(GlJournalEntryRequest request, CancellationToken ct) + { + Posted.Add(request); + return Task.FromResult(new GlJournalEntryResult($"JV-2026-{Posted.Count:000000}", true)); + } + + public Task GetPeriodByDateAsync(DateOnly date, CancellationToken ct) => Task.FromResult(new GlPeriod(1, 1)); + + public Task> ListBankAccountsAsync(CancellationToken ct) => + Task.FromResult>( + [ + new("Cash", 1, "Main Cash Account", null, "Petty Cash", "CASH-000001", 17, "1400-01-01", "LKR"), + new("Bank", 2, "BOC Current", "Bank of Ceylon", null, "0001234567", 18, "1000-01", BankCurrency) + ]); + + public Task> ListAccountsAsync(CancellationToken ct) => + Task.FromResult>(EquityExists + ? [new("3000", "Opening Balance Equity", 3, true, true)] + : [new("1000", "Bank", 1, false, true)]); + } + + private static OpeningBalanceService OpeningSvc(ErpDbContext db, OpeningGl gl) => + new(new Repository(db), gl, new TestCurrentUser(), new PassThroughUnitOfWork(db)); + + private static PostCashBankOpeningRequest Opening(DateOnly date, params (string Code, decimal Amount)[] lines) => new() + { + CutoverDate = date, + Lines = lines.Select(l => new OpeningBalanceLineInput { GlAccountCode = l.Code, Amount = l.Amount }).ToList() + }; + + private static readonly DateOnly OpeningDate = new(2026, 4, 1); + + [Fact] + public async Task Opening_PostsOneBalancedJournal_DebitingEachAccount_AndCreditingEquity() + { + await using var db = CreateContext(); + var gl = new OpeningGl(); + + var run = await OpeningSvc(db, gl).PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 500m), ("1000-01", 1500m))); + + var entry = Assert.Single(gl.Posted); + Assert.Equal("OPENING_BALANCE", entry.JournalType); + Assert.Equal("OPENING", entry.SourceModule); + Assert.Equal(OpeningDate, entry.EntryDate); + Assert.Equal(entry.Lines.Sum(l => l.DebitAmount), entry.Lines.Sum(l => l.CreditAmount)); + Assert.Contains(entry.Lines, l => l is { AccountCode: "1400-01-01", DebitAmount: 500m, CreditAmount: 0m }); + Assert.Contains(entry.Lines, l => l is { AccountCode: "1000-01", DebitAmount: 1500m, CreditAmount: 0m }); + Assert.Contains(entry.Lines, l => l is { AccountCode: "3000", DebitAmount: 0m, CreditAmount: 2000m }); + + Assert.Equal(2000m, run.TotalAmount); + Assert.Equal("JV-2026-000001", run.GlJournalNo); + Assert.Equal(2, run.Lines.Count); + Assert.Equal(0, gl.AccountCreates); // the equity account already existed + } + + [Fact] + public async Task Opening_CreatesTheEquityAccount_WhenTheChartHasNone() + { + await using var db = CreateContext(); + var gl = new OpeningGl { EquityExists = false }; + + await OpeningSvc(db, gl).PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 100m))); + + Assert.Equal(1, gl.AccountCreates); + Assert.Contains(Assert.Single(gl.Posted).Lines, l => l.AccountCode == "3000" && l.CreditAmount == 100m); + } + + [Fact] + public async Task Opening_SecondPosting_IsRefused_AndPostsNothingMore() + { + await using var db = CreateContext(); + var gl = new OpeningGl(); + var svc = OpeningSvc(db, gl); + await svc.PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 100m))); + + var ex = await Assert.ThrowsAsync( + () => svc.PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 999m)))); + + Assert.Equal(ErrorCodes.OpeningBalanceAlreadyPosted, ex.Code); + Assert.Equal(409, ex.StatusCode); + Assert.Single(gl.Posted); + Assert.Equal(100m, Assert.Single(db.OpeningBalanceRuns.Include(r => r.Lines)).Lines.Single().Amount); + } + + [Fact] + public async Task Opening_Overdraft_IsCreditedToTheAccount_AndEquityTakesTheNet() + { + await using var db = CreateContext(); + var gl = new OpeningGl(); + + var run = await OpeningSvc(db, gl).PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 300m), ("1000-01", -100m))); + + var entry = Assert.Single(gl.Posted); + Assert.Contains(entry.Lines, l => l is { AccountCode: "1000-01", DebitAmount: 0m, CreditAmount: 100m }); + Assert.Contains(entry.Lines, l => l is { AccountCode: "3000", DebitAmount: 0m, CreditAmount: 200m }); + Assert.Equal(entry.Lines.Sum(l => l.DebitAmount), entry.Lines.Sum(l => l.CreditAmount)); + Assert.Equal(200m, run.TotalAmount); + } + + [Fact] + public async Task Opening_ShowsPostedAmounts_OnTheStateEndpoint() + { + await using var db = CreateContext(); + var svc = OpeningSvc(db, new OpeningGl()); + + var before = await svc.GetCashBankAsync(); + Assert.Null(before.Posted); + Assert.All(before.Accounts, a => Assert.Null(a.Amount)); + + await svc.PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 250m))); + var after = await svc.GetCashBankAsync(); + + Assert.NotNull(after.Posted); + Assert.Equal(250m, after.Accounts.Single(a => a.GlAccountCode == "1400-01-01").Amount); + Assert.Null(after.Accounts.Single(a => a.GlAccountCode == "1000-01").Amount); + } + + [Fact] + public async Task Opening_RejectsBadInput_WithoutPostingAnything() + { + await using var db = CreateContext(); + var gl = new OpeningGl(); + var svc = OpeningSvc(db, gl); + + // not a cash/bank account + await Assert.ThrowsAsync(() => svc.PostCashBankAsync(Opening(OpeningDate, ("9999", 10m)))); + // more than 2 decimals + await Assert.ThrowsAsync(() => svc.PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 10.005m)))); + // nothing but zeros + await Assert.ThrowsAsync(() => svc.PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 0m)))); + // the same account twice + await Assert.ThrowsAsync(() => svc.PostCashBankAsync(Opening(OpeningDate, ("1400-01-01", 5m), ("1400-01-01", 6m)))); + // in the future + await Assert.ThrowsAsync(() => svc.PostCashBankAsync(Opening(DateOnly.FromDateTime(DateTime.UtcNow).AddDays(30), ("1400-01-01", 5m)))); + + Assert.Empty(gl.Posted); + Assert.Empty(db.OpeningBalanceRuns); + } + + [Fact] + public async Task Opening_RefusesANonBaseCurrencyAccount() + { + await using var db = CreateContext(); + var gl = new OpeningGl { BankCurrency = "USD" }; + + var ex = await Assert.ThrowsAsync( + () => OpeningSvc(db, gl).PostCashBankAsync(Opening(OpeningDate, ("1000-01", 100m)))); + + Assert.Equal(ErrorCodes.Validation, ex.Code); + Assert.Empty(gl.Posted); + } + + // ---------------------------------------------------------------- opening stock + + private static OpeningStockService StockSvc(ErpDbContext db, OpeningGl gl) + { + var glAccounts = new StubGlAccountConfigService((GlAccountRole.Inventory, "1100"), (GlAccountRole.Clearing, "2000")); + var fifo = new FifoCostingService( + new Repository(db), new Repository(db), new Repository(db), db, gl, glAccounts); + return new OpeningStockService( + new Repository(db), new Repository(db), new Repository(db), + new Repository(db), new Repository(db), fifo, gl, glAccounts, + new TestCurrentUser(), new PassThroughUnitOfWork(db)); + } + + private static Item StockedItem(int id, Action? tweak = null) + { + var item = new Item + { + ItemId = id, Sku = $"SKU-{id}", Name = $"Item {id}", CategoryId = 1, BaseUomId = 1, + StockNature = StockNature.Stocked, TrackingMode = TrackingMode.None, Status = EntityStatus.Active + }; + tweak?.Invoke(item); + return item; + } + + private static PostOpeningStockRequest StockRequest(params (int Item, decimal Qty, decimal Cost)[] lines) => new() + { + CutoverDate = OpeningDate, + Lines = lines.Select(l => new OpeningStockLineInput { ItemId = l.Item, WarehouseId = Warehouse1, Qty = l.Qty, UnitCost = l.Cost }).ToList() + }; + + [Fact] + public async Task OpeningStock_CreatesLayerAndLedgerRow_AndPostsExactlyOneJournal() + { + await using var db = CreateContext(); + db.Items.AddRange(StockedItem(1), StockedItem(2)); + await db.SaveChangesAsync(); + var gl = new OpeningGl(); + + var run = await StockSvc(db, gl).PostAsync(StockRequest((1, 24m, 12.5m), (2, 10m, 3m))); + + Assert.Equal(330m, run.TotalValue); + Assert.Equal(2, run.LineCount); + + var layer = db.StockLayers.Single(l => l.ItemId == 1); + Assert.Equal((24m, 24m, 12.5m), (layer.QtyReceived, layer.QtyRemaining, layer.UnitCost)); + var row = db.StockLedger.Single(l => l.ItemId == 1); + Assert.Equal(("OPN", Direction.In, 24m, 300m, 24m), (row.SourceDocType, row.Direction, row.QtyBase, row.Value, row.RunningBalance)); + Assert.Equal(run.OpeningBalanceRunId, row.SourceDocId); + + // One journal in total — the regular stock path would have added a STOCK journal (Dr Inventory / Cr Clearing) per row. + var entry = Assert.Single(gl.Posted); + Assert.Equal(("OPENING", "OPENING_BALANCE"), (entry.SourceModule, entry.JournalType)); + Assert.Collection(entry.Lines, + l => Assert.Equal(("1100", 330m, 0m), (l.AccountCode, l.DebitAmount, l.CreditAmount)), + l => Assert.Equal(("3000", 0m, 330m), (l.AccountCode, l.DebitAmount, l.CreditAmount))); + Assert.Equal(2, db.JournalEntryStubs.Count(j => j.SourceDocType == "OPN")); + } + + [Fact] + public async Task OpeningStock_RefusesAnItemThatAlreadyHasMovements_InThatWarehouse() + { + await using var db = CreateContext(); + db.Items.AddRange(StockedItem(1), StockedItem(2)); + db.StockLedger.Add(new StockLedger { ItemId = 1, WarehouseId = Warehouse1, Direction = Direction.In, QtyBase = 5, UnitCost = 1, Value = 5, RunningBalance = 5, SourceDocType = "GRN", SourceDocId = 1 }); + await db.SaveChangesAsync(); + var gl = new OpeningGl(); + + var ex = await Assert.ThrowsAsync(() => StockSvc(db, gl).PostAsync(StockRequest((1, 3m, 2m), (2, 3m, 2m)))); + + Assert.Equal(409, ex.StatusCode); + Assert.Contains("SKU-1", ex.Message); + Assert.Empty(gl.Posted); + Assert.Empty(db.OpeningBalanceRuns); + Assert.Empty(db.StockLayers); // nothing at all was written, not even for the clean item + } + + [Fact] + public async Task OpeningStock_CanBeLoadedInSeveralRuns_ForDifferentItems() + { + await using var db = CreateContext(); + db.Items.AddRange(StockedItem(1), StockedItem(2)); + await db.SaveChangesAsync(); + var gl = new OpeningGl(); + var svc = StockSvc(db, gl); + + await svc.PostAsync(StockRequest((1, 5m, 2m))); + await svc.PostAsync(StockRequest((2, 7m, 4m))); + + Assert.Equal(2, gl.Posted.Count); + Assert.Equal(2, (await svc.ListAsync()).Count); + // ...but the same item cannot be opened a second time. + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((1, 1m, 1m)))); + } + + [Fact] + public async Task OpeningStock_RefusesUnsuitableItemsAndBadNumbers() + { + await using var db = CreateContext(); + db.Items.AddRange( + StockedItem(1, i => i.TrackingMode = TrackingMode.Serial), + StockedItem(2, i => i.TrackingMode = TrackingMode.Batch), + StockedItem(3, i => i.StockNature = StockNature.Service), + StockedItem(4, i => i.Status = EntityStatus.Inactive), + StockedItem(5)); + await db.SaveChangesAsync(); + var gl = new OpeningGl(); + var svc = StockSvc(db, gl); + + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((1, 1m, 1m)))); // serial + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((2, 1m, 1m)))); // batch + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((3, 1m, 1m)))); // service + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((4, 1m, 1m)))); // inactive + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((99, 1m, 1m)))); // unknown + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((5, 0m, 1m)))); // zero qty + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((5, 1m, -1m)))); // negative cost + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((5, 1.00001m, 1m)))); // > 4 decimals + await Assert.ThrowsAsync(() => svc.PostAsync(StockRequest((5, 1m, 1m), (5, 2m, 1m)))); // duplicate + + Assert.Empty(gl.Posted); + Assert.Empty(db.StockLayers); + } + + [Fact] + public async Task OpeningStock_ZeroCostRun_PostsNoJournal() + { + await using var db = CreateContext(); + db.Items.Add(StockedItem(1)); + await db.SaveChangesAsync(); + var gl = new OpeningGl(); + + var run = await StockSvc(db, gl).PostAsync(StockRequest((1, 10m, 0m))); + + Assert.Equal(0m, run.TotalValue); + Assert.Null(run.GlJournalNo); + Assert.Empty(gl.Posted); + Assert.Equal(10m, db.StockLayers.Single().QtyRemaining); + } + + // ---------------------------------------------------------------- purchase-order rejection + + [Fact] + public async Task Po_Reject_ShowsRejected_NotDraftOrPending_AndCanBeRevisedAndResubmitted() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); + await SeedVendorAndItemAsync(db); + var svc = PoSvc(db); + + var po = (await svc.CreateAsync(PoRequest(0m))).Value; + await svc.SubmitAsync(po.PoId); + // Somebody else raised it: the approver can't be its creator. + db.PurchaseOrders.Single().CreatedBy = 999; + await db.SaveChangesAsync(); + + var rejected = await svc.RejectAsync(po.PoId, "Wrong price"); + Assert.Equal(PurchaseOrderStatus.Rejected, rejected.Status); + + // A rejected order is reopened like a draft: it can be sent for approval again... + var resubmitted = await svc.SubmitAsync(po.PoId); + Assert.Equal(PurchaseOrderStatus.PendingApproval, resubmitted.Status); + } + + [Fact] + public async Task Po_Rejected_CanBeDeleted_ButAnApprovedOneCannot() + { + await using var db = CreateContext(); + await SeedProcurementConfigAsync(db); + await SeedVendorAndItemAsync(db); + var svc = PoSvc(db); + + var po = (await svc.CreateAsync(PoRequest(0m))).Value; + await svc.SubmitAsync(po.PoId); + db.PurchaseOrders.Single().CreatedBy = 999; + await db.SaveChangesAsync(); + await svc.RejectAsync(po.PoId, null); + + await svc.DeleteAsync(po.PoId); + Assert.Empty(db.PurchaseOrders); + } + + // ---------------------------------------------------------------- General Ledger payload + + private sealed class CapturingGlClient(int status, string body) : IGeneralLedgerClient + { + public string? SentBody { get; private set; } + + public async Task SendAsync( + HttpMethod method, string path, string? queryString, string? contentType, Stream? requestBody, CancellationToken ct) + { + if (requestBody is not null) + { + using var reader = new StreamReader(requestBody); + SentBody = await reader.ReadToEndAsync(ct); + } + return new GeneralLedgerResponse { StatusCode = status, ContentType = "application/json", Body = body }; + } + } + + private static GlJournalEntryRequest GrnJournal() => new() + { + PeriodId = 1, + EntryDate = new DateOnly(2026, 10, 18), + SourceModule = "GRN", + Reference = "GRN-1", + Lines = [new GlJournalEntryLineRequest("1100", 10m, 0m, "x"), new GlJournalEntryLineRequest("2100", 0m, 10m)] + }; + + [Fact] + public async Task GlPost_NeverSendsNulls_BecauseGlTreatsANullJournalTypeAsMissing() + { + // GL's request DTO has a non-nullable JournalType; an explicit "journalType": null is a 400 from its model + // validation. Only opening balances set one, so every other posting (GRN, sales, ...) must leave it out. + var client = new CapturingGlClient(201, "{\"statusCode\":201,\"success\":true,\"message\":\"ok\",\"data\":{\"journalNo\":\"J-1\",\"isPosted\":true}}"); + + var result = await new GeneralLedgerService(client).PostJournalEntryAsync(GrnJournal(), CancellationToken.None); + + Assert.Equal("J-1", result.JournalNo); + Assert.DoesNotContain("journalType", client.SentBody!); + Assert.DoesNotContain("null", client.SentBody!); + } + + [Fact] + public async Task GlPost_KeepsAJournalTypeWhenOneIsSet() + { + var client = new CapturingGlClient(201, "{\"statusCode\":201,\"success\":true,\"message\":\"ok\",\"data\":{\"journalNo\":\"J-1\",\"isPosted\":true}}"); + var request = GrnJournal(); + request.JournalType = "OPENING_BALANCE"; + + await new GeneralLedgerService(client).PostJournalEntryAsync(request, CancellationToken.None); + + Assert.Contains("\"journalType\":\"OPENING_BALANCE\"", client.SentBody!); + } + + [Fact] + public async Task GlPost_AValidationFailureFromGl_IsReportedWithItsReason_NotAsAGenericRejection() + { + var problem = "{\"title\":\"One or more validation errors occurred.\",\"status\":400,\"errors\":{\"JournalType\":[\"The JournalType field is required.\"]}}"; + var client = new CapturingGlClient(400, problem); + + var ex = await Assert.ThrowsAsync( + () => new GeneralLedgerService(client).PostJournalEntryAsync(GrnJournal(), CancellationToken.None)); + + Assert.Equal(ErrorCodes.GlRequestFailed, ex.Code); + Assert.Contains("JournalType", ex.Message); + Assert.Contains("required", ex.Message); + } + + [Fact] + public async Task GlPost_GlsOwnMessage_StillWins() + { + var client = new CapturingGlClient(400, "{\"StatusCode\":400,\"Success\":false,\"Message\":\"Period is locked\",\"Data\":null}"); + + var ex = await Assert.ThrowsAsync( + () => new GeneralLedgerService(client).PostJournalEntryAsync(GrnJournal(), CancellationToken.None)); + + Assert.Equal("Period is locked", ex.Message); + } + + // ---------------------------------------------------------------- stock movement vs. document journal + + [Fact] + public async Task StockLedgerPost_AddsTheGenericStockJournal_OnlyWhenTheDocumentHasNoJournalOfItsOwn() + { + await using var db = CreateContext(); + var gl = new OpeningGl(); + var glAccounts = new StubGlAccountConfigService((GlAccountRole.Inventory, "1100"), (GlAccountRole.Clearing, "2000")); + var fifo = new FifoCostingService( + new Repository(db), new Repository(db), new Repository(db), db, gl, glAccounts); + var now = DateTime.UtcNow; + + // A GRN / sales invoice / adjustment posts its own balanced journal: the ledger row must not add a second one, + // or the movement is counted twice in the General Ledger. + await fifo.PostLedgerAsync(1, Warehouse1, null, null, null, 1, Direction.In, 100m, 200m, 100m, "GRN", 1, now, + CancellationToken.None, postGlJournal: false); + await db.SaveChangesAsync(); + Assert.Empty(gl.Posted); + Assert.Single(db.StockLedger); + + // Production and transfers have no document journal, so they keep the generic one. + await fifo.PostLedgerAsync(1, Warehouse1, null, null, null, 1, Direction.In, 5m, 10m, 105m, "TRF", 1, now, + CancellationToken.None); + var journal = Assert.Single(gl.Posted); + Assert.Equal("STOCK", journal.SourceModule); + } +} diff --git a/Backend/ERPCore.Tests/TestSupport/NotificationConfigTestSupport.cs b/Backend/ERPCore.Tests/TestSupport/NotificationConfigTestSupport.cs index cea732a..9614ab0 100644 --- a/Backend/ERPCore.Tests/TestSupport/NotificationConfigTestSupport.cs +++ b/Backend/ERPCore.Tests/TestSupport/NotificationConfigTestSupport.cs @@ -14,6 +14,7 @@ internal sealed class TestCurrentUser : ICurrentUser // Unauthenticated, so no role claim and no branch scoping: the three members the // AuthHex integration added to ICurrentUser. + public int? RoleId => null; public string? RoleCode => null; public bool IsAdmin => false; public int? BranchId => null; diff --git a/Backend/ERPCore/Controllers/AccessConfigController.cs b/Backend/ERPCore/Controllers/AccessConfigController.cs new file mode 100644 index 0000000..b661a06 --- /dev/null +++ b/Backend/ERPCore/Controllers/AccessConfigController.cs @@ -0,0 +1,40 @@ +using ERPCore.Dtos.Config; +using ERPCore.Infra.Auth; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// +/// Access configuration — the default role for people who have none. Both reading and changing it need the +/// settings nav permission (it decides what a brand-new sign-in can do). +/// +[Route("api/v1/access-config")] +[Authorize(Policy = NavPolicies.Prefix + "settings")] +public sealed class AccessConfigController : ApiControllerBase +{ + private readonly IAccessConfigService _config; + + public AccessConfigController(IAccessConfigService config) => _config = config; + + [HttpGet] + [ProducesResponseType(typeof(AccessConfigDto), StatusCodes.Status200OK)] + public async Task> Get(CancellationToken ct) + { + var result = await _config.GetAsync(ct); + SetETag(result.RowVersion); + return Ok(result.Value); + } + + [HttpPut] + [ProducesResponseType(typeof(AccessConfigDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] + public async Task> Update([FromBody] UpdateAccessConfigRequest request, CancellationToken ct) + { + var expected = RequireIfMatch(); + var result = await _config.UpdateAsync(request, expected, ct); + SetETag(result.RowVersion); + return Ok(result.Value); + } +} diff --git a/Backend/ERPCore/Controllers/AuditLogsController.cs b/Backend/ERPCore/Controllers/AuditLogsController.cs index 6f66cf4..ae012a2 100644 --- a/Backend/ERPCore/Controllers/AuditLogsController.cs +++ b/Backend/ERPCore/Controllers/AuditLogsController.cs @@ -1,6 +1,8 @@ using ERPCore.Dtos.Audit; using ERPCore.Dtos.Common; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers; @@ -10,6 +12,7 @@ namespace ERPCore.Controllers; /// the audit trail is required (AR-01 compensating control) and read access is the /// only way to consume it. /// +[Authorize(Policy = NavPolicies.Prefix + "settings.audit-logs")] [Route("api/v1/audit-logs")] public sealed class AuditLogsController : ApiControllerBase { diff --git a/Backend/ERPCore/Controllers/AuthController.cs b/Backend/ERPCore/Controllers/AuthController.cs index 172b8db..ef920bc 100644 --- a/Backend/ERPCore/Controllers/AuthController.cs +++ b/Backend/ERPCore/Controllers/AuthController.cs @@ -47,8 +47,7 @@ public sealed class AuthController : ControllerBase [ProducesResponseType(typeof(MeResponseDto), StatusCodes.Status200OK)] public async Task> Me(CancellationToken ct) { - var roleCode = User.FindFirst(AuthHexClaims.RoleCode)?.Value; - return Ok(await _roles.GetMeAsync(roleCode, ct)); + return Ok(await _roles.GetMeAsync(ct)); } // ---- Session-issuing (UserManager) ------------------------------------ diff --git a/Backend/ERPCore/Controllers/GrnsController.cs b/Backend/ERPCore/Controllers/GrnsController.cs index 3c45ea4..edf0f6e 100644 --- a/Backend/ERPCore/Controllers/GrnsController.cs +++ b/Backend/ERPCore/Controllers/GrnsController.cs @@ -25,8 +25,9 @@ public sealed class GrnsController : ApiControllerBase [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> List( [FromQuery] PageQuery query, [FromQuery] GrnStatus? status, [FromQuery] int? poId, - [FromQuery] int? vendorId, [FromQuery] int? warehouseId, [FromQuery] int? branchId, CancellationToken ct) - => Ok(await _grns.ListAsync(query, status, poId, vendorId, warehouseId, branchId, ct)); + [FromQuery] int? vendorId, [FromQuery] int? warehouseId, [FromQuery] int? branchId, + [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, CancellationToken ct) + => Ok(await _grns.ListAsync(query, status, poId, vendorId, warehouseId, branchId, from, to, ct)); [HttpGet("{grnId:int}")] [ProducesResponseType(typeof(GrnDto), StatusCodes.Status200OK)] diff --git a/Backend/ERPCore/Controllers/Hrm/AttendanceBatchesController.cs b/Backend/ERPCore/Controllers/Hrm/AttendanceBatchesController.cs index 83c1f20..916f349 100644 --- a/Backend/ERPCore/Controllers/Hrm/AttendanceBatchesController.cs +++ b/Backend/ERPCore/Controllers/Hrm/AttendanceBatchesController.cs @@ -3,11 +3,13 @@ using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Attendance upload batch endpoints (docs/13-BACKEND-HRM-API.md §4). +[Authorize(Policy = NavPolicies.Prefix + "hrm.attendance")] [Route("api/v1/attendance-batches")] public sealed class AttendanceBatchesController : ApiControllerBase { @@ -44,6 +46,7 @@ public sealed class AttendanceBatchesController : ApiControllerBase return result is null ? NotFound() : Ok(result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.attendance" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost] [RequestSizeLimit(20 * 1024 * 1024)] [ProducesResponseType(typeof(AttendanceUploadBatchDto), StatusCodes.Status201Created)] @@ -62,6 +65,7 @@ public sealed class AttendanceBatchesController : ApiControllerBase int batchId, [FromQuery] RowValidationStatus? status, CancellationToken ct) => Ok(await _attendance.ListRecordsAsync(batchId, status, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.attendance" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPut("{batchId:int}/records/{recordId:int}")] [ProducesResponseType(typeof(AttendanceRecordDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -70,6 +74,7 @@ public sealed class AttendanceBatchesController : ApiControllerBase int batchId, int recordId, [FromBody] UpdateAttendanceRecordRequest request, CancellationToken ct) => Ok(await _attendance.UpdateRecordAsync(batchId, recordId, request, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.attendance" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPost("{batchId:int}/resolve-duplicate")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -79,18 +84,21 @@ public sealed class AttendanceBatchesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.attendance" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPost("{batchId:int}/validate")] [ProducesResponseType(typeof(AttendanceUploadBatchDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] public async Task> Validate(int batchId, CancellationToken ct) => Ok(await _attendance.ValidateAsync(batchId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.attendance" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{batchId:int}/confirm")] [ProducesResponseType(typeof(AttendanceUploadBatchDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Confirm(int batchId, CancellationToken ct) => Ok(await _attendance.ConfirmAsync(batchId, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.attendance" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{batchId:int}/unlock")] [ProducesResponseType(typeof(AttendanceUploadBatchDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] diff --git a/Backend/ERPCore/Controllers/Hrm/BranchesController.cs b/Backend/ERPCore/Controllers/Hrm/BranchesController.cs index 95739e6..38410b6 100644 --- a/Backend/ERPCore/Controllers/Hrm/BranchesController.cs +++ b/Backend/ERPCore/Controllers/Hrm/BranchesController.cs @@ -1,7 +1,9 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; @@ -31,6 +33,7 @@ public sealed class BranchesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "settings.branches")] [HttpPost] [ProducesResponseType(typeof(BranchDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +44,7 @@ public sealed class BranchesController : ApiControllerBase return Created($"/api/v1/branches/{result.Value.BranchId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "settings.branches")] [HttpPut("{branchId:int}")] [ProducesResponseType(typeof(BranchDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +57,7 @@ public sealed class BranchesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "settings.branches")] [HttpPatch("{branchId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +67,7 @@ public sealed class BranchesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "settings.branches")] [HttpDelete("{branchId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/DepartmentsController.cs b/Backend/ERPCore/Controllers/Hrm/DepartmentsController.cs index 25af869..6d0caec 100644 --- a/Backend/ERPCore/Controllers/Hrm/DepartmentsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/DepartmentsController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Department master endpoints (docs/13-BACKEND-HRM-API.md §2). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/departments")] public sealed class DepartmentsController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class DepartmentsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(DepartmentDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class DepartmentsController : ApiControllerBase return Created($"/api/v1/departments/{result.Value.DepartmentId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{departmentId:int}")] [ProducesResponseType(typeof(DepartmentDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -54,6 +59,7 @@ public sealed class DepartmentsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{departmentId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -63,6 +69,7 @@ public sealed class DepartmentsController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{departmentId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/DesignationsController.cs b/Backend/ERPCore/Controllers/Hrm/DesignationsController.cs index ba2000b..8573fa9 100644 --- a/Backend/ERPCore/Controllers/Hrm/DesignationsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/DesignationsController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Designation (job title) master endpoints (docs/13-BACKEND-HRM-API.md §2). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/designations")] public sealed class DesignationsController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class DesignationsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(DesignationDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class DesignationsController : ApiControllerBase return Created($"/api/v1/designations/{result.Value.DesignationId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{designationId:int}")] [ProducesResponseType(typeof(DesignationDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +58,7 @@ public sealed class DesignationsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{designationId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +68,7 @@ public sealed class DesignationsController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{designationId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/EmployeesController.cs b/Backend/ERPCore/Controllers/Hrm/EmployeesController.cs index 674744f..ea0222a 100644 --- a/Backend/ERPCore/Controllers/Hrm/EmployeesController.cs +++ b/Backend/ERPCore/Controllers/Hrm/EmployeesController.cs @@ -3,6 +3,7 @@ using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; @@ -11,6 +12,9 @@ namespace ERPCore.Controllers.Hrm; /// Employee (staff) endpoints, incl. the Employee<->User cross-link and staff /// document sub-resources (docs/13-BACKEND-HRM-API.md §3). /// +// Any HR role may list employees (the leave and payroll screens pick from it); everything else about a person +// - the record itself, documents, pay data - is behind the Employees (or Payroll) permission, endpoint by endpoint. +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/employees")] public sealed class EmployeesController : ApiControllerBase { @@ -44,11 +48,13 @@ public sealed class EmployeesController : ApiControllerBase => Ok(await _employees.ListAsync(query, status, departmentId, designationId, branchId, ct)); /// Advisory reverse-direction lookup: does a System User already exist with this email? (docs/12-BACKEND-HRM.md A.5) + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees")] [HttpGet("email-lookup")] [ProducesResponseType(typeof(UserMatchResponse), StatusCodes.Status200OK)] public async Task> EmailLookup([FromQuery] string email, CancellationToken ct) => Ok(new UserMatchResponse(await _links.FindUserCandidateByEmailAsync(email, ct))); + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees")] [HttpGet("{employeeId:int}")] [ProducesResponseType(typeof(EmployeeDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -60,6 +66,7 @@ public sealed class EmployeesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost] [ProducesResponseType(typeof(EmployeeDetailDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -71,6 +78,7 @@ public sealed class EmployeesController : ApiControllerBase return Created($"/api/v1/employees/{result.Value.EmployeeId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPut("{employeeId:int}")] [ProducesResponseType(typeof(EmployeeDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -83,6 +91,7 @@ public sealed class EmployeesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPatch("{employeeId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -93,6 +102,7 @@ public sealed class EmployeesController : ApiControllerBase } /// Permanently deletes the employee and all dependent HR records. Blocked (409) if payroll history exists. + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.DeleteAction)] [HttpDelete("{employeeId:int}")] [ProducesResponseType(typeof(EmployeeDeleteResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -103,6 +113,7 @@ public sealed class EmployeesController : ApiControllerBase return Ok(result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPost("{employeeId:int}/link-user")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -113,6 +124,7 @@ public sealed class EmployeesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpDelete("{employeeId:int}/link-user")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -122,11 +134,13 @@ public sealed class EmployeesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [HttpGet("{employeeId:int}/bank-details")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> ListBankDetails(int employeeId, CancellationToken ct) => Ok(await _employees.ListBankDetailsAsync(employeeId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPut("{employeeId:int}/bank-details")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -140,6 +154,7 @@ public sealed class EmployeesController : ApiControllerBase public async Task>> ListLeaveBalances(int employeeId, [FromQuery] int? year, CancellationToken ct) => Ok(await _leaveBalances.ListAsync(employeeId, year, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPut("{employeeId:int}/leave-balances")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -147,11 +162,13 @@ public sealed class EmployeesController : ApiControllerBase int employeeId, [FromBody] UpdateLeaveBalancesRequest request, CancellationToken ct) => Ok(await _leaveBalances.ApplyAdjustmentsAsync(employeeId, request, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [HttpGet("{employeeId:int}/salary-structure")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> GetSalaryStructureHistory(int employeeId, CancellationToken ct) => Ok(await _salaryStructures.ListHistoryAsync(employeeId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPost("{employeeId:int}/salary-structure")] [ProducesResponseType(typeof(EmployeeSalaryStructureDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -163,11 +180,13 @@ public sealed class EmployeesController : ApiControllerBase return Created($"/api/v1/employees/{employeeId}/salary-structure", result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [HttpGet("{employeeId:int}/loans")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> ListLoans(int employeeId, CancellationToken ct) => Ok(await _loans.ListAsync(employeeId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [HttpGet("{employeeId:int}/loans/{loanId:int}")] [ProducesResponseType(typeof(EmployeeLoanDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -177,6 +196,7 @@ public sealed class EmployeesController : ApiControllerBase return result is null ? NotFound() : Ok(result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost("{employeeId:int}/loans")] [ProducesResponseType(typeof(EmployeeLoanDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -186,11 +206,13 @@ public sealed class EmployeesController : ApiControllerBase return Created($"/api/v1/employees/{employeeId}/loans/{result.EmployeeLoanId}", result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees")] [HttpGet("{employeeId:int}/documents")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> ListDocuments(int employeeId, CancellationToken ct) => Ok(await _documents.ListAsync(employeeId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPost("{employeeId:int}/documents")] [RequestSizeLimit(20 * 1024 * 1024)] [ProducesResponseType(typeof(EmployeeDocumentDto), StatusCodes.Status201Created)] @@ -206,6 +228,7 @@ public sealed class EmployeesController : ApiControllerBase return Created($"/api/v1/employees/{employeeId}/documents/{result.EmployeeDocumentId}", result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees")] [HttpGet("{employeeId:int}/documents/{documentId:int}/download")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -215,6 +238,7 @@ public sealed class EmployeesController : ApiControllerBase return File(content, contentType, fileName); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.employees" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPatch("{employeeId:int}/documents/{documentId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/EmploymentTypesController.cs b/Backend/ERPCore/Controllers/Hrm/EmploymentTypesController.cs index e954b73..d1a73e9 100644 --- a/Backend/ERPCore/Controllers/Hrm/EmploymentTypesController.cs +++ b/Backend/ERPCore/Controllers/Hrm/EmploymentTypesController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// EmploymentType master endpoints (docs/13-BACKEND-HRM-API.md §2). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/employment-types")] public sealed class EmploymentTypesController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class EmploymentTypesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(EmploymentTypeDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class EmploymentTypesController : ApiControllerBase return Created($"/api/v1/employment-types/{result.Value.EmploymentTypeId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{employmentTypeId:int}")] [ProducesResponseType(typeof(EmploymentTypeDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +58,7 @@ public sealed class EmploymentTypesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{employmentTypeId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +68,7 @@ public sealed class EmploymentTypesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{employmentTypeId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/HrDocumentTypesController.cs b/Backend/ERPCore/Controllers/Hrm/HrDocumentTypesController.cs index 8e6824e..3488ef4 100644 --- a/Backend/ERPCore/Controllers/Hrm/HrDocumentTypesController.cs +++ b/Backend/ERPCore/Controllers/Hrm/HrDocumentTypesController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Staff document-type catalog ("DocType") endpoints (docs/13-BACKEND-HRM-API.md §2). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/hr-document-types")] public sealed class HrDocumentTypesController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class HrDocumentTypesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(HrDocumentTypeDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class HrDocumentTypesController : ApiControllerBase return Created($"/api/v1/hr-document-types/{result.Value.HrDocumentTypeId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{hrDocumentTypeId:int}")] [ProducesResponseType(typeof(HrDocumentTypeDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +58,7 @@ public sealed class HrDocumentTypesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{hrDocumentTypeId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +68,7 @@ public sealed class HrDocumentTypesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{hrDocumentTypeId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/HrReportsController.cs b/Backend/ERPCore/Controllers/Hrm/HrReportsController.cs index e139ee9..fc9f075 100644 --- a/Backend/ERPCore/Controllers/Hrm/HrReportsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/HrReportsController.cs @@ -1,10 +1,13 @@ using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Read-only HRM reports (FR-HR-RPT, docs/13-BACKEND-HRM-API.md §6). No new entities — aggregation over existing tables. +[Authorize(Policy = NavPolicies.Prefix + "hrm.reports")] [Route("api/v1/reports/hrm")] public sealed class HrReportsController : ApiControllerBase { @@ -30,11 +33,13 @@ public sealed class HrReportsController : ApiControllerBase [FromQuery] int periodYear, [FromQuery] int periodMonth, CancellationToken ct) => Ok(await _reports.LateArrivalReportAsync(periodYear, periodMonth, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [HttpGet("payroll-register")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> PayrollRegister([FromQuery] int payrollRunId, CancellationToken ct) => Ok(await _reports.PayrollRegisterAsync(payrollRunId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [HttpGet("salary-history")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> SalaryHistory([FromQuery] int employeeId, CancellationToken ct) diff --git a/Backend/ERPCore/Controllers/Hrm/LeaveRequestsController.cs b/Backend/ERPCore/Controllers/Hrm/LeaveRequestsController.cs index 7b6c1fc..88de5c3 100644 --- a/Backend/ERPCore/Controllers/Hrm/LeaveRequestsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/LeaveRequestsController.cs @@ -3,11 +3,13 @@ using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Leave request endpoints (docs/13-BACKEND-HRM-API.md §5). +[Authorize(Policy = NavPolicies.Prefix + "hrm.leave")] [Route("api/v1/leave-requests")] public sealed class LeaveRequestsController : ApiControllerBase { @@ -35,6 +37,7 @@ public sealed class LeaveRequestsController : ApiControllerBase return result is null ? NotFound() : Ok(result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.leave" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost] [ProducesResponseType(typeof(LeaveRequestDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -45,24 +48,28 @@ public sealed class LeaveRequestsController : ApiControllerBase return Created($"/api/v1/leave-requests/{result.LeaveRequestId}", result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.leave" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost("{leaveRequestId:int}/submit")] [ProducesResponseType(typeof(LeaveRequestDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Submit(int leaveRequestId, CancellationToken ct) => Ok(await _requests.SubmitAsync(leaveRequestId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.leave" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{leaveRequestId:int}/approve")] [ProducesResponseType(typeof(LeaveRequestDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Approve(int leaveRequestId, CancellationToken ct) => Ok(await _requests.ApproveAsync(leaveRequestId, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.leave" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{leaveRequestId:int}/reject")] [ProducesResponseType(typeof(LeaveRequestDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Reject(int leaveRequestId, [FromBody] RejectLeaveRequestRequest request, CancellationToken ct) => Ok(await _requests.RejectAsync(leaveRequestId, request.Reason, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.leave" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost("{leaveRequestId:int}/cancel")] [ProducesResponseType(typeof(LeaveRequestDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] diff --git a/Backend/ERPCore/Controllers/Hrm/LeaveTypesController.cs b/Backend/ERPCore/Controllers/Hrm/LeaveTypesController.cs index 91aef6d..f1d7678 100644 --- a/Backend/ERPCore/Controllers/Hrm/LeaveTypesController.cs +++ b/Backend/ERPCore/Controllers/Hrm/LeaveTypesController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Leave type master endpoints (docs/13-BACKEND-HRM-API.md §5). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/leave-types")] public sealed class LeaveTypesController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class LeaveTypesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(LeaveTypeDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class LeaveTypesController : ApiControllerBase return Created($"/api/v1/leave-types/{result.Value.LeaveTypeId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{leaveTypeId:int}")] [ProducesResponseType(typeof(LeaveTypeDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +58,7 @@ public sealed class LeaveTypesController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{leaveTypeId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +68,7 @@ public sealed class LeaveTypesController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{leaveTypeId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/PayrollRunsController.cs b/Backend/ERPCore/Controllers/Hrm/PayrollRunsController.cs index d2bc6c2..0c102c7 100644 --- a/Backend/ERPCore/Controllers/Hrm/PayrollRunsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/PayrollRunsController.cs @@ -3,11 +3,13 @@ using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Payroll run endpoints (docs/13-BACKEND-HRM-API.md §6). +[Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [Route("api/v1/payroll-runs")] public sealed class PayrollRunsController : ApiControllerBase { @@ -50,6 +52,7 @@ public sealed class PayrollRunsController : ApiControllerBase return result is null ? NotFound() : Ok(result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost] [ProducesResponseType(typeof(PayrollRunDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] @@ -59,24 +62,28 @@ public sealed class PayrollRunsController : ApiControllerBase return Created($"/api/v1/payroll-runs/{result.PayrollRunId}", result); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{payrollRunId:int}/approve")] [ProducesResponseType(typeof(PayrollRunDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Approve(int payrollRunId, CancellationToken ct) => Ok(await _payrollRuns.ApproveAsync(payrollRunId, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{payrollRunId:int}/lock")] [ProducesResponseType(typeof(PayrollRunDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Lock(int payrollRunId, CancellationToken ct) => Ok(await _payrollRuns.LockAsync(payrollRunId, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.ApproveAction)] [HttpPost("{payrollRunId:int}/unlock")] [ProducesResponseType(typeof(PayrollRunDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> Unlock(int payrollRunId, [FromBody] UnlockPayrollRunRequest request, CancellationToken ct) => Ok(await _payrollRuns.UnlockAsync(payrollRunId, request.Reason, _currentUser.AuditUserId, ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.payroll" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost("{payrollRunId:int}/generate-payslips")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] diff --git a/Backend/ERPCore/Controllers/Hrm/PayrollStatutorySettingsController.cs b/Backend/ERPCore/Controllers/Hrm/PayrollStatutorySettingsController.cs index 15dac2f..5d35feb 100644 --- a/Backend/ERPCore/Controllers/Hrm/PayrollStatutorySettingsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/PayrollStatutorySettingsController.cs @@ -1,11 +1,13 @@ using ERPCore.Dtos.Hrm; using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Effective-dated EPF/ETF settings (docs/13-BACKEND-HRM-API.md §6). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/payroll-statutory-settings")] public sealed class PayrollStatutorySettingsController : ApiControllerBase { @@ -23,6 +25,7 @@ public sealed class PayrollStatutorySettingsController : ApiControllerBase public async Task>> List(CancellationToken ct) => Ok(await _settings.ListAsync(ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(PayrollStatutorySettingDto), StatusCodes.Status201Created)] public async Task> Create([FromBody] UpsertPayrollStatutorySettingRequest request, CancellationToken ct) diff --git a/Backend/ERPCore/Controllers/Hrm/PayslipsController.cs b/Backend/ERPCore/Controllers/Hrm/PayslipsController.cs index 4163d81..51c389d 100644 --- a/Backend/ERPCore/Controllers/Hrm/PayslipsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/PayslipsController.cs @@ -1,10 +1,13 @@ using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Payslip retrieval + HTML print view (docs/13-BACKEND-HRM-API.md §6). +[Authorize(Policy = NavPolicies.Prefix + "hrm.payroll")] [Route("api/v1/payslips")] public sealed class PayslipsController : ApiControllerBase { diff --git a/Backend/ERPCore/Controllers/Hrm/SalaryComponentsController.cs b/Backend/ERPCore/Controllers/Hrm/SalaryComponentsController.cs index a84322d..2d11717 100644 --- a/Backend/ERPCore/Controllers/Hrm/SalaryComponentsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/SalaryComponentsController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// SalaryComponent master endpoints (docs/13-BACKEND-HRM-API.md §6). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/salary-components")] public sealed class SalaryComponentsController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class SalaryComponentsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(SalaryComponentDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class SalaryComponentsController : ApiControllerBase return Created($"/api/v1/salary-components/{result.Value.SalaryComponentId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{salaryComponentId:int}")] [ProducesResponseType(typeof(SalaryComponentDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +58,7 @@ public sealed class SalaryComponentsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{salaryComponentId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +68,7 @@ public sealed class SalaryComponentsController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{salaryComponentId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/Hrm/TaxSlabsController.cs b/Backend/ERPCore/Controllers/Hrm/TaxSlabsController.cs index df8aec4..f05d731 100644 --- a/Backend/ERPCore/Controllers/Hrm/TaxSlabsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/TaxSlabsController.cs @@ -1,10 +1,13 @@ using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// Configurable APIT-style tax slabs (docs/13-BACKEND-HRM-API.md §6). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/tax-slabs")] public sealed class TaxSlabsController : ApiControllerBase { @@ -17,6 +20,7 @@ public sealed class TaxSlabsController : ApiControllerBase public async Task>> List(CancellationToken ct) => Ok(await _taxSlabs.ListAsync(ct)); + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(TaxSlabDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] diff --git a/Backend/ERPCore/Controllers/Hrm/WorkShiftsController.cs b/Backend/ERPCore/Controllers/Hrm/WorkShiftsController.cs index 2b2dd2d..42b2013 100644 --- a/Backend/ERPCore/Controllers/Hrm/WorkShiftsController.cs +++ b/Backend/ERPCore/Controllers/Hrm/WorkShiftsController.cs @@ -1,12 +1,15 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers.Hrm; /// WorkShift master endpoints (docs/13-BACKEND-HRM-API.md §2). +[Authorize(Policy = NavPolicies.Prefix + "hrm")] [Route("api/v1/work-shifts")] public sealed class WorkShiftsController : ApiControllerBase { @@ -31,6 +34,7 @@ public sealed class WorkShiftsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPost] [ProducesResponseType(typeof(WorkShiftDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -41,6 +45,7 @@ public sealed class WorkShiftsController : ApiControllerBase return Created($"/api/v1/work-shifts/{result.Value.WorkShiftId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPut("{workShiftId:int}")] [ProducesResponseType(typeof(WorkShiftDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -53,6 +58,7 @@ public sealed class WorkShiftsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpPatch("{workShiftId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -62,6 +68,7 @@ public sealed class WorkShiftsController : ApiControllerBase return NoContent(); } + [Authorize(Policy = NavPolicies.Prefix + "hrm.settings")] [HttpDelete("{workShiftId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Backend/ERPCore/Controllers/OpeningBalancesController.cs b/Backend/ERPCore/Controllers/OpeningBalancesController.cs new file mode 100644 index 0000000..9ceab8a --- /dev/null +++ b/Backend/ERPCore/Controllers/OpeningBalancesController.cs @@ -0,0 +1,71 @@ +using ERPCore.Dtos.OpeningBalances; +using ERPCore.Infra.Auth; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// +/// One-time opening balances. Gated on the accounts.opening-balances nav code (Admin always passes; +/// other roles need it granted under Settings → Roles). +/// +[Route("api/v1/opening-balances")] +[Authorize(Policy = NavPolicies.Prefix + "accounts.opening-balances")] +public sealed class OpeningBalancesController : ApiControllerBase +{ + private readonly IOpeningBalanceService _openingBalances; + private readonly IOpeningStockService _openingStock; + + public OpeningBalancesController(IOpeningBalanceService openingBalances, IOpeningStockService openingStock) + { + _openingBalances = openingBalances; + _openingStock = openingStock; + } + + /// The cash and bank accounts to enter opening amounts for — or the posted record, once done. + [HttpGet("cash-bank")] + [ProducesResponseType(typeof(CashBankOpeningStateDto), StatusCodes.Status200OK)] + public async Task> GetCashBank(CancellationToken ct) => + Ok(await _openingBalances.GetCashBankAsync(ct)); + + /// Posts the cash and bank opening balances as one journal. Allowed once; later calls get 409. + [HttpPost("cash-bank")] + [ProducesResponseType(typeof(OpeningBalanceRunDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> PostCashBank( + [FromBody] PostCashBankOpeningRequest request, CancellationToken ct) + { + var run = await _openingBalances.PostCashBankAsync(request, ct); + return StatusCode(StatusCodes.Status201Created, run); + } + + /// Opening-stock runs posted so far, newest first. + [HttpGet("stock")] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + public async Task>> ListStock(CancellationToken ct) => + Ok(await _openingStock.ListAsync(ct)); + + /// One opening-stock run with its lines. + [HttpGet("stock/{runId:int}")] + [ProducesResponseType(typeof(OpeningStockRunDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetStock(int runId, CancellationToken ct) + { + var run = await _openingStock.GetAsync(runId, ct); + return run is null ? NotFound() : Ok(run); + } + + /// + /// Posts opening stock: a FIFO layer per line and one journal (Dr Inventory / Cr Opening Balance Equity). + /// An item that already has stock movements in that warehouse is refused with 409. + /// + [HttpPost("stock")] + [ProducesResponseType(typeof(OpeningStockRunDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task> PostStock([FromBody] PostOpeningStockRequest request, CancellationToken ct) + { + var run = await _openingStock.PostAsync(request, ct); + return StatusCode(StatusCodes.Status201Created, run); + } +} diff --git a/Backend/ERPCore/Controllers/ProcurementConfigController.cs b/Backend/ERPCore/Controllers/ProcurementConfigController.cs new file mode 100644 index 0000000..923210c --- /dev/null +++ b/Backend/ERPCore/Controllers/ProcurementConfigController.cs @@ -0,0 +1,46 @@ +using ERPCore.Dtos.Config; +using ERPCore.Infra.Auth; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// +/// Procurement configuration endpoints — optional vendor fields and PO tax. +/// +/// Authorization: reading is open to any signed-in ERP user, because the vendor and purchase-order +/// forms must read the flags and those screens sit under different nav codes. The flags are not +/// sensitive. Changing them needs the settings nav policy, like . +/// +/// +[Route("api/v1/procurement-config")] +public sealed class ProcurementConfigController : ApiControllerBase +{ + private readonly IProcurementConfigService _config; + + public ProcurementConfigController(IProcurementConfigService config) => _config = config; + + [HttpGet] + [ProducesResponseType(typeof(ProcurementConfigDto), StatusCodes.Status200OK)] + public async Task> Get(CancellationToken ct) + { + var result = await _config.GetAsync(ct); + SetETag(result.RowVersion); + return Ok(result.Value); + } + + [HttpPut] + [Authorize(Policy = NavPolicies.Prefix + "settings")] + [ProducesResponseType(typeof(ProcurementConfigDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] + public async Task> Update( + [FromBody] UpdateProcurementConfigRequest request, CancellationToken ct) + { + var expected = RequireIfMatch(); + var result = await _config.UpdateAsync(request, expected, ct); + SetETag(result.RowVersion); + return Ok(result.Value); + } +} diff --git a/Backend/ERPCore/Controllers/PurchaseOrdersController.cs b/Backend/ERPCore/Controllers/PurchaseOrdersController.cs index 3d674e6..46c6f8a 100644 --- a/Backend/ERPCore/Controllers/PurchaseOrdersController.cs +++ b/Backend/ERPCore/Controllers/PurchaseOrdersController.cs @@ -20,8 +20,9 @@ public sealed class PurchaseOrdersController : ApiControllerBase [HttpGet] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] public async Task>> List( - [FromQuery] PageQuery query, [FromQuery] PurchaseOrderStatus? status, [FromQuery] int? vendorId, CancellationToken ct) - => Ok(await _pos.ListAsync(query, status, vendorId, ct)); + [FromQuery] PageQuery query, [FromQuery] PurchaseOrderStatus? status, [FromQuery] int? vendorId, + [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, CancellationToken ct) + => Ok(await _pos.ListAsync(query, status, vendorId, from, to, ct)); [HttpGet("{poId:int}")] [ProducesResponseType(typeof(PurchaseOrderDto), StatusCodes.Status200OK)] diff --git a/Backend/ERPCore/Controllers/ReasonCodesController.cs b/Backend/ERPCore/Controllers/ReasonCodesController.cs index e67aa32..0ac030b 100644 --- a/Backend/ERPCore/Controllers/ReasonCodesController.cs +++ b/Backend/ERPCore/Controllers/ReasonCodesController.cs @@ -1,7 +1,9 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Reference; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers; @@ -20,6 +22,7 @@ public sealed class ReasonCodesController : ApiControllerBase [FromQuery] ReasonContext? context, [FromQuery] PageQuery query, CancellationToken ct) => Ok(await _codes.ListAsync(context, query, ct)); + [Authorize(Policy = NavPolicies.Prefix + "settings")] [HttpPost] [ProducesResponseType(typeof(ReasonCodeDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] diff --git a/Backend/ERPCore/Controllers/SalesReturnsController.cs b/Backend/ERPCore/Controllers/SalesReturnsController.cs index 7135dbc..6bb779a 100644 --- a/Backend/ERPCore/Controllers/SalesReturnsController.cs +++ b/Backend/ERPCore/Controllers/SalesReturnsController.cs @@ -1,15 +1,15 @@ -using Microsoft.AspNetCore.Authorization; -using ERPCore.Infra.Auth; using ERPCore.Dtos.Common; using ERPCore.Dtos.Sales; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers; /// Sales-return endpoints — customer returns of previously sold goods. [Route("api/v1/sales-returns")] -[Authorize(Policy = NavPolicies.Prefix + "sales")] +[Authorize(Policy = NavPolicies.Prefix + "sales-returns")] public sealed class SalesReturnsController : ApiControllerBase { private readonly ISalesReturnService _returns; diff --git a/Backend/ERPCore/Controllers/UsersController.cs b/Backend/ERPCore/Controllers/UsersController.cs index 1ac53c7..d68b6c9 100644 --- a/Backend/ERPCore/Controllers/UsersController.cs +++ b/Backend/ERPCore/Controllers/UsersController.cs @@ -41,8 +41,8 @@ public sealed class UsersController : ApiControllerBase [HttpGet] [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] PageQuery query, [FromQuery] EntityStatus? status, [FromQuery] string? roleCode, CancellationToken ct) - => Ok(await _users.ListAsync(query, status, roleCode, ct)); + public async Task>> List([FromQuery] PageQuery query, [FromQuery] EntityStatus? status, [FromQuery] string? roleCode, [FromQuery] bool? isAdmin, CancellationToken ct) + => Ok(await _users.ListAsync(query, status, roleCode, isAdmin, ct)); /// AuthHex UserType options for the create-user form's select. [HttpGet("user-types")] @@ -117,4 +117,20 @@ public sealed class UsersController : ApiControllerBase return Ok(await _users.PurgeAuthHexIdentityAsync(request, ct)); } + + /// + /// One-time migration helper (administrators only): copies each role-less user's role from AuthHex into the ERP. + /// Safe to repeat — users that already have a role are left alone. See + /// . + /// + [HttpPost("backfill-roles")] + [ProducesResponseType(typeof(BackfillRolesResultDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> BackfillRoles(CancellationToken ct) + { + if (!_currentUser.IsAdmin) + throw new DomainException("FORBIDDEN", "Only an administrator can run the role backfill.", 403); + + return Ok(await _users.BackfillRolesFromAuthHexAsync(ct)); + } } diff --git a/Backend/ERPCore/Controllers/VendorsController.cs b/Backend/ERPCore/Controllers/VendorsController.cs index 05199c7..8cc1319 100644 --- a/Backend/ERPCore/Controllers/VendorsController.cs +++ b/Backend/ERPCore/Controllers/VendorsController.cs @@ -1,16 +1,16 @@ -using Microsoft.AspNetCore.Authorization; -using ERPCore.Infra.Auth; using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Vendors; +using ERPCore.Infra.Auth; using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ERPCore.Controllers; /// Vendor master endpoints (docs/11-BACKEND-PHASE1.md §2.4). [Route("api/v1/vendors")] -[Authorize(Policy = NavPolicies.Prefix + "vendors")] +[Authorize(Policy = NavPolicies.Prefix + "procurement")] public sealed class VendorsController : ApiControllerBase { private readonly IVendorService _vendors; @@ -35,6 +35,7 @@ public sealed class VendorsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "procurement.vendors" + NavPolicies.ActionSeparator + NavPolicies.CreateAction)] [HttpPost] [ProducesResponseType(typeof(VendorDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] @@ -45,6 +46,7 @@ public sealed class VendorsController : ApiControllerBase return Created($"/api/v1/vendors/{result.Value.VendorId}", result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "procurement.vendors" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPut("{vendorId:int}")] [ProducesResponseType(typeof(VendorDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -57,6 +59,7 @@ public sealed class VendorsController : ApiControllerBase return Ok(result.Value); } + [Authorize(Policy = NavPolicies.Prefix + "procurement.vendors" + NavPolicies.ActionSeparator + NavPolicies.EditAction)] [HttpPatch("{vendorId:int}/status")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -67,7 +70,7 @@ public sealed class VendorsController : ApiControllerBase } [HttpDelete("{vendorId:int}")] - [Authorize(Policy = NavPolicies.Prefix + "vendors" + NavPolicies.ActionSeparator + NavPolicies.DeleteAction)] + [Authorize(Policy = NavPolicies.Prefix + "procurement.vendors" + NavPolicies.ActionSeparator + NavPolicies.DeleteAction)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] diff --git a/Backend/ERPCore/Domain/Entities/AccessConfig.cs b/Backend/ERPCore/Domain/Entities/AccessConfig.cs new file mode 100644 index 0000000..0617613 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/AccessConfig.cs @@ -0,0 +1,26 @@ +namespace ERPCore.Domain.Entities; + +/// +/// Access configuration — a singleton row (same shape as ). +/// is the role a person gets on their first sign-in, and when they have none: +/// without it they would have no permissions at all. It must be a least-privilege role and can never be the +/// administrator role (enforced when it is saved). +/// +public class AccessConfig +{ + /// Always 1 - the singleton row's id. + public const int SingletonId = 1; + + public int ConfigId { get; set; } + + public int? DefaultRoleId { get; set; } + public Role? DefaultRole { get; set; } + + public DateTime? UpdatedAt { get; set; } + + public int? UpdatedBy { get; set; } + public User? UpdatedByUser { get; set; } + + /// Optimistic concurrency token (ETag source). + public uint RowVersion { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/DocumentApproval.cs b/Backend/ERPCore/Domain/Entities/DocumentApproval.cs index eaffa73..57acf75 100644 --- a/Backend/ERPCore/Domain/Entities/DocumentApproval.cs +++ b/Backend/ERPCore/Domain/Entities/DocumentApproval.cs @@ -39,8 +39,9 @@ public class ApprovalConfig /// With 2 levels: a change smaller than this percent (largest across its lines) needs only one approval. 0 = always two. public decimal PriceAdjustmentLevel2ThresholdPercent { get; set; } - /// Comma-separated role codes allowed to give the second approval; empty = anyone with the Approve action. - public string? Level2RoleCodes { get; set; } + /// Comma-separated role IDS allowed to give the second approval; empty = anyone with the Approve action. + /// (Ids, not codes: a role can be renamed without breaking this rule.) + public string? Level2RoleIds { get; set; } public DateTime? UpdatedAt { get; set; } public int? UpdatedBy { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/OpeningBalanceRun.cs b/Backend/ERPCore/Domain/Entities/OpeningBalanceRun.cs new file mode 100644 index 0000000..1123875 --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/OpeningBalanceRun.cs @@ -0,0 +1,74 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// One posted opening balance (cash and bank, or stock) — the record that the opening entry was made, when, +/// by whom, and which General Ledger journal carries it. The General Ledger has no "opening posted" notion, so +/// this table is what refuses a second posting: cash/bank sets (unique index), so it can +/// be posted once. Stock leaves it null and can be loaded in several runs; each item in a warehouse can be +/// opened only once (an item that already has stock movements there is refused). +/// +public class OpeningBalanceRun +{ + public int OpeningBalanceRunId { get; set; } + + public OpeningBalanceKind Kind { get; set; } + + /// Set (to the kind's name) only for kinds that may be posted once; a unique index on it is the + /// once-only guard. Null for stock, which may be loaded in batches (MySQL allows many NULLs under a unique index). + public string? OnceKey { get; set; } + + /// The opening date — the GL journal's entry date. Reports starting after it show these amounts as opening. + public DateOnly CutoverDate { get; set; } + + /// The GL journal number (JV-…). Null only for the instant between claiming the run and the GL + /// confirming the posting, inside one transaction; never null on a committed row. + public string? GlJournalNo { get; set; } + + /// Sum of the debit amounts (what the Opening Balance Equity account is credited with). + public decimal TotalAmount { get; set; } + + public string Currency { get; set; } = "LKR"; + + public int PostedBy { get; set; } + public DateTime PostedAt { get; set; } + + public ICollection Lines { get; set; } = new List(); + public ICollection StockLines { get; set; } = new List(); +} + +/// One cash or bank account's opening amount within an . +/// Positive = a debit balance (money held); negative = a credit balance (an overdraft). +public class OpeningBalanceLine +{ + public int OpeningBalanceLineId { get; set; } + + public int OpeningBalanceRunId { get; set; } + public OpeningBalanceRun? Run { get; set; } + + public string AccountCode { get; set; } = string.Empty; + public string AccountName { get; set; } = string.Empty; + public decimal Amount { get; set; } +} + +/// One item's opening stock in one warehouse within a stock . +public class OpeningStockLine +{ + public int OpeningStockLineId { get; set; } + + public int OpeningBalanceRunId { get; set; } + public OpeningBalanceRun? Run { get; set; } + + public int ItemId { get; set; } + public Item? Item { get; set; } + + public int WarehouseId { get; set; } + public Warehouse? Warehouse { get; set; } + + /// Quantity in the item's base UOM. + public decimal Qty { get; set; } + public decimal UnitCost { get; set; } + /// Qty x UnitCost, rounded to 4 dp — what the inventory account is debited with. + public decimal Value { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/PoLine.cs b/Backend/ERPCore/Domain/Entities/PoLine.cs index e588dd7..40c8dd3 100644 --- a/Backend/ERPCore/Domain/Entities/PoLine.cs +++ b/Backend/ERPCore/Domain/Entities/PoLine.cs @@ -21,5 +21,10 @@ public class PoLine public decimal Qty { get; set; } public decimal UnitPrice { get; set; }// public decimal Tax { get; set; } + + /// Tax amount for this line = round(Qty x UnitPrice x , 2). Stored so the + /// figure on the PO, its PDF and the totals never drifts from what was approved. Always 0 while + /// Procurement configuration's PO tax switch is off. + public decimal TaxAmount { get; set; } public decimal QtyReceived { get; set; } } diff --git a/Backend/ERPCore/Domain/Entities/ProcurementConfig.cs b/Backend/ERPCore/Domain/Entities/ProcurementConfig.cs new file mode 100644 index 0000000..69d021b --- /dev/null +++ b/Backend/ERPCore/Domain/Entities/ProcurementConfig.cs @@ -0,0 +1,42 @@ +using ERPCore.Domain.Enums; + +namespace ERPCore.Domain.Entities; + +/// +/// Procurement configuration — a singleton row, same shape as . +/// Controls the optional vendor fields and whether purchase orders carry tax. +/// +public class ProcurementConfig +{ + /// Always 1 - the singleton row's id. + public const int SingletonId = 1; + + public int ConfigId { get; set; } + + /// Which contact detail vendors carry: none (default), a mobile number or an email. + /// A value sent for a field that is not selected is rejected server-side (CONFIG_DISABLED). + public VendorContactMode VendorContactMode { get; set; } = VendorContactMode.None; + + /// When on, vendors carry an optional company address. + public bool VendorAddressEnabled { get; set; } + + /// When on (default — the field existed before it became optional), vendors carry optional payment terms. + /// When off the field is hidden and a value for it is rejected (CONFIG_DISABLED); stored values are kept. + public bool VendorPaymentTermsEnabled { get; set; } = true; + + /// When on (default), vendors carry an optional tax registration number. Same rules as payment terms. + /// Purchase invoices still snapshot a vendor's stored number either way. + public bool VendorTaxRegEnabled { get; set; } = true; + + /// When on, purchase-order lines carry a tax rate (prefilled from the item) and tax amount; + /// when off, tax is hidden everywhere on POs and a non-zero tax is rejected server-side. + public bool PoTaxEnabled { get; set; } + + public DateTime? UpdatedAt { get; set; } + + public int? UpdatedBy { get; set; } + public User? UpdatedByUser { get; set; } + + /// Optimistic concurrency token (ETag source). + public uint RowVersion { get; set; } +} diff --git a/Backend/ERPCore/Domain/Entities/Role.cs b/Backend/ERPCore/Domain/Entities/Role.cs index 8ae9f43..4911745 100644 --- a/Backend/ERPCore/Domain/Entities/Role.cs +++ b/Backend/ERPCore/Domain/Entities/Role.cs @@ -3,25 +3,30 @@ using ERPCore.Domain.Enums; namespace ERPCore.Domain.Entities; /// -/// Local shadow/projection of an AuthHex Role row, mirroring the same -/// pattern uses for AuthHex identities: -/// maps to AuthHex's Guid RoleId, while the local (int) -/// is what // -/// FKs reference. AuthHex remains the source of truth; writes are forwarded there -/// first (IAuthHexClient) and mirrored here on success. +/// An ERP role. The ERP owns roles and what they may do: points here and +/// holds the permissions. AuthHex only authenticates people and knows nothing about roles. /// public class Role { public int RoleId { get; set; } - public Guid AuthRoleId { get; set; } + + /// Legacy link to the AuthHex role this one used to mirror. Unused now; kept (nullable) until the AuthHex + /// role table is retired, then dropped. + public Guid? AuthRoleId { get; set; } + public string Code { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public bool IsSystemRole { get; set; } + + /// True for the administrator role: its holders pass every permission check, bypass branch scoping and can + /// manage users and roles. This flag — never the role's code or name — is what "admin" means, so the role can be + /// renamed freely. Not settable through the API; only the seeded Admin role carries it. + public bool IsAdmin { get; set; } + public EntityStatus Status { get; set; } = EntityStatus.Active; public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } - /// PostgreSQL xmin-backed optimistic concurrency token (ETag source). public uint RowVersion { get; set; } } diff --git a/Backend/ERPCore/Domain/Entities/SalesConfig.cs b/Backend/ERPCore/Domain/Entities/SalesConfig.cs index d7f3a58..a93743b 100644 --- a/Backend/ERPCore/Domain/Entities/SalesConfig.cs +++ b/Backend/ERPCore/Domain/Entities/SalesConfig.cs @@ -51,6 +51,31 @@ public class SalesConfig public int? DefaultWarehouseId { get; set; } public Warehouse? DefaultWarehouse { get; set; } +// ---- Optional customer fields. Each is on by default (the field existed before it became optional). When off the + // field is hidden on the customer forms and a new value for it is rejected server-side (CONFIG_DISABLED); values + // already stored are kept. + + /// Customers carry an optional phone number. + public bool CustomerPhoneEnabled { get; set; } = true; + + /// Customers carry an optional email address. + public bool CustomerEmailEnabled { get; set; } = true; + + /// Customers carry an optional address (lines 1 and 2). + public bool CustomerAddressEnabled { get; set; } = true; + + /// Customers carry an optional city. + public bool CustomerCityEnabled { get; set; } = true; + + /// Customers carry an optional country. + public bool CustomerCountryEnabled { get; set; } = true; + + /// Customers have a credit limit. + public bool CustomerCreditLimitEnabled { get; set; } = true; + + /// Customers have credit days (how long an invoice may stay unpaid — the customer's "age"). + public bool CustomerCreditDaysEnabled { get; set; } = true; + public DateTime? UpdatedAt { get; set; } public int? UpdatedBy { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/Vendor.cs b/Backend/ERPCore/Domain/Entities/Vendor.cs index aad62de..66e6d21 100644 --- a/Backend/ERPCore/Domain/Entities/Vendor.cs +++ b/Backend/ERPCore/Domain/Entities/Vendor.cs @@ -15,6 +15,13 @@ public class Vendor public string? Terms { get; set; } public string? TaxReg { get; set; } public string Currency { get; set; } = "LKR"; + + /// Mobile number — only recorded while Procurement configuration's vendor contact mode is Mobile. + public string? Mobile { get; set; } + /// Email — only recorded while the vendor contact mode is Email. + public string? Email { get; set; } + /// Company address — only recorded while vendor address is enabled. + public string? Address { get; set; } public EntityStatus Status { get; set; } = EntityStatus.Active; public int BranchId { get; set; } diff --git a/Backend/ERPCore/Domain/Enums/OpeningBalanceKind.cs b/Backend/ERPCore/Domain/Enums/OpeningBalanceKind.cs new file mode 100644 index 0000000..8306ff3 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/OpeningBalanceKind.cs @@ -0,0 +1,12 @@ +namespace ERPCore.Domain.Enums; + +/// +/// Which opening balance a run carries. Each kind is posted ONCE for the life of the system, so a +/// unique index on the kind is the "already posted" guard (the General Ledger itself does not stop a +/// second opening entry). Stored as a string. +/// +public enum OpeningBalanceKind +{ + CashBank, + Stock +} diff --git a/Backend/ERPCore/Domain/Enums/PurchaseOrderStatus.cs b/Backend/ERPCore/Domain/Enums/PurchaseOrderStatus.cs index 88cc4f5..82475cf 100644 --- a/Backend/ERPCore/Domain/Enums/PurchaseOrderStatus.cs +++ b/Backend/ERPCore/Domain/Enums/PurchaseOrderStatus.cs @@ -3,8 +3,10 @@ namespace ERPCore.Domain.Enums; /// /// Purchase-order lifecycle (docs/11 §8; docs/10 §B.8.1). Issuing a PO (create /// non-draft, or submit a Draft) enters ; an Admin who -/// isn't the creator must Approve it to reach , or Reject it back -/// to (FR-PROC-04). Stored as a string. +/// isn't the creator must Approve it to reach , or Reject it to +/// (FR-PROC-04) - a status of its own, so a rejected order is never mistaken for a fresh +/// Draft or one still awaiting approval. A Rejected order is editable like a Draft and goes back through +/// approval when resubmitted. Stored as a string. /// public enum PurchaseOrderStatus { @@ -14,5 +16,6 @@ public enum PurchaseOrderStatus PartiallyReceived, FullyReceived, Closed, - Cancelled + Cancelled, + Rejected } diff --git a/Backend/ERPCore/Domain/Enums/VendorContactMode.cs b/Backend/ERPCore/Domain/Enums/VendorContactMode.cs new file mode 100644 index 0000000..0bc2a2b --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/VendorContactMode.cs @@ -0,0 +1,12 @@ +namespace ERPCore.Domain.Enums; + +/// +/// Which single contact detail a deployment records for vendors (Procurement configuration). +/// hides the field everywhere. Stored as a string. +/// +public enum VendorContactMode +{ + None, + Mobile, + Email +} diff --git a/Backend/ERPCore/Dtos/Approvals/ApprovalDtos.cs b/Backend/ERPCore/Dtos/Approvals/ApprovalDtos.cs index 94eaf00..fa49aef 100644 --- a/Backend/ERPCore/Dtos/Approvals/ApprovalDtos.cs +++ b/Backend/ERPCore/Dtos/Approvals/ApprovalDtos.cs @@ -10,7 +10,7 @@ public sealed record ApprovalStepDto(int Level, ApprovalDecision Decision, int D public sealed record ApprovalConfigDto( int StockAdjustmentLevels, decimal StockAdjustmentLevel2ThresholdValue, int PriceAdjustmentLevels, decimal PriceAdjustmentLevel2ThresholdPercent, - IReadOnlyList Level2RoleCodes, DateTime? UpdatedAt); + IReadOnlyList Level2RoleIds, DateTime? UpdatedAt); public sealed class UpdateApprovalConfigRequest { @@ -18,7 +18,8 @@ public sealed class UpdateApprovalConfigRequest [Range(0, double.MaxValue)] public decimal StockAdjustmentLevel2ThresholdValue { get; set; } [Range(0, 2)] public int PriceAdjustmentLevels { get; set; } [Range(0, 1000)] public decimal PriceAdjustmentLevel2ThresholdPercent { get; set; } - public List Level2RoleCodes { get; set; } = new(); + /// Roles allowed to give the second approval; empty = anyone with the Approve action. + public List Level2RoleIds { get; set; } = new(); } public sealed class ApproveRequest diff --git a/Backend/ERPCore/Dtos/Auth/AuthRoleDtos.cs b/Backend/ERPCore/Dtos/Auth/AuthRoleDtos.cs deleted file mode 100644 index 6222a6b..0000000 --- a/Backend/ERPCore/Dtos/Auth/AuthRoleDtos.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace ERPCore.Dtos.Auth; - -/// AuthHex's Role projection (ERP_Auth_Service/API_DOCUMENTATION.md, RoleManager section). -public sealed class AuthHexRoleDto -{ - public Guid RoleId { get; set; } - public string? Code { get; set; } - public string? Name { get; set; } - public bool? IsSystemRole { get; set; } - public DateTime CreatedAt { get; set; } -} - -public sealed class CreateAuthHexRoleRequest -{ - [Required] public string Code { get; set; } = string.Empty; - public string? Name { get; set; } - public bool? IsSystemRole { get; set; } -} - -public sealed class UpdateAuthHexRoleRequest -{ - [Required] public Guid RoleId { get; set; } - public string? Code { get; set; } - public string? Name { get; set; } - public bool? IsSystemRole { get; set; } -} diff --git a/Backend/ERPCore/Dtos/Auth/AuthUserDtos.cs b/Backend/ERPCore/Dtos/Auth/AuthUserDtos.cs index 0282282..938ca0b 100644 --- a/Backend/ERPCore/Dtos/Auth/AuthUserDtos.cs +++ b/Backend/ERPCore/Dtos/Auth/AuthUserDtos.cs @@ -57,7 +57,9 @@ public sealed class RegisterRequest { /// Optional — AuthHex requires a client-supplied id; ERPCore generates one when omitted. public Guid? UserId { get; set; } - [Required] public Guid RoleId { get; set; } + /// Ignored for self-registration and never taken from a caller: the ERP assigns roles itself. Sent to AuthHex only + /// while the old AuthHex (which still demands a role id) is deployed — see AuthHex:LegacyRoleId. + public Guid? RoleId { get; set; } [Required] public Guid UserTypeId { get; set; } public string? Fullname { get; set; } public string? UserName { get; set; } diff --git a/Backend/ERPCore/Dtos/Config/AccessConfigDtos.cs b/Backend/ERPCore/Dtos/Config/AccessConfigDtos.cs new file mode 100644 index 0000000..ddbfcc5 --- /dev/null +++ b/Backend/ERPCore/Dtos/Config/AccessConfigDtos.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace ERPCore.Dtos.Config; + +/// Access configuration resource: which role a person gets when they have none. +public sealed record AccessConfigDto(int? DefaultRoleId, string? DefaultRoleName, DateTime? UpdatedAt, int? UpdatedBy); + +/// Full replacement. null clears the default (people without a role then have no permissions at all). +public sealed class UpdateAccessConfigRequest +{ + [Range(1, int.MaxValue)] public int? DefaultRoleId { get; set; } +} diff --git a/Backend/ERPCore/Dtos/Config/ProcurementConfigDtos.cs b/Backend/ERPCore/Dtos/Config/ProcurementConfigDtos.cs new file mode 100644 index 0000000..09f8661 --- /dev/null +++ b/Backend/ERPCore/Dtos/Config/ProcurementConfigDtos.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using ERPCore.Domain.Enums; + +namespace ERPCore.Dtos.Config; + +/// Procurement configuration resource. +public sealed record ProcurementConfigDto( + VendorContactMode VendorContactMode, bool VendorAddressEnabled, bool PoTaxEnabled, + DateTime? UpdatedAt, int? UpdatedBy, + bool VendorPaymentTermsEnabled = true, bool VendorTaxRegEnabled = true); + +/// +/// Full replacement of the flags. UpdatedBy is derived from the token, never posted. +/// Nullable so an omitted flag is a 400 rather than silently binding to a default +/// (same reasoning as ). +/// +public sealed class UpdateProcurementConfigRequest +{ + [Required] public VendorContactMode? VendorContactMode { get; set; } + [Required] public bool? VendorAddressEnabled { get; set; } + [Required] public bool? PoTaxEnabled { get; set; } + [Required] public bool? VendorPaymentTermsEnabled { get; set; } + [Required] public bool? VendorTaxRegEnabled { get; set; } +} diff --git a/Backend/ERPCore/Dtos/Config/SalesConfigDtos.cs b/Backend/ERPCore/Dtos/Config/SalesConfigDtos.cs index bd13155..8a1a71f 100644 --- a/Backend/ERPCore/Dtos/Config/SalesConfigDtos.cs +++ b/Backend/ERPCore/Dtos/Config/SalesConfigDtos.cs @@ -6,7 +6,10 @@ namespace ERPCore.Dtos.Config; /// the generic cash customer (null until the option has been switched on at least once). public sealed record SalesConfigDto( bool AllowCashCustomer, int? CashCustomerId, string? CashCustomerName, bool AllowFreeItems, bool AllowDiscounts, bool AllowOverStockSale, - bool AllowWarehouseSelection, int? DefaultWarehouseId, string? DefaultWarehouseName, DateTime? UpdatedAt, int? UpdatedBy); + bool AllowWarehouseSelection, int? DefaultWarehouseId, string? DefaultWarehouseName, DateTime? UpdatedAt, int? UpdatedBy, + bool CustomerPhoneEnabled = true, bool CustomerEmailEnabled = true, bool CustomerAddressEnabled = true, + bool CustomerCityEnabled = true, bool CustomerCountryEnabled = true, + bool CustomerCreditLimitEnabled = true, bool CustomerCreditDaysEnabled = true); /// /// Full replacement of the flags. UpdatedBy is derived from the token, never posted. @@ -22,4 +25,11 @@ public sealed class UpdateSalesConfigRequest [Required] public bool? AllowWarehouseSelection { get; set; } /// Required when is false. public int? DefaultWarehouseId { get; set; } + [Required] public bool? CustomerPhoneEnabled { get; set; } + [Required] public bool? CustomerEmailEnabled { get; set; } + [Required] public bool? CustomerAddressEnabled { get; set; } + [Required] public bool? CustomerCityEnabled { get; set; } + [Required] public bool? CustomerCountryEnabled { get; set; } + [Required] public bool? CustomerCreditLimitEnabled { get; set; } + [Required] public bool? CustomerCreditDaysEnabled { get; set; } } diff --git a/Backend/ERPCore/Dtos/Grn/GrnDtos.cs b/Backend/ERPCore/Dtos/Grn/GrnDtos.cs index 9fad1dc..26fbe3a 100644 --- a/Backend/ERPCore/Dtos/Grn/GrnDtos.cs +++ b/Backend/ERPCore/Dtos/Grn/GrnDtos.cs @@ -27,7 +27,8 @@ public sealed record GrnDto( public sealed record GrnSummaryDto( int GrnId, string DocNo, int? PoId, int VendorId, int WarehouseId, int BranchId, GrnStatus Status, int CreatedBy, DateTime CreatedAt, DateTime? PostedAt, int LineCount, decimal TotalValue, - int? PurchaseInvoiceId, string? PurchaseInvoiceNo); + int? PurchaseInvoiceId, string? PurchaseInvoiceNo, + string? PoDocNo = null, Dtos.Procurement.PoVendorDto? Vendor = null); public sealed record CreatedLayerDto( int LayerId, int ItemId, int WarehouseId, int? BatchId, @@ -93,7 +94,8 @@ public sealed class CreateGrnRequest public int? VendorId { get; set; } [Required] public int WarehouseId { get; set; } /// Owning branch for this GRN — warehouses are shared, so this is what actually - /// scopes the document (resolved via IBranchConfigService; required when branching is enabled). + /// scopes the document (resolved via IBranchConfigService; required when branching is enabled). + /// Ignored when is set: a GRN against a purchase order always belongs to that order's branch. [Required] public int BranchId { get; set; } [Required, MinLength(1)] public List Lines { get; set; } = new(); } diff --git a/Backend/ERPCore/Dtos/OpeningBalances/OpeningBalanceDtos.cs b/Backend/ERPCore/Dtos/OpeningBalances/OpeningBalanceDtos.cs new file mode 100644 index 0000000..12af0cf --- /dev/null +++ b/Backend/ERPCore/Dtos/OpeningBalances/OpeningBalanceDtos.cs @@ -0,0 +1,71 @@ +using System.ComponentModel.DataAnnotations; + +namespace ERPCore.Dtos.OpeningBalances; + +/// One cash or bank account as the opening-balance screen shows it. Amount is filled only once posted. +public sealed record OpeningCashBankAccountDto( + string AccountType, string AccountName, string? BankName, string? AccountNumber, + string GlAccountCode, string CurrencyCode, decimal? Amount); + +public sealed record OpeningBalanceLineDto(string AccountCode, string AccountName, decimal Amount); + +/// A posted opening balance (the record of the one-time entry). +public sealed record OpeningBalanceRunDto( + int OpeningBalanceRunId, string Kind, DateOnly CutoverDate, string? GlJournalNo, + decimal TotalAmount, string Currency, DateTime PostedAt, int PostedBy, + IReadOnlyList Lines); + +/// The cash/bank screen's state: the accounts to enter amounts for, or the posted record once done. +public sealed record CashBankOpeningStateDto( + OpeningBalanceRunDto? Posted, IReadOnlyList Accounts, string BaseCurrency); + +public sealed class OpeningBalanceLineInput +{ + /// The GL account code of a cash or bank account, as returned by the state endpoint. + [Required, StringLength(20)] public string GlAccountCode { get; set; } = string.Empty; + + /// Opening balance, at most 2 decimals. Positive = money held; negative = an overdraft. Zero lines are ignored. + public decimal Amount { get; set; } +} + +public sealed class PostCashBankOpeningRequest +{ + /// The opening date (the GL entry date). Must fall in an open accounting period and not be in the future. + [Required] public DateOnly? CutoverDate { get; set; } + + [Required, MinLength(1)] public List Lines { get; set; } = new(); +} + +// ---------------------------------------------------------------- opening stock + +public sealed class OpeningStockLineInput +{ + [Required] public int ItemId { get; set; } + [Required] public int WarehouseId { get; set; } + + /// Quantity in the item's base UOM; more than 0, at most 4 decimals. + public decimal Qty { get; set; } + + /// Cost of ONE base unit, 0 or more, at most 4 decimals — this becomes the FIFO layer's cost. + public decimal UnitCost { get; set; } +} + +public sealed class PostOpeningStockRequest +{ + /// The opening date (the stock layers' receipt date and the GL entry date). Not in the future. + [Required] public DateOnly? CutoverDate { get; set; } + + [Required, MinLength(1), MaxLength(500)] public List Lines { get; set; } = new(); +} + +public sealed record OpeningStockLineDto( + int ItemId, string Sku, string ItemName, int WarehouseId, string WarehouseCode, + decimal Qty, decimal UnitCost, decimal Value); + +public sealed record OpeningStockRunSummaryDto( + int OpeningBalanceRunId, DateOnly CutoverDate, string? GlJournalNo, decimal TotalValue, int LineCount, + DateTime PostedAt, int PostedBy); + +public sealed record OpeningStockRunDto( + int OpeningBalanceRunId, DateOnly CutoverDate, string? GlJournalNo, decimal TotalValue, int LineCount, + DateTime PostedAt, int PostedBy, IReadOnlyList Lines); diff --git a/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs b/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs index 59a1a26..e7d82b9 100644 --- a/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs +++ b/Backend/ERPCore/Dtos/Procurement/PurchaseOrderDtos.cs @@ -5,9 +5,14 @@ namespace ERPCore.Dtos.Procurement; // Responses (docs/11 §3.3) ------------------------------------------------------ +/// Tax is the rate (0..1); TaxAmount the stored amount for the line. public sealed record PoLineDto( int PoLineId, int ItemId, int WarehouseId, - decimal Qty, decimal UnitPrice, decimal Tax, decimal QtyReceived); + decimal Qty, decimal UnitPrice, decimal Tax, decimal QtyReceived, decimal TaxAmount = 0); + +/// Vendor details shown on a purchase order. Mobile/Email/Address are null +/// unless the matching Procurement configuration switch is on. +public sealed record PoVendorDto(int VendorId, string Code, string Name, string? Mobile, string? Email, string? Address); public sealed record PoTotalsDto(decimal SubTotal, decimal Tax, decimal GrandTotal, string Currency); @@ -15,11 +20,12 @@ public sealed record PurchaseOrderDto( int PoId, string DocNo, int VendorId, int BranchId, int? RequisitionId, PurchaseOrderStatus Status, bool ApprovalRequired, int CreatedBy, int? ApprovedBy, DateTime? ApprovedAt, DateTime CreatedAt, DateTime? UpdatedAt, - PoTotalsDto Totals, IReadOnlyList Lines); + PoTotalsDto Totals, IReadOnlyList Lines, PoVendorDto? Vendor = null); public sealed record PurchaseOrderSummaryDto( int PoId, string DocNo, int VendorId, int BranchId, PurchaseOrderStatus Status, - bool ApprovalRequired, int CreatedBy, DateTime CreatedAt, PoTotalsDto Totals); + bool ApprovalRequired, int CreatedBy, DateTime CreatedAt, PoTotalsDto Totals, + PoVendorDto? Vendor = null); // Requests — server sets docNo, status, createdBy, timestamps, qtyReceived, totals diff --git a/Backend/ERPCore/Dtos/Rbac/MeDtos.cs b/Backend/ERPCore/Dtos/Rbac/MeDtos.cs index 93bbbf4..e3ff796 100644 --- a/Backend/ERPCore/Dtos/Rbac/MeDtos.cs +++ b/Backend/ERPCore/Dtos/Rbac/MeDtos.cs @@ -11,4 +11,6 @@ namespace ERPCore.Dtos.Rbac; /// entirely rather than merely restricting them by role. public sealed record MeResponseDto( string? RoleCode, string? RoleName, IReadOnlyList NavCodes, - bool IsAdmin, int? BranchId, string? BranchName, bool BranchingEnabled, bool WarehouseBranchingEnabled); + bool IsAdmin, int? BranchId, string? BranchName, bool BranchingEnabled, bool WarehouseBranchingEnabled, + int? RoleId = null, bool HasRole = false, + IReadOnlyDictionary? Permissions = null); diff --git a/Backend/ERPCore/Dtos/Rbac/RoleDtos.cs b/Backend/ERPCore/Dtos/Rbac/RoleDtos.cs index 77a7cc7..b72c0da 100644 --- a/Backend/ERPCore/Dtos/Rbac/RoleDtos.cs +++ b/Backend/ERPCore/Dtos/Rbac/RoleDtos.cs @@ -5,7 +5,7 @@ namespace ERPCore.Dtos.Rbac; public sealed record RoleDto( int RoleId, string Code, string Name, bool IsSystemRole, EntityStatus Status, - DateTime CreatedAt, DateTime? UpdatedAt); + DateTime CreatedAt, DateTime? UpdatedAt, bool IsAdmin = false); public sealed class CreateRoleRequest { diff --git a/Backend/ERPCore/Dtos/Users/UserDtos.cs b/Backend/ERPCore/Dtos/Users/UserDtos.cs index 597fe83..444fb4e 100644 --- a/Backend/ERPCore/Dtos/Users/UserDtos.cs +++ b/Backend/ERPCore/Dtos/Users/UserDtos.cs @@ -84,3 +84,10 @@ public sealed record SendResetLinkResponse(DateTime? ExpiresAt); /// AuthHex UserType lookup, for populating the create-user form's select (no local shadow — read-only passthrough). public sealed record UserTypeOptionDto(Guid UserTypeId, string? Code, string? Description); + +/// Outcome of the one-time copy of each user's role from AuthHex into the ERP's users table. +public sealed record BackfillRolesResultDto( + int Examined, int Assigned, int AlreadyHadRole, IReadOnlyList Issues); + +/// A user the backfill could not give a role, and why — for an administrator to assign by hand. +public sealed record BackfillRolesIssueDto(int UserId, string Username, string Reason); diff --git a/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs b/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs index b646dd1..2047a3d 100644 --- a/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs +++ b/Backend/ERPCore/Dtos/Vendors/VendorDtos.cs @@ -6,7 +6,8 @@ namespace ERPCore.Dtos.Vendors; /// Vendor resource (docs/11-BACKEND-PHASE1.md §2.4). public sealed record VendorDto( int VendorId, string Code, string Name, string? Terms, string? TaxReg, - string Currency, EntityStatus Status, int BranchId, DateTime CreatedAt, DateTime? UpdatedAt); + string Currency, EntityStatus Status, int BranchId, DateTime CreatedAt, DateTime? UpdatedAt, + string? Mobile = null, string? Email = null, string? Address = null); public sealed class CreateVendorRequest { @@ -15,6 +16,12 @@ public sealed class CreateVendorRequest [StringLength(50)] public string? Terms { get; set; } [StringLength(50)] public string? TaxReg { get; set; } [Required, StringLength(3, MinimumLength = 3)] public string Currency { get; set; } = "LKR"; + /// Only accepted while the vendor contact mode is Mobile (otherwise 422 CONFIG_DISABLED). + [StringLength(20)] public string? Mobile { get; set; } + /// Only accepted while the vendor contact mode is Email (otherwise 422 CONFIG_DISABLED). + [StringLength(200)] public string? Email { get; set; } + /// Only accepted while vendor address is enabled (otherwise 422 CONFIG_DISABLED). + [StringLength(500)] public string? Address { get; set; } /// Optional — resolved via /// when branching is enabled; ignored (defaulted) otherwise. public int BranchId { get; set; } @@ -27,6 +34,12 @@ public sealed class UpdateVendorRequest [StringLength(50)] public string? Terms { get; set; } [StringLength(50)] public string? TaxReg { get; set; } [Required, StringLength(3, MinimumLength = 3)] public string Currency { get; set; } = "LKR"; + /// Only accepted while the vendor contact mode is Mobile (otherwise 422 CONFIG_DISABLED). + [StringLength(20)] public string? Mobile { get; set; } + /// Only accepted while the vendor contact mode is Email (otherwise 422 CONFIG_DISABLED). + [StringLength(200)] public string? Email { get; set; } + /// Only accepted while vendor address is enabled (otherwise 422 CONFIG_DISABLED). + [StringLength(500)] public string? Address { get; set; } public int BranchId { get; set; } } diff --git a/Backend/ERPCore/Infra/Auth/AuthHex/AuthHexClient.cs b/Backend/ERPCore/Infra/Auth/AuthHex/AuthHexClient.cs index df53201..9f500a4 100644 --- a/Backend/ERPCore/Infra/Auth/AuthHex/AuthHexClient.cs +++ b/Backend/ERPCore/Infra/Auth/AuthHex/AuthHexClient.cs @@ -39,7 +39,12 @@ public sealed class AuthHexClient : IAuthHexClient // ---- UserManager -------------------------------------------------- public Task RegisterAsync(RegisterRequest request, CancellationToken ct) - => CallAsync("user", "registerUser", request, null, ct); + { + // Until AuthHex stops requiring a role (its roles are being retired), give it a fixed placeholder so + // registration keeps working. The ERP never reads this value; the person's real role is users.RoleId. + request.RoleId ??= Guid.TryParse(_config["AuthHex:LegacyRoleId"], out var legacy) ? legacy : null; + return CallAsync("user", "registerUser", request, null, ct); + } public Task LoginAsync(LoginRequest request, CancellationToken ct) => CallAsync("user", "loginUser", request, null, ct); @@ -149,23 +154,6 @@ public sealed class AuthHexClient : IAuthHexClient public Task VerifyAltOtpAsync(VerifyAltOtpRequest request, CancellationToken ct) => CallAsync("alt", "VerifyOTP", request, null, ct); - // ---- RoleManager -------------------------------------------------- - - public Task CreateRoleAsync(CreateAuthHexRoleRequest request, CancellationToken ct) - => CallAsync("role", "createRole", request, null, ct); - - public Task> ListRolesAsync(CancellationToken ct) - => CallAsync>("role", "listRoles", new { }, null, ct); - - public Task GetRoleAsync(Guid roleId, CancellationToken ct) - => CallAsync("role", "getRole", new { roleId }, null, ct); - - public Task UpdateRoleAsync(UpdateAuthHexRoleRequest request, CancellationToken ct) - => CallAsync("role", "updateRole", request, null, ct); - - public Task DeleteRoleAsync(Guid roleId, CancellationToken ct) - => CallVoidAsync("role", "deleteRole", new { roleId }, null, ct); - // ---- Transport -------------------------------------------------------- private async Task CallVoidAsync(string routeGroup, string functionName, object payload, string? bearerToken, CancellationToken ct) diff --git a/Backend/ERPCore/Infra/Auth/AuthHex/IAuthHexClient.cs b/Backend/ERPCore/Infra/Auth/AuthHex/IAuthHexClient.cs index 23ce724..54efe32 100644 --- a/Backend/ERPCore/Infra/Auth/AuthHex/IAuthHexClient.cs +++ b/Backend/ERPCore/Infra/Auth/AuthHex/IAuthHexClient.cs @@ -57,12 +57,4 @@ public interface IAuthHexClient Task IsAvailableAsync(IsAvailableRequest request, CancellationToken ct); Task SendOtpAsync(SendOtpRequest request, CancellationToken ct); Task VerifyAltOtpAsync(VerifyAltOtpRequest request, CancellationToken ct); - - // RoleManager (POST /api/role) — AuthHex is the source of truth for Role; - // ERPCore mirrors the result into a local shadow Role row (see RoleService). - Task CreateRoleAsync(CreateAuthHexRoleRequest request, CancellationToken ct); - Task> ListRolesAsync(CancellationToken ct); - Task GetRoleAsync(Guid roleId, CancellationToken ct); - Task UpdateRoleAsync(UpdateAuthHexRoleRequest request, CancellationToken ct); - Task DeleteRoleAsync(Guid roleId, CancellationToken ct); } diff --git a/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs b/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs index 325aaa4..6c2e3f8 100644 --- a/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs +++ b/Backend/ERPCore/Infra/Auth/AuthHexClaims.cs @@ -22,4 +22,24 @@ public static class AuthHexClaims /// branch-scoped query filtering (docs/BRANCH_ISOLATION_PLAN.md). /// public const string BranchId = "erp:BranchId"; + + /// + /// Local claim: "false" when the deployment has branching switched off (Settings → Branch Configuration), absent or + /// "true" otherwise. Lets tell "a branch-less user in a single-branch + /// deployment" (sees everything) from "a branch-less user in a multi-branch deployment" (sees no branch's data). + /// + public const string BranchingEnabled = "erp:BranchingEnabled"; + + // ---- Local claims: the ERP's own authorization identity --------------------------------------------------- + // Injected by ShadowUserClaimsTransformation from the ERP's users/roles tables on every request. They are NEVER + // read from the token: the role belongs to the ERP, AuthHex only says who the person is. The AuthHex RoleId / + // RoleCode claims above are used only by the temporary legacy fallback. + + /// The effective role's id (the user's role, or the default role). Absent when the person has no usable role. + public const string ErpRoleId = "erp:RoleId"; + public const string ErpRoleCode = "erp:RoleCode"; + /// "true" when the effective role is the administrator role (). + public const string ErpIsAdmin = "erp:IsAdmin"; + /// Present when the ERP user is inactive; the ErpAccess policy then refuses the request (403). + public const string ErpBlocked = "erp:Blocked"; } diff --git a/Backend/ERPCore/Infra/Auth/CurrentUser.cs b/Backend/ERPCore/Infra/Auth/CurrentUser.cs index 676e1fb..74c6e95 100644 --- a/Backend/ERPCore/Infra/Auth/CurrentUser.cs +++ b/Backend/ERPCore/Infra/Auth/CurrentUser.cs @@ -4,9 +4,10 @@ using ERPCore.Domain.Entities; namespace ERPCore.Infra.Auth; /// -/// Resolves the audit actor from the current HTTP request's principal -/// (token `sub` / NameIdentifier). Falls back to -/// when the request is unauthenticated (e.g. health checks, seeding). +/// Resolves the audit actor and the ERP-owned role from the current HTTP request's principal. +/// Every value here was put on the principal by from the ERP's own +/// tables — none comes from AuthHex role claims. Falls back to when the request is +/// unauthenticated (e.g. health checks, seeding). /// public sealed class CurrentUser : ICurrentUser { @@ -32,9 +33,18 @@ public sealed class CurrentUser : ICurrentUser public int AuditUserId => int.TryParse(UserId, out var id) ? id : User.SystemUserId; - public string? RoleCode => _accessor.HttpContext?.User?.FindFirstValue(AuthHexClaims.RoleCode); + public int? RoleId + { + get + { + var raw = _accessor.HttpContext?.User?.FindFirstValue(AuthHexClaims.ErpRoleId); + return int.TryParse(raw, out var id) ? id : null; + } + } - public bool IsAdmin => string.Equals(RoleCode?.Trim(), "Admin", StringComparison.OrdinalIgnoreCase); + public string? RoleCode => _accessor.HttpContext?.User?.FindFirstValue(AuthHexClaims.ErpRoleCode); + + public bool IsAdmin => string.Equals(_accessor.HttpContext?.User?.FindFirstValue(AuthHexClaims.ErpIsAdmin), "true", StringComparison.Ordinal); public int? BranchId { @@ -44,4 +54,7 @@ public sealed class CurrentUser : ICurrentUser return int.TryParse(raw, out var id) ? id : null; } } + + public bool BranchingEnabled => + !string.Equals(_accessor.HttpContext?.User?.FindFirstValue(AuthHexClaims.BranchingEnabled), "false", StringComparison.Ordinal); } diff --git a/Backend/ERPCore/Infra/Auth/ICurrentBranchContext.cs b/Backend/ERPCore/Infra/Auth/ICurrentBranchContext.cs index 04baab7..7c97858 100644 --- a/Backend/ERPCore/Infra/Auth/ICurrentBranchContext.cs +++ b/Backend/ERPCore/Infra/Auth/ICurrentBranchContext.cs @@ -9,7 +9,9 @@ namespace ERPCore.Infra.Auth; public interface ICurrentBranchContext { /// The branch to scope reads to. Null means "don't filter" — used for - /// admins, unauthenticated/system contexts, and any caller with no resolved branch. + /// admins, unauthenticated/system contexts, and (only when the deployment has branching switched off) people with + /// no branch. In a branching deployment, a signed-in non-admin with no linked employee gets + /// : a branch that matches no data, so they see none (deny by default). int? BranchId { get; } /// False disables filtering entirely (e.g. background jobs, migrations, @@ -18,17 +20,29 @@ public interface ICurrentBranchContext } /// -/// Default implementation: derives from . Admins and users with -/// no resolved branch are never filtered — the same bypass -/// already applies for write-path checks. +/// Default implementation: derives from . Admins are never filtered, nor is anyone in a +/// deployment with branching off. A non-admin without a branch (no linked employee) in a branching deployment is scoped to +/// — they used to fall through unfiltered and could read every branch's records, while the +/// write path () already refused them. /// public sealed class CurrentBranchContext : ICurrentBranchContext { + /// The "branch" of a person who has none in a branching deployment. No row has branch 0, so filters on it match nothing. + public const int NoBranchId = 0; + private readonly ICurrentUser _currentUser; public CurrentBranchContext(ICurrentUser currentUser) => _currentUser = currentUser; - public int? BranchId => _currentUser.IsAdmin ? null : _currentUser.BranchId; + public int? BranchId + { + get + { + if (!_currentUser.IsAuthenticated || _currentUser.IsAdmin) return null; + if (_currentUser.BranchId is { } own) return own; + return _currentUser.BranchingEnabled ? NoBranchId : null; + } + } - public bool IsActive => _currentUser.IsAuthenticated && !_currentUser.IsAdmin && _currentUser.BranchId is not null; + public bool IsActive => BranchId is not null; } diff --git a/Backend/ERPCore/Infra/Auth/ICurrentUser.cs b/Backend/ERPCore/Infra/Auth/ICurrentUser.cs index 904df01..58f6cc9 100644 --- a/Backend/ERPCore/Infra/Auth/ICurrentUser.cs +++ b/Backend/ERPCore/Infra/Auth/ICurrentUser.cs @@ -1,9 +1,9 @@ namespace ERPCore.Infra.Auth; /// -/// Abstraction over the authenticated principal, used purely as the audit actor -/// (RBAC is deferred for Phase 1). The actor is derived from the token's `sub` -/// claim — never from the request body (see 00-CORE §4). +/// Abstraction over the authenticated principal: the audit actor plus the ERP-owned role. Everything here is +/// derived from the ERP's own users/roles tables via ShadowUserClaimsTransformation, keyed by the AuthHex +/// identity — never from the request body, and never from AuthHex role claims (see 00-CORE §4). /// public interface ICurrentUser { @@ -20,13 +20,20 @@ public interface ICurrentUser /// True when the request carries an authenticated principal. bool IsAuthenticated { get; } - /// The AuthHex RoleCode claim verbatim, or null if absent/unauthenticated. + /// + /// The id of the person's effective ERP role (their own, else the configured default), read from the ERP's users + /// table on each request. Null when they have no usable role (none assigned and no default, or the role is + /// inactive) — they then have no permissions. + /// + int? RoleId { get; } + + /// The effective role's code, for display and logging. Never use it to make a permission decision. string? RoleCode { get; } /// - /// True when is "Admin" (case-insensitive — AuthHex mints the claim - /// independently of ERPCore's stored casing). Mirrors RoleService.GetMeAsync's check; - /// the only role that bypasses branch-scoping (see ). + /// True when the effective role is the administrator role () — not when its + /// name happens to be "Admin". Admins pass every permission check and are the only users that bypass + /// branch scoping (see ). /// bool IsAdmin { get; } @@ -37,4 +44,10 @@ public interface ICurrentUser /// see and ). /// int? BranchId { get; } + + /// + /// Whether the deployment uses branches at all (Settings → Branch Configuration). When off, every document lives on the + /// one default branch and nobody is branch-scoped. Defaults to true — the safe side — for contexts that don't say. + /// + bool BranchingEnabled => true; } diff --git a/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs b/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs index 658433f..e830fa9 100644 --- a/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs +++ b/Backend/ERPCore/Infra/Auth/JwtAuthExtensions.cs @@ -37,7 +37,6 @@ public static class JwtAuthExtensions var publicKeyXml = config["Auth:RsaPublicKeyXml"] ?? throw new InvalidOperationException("Auth:RsaPublicKeyXml (AuthHex RSA public key) is not configured."); var requiredUserType = config["Auth:RequiredUserTypeCode"]; - var requiredRole = config["Auth:RequiredRoleCode"]; // AuthHex publishes no JWKS; the RSA public key is configured statically. var rsa = RSA.Create(); @@ -85,12 +84,13 @@ public static class JwtAuthExtensions options.AddPolicy(ErpAccessPolicy, policy => { policy.RequireAuthenticatedUser(); - // Door gate: only enforce a UserType/Role when configured (AuthHex is a - // shared IdP). Empty config = require a valid ERP token only. + // Door gate: only enforce a UserType when configured (AuthHex is a shared IdP). Empty config = + // require a valid ERP token only. (There is no role door gate any more: roles are the ERP's own, + // and are enforced per endpoint by the Nav policies.) if (!string.IsNullOrWhiteSpace(requiredUserType)) policy.RequireClaim(AuthHexClaims.UserTypeCode, requiredUserType); - if (!string.IsNullOrWhiteSpace(requiredRole)) - policy.RequireClaim(AuthHexClaims.RoleCode, requiredRole); + // An inactive ERP user is refused outright, even while their AuthHex token is still valid. + policy.RequireAssertion(ctx => !ctx.User.HasClaim(c => c.Type == AuthHexClaims.ErpBlocked)); }); }); diff --git a/Backend/ERPCore/Infra/Auth/NavAuthorization.cs b/Backend/ERPCore/Infra/Auth/NavAuthorization.cs index 4be04f4..6f6c6ed 100644 --- a/Backend/ERPCore/Infra/Auth/NavAuthorization.cs +++ b/Backend/ERPCore/Infra/Auth/NavAuthorization.cs @@ -57,9 +57,9 @@ public sealed class NavRequirement : IAuthorizationRequirement } } -/// Admin always passes (mirrors every other Admin-bypasses-scoping rule in this -/// codebase, e.g. BranchAccessService); everyone else needs the role permission, -/// and — when the policy names one — the specific action within it too. +/// An administrator ( — the role flag, not a role name) always passes, +/// mirroring every other admin-bypasses-scoping rule in this codebase (e.g. BranchAccessService); everyone +/// else needs the role's permission and — when the policy names one — the specific action within it too. public sealed class NavAuthorizationHandler : AuthorizationHandler { private readonly ICurrentUser _currentUser; @@ -81,9 +81,9 @@ public sealed class NavAuthorizationHandler : AuthorizationHandler -/// Maps an authenticated AuthHex principal to ERPCore's local identity (docs/10 A.4/A.5). -/// AuthHex tokens carry the user as a custom UserId (GUID) claim and no -/// sub/nameid. This transformation JIT-provisions a local shadow -/// (keyed by auth_user_id) and injects the local -/// int id as , so -/// /AuditUserId resolve the real user unchanged. +/// Turns an authenticated AuthHex principal into ERPCore's authorization identity. +/// +/// AuthHex says WHO the person is: its token carries a custom UserId (GUID) and no sub/nameid. +/// The ERP decides WHAT they may do, from its own tables. In one query this finds the local shadow +/// (keyed by auth_user_id, JIT-provisioned on first sign-in) together with its +/// and branch, then injects: +/// +/// NameIdentifier — the local int user id (audit actor); +/// / / — the effective role; +/// — the branch of the linked employee; +/// — when the ERP user is inactive (the door policy then answers 403). +/// +/// Because the role is read from the database on every request, changing someone's role or a role's permissions +/// takes effect on their very next request — no re-login, no stale token. +/// +/// +/// Effective role = the user's own role if it is active; otherwise nothing. A user with no role gets one +/// assigned and saved: during the rollout, the role named by a still-present AuthHex RoleCode claim +/// (Auth:LegacyRoleClaimFallback, on by default, to be switched off once every user has a role); else the +/// configured default role (). With neither, they have no permissions. +/// /// Idempotent — may run several times per request. /// public sealed class ShadowUserClaimsTransformation : IClaimsTransformation { private readonly ErpDbContext _db; + private readonly bool _legacyRoleFallback; - public ShadowUserClaimsTransformation(ErpDbContext db) => _db = db; + public ShadowUserClaimsTransformation(ErpDbContext db, IConfiguration configuration) + { + _db = db; + // Default ON: the safe choice while some users may still have no local role. + _legacyRoleFallback = !bool.TryParse(configuration["Auth:LegacyRoleClaimFallback"], out var enabled) || enabled; + } + + private sealed record UserRow( + int UserId, EntityStatus Status, int? RoleId, string? RoleCode, EntityStatus? RoleStatus, bool RoleIsAdmin, int? BranchId, bool? BranchingEnabled); public async Task TransformAsync(ClaimsPrincipal principal) { @@ -33,29 +57,63 @@ public sealed class ShadowUserClaimsTransformation : IClaimsTransformation if (!Guid.TryParse(raw, out var authUserId)) return principal; // no mappable identity → CurrentUser falls back to system - var nic = principal.FindFirstValue(AuthHexClaims.Nic); - var localId = await ResolveOrProvisionAsync(authUserId, nic); + var row = await LoadAsync(authUserId) + ?? await ProvisionAsync(authUserId, principal.FindFirstValue(AuthHexClaims.Nic)); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, localId.ToString())); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, row.UserId.ToString())); + if (row.BranchId is not null) + identity.AddClaim(new Claim(AuthHexClaims.BranchId, row.BranchId.Value.ToString())); + if (row.BranchingEnabled == false) + identity.AddClaim(new Claim(AuthHexClaims.BranchingEnabled, "false")); - var branchId = await _db.Employees.AsNoTracking() - .Where(e => e.UserId == localId) - .Select(e => (int?)e.BranchId) - .FirstOrDefaultAsync(); - if (branchId is not null) - identity.AddClaim(new Claim(AuthHexClaims.BranchId, branchId.Value.ToString())); + if (row.Status != EntityStatus.Active) + { + identity.AddClaim(new Claim(AuthHexClaims.ErpBlocked, "1")); + return principal; + } + + // No role yet? Assign one (and save it) so the Users screen shows what they actually have. + if (row.RoleId is null) + row = await AssignMissingRoleAsync(row, principal.FindFirstValue(AuthHexClaims.RoleCode)); + + // An inactive role grants nothing: the person is treated as having no role until it is reactivated or changed. + if (row.RoleId is not null && row.RoleStatus == EntityStatus.Active) + { + identity.AddClaim(new Claim(AuthHexClaims.ErpRoleId, row.RoleId.Value.ToString())); + identity.AddClaim(new Claim(AuthHexClaims.ErpRoleCode, row.RoleCode ?? string.Empty)); + identity.AddClaim(new Claim(AuthHexClaims.ErpIsAdmin, row.RoleIsAdmin ? "true" : "false")); + } return principal; } - private async Task ResolveOrProvisionAsync(Guid authUserId, string? nic) - { - var existing = await _db.Users.AsNoTracking() + /// The user with its role and branch, in ONE round trip. + private Task LoadAsync(Guid authUserId) => + _db.Users.AsNoTracking() .Where(u => u.AuthUserId == authUserId) - .Select(u => u.UserId) + .Select(u => new UserRow( + u.UserId, u.Status, u.RoleId, + u.Role != null ? u.Role.Code : null, + u.Role != null ? (EntityStatus?)u.Role.Status : null, + u.Role != null && u.Role.IsAdmin, + _db.Employees.Where(e => e.UserId == u.UserId).Select(e => (int?)e.BranchId).FirstOrDefault(), + _db.BranchConfig.Where(c => c.ConfigId == BranchConfig.SingletonId).Select(c => (bool?)c.BranchingEnabled).FirstOrDefault())) .FirstOrDefaultAsync(); - if (existing != 0) return existing; + private Task LoadAsync(int userId) => + _db.Users.AsNoTracking() + .Where(u => u.UserId == userId) + .Select(u => new UserRow( + u.UserId, u.Status, u.RoleId, + u.Role != null ? u.Role.Code : null, + u.Role != null ? (EntityStatus?)u.Role.Status : null, + u.Role != null && u.Role.IsAdmin, + _db.Employees.Where(e => e.UserId == u.UserId).Select(e => (int?)e.BranchId).FirstOrDefault(), + _db.BranchConfig.Where(c => c.ConfigId == BranchConfig.SingletonId).Select(c => (bool?)c.BranchingEnabled).FirstOrDefault())) + .FirstOrDefaultAsync(); + + private async Task ProvisionAsync(Guid authUserId, string? nic) + { var label = string.IsNullOrWhiteSpace(nic) ? authUserId.ToString() : nic.Trim(); var user = new User { @@ -69,16 +127,58 @@ public sealed class ShadowUserClaimsTransformation : IClaimsTransformation { _db.Users.Add(user); await _db.SaveChangesAsync(); - return user.UserId; } catch (DbUpdateException) { - // Lost a race (unique auth_user_id) — the row now exists; re-read it. + // Lost a race (unique auth_user_id) — the row now exists; re-read it below. _db.Entry(user).State = EntityState.Detached; - return await _db.Users.AsNoTracking() - .Where(u => u.AuthUserId == authUserId) - .Select(u => u.UserId) - .FirstAsync(); } + + return await LoadAsync(authUserId) ?? throw new InvalidOperationException("Could not provision the ERP user."); + } + + private async Task AssignMissingRoleAsync(UserRow row, string? legacyRoleCode) + { + var role = null as Role; + + // 1) Rollout safety net: the role the (old) AuthHex token still names — what this person could do until now. + if (_legacyRoleFallback && !string.IsNullOrWhiteSpace(legacyRoleCode)) + { + var code = legacyRoleCode.Trim().ToLower(); + role = await _db.Roles.AsNoTracking().FirstOrDefaultAsync(r => r.Code.ToLower() == code && r.Status == EntityStatus.Active); + } + + // 2) The configured default role (least privilege by design; never the admin role). + if (role is null) + { + role = await _db.AccessConfig.AsNoTracking() + .Where(c => c.ConfigId == AccessConfig.SingletonId && c.DefaultRole != null && c.DefaultRole.Status == EntityStatus.Active) + .Select(c => c.DefaultRole) + .FirstOrDefaultAsync(); + } + + if (role is null) return row; // no role and no default → no permissions + + try + { + // Only fill a role that is still empty: one an administrator assigned a moment ago is never overwritten. + var tracked = await _db.Users.FirstOrDefaultAsync(u => u.UserId == row.UserId); + if (tracked is not null && tracked.RoleId is null) + { + tracked.RoleId = role.RoleId; + await _db.SaveChangesAsync(); + } + else if (tracked?.RoleId is int existing && existing != role.RoleId) + { + return await LoadAsync(row.UserId) ?? row; // somebody assigned a role meanwhile: use theirs + } + } + catch (DbUpdateException) + { + // Not fatal: the request still carries the role for this call; the next request retries the save. + _db.ChangeTracker.Clear(); + } + + return row with { RoleId = role.RoleId, RoleCode = role.Code, RoleStatus = role.Status, RoleIsAdmin = role.IsAdmin }; } } diff --git a/Backend/ERPCore/Infra/Auth/ValidateCsrfAttribute.cs b/Backend/ERPCore/Infra/Auth/ValidateCsrfAttribute.cs index cf4a631..7456b5e 100644 --- a/Backend/ERPCore/Infra/Auth/ValidateCsrfAttribute.cs +++ b/Backend/ERPCore/Infra/Auth/ValidateCsrfAttribute.cs @@ -1,27 +1,34 @@ using ERPCore.System.Errors; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Filters; namespace ERPCore.Infra.Auth; /// /// Double-submit-cookie CSRF check for cookie-authenticated, state-changing -/// AuthController actions (docs/02-SECURITY.md §B.2). Bearer-token callers +/// actions (docs/02-SECURITY.md §B.2). Bearer-token callers /// (Swagger, service-to-service) are exempt — CSRF only threatens requests a /// browser sends automatically via cookies. Requires the X-XSRF-TOKEN /// header to match the non-httpOnly XSRF-TOKEN cookie AuthController /// issues alongside the session cookies. +/// +/// Every mutating cookie-authenticated endpoint is covered by (registered globally), +/// so this attribute is only needed on an action that must be checked even when the session cookie is absent +/// (AuthController's token-in-body endpoints). +/// /// public sealed class ValidateCsrfAttribute : Attribute, IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - var request = context.HttpContext.Request; + Enforce(context.HttpContext.Request); + await next(); + } - if (!string.IsNullOrEmpty(request.Headers.Authorization.ToString())) - { - await next(); - return; - } + /// Throws a 403 CSRF_TOKEN_MISMATCH unless the request is bearer-authenticated or carries a header matching the CSRF cookie. + public static void Enforce(HttpRequest request) + { + if (!string.IsNullOrEmpty(request.Headers.Authorization.ToString())) return; if (!request.Cookies.TryGetValue(JwtAuthExtensions.CsrfCookie, out var cookieToken) || string.IsNullOrEmpty(cookieToken)) throw new DomainException(ErrorCodes.CsrfTokenMismatch, "Missing CSRF cookie.", 403); @@ -29,6 +36,33 @@ public sealed class ValidateCsrfAttribute : Attribute, IAsyncActionFilter var headerToken = request.Headers[JwtAuthExtensions.CsrfHeader].ToString(); if (string.IsNullOrEmpty(headerToken) || !string.Equals(headerToken, cookieToken, StringComparison.Ordinal)) throw new DomainException(ErrorCodes.CsrfTokenMismatch, "CSRF token mismatch.", 403); + } +} + +/// +/// Registered globally: any state-changing request (POST/PUT/PATCH/DELETE) that is authenticated by the browser's +/// session cookie must pass the double-submit check. Not applied to safe methods, to anonymous endpoints (login, +/// registration — no session to ride on), to actions that already carry , or to +/// requests with no session cookie at all (nothing for a forged request to ride on; those fail authentication anyway). +/// +public sealed class CsrfProtectionFilter : IAsyncActionFilter +{ + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var http = context.HttpContext; + var request = http.Request; + + var unsafeMethod = HttpMethods.IsPost(request.Method) || HttpMethods.IsPut(request.Method) + || HttpMethods.IsPatch(request.Method) || HttpMethods.IsDelete(request.Method); + var endpoint = http.GetEndpoint(); + + if (unsafeMethod + && request.Cookies.ContainsKey(JwtAuthExtensions.AccessTokenCookie) + && endpoint?.Metadata.GetMetadata() is null + && endpoint?.Metadata.GetMetadata() is null) + { + ValidateCsrfAttribute.Enforce(request); + } await next(); } diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/AccessConfigConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/AccessConfigConfiguration.cs new file mode 100644 index 0000000..713f3b2 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/AccessConfigConfiguration.cs @@ -0,0 +1,31 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +/// The singleton access-configuration row; the check constraint makes "singleton" a database guarantee. +public sealed class AccessConfigConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("access_config", t => + t.HasCheckConstraint("ck_access_config_singleton", $"`ConfigId` = {AccessConfig.SingletonId}")); + + builder.HasKey(c => c.ConfigId); + builder.Property(c => c.ConfigId).ValueGeneratedNever(); + + // If the default role is deleted the setting clears itself rather than blocking the delete. + builder.HasOne(c => c.DefaultRole) + .WithMany() + .HasForeignKey(c => c.DefaultRoleId) + .OnDelete(DeleteBehavior.SetNull); + + builder.Property(c => c.RowVersion).IsRowVersion(); + + builder.HasOne(c => c.UpdatedByUser) + .WithMany() + .HasForeignKey(c => c.UpdatedBy) + .OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/ApprovalConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/ApprovalConfiguration.cs index a301c8d..cc3539b 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/ApprovalConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/ApprovalConfiguration.cs @@ -34,7 +34,7 @@ public sealed class ApprovalConfigConfiguration : IEntityTypeConfiguration c.StockAdjustmentLevel2ThresholdValue).HasPrecision(18, 4); builder.Property(c => c.PriceAdjustmentLevel2ThresholdPercent).HasPrecision(9, 4); - builder.Property(c => c.Level2RoleCodes).HasMaxLength(500); + builder.Property(c => c.Level2RoleIds).HasMaxLength(500); // Dual approval on by default for both documents (plan §8 recommended defaults). builder.HasData(new ApprovalConfig diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/NavItemConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/NavItemConfiguration.cs index b5ab141..679a6cc 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/NavItemConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/NavItemConfiguration.cs @@ -41,7 +41,11 @@ public sealed class NavItemConfiguration : IEntityTypeConfiguration new NavItem { NavItemId = 11, Code = "ledgers", Label = "Ledgers", Href = "/dashboard/ledgers", SortOrder = 11 }, new NavItem { NavItemId = 12, Code = "accounts", Label = "Accounts", Href = "/dashboard/accounts", SortOrder = 12 }, new NavItem { NavItemId = 13, Code = "sales", Label = "Sales", Href = "/dashboard/sales", SortOrder = 13 }, - new NavItem { NavItemId = 14, Code = "production", Label = "Production", Href = "/dashboard/production", SortOrder = 14 } + new NavItem { NavItemId = 14, Code = "production", Label = "Production", Href = "/dashboard/production", SortOrder = 14 }, + // Added with the permission-coverage pass (Phase B): these two sidebar sections had no row, so no role could be + // granted them and the sidebar had to bypass the check for everybody. + new NavItem { NavItemId = 15, Code = "hrm", Label = "Human Resources", Href = "/dashboard/hrm", SortOrder = 15 }, + new NavItem { NavItemId = 16, Code = "sales-returns", Label = "Sales Returns", Href = "/dashboard/sales/sales-returns", SortOrder = 16 } ); } } diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/OpeningBalanceConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/OpeningBalanceConfiguration.cs new file mode 100644 index 0000000..f73b2b6 --- /dev/null +++ b/Backend/ERPCore/Infra/Persistence/Configurations/OpeningBalanceConfiguration.cs @@ -0,0 +1,67 @@ +using ERPCore.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ERPCore.Infra.Persistence.Configurations; + +/// Posted opening balances. The unique index on Kind is the once-only guard. +public sealed class OpeningBalanceRunConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("opening_balance_runs"); + builder.HasKey(x => x.OpeningBalanceRunId); + + builder.Property(x => x.Kind).HasConversion().HasMaxLength(20).IsRequired(); + builder.HasIndex(x => x.Kind); + + // The once-only guard: only kinds that may be posted once set this, so a second attempt hits the unique index. + builder.Property(x => x.OnceKey).HasMaxLength(20); + builder.HasIndex(x => x.OnceKey).IsUnique(); + + builder.Property(x => x.CutoverDate).IsRequired(); + builder.Property(x => x.GlJournalNo).HasMaxLength(30); + builder.Property(x => x.TotalAmount).HasPrecision(18, 4); + builder.Property(x => x.Currency).IsRequired().HasMaxLength(3); + builder.Property(x => x.PostedAt).IsRequired(); + } +} + +public sealed class OpeningBalanceLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("opening_balance_lines"); + builder.HasKey(x => x.OpeningBalanceLineId); + + builder.Property(x => x.AccountCode).IsRequired().HasMaxLength(20); + builder.Property(x => x.AccountName).IsRequired().HasMaxLength(200); + builder.Property(x => x.Amount).HasPrecision(18, 4); + + builder.HasOne(x => x.Run) + .WithMany(r => r.Lines) + .HasForeignKey(x => x.OpeningBalanceRunId) + .OnDelete(DeleteBehavior.Cascade); + } +} + +public sealed class OpeningStockLineConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("opening_stock_lines"); + builder.HasKey(x => x.OpeningStockLineId); + + builder.Property(x => x.Qty).HasPrecision(18, 4); + builder.Property(x => x.UnitCost).HasPrecision(18, 4); + builder.Property(x => x.Value).HasPrecision(18, 4); + + builder.HasOne(x => x.Run) + .WithMany(r => r.StockLines) + .HasForeignKey(x => x.OpeningBalanceRunId) + .OnDelete(DeleteBehavior.Cascade); + builder.HasOne(x => x.Item).WithMany().HasForeignKey(x => x.ItemId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(x => x.Warehouse).WithMany().HasForeignKey(x => x.WarehouseId).OnDelete(DeleteBehavior.Restrict); + builder.HasIndex(x => new { x.ItemId, x.WarehouseId }); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/PermissionConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/PermissionConfiguration.cs index b7dacac..80e9490 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/PermissionConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/PermissionConfiguration.cs @@ -90,7 +90,20 @@ public sealed class PermissionConfiguration : IEntityTypeConfiguration +/// Configures the singleton procurement-configuration row. The check constraint is what makes +/// "singleton" a database guarantee rather than a convention (mirrors GrnConfig). +/// +public sealed class ProcurementConfigConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("procurement_config", t => + t.HasCheckConstraint("ck_procurement_config_singleton", $"`ConfigId` = {ProcurementConfig.SingletonId}")); + + builder.HasKey(c => c.ConfigId); + + // The id is fixed, never generated — there is exactly one row, seeded by DataSeeder. + builder.Property(c => c.ConfigId).ValueGeneratedNever(); + + builder.Property(c => c.VendorContactMode) + .HasConversion() + .HasMaxLength(10) + .IsRequired() + .HasDefaultValue(VendorContactMode.None); + builder.Property(c => c.VendorAddressEnabled).IsRequired().HasDefaultValue(false); + builder.Property(c => c.PoTaxEnabled).IsRequired().HasDefaultValue(false); + // Default on, and the sentinel is the CLR initializer (true), so an explicit false is still written on insert. + builder.Property(c => c.VendorPaymentTermsEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.VendorTaxRegEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + + builder.Property(c => c.RowVersion).IsRowVersion(); + + builder.HasOne(c => c.UpdatedByUser) + .WithMany() + .HasForeignKey(c => c.UpdatedBy) + .OnDelete(DeleteBehavior.Restrict); + } +} diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseOrderConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseOrderConfiguration.cs index a03acfc..1a90e59 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseOrderConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/PurchaseOrderConfiguration.cs @@ -52,6 +52,7 @@ public sealed class PoLineConfiguration : IEntityTypeConfiguration builder.Property(l => l.Qty).HasPrecision(18, 4); builder.Property(l => l.UnitPrice).HasPrecision(18, 4); builder.Property(l => l.Tax).HasPrecision(9, 4); + builder.Property(l => l.TaxAmount).HasPrecision(18, 4).HasDefaultValue(0m); builder.Property(l => l.QtyReceived).HasPrecision(18, 4); builder.HasOne(l => l.PurchaseOrder) diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/RoleConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/RoleConfiguration.cs index 6d4ce86..e9ac6f0 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/RoleConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/RoleConfiguration.cs @@ -12,7 +12,7 @@ public sealed class RoleConfiguration : IEntityTypeConfiguration builder.ToTable("roles"); builder.HasKey(r => r.RoleId); - builder.Property(r => r.AuthRoleId).HasColumnName("auth_role_id").IsRequired(); + builder.Property(r => r.AuthRoleId).HasColumnName("auth_role_id"); builder.HasIndex(r => r.AuthRoleId).IsUnique(); builder.Property(r => r.Code).IsRequired().HasMaxLength(50); @@ -20,6 +20,7 @@ public sealed class RoleConfiguration : IEntityTypeConfiguration builder.Property(r => r.Name).IsRequired().HasMaxLength(200); builder.Property(r => r.IsSystemRole).IsRequired().HasDefaultValue(false); + builder.Property(r => r.IsAdmin).IsRequired().HasDefaultValue(false); builder.Property(r => r.Status) .HasConversion().HasMaxLength(20).IsRequired() diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/SalesConfigConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/SalesConfigConfiguration.cs index 0aa6ebf..dc0a14f 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/SalesConfigConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/SalesConfigConfiguration.cs @@ -27,6 +27,13 @@ public sealed class SalesConfigConfiguration : IEntityTypeConfiguration c.AllowDiscounts).IsRequired().HasDefaultValue(true).HasSentinel(true); builder.Property(c => c.AllowOverStockSale).IsRequired().HasDefaultValue(false); builder.Property(c => c.AllowWarehouseSelection).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerPhoneEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerEmailEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerAddressEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerCityEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerCountryEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerCreditLimitEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); + builder.Property(c => c.CustomerCreditDaysEnabled).IsRequired().HasDefaultValue(true).HasSentinel(true); builder.HasOne(c => c.DefaultWarehouse) .WithMany() diff --git a/Backend/ERPCore/Infra/Persistence/Configurations/SubNavItemConfiguration.cs b/Backend/ERPCore/Infra/Persistence/Configurations/SubNavItemConfiguration.cs index a345cba..66113ce 100644 --- a/Backend/ERPCore/Infra/Persistence/Configurations/SubNavItemConfiguration.cs +++ b/Backend/ERPCore/Infra/Persistence/Configurations/SubNavItemConfiguration.cs @@ -55,6 +55,7 @@ public sealed class SubNavItemConfiguration : IEntityTypeConfiguration builder.Property(v => v.Name).IsRequired().HasMaxLength(200); builder.Property(v => v.Terms).HasMaxLength(50); builder.Property(v => v.TaxReg).HasMaxLength(50); + builder.Property(v => v.Mobile).HasMaxLength(20); + builder.Property(v => v.Email).HasMaxLength(200); + builder.Property(v => v.Address).HasMaxLength(500); builder.Property(v => v.Currency).IsRequired().HasMaxLength(3).HasDefaultValue("LKR"); builder.Property(v => v.Status) diff --git a/Backend/ERPCore/Infra/Persistence/DataSeeder.cs b/Backend/ERPCore/Infra/Persistence/DataSeeder.cs index 4e3e681..206b622 100644 --- a/Backend/ERPCore/Infra/Persistence/DataSeeder.cs +++ b/Backend/ERPCore/Infra/Persistence/DataSeeder.cs @@ -61,12 +61,67 @@ public static class DataSeeder dirty |= await SeedBranchConfigAsync(db, ct); dirty |= await SeedSalesConfigAsync(db, ct); dirty |= await SeedGrnConfigAsync(db, ct); + dirty |= await SeedProcurementConfigAsync(db, ct); dirty |= await SeedGlAccountConfigAsync(db, configuration, ct); dirty |= await SeedNotificationConfigAsync(db, ct); if (dirty) await db.SaveChangesAsync(ct); + + // After the rest: needs the permission rows (HasData) and saves in steps, because the access config + // points at roles that must have real generated ids first. + await SeedAccessAsync(db, ct); } + /// + /// The roles every deployment must have, and the access configuration (idempotent): + /// + /// an Administrator role flagged — an existing role whose code is "admin" is + /// flagged rather than duplicated; a fresh database gets one with every permission, so it is never lock-out empty; + /// a No access role with no permissions at all — the default for new sign-ins, so a mistake can never grant access; + /// the singleton access config, pointing its default role at "No access" the first time only (an admin's later choice is never overwritten). + /// + /// + private static async Task SeedAccessAsync(ErpDbContext db, CancellationToken ct) + { + var now = DateTime.UtcNow; + + var admin = await db.Roles.FirstOrDefaultAsync(r => r.IsAdmin, ct) + ?? await db.Roles.FirstOrDefaultAsync(r => r.Code.ToLower() == "admin", ct); + if (admin is null) + { + admin = new Role { Code = "Admin", Name = "Administrator", IsSystemRole = true, IsAdmin = true, Status = EntityStatus.Active, CreatedAt = now }; + db.Roles.Add(admin); + await db.SaveChangesAsync(ct); + + foreach (var permissionId in await db.Permissions.Select(p => p.PermissionId).ToListAsync(ct)) + db.RolePermissions.Add(new RolePermission { RoleId = admin.RoleId, PermissionId = permissionId, Actions = RolePermissionAction.All }); + await db.SaveChangesAsync(ct); + } + else if (!admin.IsAdmin) + { + admin.IsAdmin = true; + admin.UpdatedAt = now; + await db.SaveChangesAsync(ct); + } + + var noAccess = await db.Roles.FirstOrDefaultAsync(r => r.Code == DefaultNoAccessRoleCode, ct); + if (noAccess is null) + { + noAccess = new Role { Code = DefaultNoAccessRoleCode, Name = "No access", IsSystemRole = true, Status = EntityStatus.Active, CreatedAt = now }; + db.Roles.Add(noAccess); + await db.SaveChangesAsync(ct); + } + + if (!await db.AccessConfig.AnyAsync(c => c.ConfigId == AccessConfig.SingletonId, ct)) + { + db.AccessConfig.Add(new AccessConfig { ConfigId = AccessConfig.SingletonId, DefaultRoleId = noAccess.RoleId }); + await db.SaveChangesAsync(ct); + } + } + + /// Code of the seeded least-privilege role new sign-ins start with. + public const string DefaultNoAccessRoleCode = "NoAccess"; + /// /// Creates the deployment's default branch () if absent. /// @@ -201,6 +256,16 @@ public static class DataSeeder return true; } + /// Seeds the procurement configuration singleton — vendor contact/address and PO tax all off, + /// until an admin opts in from Settings → Configuration → Procurement Configuration. + private static async Task SeedProcurementConfigAsync(ErpDbContext db, CancellationToken ct) + { + if (await db.ProcurementConfig.AnyAsync(c => c.ConfigId == ProcurementConfig.SingletonId, ct)) return false; + + db.ProcurementConfig.Add(new ProcurementConfig { ConfigId = ProcurementConfig.SingletonId }); + return true; + } + /// /// Seeds the GL account mapping singleton, carrying over the legacy Gl*Code appsettings /// values once so an existing deployment keeps the mapping it has been running with. diff --git a/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs b/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs index 7b6b31e..b9bba97 100644 --- a/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs +++ b/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs @@ -48,6 +48,9 @@ public class ErpDbContext : DbContext public DbSet BranchConfig => Set(); public DbSet SalesConfig => Set(); public DbSet GrnConfig => Set(); + public DbSet ProcurementConfig => Set(); + /// Singleton row: the default role for people who have none. + public DbSet AccessConfig => Set(); /// Singleton row mapping each accounting role to a GL account code. public DbSet GlAccountConfig => Set(); /// Singleton row for notification delivery preferences. @@ -98,6 +101,9 @@ public class ErpDbContext : DbContext // --- Accounts: bank deposits of till cash (docs/BANK_DEPOSIT_MODULE_PLAN.md) --- public DbSet BankDeposits => Set(); + public DbSet OpeningBalanceRuns => Set(); + public DbSet OpeningBalanceLines => Set(); + public DbSet OpeningStockLines => Set(); // --- Dual approval: stock & price adjustments (docs/DUAL_APPROVAL_PLAN.md) --- public DbSet DocumentApprovals => Set(); diff --git a/Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.Designer.cs b/Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.Designer.cs new file mode 100644 index 0000000..dab9ea6 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.Designer.cs @@ -0,0 +1,10917 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017193713_AddProcurementConfigAndVendorContact")] + partial class AddProcurementConfigAndVendorContact + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleCodes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.cs b/Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.cs new file mode 100644 index 0000000..e1e2d57 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017193713_AddProcurementConfigAndVendorContact.cs @@ -0,0 +1,88 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddProcurementConfigAndVendorContact : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Address", + table: "vendors", + type: "varchar(500)", + maxLength: 500, + nullable: true, + collation: "utf8mb4_unicode_ci"); + + migrationBuilder.AddColumn( + name: "Email", + table: "vendors", + type: "varchar(200)", + maxLength: 200, + nullable: true, + collation: "utf8mb4_unicode_ci"); + + migrationBuilder.AddColumn( + name: "Mobile", + table: "vendors", + type: "varchar(20)", + maxLength: 20, + nullable: true, + collation: "utf8mb4_unicode_ci"); + + migrationBuilder.CreateTable( + name: "procurement_config", + columns: table => new + { + ConfigId = table.Column(type: "int", nullable: false), + VendorContactMode = table.Column(type: "varchar(10)", maxLength: 10, nullable: false, defaultValue: "None", collation: "utf8mb4_unicode_ci"), + VendorAddressEnabled = table.Column(type: "tinyint(1)", nullable: false, defaultValue: false), + PoTaxEnabled = table.Column(type: "tinyint(1)", nullable: false, defaultValue: false), + UpdatedAt = table.Column(type: "datetime(6)", nullable: true), + UpdatedBy = table.Column(type: "int", nullable: true), + RowVersion = table.Column(type: "int unsigned", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_procurement_config", x => x.ConfigId); + table.CheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + table.ForeignKey( + name: "FK_procurement_config_users_UpdatedBy", + column: x => x.UpdatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("Relational:Collation", "utf8mb4_unicode_ci"); + + migrationBuilder.CreateIndex( + name: "IX_procurement_config_UpdatedBy", + table: "procurement_config", + column: "UpdatedBy"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "procurement_config"); + + migrationBuilder.DropColumn( + name: "Address", + table: "vendors"); + + migrationBuilder.DropColumn( + name: "Email", + table: "vendors"); + + migrationBuilder.DropColumn( + name: "Mobile", + table: "vendors"); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.Designer.cs b/Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.Designer.cs new file mode 100644 index 0000000..2a8b962 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.Designer.cs @@ -0,0 +1,10923 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017194128_AddPoLineTaxAmount")] + partial class AddPoLineTaxAmount + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleCodes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.cs b/Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.cs new file mode 100644 index 0000000..5a91d60 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017194128_AddPoLineTaxAmount.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddPoLineTaxAmount : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TaxAmount", + table: "po_lines", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + defaultValue: 0m); + + // Totals now sum the stored amount instead of recomputing qty x price x rate, so give any + // line that already carries a rate its amount (same rounding: half away from zero, 2 dp). + migrationBuilder.Sql("UPDATE po_lines SET TaxAmount = ROUND(Qty * UnitPrice * Tax, 2) WHERE Tax <> 0;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TaxAmount", + table: "po_lines"); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.Designer.cs b/Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.Designer.cs new file mode 100644 index 0000000..86f3b8c --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.Designer.cs @@ -0,0 +1,10939 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017194653_AddPoApprovalsNav")] + partial class AddPoApprovalsNav + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleCodes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.cs b/Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.cs new file mode 100644 index 0000000..3359929 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017194653_AddPoApprovalsNav.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddPoApprovalsNav : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "sub_nav_items", + columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" }, + values: new object[] { 50, "procurement.po-approvals", "/dashboard/procurement/purchase-orders/approvals", null, "PO Approval Queue", 4, 6 }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[] { 60, "NAV:procurement.po-approvals", null, 50 }); + + // Grant the new queue entry. Admin gets everything (the server only lets an Admin approve + // anyway); any other role that can already open Purchase Orders (permission 30) gets View. + // Idempotent, like the grants in AddDualApproval. + migrationBuilder.Sql(""" + INSERT IGNORE INTO `role_permissions` (`RoleId`, `PermissionId`, `Actions`) + SELECT r.`RoleId`, 60, CASE WHEN LOWER(r.`Code`) = 'admin' THEN 31 ELSE 1 END + FROM `roles` r + WHERE LOWER(r.`Code`) = 'admin' + OR EXISTS (SELECT 1 FROM `role_permissions` rp + WHERE rp.`RoleId` = r.`RoleId` AND rp.`PermissionId` = 30); + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM `role_permissions` WHERE `PermissionId` = 60;"); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 60); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 50); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.Designer.cs b/Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.Designer.cs new file mode 100644 index 0000000..031e5b0 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.Designer.cs @@ -0,0 +1,11121 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017200125_AddOpeningBalances")] + partial class AddOpeningBalances + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleCodes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.Property("OpeningBalanceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceLineId")); + + b.Property("AccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.HasKey("OpeningBalanceLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.ToTable("opening_balance_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Property("OpeningBalanceRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceRunId")); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("CutoverDate") + .HasColumnType("date"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("OnceKey") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedBy") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("OpeningBalanceRunId"); + + b.HasIndex("Kind"); + + b.HasIndex("OnceKey") + .IsUnique(); + + b.ToTable("opening_balance_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.Property("OpeningStockLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningStockLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("OpeningStockLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId"); + + b.ToTable("opening_stock_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }, + new + { + PermissionId = 61, + Code = "NAV:accounts.opening-balances", + SubNavItemId = 51 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 51, + Code = "accounts.opening-balances", + Href = "/dashboard/accounts/opening-balances", + Label = "Opening Balances", + NavItemId = 12, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("Lines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("StockLines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Run"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Navigation("Lines"); + + b.Navigation("StockLines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.cs b/Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.cs new file mode 100644 index 0000000..6537798 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017200125_AddOpeningBalances.cs @@ -0,0 +1,170 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddOpeningBalances : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "opening_balance_runs", + columns: table => new + { + OpeningBalanceRunId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Kind = table.Column(type: "varchar(20)", maxLength: 20, nullable: false, collation: "utf8mb4_unicode_ci"), + OnceKey = table.Column(type: "varchar(20)", maxLength: 20, nullable: true, collation: "utf8mb4_unicode_ci"), + CutoverDate = table.Column(type: "date", nullable: false), + GlJournalNo = table.Column(type: "varchar(30)", maxLength: 30, nullable: true, collation: "utf8mb4_unicode_ci"), + TotalAmount = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false), + Currency = table.Column(type: "varchar(3)", maxLength: 3, nullable: false, collation: "utf8mb4_unicode_ci"), + PostedBy = table.Column(type: "int", nullable: false), + PostedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_opening_balance_runs", x => x.OpeningBalanceRunId); + }) + .Annotation("Relational:Collation", "utf8mb4_unicode_ci"); + + migrationBuilder.CreateTable( + name: "opening_balance_lines", + columns: table => new + { + OpeningBalanceLineId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + OpeningBalanceRunId = table.Column(type: "int", nullable: false), + AccountCode = table.Column(type: "varchar(20)", maxLength: 20, nullable: false, collation: "utf8mb4_unicode_ci"), + AccountName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, collation: "utf8mb4_unicode_ci"), + Amount = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_opening_balance_lines", x => x.OpeningBalanceLineId); + table.ForeignKey( + name: "FK_opening_balance_lines_opening_balance_runs_OpeningBalanceRun~", + column: x => x.OpeningBalanceRunId, + principalTable: "opening_balance_runs", + principalColumn: "OpeningBalanceRunId", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("Relational:Collation", "utf8mb4_unicode_ci"); + + migrationBuilder.CreateTable( + name: "opening_stock_lines", + columns: table => new + { + OpeningStockLineId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + OpeningBalanceRunId = table.Column(type: "int", nullable: false), + ItemId = table.Column(type: "int", nullable: false), + WarehouseId = table.Column(type: "int", nullable: false), + Qty = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false), + Value = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_opening_stock_lines", x => x.OpeningStockLineId); + table.ForeignKey( + name: "FK_opening_stock_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_opening_stock_lines_opening_balance_runs_OpeningBalanceRunId", + column: x => x.OpeningBalanceRunId, + principalTable: "opening_balance_runs", + principalColumn: "OpeningBalanceRunId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_opening_stock_lines_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("Relational:Collation", "utf8mb4_unicode_ci"); + + migrationBuilder.InsertData( + table: "sub_nav_items", + columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" }, + values: new object[] { 51, "accounts.opening-balances", "/dashboard/accounts/opening-balances", null, "Opening Balances", 12, 7 }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[] { 61, "NAV:accounts.opening-balances", null, 51 }); + + // Admin only: posting opening balances is a one-time ledger entry. Other roles can be granted it + // under Settings → Roles. Idempotent, like the other nav grants. + migrationBuilder.Sql(""" + INSERT IGNORE INTO `role_permissions` (`RoleId`, `PermissionId`, `Actions`) + SELECT r.`RoleId`, 61, 31 FROM `roles` r WHERE LOWER(r.`Code`) = 'admin'; + """); + + migrationBuilder.CreateIndex( + name: "IX_opening_balance_lines_OpeningBalanceRunId", + table: "opening_balance_lines", + column: "OpeningBalanceRunId"); + + migrationBuilder.CreateIndex( + name: "IX_opening_balance_runs_Kind", + table: "opening_balance_runs", + column: "Kind"); + + migrationBuilder.CreateIndex( + name: "IX_opening_balance_runs_OnceKey", + table: "opening_balance_runs", + column: "OnceKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_opening_stock_lines_ItemId_WarehouseId", + table: "opening_stock_lines", + columns: new[] { "ItemId", "WarehouseId" }); + + migrationBuilder.CreateIndex( + name: "IX_opening_stock_lines_OpeningBalanceRunId", + table: "opening_stock_lines", + column: "OpeningBalanceRunId"); + + migrationBuilder.CreateIndex( + name: "IX_opening_stock_lines_WarehouseId", + table: "opening_stock_lines", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM `role_permissions` WHERE `PermissionId` = 61;"); + + migrationBuilder.DropTable( + name: "opening_balance_lines"); + + migrationBuilder.DropTable( + name: "opening_stock_lines"); + + migrationBuilder.DropTable( + name: "opening_balance_runs"); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 61); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 51); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.Designer.cs b/Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.Designer.cs new file mode 100644 index 0000000..a410e11 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.Designer.cs @@ -0,0 +1,11131 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017201933_AddVendorFieldSwitches")] + partial class AddVendorFieldSwitches + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleCodes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.Property("OpeningBalanceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceLineId")); + + b.Property("AccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.HasKey("OpeningBalanceLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.ToTable("opening_balance_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Property("OpeningBalanceRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceRunId")); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("CutoverDate") + .HasColumnType("date"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("OnceKey") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedBy") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("OpeningBalanceRunId"); + + b.HasIndex("Kind"); + + b.HasIndex("OnceKey") + .IsUnique(); + + b.ToTable("opening_balance_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.Property("OpeningStockLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningStockLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("OpeningStockLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId"); + + b.ToTable("opening_stock_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }, + new + { + PermissionId = 61, + Code = "NAV:accounts.opening-balances", + SubNavItemId = 51 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.Property("VendorPaymentTermsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("VendorTaxRegEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 51, + Code = "accounts.opening-balances", + Href = "/dashboard/accounts/opening-balances", + Label = "Opening Balances", + NavItemId = 12, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("Lines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("StockLines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Run"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Navigation("Lines"); + + b.Navigation("StockLines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.cs b/Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.cs new file mode 100644 index 0000000..9b4f94a --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017201933_AddVendorFieldSwitches.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddVendorFieldSwitches : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VendorPaymentTermsEnabled", + table: "procurement_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "VendorTaxRegEnabled", + table: "procurement_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VendorPaymentTermsEnabled", + table: "procurement_config"); + + migrationBuilder.DropColumn( + name: "VendorTaxRegEnabled", + table: "procurement_config"); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.Designer.cs b/Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.Designer.cs new file mode 100644 index 0000000..6996a99 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.Designer.cs @@ -0,0 +1,11183 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017214857_RolesOwnedByErp")] + partial class RolesOwnedByErp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("DefaultRoleId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultRoleId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("access_config", null, t => + { + t.HasCheckConstraint("ck_access_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleIds") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.Property("OpeningBalanceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceLineId")); + + b.Property("AccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.HasKey("OpeningBalanceLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.ToTable("opening_balance_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Property("OpeningBalanceRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceRunId")); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("CutoverDate") + .HasColumnType("date"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("OnceKey") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedBy") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("OpeningBalanceRunId"); + + b.HasIndex("Kind"); + + b.HasIndex("OnceKey") + .IsUnique(); + + b.ToTable("opening_balance_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.Property("OpeningStockLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningStockLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("OpeningStockLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId"); + + b.ToTable("opening_stock_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }, + new + { + PermissionId = 61, + Code = "NAV:accounts.opening-balances", + SubNavItemId = 51 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.Property("VendorPaymentTermsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("VendorTaxRegEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 51, + Code = "accounts.opening-balances", + Href = "/dashboard/accounts/opening-balances", + Label = "Opening Balances", + NavItemId = 12, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "DefaultRole") + .WithMany() + .HasForeignKey("DefaultRoleId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultRole"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("Lines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("StockLines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Run"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Navigation("Lines"); + + b.Navigation("StockLines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.cs b/Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.cs new file mode 100644 index 0000000..4b04871 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017214857_RolesOwnedByErp.cs @@ -0,0 +1,129 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class RolesOwnedByErp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Level2RoleCodes", + table: "approval_config", + newName: "Level2RoleIds"); + + migrationBuilder.AlterColumn( + name: "auth_role_id", + table: "roles", + type: "char(36)", + nullable: true, + collation: "ascii_general_ci", + oldClrType: typeof(Guid), + oldType: "char(36)") + .OldAnnotation("Relational:Collation", "ascii_general_ci"); + + migrationBuilder.AddColumn( + name: "IsAdmin", + table: "roles", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "access_config", + columns: table => new + { + ConfigId = table.Column(type: "int", nullable: false), + DefaultRoleId = table.Column(type: "int", nullable: true), + UpdatedAt = table.Column(type: "datetime(6)", nullable: true), + UpdatedBy = table.Column(type: "int", nullable: true), + RowVersion = table.Column(type: "int unsigned", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_access_config", x => x.ConfigId); + table.CheckConstraint("ck_access_config_singleton", "`ConfigId` = 1"); + table.ForeignKey( + name: "FK_access_config_roles_DefaultRoleId", + column: x => x.DefaultRoleId, + principalTable: "roles", + principalColumn: "RoleId", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_access_config_users_UpdatedBy", + column: x => x.UpdatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("Relational:Collation", "utf8mb4_unicode_ci"); + + // ---- Data: the existing administrator role becomes THE administrator by flag, not by name. + migrationBuilder.Sql("UPDATE `roles` SET `IsAdmin` = 1 WHERE LOWER(`Code`) = 'admin';"); + + // ---- Data: "who may give the second approval" was a list of role CODES; it is now a list of role IDS + // (a rename can no longer break it). The renamed column still holds the codes — convert them in place. + // A code that matches no role simply drops out. + migrationBuilder.Sql(""" + UPDATE `approval_config` c + SET c.`Level2RoleIds` = ( + SELECT GROUP_CONCAT(r.`RoleId` ORDER BY r.`RoleId` SEPARATOR ',') + FROM `roles` r + WHERE FIND_IN_SET(LOWER(r.`Code`), LOWER(REPLACE(c.`Level2RoleIds`, ' ', ''))) > 0) + WHERE c.`Level2RoleIds` IS NOT NULL AND c.`Level2RoleIds` <> ''; + """); + + migrationBuilder.CreateIndex( + name: "IX_access_config_DefaultRoleId", + table: "access_config", + column: "DefaultRoleId"); + + migrationBuilder.CreateIndex( + name: "IX_access_config_UpdatedBy", + table: "access_config", + column: "UpdatedBy"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // Turn the role ids back into codes before the column goes back to its old meaning. + migrationBuilder.Sql(""" + UPDATE `approval_config` c + SET c.`Level2RoleIds` = ( + SELECT GROUP_CONCAT(r.`Code` SEPARATOR ',') + FROM `roles` r + WHERE FIND_IN_SET(r.`RoleId`, REPLACE(c.`Level2RoleIds`, ' ', '')) > 0) + WHERE c.`Level2RoleIds` IS NOT NULL AND c.`Level2RoleIds` <> ''; + """); + + migrationBuilder.DropTable( + name: "access_config"); + + migrationBuilder.DropColumn( + name: "IsAdmin", + table: "roles"); + + migrationBuilder.RenameColumn( + name: "Level2RoleIds", + table: "approval_config", + newName: "Level2RoleCodes"); + + migrationBuilder.AlterColumn( + name: "auth_role_id", + table: "roles", + type: "char(36)", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + collation: "ascii_general_ci", + oldClrType: typeof(Guid), + oldType: "char(36)", + oldNullable: true) + .OldAnnotation("Relational:Collation", "ascii_general_ci"); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.Designer.cs b/Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.Designer.cs new file mode 100644 index 0000000..1a54fb1 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.Designer.cs @@ -0,0 +1,11331 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261017235430_PermissionCoverage")] + partial class PermissionCoverage + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("DefaultRoleId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultRoleId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("access_config", null, t => + { + t.HasCheckConstraint("ck_access_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleIds") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }, + new + { + NavItemId = 15, + Code = "hrm", + Href = "/dashboard/hrm", + Label = "Human Resources", + SortOrder = 15, + Status = "Active" + }, + new + { + NavItemId = 16, + Code = "sales-returns", + Href = "/dashboard/sales/sales-returns", + Label = "Sales Returns", + SortOrder = 16, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.Property("OpeningBalanceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceLineId")); + + b.Property("AccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.HasKey("OpeningBalanceLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.ToTable("opening_balance_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Property("OpeningBalanceRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceRunId")); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("CutoverDate") + .HasColumnType("date"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("OnceKey") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedBy") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("OpeningBalanceRunId"); + + b.HasIndex("Kind"); + + b.HasIndex("OnceKey") + .IsUnique(); + + b.ToTable("opening_balance_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.Property("OpeningStockLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningStockLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("OpeningStockLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId"); + + b.ToTable("opening_stock_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }, + new + { + PermissionId = 61, + Code = "NAV:accounts.opening-balances", + SubNavItemId = 51 + }, + new + { + PermissionId = 62, + Code = "NAV:hrm", + NavItemId = 15 + }, + new + { + PermissionId = 63, + Code = "NAV:sales-returns", + NavItemId = 16 + }, + new + { + PermissionId = 64, + Code = "NAV:hrm.employees", + SubNavItemId = 52 + }, + new + { + PermissionId = 65, + Code = "NAV:hrm.attendance", + SubNavItemId = 53 + }, + new + { + PermissionId = 66, + Code = "NAV:hrm.leave", + SubNavItemId = 54 + }, + new + { + PermissionId = 67, + Code = "NAV:hrm.payroll", + SubNavItemId = 55 + }, + new + { + PermissionId = 68, + Code = "NAV:hrm.reports", + SubNavItemId = 56 + }, + new + { + PermissionId = 69, + Code = "NAV:hrm.settings", + SubNavItemId = 57 + }, + new + { + PermissionId = 70, + Code = "NAV:settings.audit-logs", + SubNavItemId = 58 + }, + new + { + PermissionId = 71, + Code = "NAV:settings.notification-config", + SubNavItemId = 24 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.Property("VendorPaymentTermsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("VendorTaxRegEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 51, + Code = "accounts.opening-balances", + Href = "/dashboard/accounts/opening-balances", + Label = "Opening Balances", + NavItemId = 12, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 52, + Code = "hrm.employees", + Href = "/dashboard/hrm/employees", + Label = "Employees", + NavItemId = 15, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 53, + Code = "hrm.attendance", + Href = "/dashboard/hrm/attendance", + Label = "Attendance", + NavItemId = 15, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 54, + Code = "hrm.leave", + Href = "/dashboard/hrm/leave", + Label = "Leave", + NavItemId = 15, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 55, + Code = "hrm.payroll", + Href = "/dashboard/hrm/payroll", + Label = "Payroll", + NavItemId = 15, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 56, + Code = "hrm.reports", + Href = "/dashboard/hrm/reports", + Label = "Reports", + NavItemId = 15, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 57, + Code = "hrm.settings", + Href = "/dashboard/hrm/settings", + Label = "HR Settings", + NavItemId = 15, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 58, + Code = "settings.audit-logs", + Href = "/dashboard/settings/audit-logs", + Label = "Audit Log", + NavItemId = 9, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "DefaultRole") + .WithMany() + .HasForeignKey("DefaultRoleId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultRole"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("Lines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("StockLines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Run"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Navigation("Lines"); + + b.Navigation("StockLines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.cs b/Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.cs new file mode 100644 index 0000000..c5afc22 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261017235430_PermissionCoverage.cs @@ -0,0 +1,195 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace ERPCore.Migrations +{ + /// + public partial class PermissionCoverage : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "nav_items", + columns: new[] { "NavItemId", "Code", "Href", "Icon", "Label", "SortOrder" }, + values: new object[,] + { + { 15, "hrm", "/dashboard/hrm", null, "Human Resources", 15 }, + { 16, "sales-returns", "/dashboard/sales/sales-returns", null, "Sales Returns", 16 } + }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[] { 71, "NAV:settings.notification-config", null, 24 }); + + migrationBuilder.InsertData( + table: "sub_nav_items", + columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" }, + values: new object[] { 58, "settings.audit-logs", "/dashboard/settings/audit-logs", null, "Audit Log", 9, 5 }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[,] + { + { 62, "NAV:hrm", 15, null }, + { 63, "NAV:sales-returns", 16, null }, + { 70, "NAV:settings.audit-logs", null, 58 } + }); + + migrationBuilder.InsertData( + table: "sub_nav_items", + columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" }, + values: new object[,] + { + { 52, "hrm.employees", "/dashboard/hrm/employees", null, "Employees", 15, 1 }, + { 53, "hrm.attendance", "/dashboard/hrm/attendance", null, "Attendance", 15, 2 }, + { 54, "hrm.leave", "/dashboard/hrm/leave", null, "Leave", 15, 3 }, + { 55, "hrm.payroll", "/dashboard/hrm/payroll", null, "Payroll", 15, 4 }, + { 56, "hrm.reports", "/dashboard/hrm/reports", null, "Reports", 15, 5 }, + { 57, "hrm.settings", "/dashboard/hrm/settings", null, "HR Settings", 15, 6 } + }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[,] + { + { 64, "NAV:hrm.employees", null, 52 }, + { 65, "NAV:hrm.attendance", null, 53 }, + { 66, "NAV:hrm.leave", null, 54 }, + { 67, "NAV:hrm.payroll", null, 55 }, + { 68, "NAV:hrm.reports", null, 56 }, + { 69, "NAV:hrm.settings", null, 57 } + }); + + // Grants. Idempotent (INSERT IGNORE on the (RoleId, PermissionId) key), and keyed on the role's IsAdmin flag, + // never on its code or name. Everything below is least-privilege: nobody who is not an administrator gets + // Human Resources, the audit log or notification config until an administrator grants it under Settings → Roles. + // (HRM used to be open to every signed-in user; this closes it.) + migrationBuilder.Sql(""" + INSERT IGNORE INTO `role_permissions` (`RoleId`, `PermissionId`, `Actions`) + SELECT r.`RoleId`, p.`PermissionId`, 31 + FROM `roles` r + JOIN `permissions` p ON p.`PermissionId` BETWEEN 62 AND 71 + WHERE r.`IsAdmin` = 1; + """); + + // Sales Returns used to ride on the Sales permission; it is its own sidebar entry now, so whoever could reach + // Sales (the module or any of its pages) keeps reaching Returns, with the same actions they hold on Sales. + migrationBuilder.Sql(""" + INSERT IGNORE INTO `role_permissions` (`RoleId`, `PermissionId`, `Actions`) + SELECT rp.`RoleId`, 63, BIT_OR(rp.`Actions`) + FROM `role_permissions` rp + JOIN `permissions` p ON p.`PermissionId` = rp.`PermissionId` + WHERE p.`Code` = 'NAV:sales' OR p.`Code` LIKE 'NAV:sales.%' + GROUP BY rp.`RoleId`; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM `role_permissions` WHERE `PermissionId` BETWEEN 62 AND 71;"); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 62); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 63); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 64); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 65); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 66); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 67); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 68); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 69); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 70); + + migrationBuilder.DeleteData( + table: "permissions", + keyColumn: "PermissionId", + keyValue: 71); + + migrationBuilder.DeleteData( + table: "nav_items", + keyColumn: "NavItemId", + keyValue: 16); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 52); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 53); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 54); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 55); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 56); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 57); + + migrationBuilder.DeleteData( + table: "sub_nav_items", + keyColumn: "SubNavItemId", + keyValue: 58); + + migrationBuilder.DeleteData( + table: "nav_items", + keyColumn: "NavItemId", + keyValue: 15); + } + } +} diff --git a/Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.Designer.cs b/Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.Designer.cs new file mode 100644 index 0000000..dd7e393 --- /dev/null +++ b/Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.Designer.cs @@ -0,0 +1,11366 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20261018070404_AddCustomerFieldSwitches")] + partial class AddCustomerFieldSwitches + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("utf8mb4_unicode_ci") + .HasAnnotation("ProductVersion", "9.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("DefaultRoleId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultRoleId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("access_config", null, t => + { + t.HasCheckConstraint("ck_access_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => + { + b.Property("ApprovalConfigId") + .HasColumnType("int"); + + b.Property("Level2RoleIds") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PriceAdjustmentLevel2ThresholdPercent") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("PriceAdjustmentLevels") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StockAdjustmentLevel2ThresholdValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StockAdjustmentLevels") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ApprovalConfigId"); + + b.ToTable("approval_config", null, t => + { + t.HasCheckConstraint("ck_approval_config_singleton", "`ApprovalConfigId` = 1"); + }); + + b.HasData( + new + { + ApprovalConfigId = 1, + PriceAdjustmentLevel2ThresholdPercent = 0m, + PriceAdjustmentLevels = 2, + RowVersion = 1u, + StockAdjustmentLevel2ThresholdValue = 0m, + StockAdjustmentLevels = 2 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("int"); + + b.Property("CheckIn") + .HasColumnType("time(6)"); + + b.Property("CheckOut") + .HasColumnType("time(6)"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("int"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("int"); + + b.Property("EditedAt") + .HasColumnType("datetime(6)"); + + b.Property("EditedBy") + .HasColumnType("int"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsManualOverride") + .HasColumnType("tinyint(1)"); + + b.Property("LateMinutes") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("int"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.Property("WorkingMinutes") + .HasColumnType("int"); + + b.HasKey("AttendanceRecordId"); + + b.HasIndex("AttendanceUploadBatchId"); + + b.HasIndex("WorkShiftId"); + + b.HasIndex("EmployeeId", "AttendanceDate"); + + b.ToTable("hr_attendance_records", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceUploadBatch", b => + { + b.Property("AttendanceUploadBatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("PeriodEnd") + .HasColumnType("datetime(6)"); + + b.Property("PeriodStart") + .HasColumnType("datetime(6)"); + + b.Property("RowCountDuplicate") + .HasColumnType("int"); + + b.Property("RowCountError") + .HasColumnType("int"); + + b.Property("RowCountTotal") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("AttendanceUploadBatchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodStart", "PeriodEnd"); + + b.ToTable("hr_attendance_upload_batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.Property("AuditId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.Property("BankDepositId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BankDepositId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DepositDate") + .HasColumnType("date"); + + b.Property("DepositSlipNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DepositedBy") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FromCashAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("FromCashAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FromGlCashAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Remarks") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Posted"); + + b.Property("ToAccountNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ToBankAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ToBankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ToBankName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("ToGlBankAccountId") + .HasColumnType("bigint"); + + b.HasKey("BankDepositId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ToGlBankAccountId"); + + b.HasIndex("BranchId", "BusinessDate", "Status"); + + b.ToTable("bank_deposits", null, t => + { + t.HasCheckConstraint("ck_bank_deposits_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BatchId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.HasKey("BatchId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BinId"); + + b.HasIndex("WarehouseId", "Code") + .IsUnique(); + + b.ToTable("bins", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Branch", b => + { + b.Property("BranchId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BranchingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarehouseBranchingEnabled") + .HasColumnType("tinyint(1)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("branch_config", null, t => + { + t.HasCheckConstraint("ck_branch_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Property("BundleSaleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleId")); + + b.Property("BundleCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundleDate") + .HasColumnType("datetime(6)"); + + b.Property("BundleName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BundleNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BundlePrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ComponentSubtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MarginAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleId"); + + b.HasIndex("BundleNo") + .IsUnique(); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sales", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.Property("BundleSaleLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleLineId")); + + b.Property("BundleSaleId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IncludeInBundle") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsComponent") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleLineId"); + + b.HasIndex("BundleSaleId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Property("BundleSaleTemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("BundleSaleTemplateId"); + + b.HasIndex("TemplateCode") + .IsUnique(); + + b.ToTable("bundle_sale_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.Property("BundleSaleTemplateLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("BundleSaleTemplateLineId")); + + b.Property("BundleSaleTemplateId") + .HasColumnType("int"); + + b.Property("IncludeInBundle") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("BundleSaleTemplateLineId"); + + b.HasIndex("BundleSaleTemplateId"); + + b.HasIndex("ItemId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("bundle_sale_template_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CategoryId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditDays") + .HasColumnType("int"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("CustomerId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CustomerCode") + .IsUnique(); + + b.HasIndex("CustomerType"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.Property("DepartmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("HeadEmployeeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DepartmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("HeadEmployeeId"); + + b.HasIndex("ParentDepartmentId"); + + b.HasIndex("Status"); + + b.ToTable("hr_departments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Designation", b => + { + b.Property("DesignationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.Property("DocumentApprovalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DocumentApprovalId")); + + b.Property("Comment") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("DecidedAt") + .HasColumnType("datetime(6)"); + + b.Property("DecidedBy") + .HasColumnType("int"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DocId") + .HasColumnType("int"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Level") + .HasColumnType("int"); + + b.HasKey("DocumentApprovalId"); + + b.HasIndex("DecidedBy"); + + b.HasIndex("DocType", "DocId"); + + b.ToTable("document_approvals", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("datetime(6)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DateOfBirth") + .HasColumnType("datetime(6)"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("int"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("HireDate") + .HasColumnType("datetime(6)"); + + b.Property("LastWorkingDate") + .HasColumnType("datetime(6)"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WorkShiftId") + .HasColumnType("int"); + + b.HasKey("EmployeeId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("DesignationId"); + + b.HasIndex("Email"); + + b.HasIndex("EmployeeCode") + .IsUnique(); + + b.HasIndex("EmploymentTypeId"); + + b.HasIndex("ReportingManagerId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("WorkShiftId"); + + b.ToTable("hr_employees", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.Property("EmployeeBankDetailId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmployeeBankDetailId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("hr_employee_bank_details", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.Property("EmployeeDocumentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("HrDocumentTypeId") + .HasColumnType("int"); + + b.Property("IssueDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.Property("VerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerifiedBy") + .HasColumnType("int"); + + b.HasKey("EmployeeDocumentId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ExpiryDate"); + + b.HasIndex("HrDocumentTypeId"); + + b.ToTable("hr_employee_documents", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Property("EmployeeLoanId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("int"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartMonth") + .HasColumnType("int"); + + b.Property("StartYear") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeLoanId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("Status"); + + b.ToTable("hr_employee_loans", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Property("EmployeeSalaryStructureId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("EmployeeSalaryStructureId"); + + b.HasIndex("EmployeeId", "EffectiveTo"); + + b.ToTable("hr_employee_salary_structures", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.Property("EmployeeSalaryStructureLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.HasKey("EmployeeSalaryStructureLineId"); + + b.HasIndex("EmployeeSalaryStructureId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_employee_salary_structure_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmploymentType", b => + { + b.Property("EmploymentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Property("ExpenseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BillReference") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("ExpenseDate") + .HasColumnType("date"); + + b.Property("ExpenseTitleId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GlReversalJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Payee") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PaymentStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Unpaid"); + + b.Property("PeriodMonth") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("ExpenseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ExpenseCategoryId"); + + b.HasIndex("ExpenseDate"); + + b.HasIndex("ExpenseTitleId"); + + b.HasIndex("PaymentStatus"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("expenses", null, t => + { + t.HasCheckConstraint("ck_expenses_amount", "`Amount` > 0"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Property("ExpenseCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseCategoryId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseCategoryId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("expense_categories", (string)null); + + b.HasData( + new + { + ExpenseCategoryId = 1, + Code = "UTILITY", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Utility", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseCategoryId = 2, + Code = "RENTAL", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Rental", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseCategoryId = 3, + Code = "SALARIES", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Salaries", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseCategoryId = 4, + Code = "OTHER", + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + IsSystem = true, + Name = "Other", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.Property("ExpensePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpensePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ChequeNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("ExpenseId") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("ExpensePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ExpenseId"); + + b.ToTable("expense_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.Property("ExpenseTitleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ExpenseTitleId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpenseCategoryId") + .HasColumnType("int"); + + b.Property("GlExpenseAccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsSystem") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ExpenseTitleId"); + + b.HasIndex("ExpenseCategoryId", "Name") + .IsUnique(); + + b.ToTable("expense_titles", (string)null); + + b.HasData( + new + { + ExpenseTitleId = 1, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Electricity", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 2, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Water", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 3, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Telephone", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 4, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Internet", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 5, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 1, + GlExpenseAccountCode = "5200-01", + IsSystem = true, + Name = "Gas", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 6, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Building Rent", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 7, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Vehicle Rent", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 8, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 2, + GlExpenseAccountCode = "5200-02", + IsSystem = true, + Name = "Equipment Rent", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 9, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Staff Salaries", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 10, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Wages", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 11, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Overtime", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 12, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "Bonus", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 13, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 3, + GlExpenseAccountCode = "5200-03", + IsSystem = true, + Name = "EPF / ETF Contribution", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }, + new + { + ExpenseTitleId = 14, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Stationery", + RowVersion = 1u, + SortOrder = 1, + Status = "Active" + }, + new + { + ExpenseTitleId = 15, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Transport", + RowVersion = 1u, + SortOrder = 2, + Status = "Active" + }, + new + { + ExpenseTitleId = 16, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Repairs & Maintenance", + RowVersion = 1u, + SortOrder = 3, + Status = "Active" + }, + new + { + ExpenseTitleId = 17, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Cleaning", + RowVersion = 1u, + SortOrder = 4, + Status = "Active" + }, + new + { + ExpenseTitleId = 18, + CreatedAt = new DateTime(2026, 10, 7, 0, 0, 0, 0, DateTimeKind.Utc), + ExpenseCategoryId = 4, + GlExpenseAccountCode = "5200-04", + IsSystem = true, + Name = "Miscellaneous", + RowVersion = 1u, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AccountsPayableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountsReceivableCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CashAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ClearingAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CogsAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryGainAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("InventoryLossAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesDiscountAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SalesRevenueAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TaxPayableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VatRecoverableAccountCode") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("gl_account_config", null, t => + { + t.HasCheckConstraint("ck_gl_account_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("GrnId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("PurchaseInvoiceId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowVat") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("grn_config", null, t => + { + t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("PoLineId") + .HasColumnType("int"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.Property("GrnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineSerialId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("GrnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("GrnLineId", "SerialNo") + .IsUnique(); + + b.ToTable("grn_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.Property("GrnLineWarrantyNumberId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("GrnLineWarrantyNumberId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("WarrantyNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("GrnLineWarrantyNumberId"); + + b.HasIndex("GrnLineId", "WarrantyNo") + .IsUnique(); + + b.ToTable("grn_line_warranty_numbers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryTracked") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("HrDocumentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_document_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Property("ItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BaseUomId") + .HasColumnType("int"); + + b.Property("BrandId") + .HasColumnType("int"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ContentBaseQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentBaseUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ContentQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ContentUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultVendorId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SellingMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SubCategoryId") + .HasColumnType("int"); + + b.Property("TaxRateId") + .HasColumnType("int"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Warranty") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("NonWarranty"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("ItemId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.HasIndex("TaxRateId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.Property("ItemImageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemImageId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("varchar(260)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.Property("UploadedBy") + .HasColumnType("int"); + + b.HasKey("ItemImageId"); + + b.HasIndex("ItemId", "SortOrder"); + + b.ToTable("item_images", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.Property("ItemPriceHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemPriceHistoryId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.HasKey("ItemPriceHistoryId"); + + b.HasIndex("PriceAdjustmentId"); + + b.HasIndex("ItemId", "AppliedAt"); + + b.ToTable("item_price_history", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReorderId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId") + .IsUnique(); + + b.ToTable("item_reorders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b => + { + b.Property("ItemTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsMeasurable") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("ItemTypeId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("item_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b => + { + b.Property("JournalId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.HasKey("JournalId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("journal_entry_stubs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.Property("LeaveBalanceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("LeaveBalanceId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("EmployeeId", "LeaveTypeId", "Year") + .IsUnique(); + + b.ToTable("hr_leave_balances", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.Property("LeaveRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveTypeId") + .HasColumnType("int"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LeaveRequestId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("EmployeeId"); + + b.HasIndex("LeaveTypeId"); + + b.HasIndex("Status"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("hr_leave_requests", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveType", b => + { + b.Property("LeaveTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("tinyint(1)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsPaid") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequiresApproval") + .HasColumnType("tinyint(1)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("LeaveTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_leave_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.Property("LoanInstallmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("int"); + + b.Property("DueYear") + .HasColumnType("int"); + + b.Property("EmployeeLoanId") + .HasColumnType("int"); + + b.Property("InstallmentNumber") + .HasColumnType("int"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("LoanInstallmentId"); + + b.HasIndex("EmployeeLoanId"); + + b.HasIndex("PayrollRunId"); + + b.HasIndex("DueYear", "DueMonth"); + + b.ToTable("hr_loan_installments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.Property("LoginActivityId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LoginActivityId")); + + b.Property("DeviceLabel") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("LoginAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("LoginActivityId"); + + b.HasIndex("UserId", "LoginAt"); + + b.ToTable("login_activities", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("NavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("nav_items", (string)null); + + b.HasData( + new + { + NavItemId = 1, + Code = "dashboard", + Href = "/dashboard", + Label = "Dashboard", + SortOrder = 1, + Status = "Active" + }, + new + { + NavItemId = 2, + Code = "products", + Href = "/dashboard/products", + Label = "Products", + SortOrder = 2, + Status = "Active" + }, + new + { + NavItemId = 4, + Code = "procurement", + Href = "/dashboard/procurement", + Label = "Procurement", + SortOrder = 4, + Status = "Active" + }, + new + { + NavItemId = 5, + Code = "receiving", + Href = "/dashboard/receiving/grn", + Label = "Receiving", + SortOrder = 5, + Status = "Active" + }, + new + { + NavItemId = 6, + Code = "stock", + Href = "/dashboard/stock", + Label = "Stock", + SortOrder = 6, + Status = "Active" + }, + new + { + NavItemId = 7, + Code = "warehouses", + Href = "/dashboard/warehouse", + Label = "Warehouses", + SortOrder = 7, + Status = "Active" + }, + new + { + NavItemId = 8, + Code = "orders", + Href = "/dashboard/orders", + Label = "Orders", + SortOrder = 8, + Status = "Active" + }, + new + { + NavItemId = 9, + Code = "settings", + Href = "/dashboard/settings", + Label = "Settings", + SortOrder = 9, + Status = "Active" + }, + new + { + NavItemId = 10, + Code = "help", + Href = "/dashboard/help", + Label = "Help", + SortOrder = 10, + Status = "Active" + }, + new + { + NavItemId = 11, + Code = "ledgers", + Href = "/dashboard/ledgers", + Label = "Ledgers", + SortOrder = 11, + Status = "Active" + }, + new + { + NavItemId = 12, + Code = "accounts", + Href = "/dashboard/accounts", + Label = "Accounts", + SortOrder = 12, + Status = "Active" + }, + new + { + NavItemId = 13, + Code = "sales", + Href = "/dashboard/sales", + Label = "Sales", + SortOrder = 13, + Status = "Active" + }, + new + { + NavItemId = 14, + Code = "production", + Href = "/dashboard/production", + Label = "Production", + SortOrder = 14, + Status = "Active" + }, + new + { + NavItemId = 15, + Code = "hrm", + Href = "/dashboard/hrm", + Label = "Human Resources", + SortOrder = 15, + Status = "Active" + }, + new + { + NavItemId = 16, + Code = "sales-returns", + Href = "/dashboard/sales/sales-returns", + Label = "Sales Returns", + SortOrder = 16, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Property("NotificationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RelatedEntityId") + .HasColumnType("int"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("NotificationId"); + + b.HasIndex("CreatedAt"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("NotificationConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("notification_config", null, t => + { + t.HasCheckConstraint("ck_notification_config_singleton", "`NotificationConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.Property("NotificationConfigModuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationConfigModuleId")); + + b.Property("EmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("InAppEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ModuleKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotificationConfigId") + .HasColumnType("int"); + + b.Property("SmsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.HasKey("NotificationConfigModuleId"); + + b.HasIndex("ModuleKey") + .IsUnique(); + + b.HasIndex("NotificationConfigId"); + + b.ToTable("notification_config_modules", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationRecipientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("NotificationRecipientId")); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("NotificationId") + .HasColumnType("int"); + + b.Property("ReadAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("NotificationRecipientId"); + + b.HasIndex("NotificationId"); + + b.HasIndex("UserId", "IsRead", "NotificationId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SequenceId")); + + b.Property("BranchId") + .HasColumnType("int") + .HasColumnName("branch_id"); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("int") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("int") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocType", "BranchId", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.Property("OpeningBalanceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceLineId")); + + b.Property("AccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.HasKey("OpeningBalanceLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.ToTable("opening_balance_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Property("OpeningBalanceRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceRunId")); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("CutoverDate") + .HasColumnType("date"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("OnceKey") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedBy") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("OpeningBalanceRunId"); + + b.HasIndex("Kind"); + + b.HasIndex("OnceKey") + .IsUnique(); + + b.ToTable("opening_balance_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.Property("OpeningStockLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningStockLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("OpeningStockLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId"); + + b.ToTable("opening_stock_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("int"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("int"); + + b.Property("EpfEmployeeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EpfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("EtfEmployerAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("GrossSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("int"); + + b.Property("LeaveDays") + .HasColumnType("int"); + + b.Property("LoanDeductionAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NetSalary") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("NoPayAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("int"); + + b.Property("OtherDeductionsAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("OvertimeAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("int"); + + b.Property("PresentDays") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("TaxAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("TotalAllowances") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("int"); + + b.HasKey("PayrollLineId"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("PayrollRunId", "EmployeeId") + .IsUnique(); + + b.ToTable("hr_payroll_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.Property("PayrollLineComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("SalaryComponentId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("PayrollLineComponentId"); + + b.HasIndex("PayrollLineId"); + + b.HasIndex("SalaryComponentId"); + + b.ToTable("hr_payroll_line_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Property("PayrollRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("GeneratedBy") + .HasColumnType("int"); + + b.Property("LockedAt") + .HasColumnType("datetime(6)"); + + b.Property("LockedBy") + .HasColumnType("int"); + + b.Property("PeriodMonth") + .HasColumnType("int"); + + b.Property("PeriodYear") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UnlockedBy") + .HasColumnType("int"); + + b.HasKey("PayrollRunId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("PeriodYear", "PeriodMonth", "BranchId"); + + b.ToTable("hr_payroll_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollStatutorySetting", b => + { + b.Property("PayrollStatutorySettingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.HasKey("PayrollStatutorySettingId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_payroll_statutory_settings", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.Property("PayslipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("datetime(6)"); + + b.Property("PayrollLineId") + .HasColumnType("int"); + + b.Property("ReleasedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReleasedBy") + .HasColumnType("int"); + + b.HasKey("PayslipId"); + + b.HasIndex("PayrollLineId") + .IsUnique(); + + b.ToTable("hr_payslips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.Property("PermissionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SubNavItemId") + .HasColumnType("int"); + + b.HasKey("PermissionId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.HasIndex("SubNavItemId"); + + b.ToTable("permissions", (string)null); + + b.HasData( + new + { + PermissionId = 1, + Code = "NAV:dashboard", + NavItemId = 1 + }, + new + { + PermissionId = 2, + Code = "NAV:products", + NavItemId = 2 + }, + new + { + PermissionId = 3, + Code = "NAV:procurement.vendors", + SubNavItemId = 30 + }, + new + { + PermissionId = 4, + Code = "NAV:procurement", + NavItemId = 4 + }, + new + { + PermissionId = 5, + Code = "NAV:receiving", + NavItemId = 5 + }, + new + { + PermissionId = 6, + Code = "NAV:stock", + NavItemId = 6 + }, + new + { + PermissionId = 7, + Code = "NAV:warehouses", + NavItemId = 7 + }, + new + { + PermissionId = 8, + Code = "NAV:orders", + NavItemId = 8 + }, + new + { + PermissionId = 9, + Code = "NAV:settings", + NavItemId = 9 + }, + new + { + PermissionId = 10, + Code = "NAV:help", + NavItemId = 10 + }, + new + { + PermissionId = 11, + Code = "NAV:products.item", + SubNavItemId = 1 + }, + new + { + PermissionId = 12, + Code = "NAV:products.category", + SubNavItemId = 2 + }, + new + { + PermissionId = 13, + Code = "NAV:products.brand", + SubNavItemId = 3 + }, + new + { + PermissionId = 14, + Code = "NAV:products.item-type", + SubNavItemId = 4 + }, + new + { + PermissionId = 15, + Code = "NAV:products.uom", + SubNavItemId = 5 + }, + new + { + PermissionId = 16, + Code = "NAV:products.configuration", + SubNavItemId = 6 + }, + new + { + PermissionId = 17, + Code = "NAV:settings.roles", + SubNavItemId = 7 + }, + new + { + PermissionId = 18, + Code = "NAV:settings.users", + SubNavItemId = 8 + }, + new + { + PermissionId = 28, + Code = "NAV:procurement.requisitions", + SubNavItemId = 17 + }, + new + { + PermissionId = 29, + Code = "NAV:procurement.rfqs", + SubNavItemId = 18 + }, + new + { + PermissionId = 30, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 19 + }, + new + { + PermissionId = 31, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 20 + }, + new + { + PermissionId = 19, + Code = "NAV:ledgers", + NavItemId = 11 + }, + new + { + PermissionId = 20, + Code = "NAV:ledgers.trial-balance", + SubNavItemId = 9 + }, + new + { + PermissionId = 21, + Code = "NAV:ledgers.balance-sheet", + SubNavItemId = 10 + }, + new + { + PermissionId = 22, + Code = "NAV:ledgers.general-ledger", + SubNavItemId = 11 + }, + new + { + PermissionId = 23, + Code = "NAV:ledgers.profit-and-loss", + SubNavItemId = 12 + }, + new + { + PermissionId = 24, + Code = "NAV:ledgers.cash-flow", + SubNavItemId = 13 + }, + new + { + PermissionId = 25, + Code = "NAV:ledgers.budget-vs-actual", + SubNavItemId = 14 + }, + new + { + PermissionId = 27, + Code = "NAV:ledgers.tax-report", + SubNavItemId = 16 + }, + new + { + PermissionId = 26, + Code = "NAV:accounts.bank-accounts", + SubNavItemId = 15 + }, + new + { + PermissionId = 32, + Code = "NAV:accounts", + NavItemId = 12 + }, + new + { + PermissionId = 33, + Code = "NAV:accounts.cheque-books", + SubNavItemId = 21 + }, + new + { + PermissionId = 34, + Code = "NAV:accounts.received-cheques", + SubNavItemId = 22 + }, + new + { + PermissionId = 35, + Code = "NAV:production", + NavItemId = 14 + }, + new + { + PermissionId = 36, + Code = "NAV:production.templates", + SubNavItemId = 27 + }, + new + { + PermissionId = 37, + Code = "NAV:production.runs", + SubNavItemId = 25 + }, + new + { + PermissionId = 38, + Code = "NAV:production.produced", + SubNavItemId = 26 + }, + new + { + PermissionId = 39, + Code = "NAV:procurement.purchase-invoices", + SubNavItemId = 28 + }, + new + { + PermissionId = 40, + Code = "NAV:settings.branches", + SubNavItemId = 29 + }, + new + { + PermissionId = 41, + Code = "NAV:stock.ledger", + SubNavItemId = 31 + }, + new + { + PermissionId = 42, + Code = "NAV:stock.enquiry", + SubNavItemId = 32 + }, + new + { + PermissionId = 43, + Code = "NAV:stock.valuation", + SubNavItemId = 33 + }, + new + { + PermissionId = 44, + Code = "NAV:stock.transfers", + SubNavItemId = 34 + }, + new + { + PermissionId = 45, + Code = "NAV:stock.adjustments", + SubNavItemId = 35 + }, + new + { + PermissionId = 46, + Code = "NAV:stock.counts", + SubNavItemId = 36 + }, + new + { + PermissionId = 47, + Code = "NAV:stock.reorder-alerts", + SubNavItemId = 37 + }, + new + { + PermissionId = 48, + Code = "NAV:stock.wastage", + SubNavItemId = 38 + }, + new + { + PermissionId = 49, + Code = "NAV:sales.bundle-templates", + SubNavItemId = 23 + }, + new + { + PermissionId = 50, + Code = "NAV:sales.invoices", + SubNavItemId = 40 + }, + new + { + PermissionId = 51, + Code = "NAV:sales.free-issues", + SubNavItemId = 41 + }, + new + { + PermissionId = 52, + Code = "NAV:sales.day-end", + SubNavItemId = 42 + }, + new + { + PermissionId = 53, + Code = "NAV:products.tax-rates", + SubNavItemId = 43 + }, + new + { + PermissionId = 54, + Code = "NAV:accounts.expenses", + SubNavItemId = 44 + }, + new + { + PermissionId = 55, + Code = "NAV:accounts.expense-setup", + SubNavItemId = 45 + }, + new + { + PermissionId = 56, + Code = "NAV:sales.day-end-report", + SubNavItemId = 46 + }, + new + { + PermissionId = 57, + Code = "NAV:accounts.bank-deposits", + SubNavItemId = 47 + }, + new + { + PermissionId = 58, + Code = "NAV:products.price-adjustments", + SubNavItemId = 48 + }, + new + { + PermissionId = 59, + Code = "NAV:settings.approvals", + SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }, + new + { + PermissionId = 61, + Code = "NAV:accounts.opening-balances", + SubNavItemId = 51 + }, + new + { + PermissionId = 62, + Code = "NAV:hrm", + NavItemId = 15 + }, + new + { + PermissionId = 63, + Code = "NAV:sales-returns", + NavItemId = 16 + }, + new + { + PermissionId = 64, + Code = "NAV:hrm.employees", + SubNavItemId = 52 + }, + new + { + PermissionId = 65, + Code = "NAV:hrm.attendance", + SubNavItemId = 53 + }, + new + { + PermissionId = 66, + Code = "NAV:hrm.leave", + SubNavItemId = 54 + }, + new + { + PermissionId = 67, + Code = "NAV:hrm.payroll", + SubNavItemId = 55 + }, + new + { + PermissionId = 68, + Code = "NAV:hrm.reports", + SubNavItemId = 56 + }, + new + { + PermissionId = 69, + Code = "NAV:hrm.settings", + SubNavItemId = 57 + }, + new + { + PermissionId = 70, + Code = "NAV:settings.audit-logs", + SubNavItemId = 58 + }, + new + { + PermissionId = 71, + Code = "NAV:settings.notification-config", + SubNavItemId = 24 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PoId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Property("PriceAdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentId")); + + b.Property("AppliedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasDefaultValue("PendingApproval"); + + b.HasKey("PriceAdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("price_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.Property("PriceAdjustmentLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PriceAdjustmentLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("NewFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldFlatPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMinPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PriceAdjustmentId") + .HasColumnType("int"); + + b.HasKey("PriceAdjustmentLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PriceAdjustmentId"); + + b.ToTable("price_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.Property("VendorPaymentTermsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("VendorTaxRegEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("BatchListsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomVariantsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("CustomWarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemImagesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SellingPricesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SerialNumbersEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("WarrantyPeriodsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("product_config", null, t => + { + t.HasCheckConstraint("ck_product_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Property("RunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunId")); + + b.Property("CancelReasonCodeId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OutputBinId") + .HasColumnType("int"); + + b.Property("ReworkCount") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("ScaleFactor") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TargetQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("RunId"); + + b.HasIndex("CancelReasonCodeId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("OutputBinId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("TemplateId", "Status"); + + b.ToTable("production_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TemplateId")); + + b.Property("Annotations") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TemplateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("production_templates", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Property("PurchaseInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("VendorInvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("VendorInvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("VendorSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("VendorSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("PurchaseInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("VendorId", "VendorInvoiceNo"); + + b.ToTable("purchase_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.Property("PurchaseInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoiceLineId")); + + b.Property("GrnId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("VatTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("PurchaseInvoiceLineId"); + + b.HasIndex("GrnId") + .IsUnique(); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.Property("PurchaseInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PurchaseInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PurchaseInvoiceId") + .HasColumnType("int"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("PurchaseInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("PurchaseInvoiceId"); + + b.ToTable("purchase_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("tinyint(1)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("int"); + + b.Property("ApproverUserId") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("PoId"); + + b.HasIndex("ApproverUserId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.ToTable("purchase_orders", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("purchase_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.ToTable("purchase_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ReasonCode", b => + { + b.Property("ReasonCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("ReasonCodeId"); + + b.HasIndex("Context", "Code") + .IsUnique(); + + b.ToTable("reason_codes", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Property("RequisitionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RequisitionId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequestedBy") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RequisitionId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.ToTable("requisitions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.Property("ReqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.HasKey("ReqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RequisitionId"); + + b.ToTable("requisition_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Property("RfqId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RequisitionId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("BranchId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.HasKey("RfqLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RfqId"); + + b.ToTable("rfq_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("char(36)") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("RoleId"); + + b.HasIndex("AuthRoleId") + .IsUnique(); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("PermissionId") + .HasColumnType("int"); + + b.Property("Actions") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(31); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.Property("RunEdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunEdgeId")); + + b.Property("ChildRunStageId") + .HasColumnType("int"); + + b.Property("ParentRunStageId") + .HasColumnType("int"); + + b.Property("RunId") + .HasColumnType("int"); + + b.HasKey("RunEdgeId"); + + b.HasIndex("ChildRunStageId"); + + b.HasIndex("RunId"); + + b.HasIndex("ParentRunStageId", "ChildRunStageId") + .IsUnique(); + + b.ToTable("run_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Property("RunStageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunStageId")); + + b.Property("ActualEndAt") + .HasColumnType("datetime(6)"); + + b.Property("ActualStartAt") + .HasColumnType("datetime(6)"); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FieldValues") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("TemplateStageId") + .HasColumnType("int"); + + b.HasKey("RunStageId"); + + b.HasIndex("TemplateStageId"); + + b.HasIndex("RunId", "Status"); + + b.ToTable("run_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EventId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("RunId") + .HasColumnType("int"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("EventId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("UserId"); + + b.HasIndex("RunId", "EventId"); + + b.ToTable("run_stage_events", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.Property("RunInputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunInputId")); + + b.Property("ConsumedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ConsumedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DeliveredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("FromRunOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ReturnedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnedValue") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("RunInputId"); + + b.HasIndex("FromRunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.ToTable("run_stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.Property("RunOutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RunOutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PlannedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ProducedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunStageId") + .HasColumnType("int"); + + b.Property("ScrapReasonCodeId") + .HasColumnType("int"); + + b.Property("ScrappedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TransferredQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("RunOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("RunStageId"); + + b.HasIndex("ScrapReasonCodeId"); + + b.HasIndex("UomId"); + + b.ToTable("run_stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("tinyint(1)"); + + b.Property("IsTaxable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("AllowCashCustomer") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowDiscounts") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowFreeItems") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("AllowOverStockSale") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("AllowWarehouseSelection") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CashCustomerId") + .HasColumnType("int"); + + b.Property("CustomerAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCityEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCountryEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCreditDaysEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCreditLimitEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerEmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerPhoneEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("DefaultWarehouseId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("CashCustomerId"); + + b.HasIndex("DefaultWarehouseId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("sales_config", null, t => + { + t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Property("SalesDayEndId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesDayEndId")); + + b.Property("BalanceTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BundleCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("BusinessDate") + .HasColumnType("date"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedBy") + .HasColumnType("int"); + + b.Property("CostOfGoodsSold") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipCount") + .HasColumnType("int"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("SalesDayEndId"); + + b.HasIndex("ClosedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("CashierUserId", "BusinessDate") + .IsUnique(); + + b.ToTable("sales_day_ends", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CreatorUserId") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatorUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("InvoiceDate"); + + b.HasIndex("InvoiceNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoices", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Property("SalesInvoiceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.Property("SalesInvoiceLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoiceLineSerialId")); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesInvoiceLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesInvoiceLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_invoice_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.Property("SalesInvoicePaymentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesInvoicePaymentId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BankAccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("GlBankAccountId") + .HasColumnType("bigint"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("PaymentDate") + .HasColumnType("datetime(6)"); + + b.Property("PaymentMethod") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Cash"); + + b.Property("Reference") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SalesInvoiceId") + .HasColumnType("int"); + + b.HasKey("SalesInvoicePaymentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("SalesInvoiceId"); + + b.ToTable("sales_invoice_payments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.Property("SalesLineAllocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesLineAllocationId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReturned") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesLineAllocationId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId", "SourceLineId"); + + b.ToTable("sales_line_allocations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Property("ReturnId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("ReturnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_returns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Property("ReturnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ReturnLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReturnId") + .HasColumnType("int"); + + b.Property("SalesInvoiceLineId") + .HasColumnType("int"); + + b.HasKey("ReturnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReturnId"); + + b.HasIndex("SalesInvoiceLineId"); + + b.ToTable("sales_return_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.Property("SalesReturnLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesReturnLineSerialId")); + + b.Property("ReturnLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesReturnLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("ReturnLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_return_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CardAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DayEndId") + .HasColumnType("int"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SlipDate") + .HasColumnType("datetime(6)"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DayEndId"); + + b.HasIndex("SlipDate"); + + b.HasIndex("SlipNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slips", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Property("SalesSlipLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("int"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("tinyint(1)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("int"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SalesSlipId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("decimal(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.Property("SalesSlipLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SalesSlipLineSerialId")); + + b.Property("SalesSlipLineId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("SalesSlipLineSerialId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SalesSlipLineId", "SerialId") + .IsUnique(); + + b.ToTable("sales_slip_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialId")); + + b.Property("Barcode") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.Property("WarrantyPeriodMonths") + .HasColumnType("int"); + + b.HasKey("SerialId"); + + b.HasIndex("Barcode") + .IsUnique(); + + b.HasIndex("BinId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.HasIndex("ItemId", "Status"); + + b.HasIndex("WarehouseId", "ItemId", "Status"); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.Property("SerialMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SerialMovementId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FromWarehouseId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("SerialMovementId"); + + b.HasIndex("SerialId", "SerialMovementId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.ToTable("serial_movements", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.Property("EdgeId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("EdgeId")); + + b.Property("ChildStageId") + .HasColumnType("int"); + + b.Property("ParentStageId") + .HasColumnType("int"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("EdgeId"); + + b.HasIndex("ChildStageId"); + + b.HasIndex("TemplateId"); + + b.HasIndex("ParentStageId", "ChildStageId") + .IsUnique(); + + b.ToTable("stage_edges", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.Property("InputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("InputId")); + + b.Property("FromOutputId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyUnit") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.HasKey("InputId"); + + b.HasIndex("FromOutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.ToTable("stage_inputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.Property("OutputId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OutputId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("QtyPerBatch") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("StageId") + .HasColumnType("int"); + + b.Property("UomId") + .HasColumnType("int"); + + b.HasKey("OutputId"); + + b.HasIndex("ItemId"); + + b.HasIndex("StageId"); + + b.HasIndex("UomId"); + + b.ToTable("stage_outputs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjustmentId")); + + b.Property("ApprovedLevel1At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel1By") + .HasColumnType("int"); + + b.Property("ApprovedLevel2At") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedLevel2By") + .HasColumnType("int"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("GlPostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReasonCodeId") + .HasColumnType("int"); + + b.Property("RejectReason") + .HasMaxLength(250) + .HasColumnType("varchar(250)"); + + b.Property("RejectedAt") + .HasColumnType("datetime(6)"); + + b.Property("RejectedBy") + .HasColumnType("int"); + + b.Property("RequiredApprovals") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("int"); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RequestedSerialIds") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("RequestedSerialNos") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.HasKey("AdjLineId"); + + b.HasIndex("AdjustmentId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_adjustment_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Property("CountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("CountId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_counts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.Property("CountLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CountLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CountId") + .HasColumnType("int"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("GrnLineId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LayerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("GrnLineId"); + + b.HasIndex("SerialId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId", "ReceiptDate", "LayerId"); + + b.ToTable("stock_layers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.Property("LedgerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("BinId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("varchar(5)"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("LedgerId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("SerialId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("ItemId", "WarehouseId", "CreatedAt"); + + b.HasIndex("ItemId", "WarehouseId", "LedgerId"); + + b.ToTable("stock_ledger", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Property("TransferId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferId")); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("DestWarehouseId") + .HasColumnType("int"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SrcWarehouseId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("TransferId"); + + b.HasIndex("BranchId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DestWarehouseId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("SrcWarehouseId"); + + b.HasIndex("Status"); + + b.ToTable("stock_transfers", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Property("TransferLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("DestBinId") + .HasColumnType("int"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SrcBinId") + .HasColumnType("int"); + + b.Property("TransferId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("TransferLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("DestBinId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SrcBinId"); + + b.HasIndex("TransferId"); + + b.ToTable("stock_transfer_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.Property("StockTransferLineSerialId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StockTransferLineSerialId")); + + b.Property("BatchId") + .HasColumnType("int"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("TransferLineId") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("decimal(18,6)"); + + b.HasKey("StockTransferLineSerialId"); + + b.HasIndex("BatchId"); + + b.HasIndex("SerialId"); + + b.HasIndex("TransferLineId", "SerialId") + .IsUnique(); + + b.ToTable("stock_transfer_line_serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("SubCategoryId"); + + b.HasIndex("Status"); + + b.HasIndex("CategoryId", "Name") + .IsUnique(); + + b.ToTable("subcategories", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.Property("SubNavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NavItemId") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.HasKey("SubNavItemId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("NavItemId"); + + b.ToTable("sub_nav_items", (string)null); + + b.HasData( + new + { + SubNavItemId = 1, + Code = "products.item", + Href = "/dashboard/products", + Label = "Item", + NavItemId = 2, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 2, + Code = "products.category", + Href = "/dashboard/products/categories", + Label = "Category", + NavItemId = 2, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 3, + Code = "products.brand", + Href = "/dashboard/products/brands", + Label = "Brand", + NavItemId = 2, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 4, + Code = "products.item-type", + Href = "/dashboard/products/item-types", + Label = "Item Type", + NavItemId = 2, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 5, + Code = "products.uom", + Href = "/dashboard/products/uoms", + Label = "UOM", + NavItemId = 2, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 6, + Code = "products.configuration", + Href = "/dashboard/products/settings", + Label = "Configuration", + NavItemId = 2, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 43, + Code = "products.tax-rates", + Href = "/dashboard/products/tax-rates", + Label = "Tax Rates", + NavItemId = 2, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 48, + Code = "products.price-adjustments", + Href = "/dashboard/products/price-adjustments", + Label = "Price Adjustments", + NavItemId = 2, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 29, + Code = "settings.branches", + Href = "/dashboard/settings/branches", + Label = "Branches", + NavItemId = 9, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 7, + Code = "settings.roles", + Href = "/dashboard/settings/roles", + Label = "Roles", + NavItemId = 9, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 8, + Code = "settings.users", + Href = "/dashboard/settings/users", + Label = "Users", + NavItemId = 9, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 24, + Code = "settings.notification-config", + Href = "/dashboard/settings/notification-config", + Label = "Notification Config", + NavItemId = 9, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 49, + Code = "settings.approvals", + Href = "/dashboard/settings/approvals", + Label = "Approvals", + NavItemId = 9, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 40, + Code = "sales.invoices", + Href = "/dashboard/sales/invoices", + Label = "Sales", + NavItemId = 13, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 23, + Code = "sales.bundle-templates", + Href = "/dashboard/sales/bundles/templates", + Label = "Bundle Templates", + NavItemId = 13, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 41, + Code = "sales.free-issues", + Href = "/dashboard/sales/free-issues", + Label = "Free Issues", + NavItemId = 13, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 42, + Code = "sales.day-end", + Href = "/dashboard/sales/day-end", + Label = "Approved Invoices", + NavItemId = 13, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 46, + Code = "sales.day-end-report", + Href = "/dashboard/sales/day-end-report", + Label = "Day-End Report", + NavItemId = 13, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 30, + Code = "procurement.vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + NavItemId = 4, + SortOrder = 0, + Status = "Active" + }, + new + { + SubNavItemId = 17, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 18, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 19, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 20, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 28, + Code = "procurement.purchase-invoices", + Href = "/dashboard/procurement/purchase-invoices", + Label = "Purchase Invoices", + NavItemId = 4, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 9, + Code = "ledgers.trial-balance", + Href = "/dashboard/ledgers/trial-balance", + Label = "Trial Balance", + NavItemId = 11, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "ledgers.balance-sheet", + Href = "/dashboard/ledgers/balance-sheet", + Label = "Balance Sheet", + NavItemId = 11, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "ledgers.general-ledger", + Href = "/dashboard/ledgers/general-ledger", + Label = "General Ledger", + NavItemId = 11, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "ledgers.profit-and-loss", + Href = "/dashboard/ledgers/profit-and-loss", + Label = "Profit & Loss", + NavItemId = 11, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 13, + Code = "ledgers.cash-flow", + Href = "/dashboard/ledgers/cash-flow", + Label = "Cash Flow", + NavItemId = 11, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 14, + Code = "ledgers.budget-vs-actual", + Href = "/dashboard/ledgers/budget-vs-actual", + Label = "Budget vs Actual", + NavItemId = 11, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 16, + Code = "ledgers.tax-report", + Href = "/dashboard/ledgers/tax-report", + Label = "Tax Report", + NavItemId = 11, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 15, + Code = "accounts.bank-accounts", + Href = "/dashboard/accounts/bank-accounts", + Label = "Cash / Bank Accounts", + NavItemId = 12, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 21, + Code = "accounts.cheque-books", + Href = "/dashboard/accounts/cheque-books", + Label = "Cheque Books", + NavItemId = 12, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 22, + Code = "accounts.received-cheques", + Href = "/dashboard/accounts/received-cheques", + Label = "Received Cheques", + NavItemId = 12, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 44, + Code = "accounts.expenses", + Href = "/dashboard/accounts/expenses", + Label = "Expenses", + NavItemId = 12, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 45, + Code = "accounts.expense-setup", + Href = "/dashboard/accounts/expense-setup", + Label = "Expense Setup", + NavItemId = 12, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 47, + Code = "accounts.bank-deposits", + Href = "/dashboard/accounts/bank-deposits", + Label = "Bank Deposits", + NavItemId = 12, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 51, + Code = "accounts.opening-balances", + Href = "/dashboard/accounts/opening-balances", + Label = "Opening Balances", + NavItemId = 12, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 27, + Code = "production.templates", + Href = "/dashboard/production/templates", + Label = "Templates", + NavItemId = 14, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 25, + Code = "production.runs", + Href = "/dashboard/production/runs", + Label = "Runs", + NavItemId = 14, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 26, + Code = "production.produced", + Href = "/dashboard/production/produced", + Label = "Produced", + NavItemId = 14, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 31, + Code = "stock.ledger", + Href = "/dashboard/stock/ledger", + Label = "Stock Ledger", + NavItemId = 6, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 32, + Code = "stock.enquiry", + Href = "/dashboard/stock/enquiry", + Label = "Stock Enquiry", + NavItemId = 6, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 33, + Code = "stock.valuation", + Href = "/dashboard/stock/valuation", + Label = "Valuation", + NavItemId = 6, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 34, + Code = "stock.transfers", + Href = "/dashboard/stock/transfers", + Label = "Transfers", + NavItemId = 6, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 35, + Code = "stock.adjustments", + Href = "/dashboard/stock/adjustments", + Label = "Adjustments", + NavItemId = 6, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 36, + Code = "stock.counts", + Href = "/dashboard/stock/counts", + Label = "Counts", + NavItemId = 6, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 37, + Code = "stock.reorder-alerts", + Href = "/dashboard/stock/reorder-alerts", + Label = "Reorder Alerts", + NavItemId = 6, + SortOrder = 7, + Status = "Active" + }, + new + { + SubNavItemId = 38, + Code = "stock.wastage", + Href = "/dashboard/stock/wastage", + Label = "Wastage", + NavItemId = 6, + SortOrder = 8, + Status = "Active" + }, + new + { + SubNavItemId = 52, + Code = "hrm.employees", + Href = "/dashboard/hrm/employees", + Label = "Employees", + NavItemId = 15, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 53, + Code = "hrm.attendance", + Href = "/dashboard/hrm/attendance", + Label = "Attendance", + NavItemId = 15, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 54, + Code = "hrm.leave", + Href = "/dashboard/hrm/leave", + Label = "Leave", + NavItemId = 15, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 55, + Code = "hrm.payroll", + Href = "/dashboard/hrm/payroll", + Label = "Payroll", + NavItemId = 15, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 56, + Code = "hrm.reports", + Href = "/dashboard/hrm/reports", + Label = "Reports", + NavItemId = 15, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 57, + Code = "hrm.settings", + Href = "/dashboard/hrm/settings", + Label = "HR Settings", + NavItemId = 15, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 58, + Code = "settings.audit-logs", + Href = "/dashboard/settings/audit-logs", + Label = "Audit Log", + NavItemId = 9, + SortOrder = 5, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Property("TicketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TicketId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("TicketId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("Status"); + + b.ToTable("support_tickets", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("MessageId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsFromAdmin") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("SenderUserId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.HasKey("MessageId"); + + b.HasIndex("TicketId", "CreatedAt"); + + b.ToTable("support_ticket_messages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxRate", b => + { + b.Property("TaxRateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxRateId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Percentage") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("TaxRateId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("tax_rates", (string)null); + + b.HasData( + new + { + TaxRateId = 1, + Code = "STANDARD", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Standard", + Percentage = 15m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 2, + Code = "ZERO", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Zero-Rated", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }, + new + { + TaxRateId = 3, + Code = "EXEMPT", + CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Name = "Exempt", + Percentage = 0m, + RowVersion = 1u, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Property("StageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("StageId")); + + b.Property("EstimatedMinutes") + .HasColumnType("int"); + + b.Property("FieldDefs") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("PosX") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("PosY") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RoleLabel") + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("TemplateId") + .HasColumnType("int"); + + b.HasKey("StageId"); + + b.HasIndex("TemplateId"); + + b.ToTable("template_stages", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UomId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("char(36)") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("varchar(320)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UserId"); + + b.HasIndex("AuthUserId") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = 1, + DisplayName = "System", + Status = "Active", + Username = "system" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.Property("VendorId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("varchar(3)") + .HasDefaultValue("LKR"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("VendorId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("RfqId") + .HasColumnType("int"); + + b.Property("VendorId") + .HasColumnType("int"); + + b.HasKey("QuotationId"); + + b.HasIndex("VendorId"); + + b.HasIndex("RfqId", "VendorId") + .IsUnique(); + + b.ToTable("vendor_quotations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.Property("QuotationLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("LeadDays") + .HasColumnType("int"); + + b.Property("QuotationId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("QuotationLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("QuotationId"); + + b.ToTable("vendor_quotation_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarehouseId")); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("BranchId") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Phone") + .HasColumnType("longtext"); + + b.HasKey("WarehouseId"); + + b.HasIndex("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyPeriod", b => + { + b.Property("WarrantyPeriodId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyPeriodId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Months") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyPeriodId"); + + b.HasIndex("Months") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("warranty_periods", null, t => + { + t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.Property("WarrantyRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WarrantyRegistrationId")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("PeriodMonths") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("SerialId") + .HasColumnType("int"); + + b.Property("SourceDocId") + .HasColumnType("int"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SourceLineId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("WarrantyRegistrationId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SerialId"); + + b.HasIndex("SourceDocType", "SourceDocId"); + + b.HasIndex("Status", "EndDate"); + + b.ToTable("warranty_registrations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndTime") + .HasColumnType("time(6)"); + + b.Property("GraceMinutes") + .HasColumnType("int"); + + b.Property("IsOvernight") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("int"); + + b.Property("StartTime") + .HasColumnType("time(6)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WorkingDaysMask") + .HasColumnType("int"); + + b.HasKey("WorkShiftId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_work_shifts", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "DefaultRole") + .WithMany() + .HasForeignKey("DefaultRoleId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultRole"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") + .WithMany() + .HasForeignKey("AttendanceUploadBatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceUploadBatch"); + + b.Navigation("Employee"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.AuditLog", b => + { + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BankDeposit", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany("Bins") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BranchConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany() + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("BundleSales") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSale", "BundleSale") + .WithMany("Lines") + .HasForeignKey("BundleSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSale"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b => + { + b.HasOne("ERPCore.Domain.Entities.BundleSaleTemplate", "BundleSaleTemplate") + .WithMany("Lines") + .HasForeignKey("BundleSaleTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("BundleSaleTemplate"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Branch"); + + b.Navigation("DefaultWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Department", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Employee", "HeadEmployee") + .WithMany() + .HasForeignKey("HeadEmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Department", "ParentDepartment") + .WithMany() + .HasForeignKey("ParentDepartmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("HeadEmployee"); + + b.Navigation("ParentDepartment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.DocumentApproval", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Decider") + .WithMany() + .HasForeignKey("DecidedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Decider"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Designation", "Designation") + .WithMany() + .HasForeignKey("DesignationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.EmploymentType", "EmploymentType") + .WithMany() + .HasForeignKey("EmploymentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Employee", "ReportingManager") + .WithMany() + .HasForeignKey("ReportingManagerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Employee", "UserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.WorkShift", "WorkShift") + .WithMany() + .HasForeignKey("WorkShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Department"); + + b.Navigation("Designation"); + + b.Navigation("EmploymentType"); + + b.Navigation("ReportingManager"); + + b.Navigation("User"); + + b.Navigation("WorkShift"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeBankDetail", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeDocument", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.HrDocumentType", "HrDocumentType") + .WithMany() + .HasForeignKey("HrDocumentTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("HrDocumentType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructureLine", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeSalaryStructure", "EmployeeSalaryStructure") + .WithMany("Lines") + .HasForeignKey("EmployeeSalaryStructureId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EmployeeSalaryStructure"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany() + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ExpenseTitle", "Title") + .WithMany() + .HasForeignKey("ExpenseTitleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + + b.Navigation("Category"); + + b.Navigation("Creator"); + + b.Navigation("Title"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpensePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Expense", "Expense") + .WithMany("Payments") + .HasForeignKey("ExpenseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Expense"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseTitle", b => + { + b.HasOne("ERPCore.Domain.Entities.ExpenseCategory", "Category") + .WithMany("Titles") + .HasForeignKey("ExpenseCategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GlAccountConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany() + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany("Lines") + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PoLine", "PoLine") + .WithMany() + .HasForeignKey("PoLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("Serials") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GrnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLineWarrantyNumber", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany("WarrantyNumbers") + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "BaseUom") + .WithMany() + .HasForeignKey("BaseUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "DefaultVendor") + .WithMany() + .HasForeignKey("DefaultVendorId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("SubCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TaxRate", "TaxRate") + .WithMany() + .HasForeignKey("TaxRateId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + + b.Navigation("TaxRate"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemImage", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemPriceHistory", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany() + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("ReorderSettings") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveBalance", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LeaveRequest", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoanInstallment", b => + { + b.HasOne("ERPCore.Domain.Entities.EmployeeLoan", "EmployeeLoan") + .WithMany("Installments") + .HasForeignKey("EmployeeLoanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany() + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("EmployeeLoan"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.LoginActivity", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfigModule", b => + { + b.HasOne("ERPCore.Domain.Entities.NotificationConfig", "NotificationConfig") + .WithMany("Modules") + .HasForeignKey("NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("ERPCore.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("Lines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("StockLines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Run"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PayrollRun", "PayrollRun") + .WithMany("Lines") + .HasForeignKey("PayrollRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("PayrollRun"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLineComponent", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithMany("Components") + .HasForeignKey("PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalaryComponent", "SalaryComponent") + .WithMany() + .HasForeignKey("SalaryComponentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("PayrollLine"); + + b.Navigation("SalaryComponent"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Payslip", b => + { + b.HasOne("ERPCore.Domain.Entities.PayrollLine", "PayrollLine") + .WithOne() + .HasForeignKey("ERPCore.Domain.Entities.Payslip", "PayrollLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PayrollLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Permission", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany() + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ERPCore.Domain.Entities.SubNavItem", "SubNavItem") + .WithMany() + .HasForeignKey("SubNavItemId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("NavItem"); + + b.Navigation("SubNavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseOrder", "PurchaseOrder") + .WithMany("Lines") + .HasForeignKey("PoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PriceAdjustment", "PriceAdjustment") + .WithMany("Lines") + .HasForeignKey("PriceAdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceAdjustment"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "CancelReason") + .WithMany() + .HasForeignKey("CancelReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Bin", "OutputBin") + .WithMany() + .HasForeignKey("OutputBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Runs") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CancelReason"); + + b.Navigation("Creator"); + + b.Navigation("OutputBin"); + + b.Navigation("Template"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Grn", "Grn") + .WithMany() + .HasForeignKey("GrnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Lines") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grn"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseInvoice", "PurchaseInvoice") + .WithMany("Payments") + .HasForeignKey("PurchaseInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("PurchaseInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApproverUserId"); + + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Requisition"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.PurchaseReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Return"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RequisitionLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany("Lines") + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Requisition"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Lines") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Rfq"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RolePermission", b => + { + b.HasOne("ERPCore.Domain.Entities.Permission", "Permission") + .WithMany() + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStage", "ChildRunStage") + .WithMany() + .HasForeignKey("ChildRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "ParentRunStage") + .WithMany() + .HasForeignKey("ParentRunStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Edges") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRunStage"); + + b.Navigation("ParentRunStage"); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Stages") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "TemplateStage") + .WithMany() + .HasForeignKey("TemplateStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Run"); + + b.Navigation("TemplateStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageEvent", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionRun", "Run") + .WithMany("Events") + .HasForeignKey("RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Events") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Run"); + + b.Navigation("RunStage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.RunStageOutput", "FromRunOutput") + .WithMany() + .HasForeignKey("FromRunOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Inputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromRunOutput"); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.RunStage", "RunStage") + .WithMany("Outputs") + .HasForeignKey("RunStageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ScrapReason") + .WithMany() + .HasForeignKey("ScrapReasonCodeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("RunStage"); + + b.Navigation("ScrapReason"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "CashCustomer") + .WithMany() + .HasForeignKey("CashCustomerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CashCustomer"); + + b.Navigation("DefaultWarehouse"); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "ClosedByUser") + .WithMany() + .HasForeignKey("ClosedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("ClosedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatorUserId"); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Lines") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany("Serials") + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesInvoiceLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoicePayment", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoice", "SalesInvoice") + .WithMany("Payments") + .HasForeignKey("SalesInvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("SalesInvoice"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesLineAllocation", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Customer"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesReturn", "Return") + .WithMany("Lines") + .HasForeignKey("ReturnId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesInvoiceLine", "SalesInvoiceLine") + .WithMany() + .HasForeignKey("SalesInvoiceLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("Return"); + + b.Navigation("SalesInvoiceLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesReturnLine", "ReturnLine") + .WithMany("Serials") + .HasForeignKey("ReturnLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ReturnLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "CashierUser") + .WithMany() + .HasForeignKey("CashierUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesDayEnd", "DayEnd") + .WithMany("Slips") + .HasForeignKey("DayEndId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("DayEnd"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.SalesSlip", "SalesSlip") + .WithMany("Lines") + .HasForeignKey("SalesSlipId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.SalesSlipLine", "SalesSlipLine") + .WithMany("Serials") + .HasForeignKey("SalesSlipLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SalesSlipLine"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Bin", "Bin") + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Bin"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SerialMovement", b => + { + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageEdge", b => + { + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ChildStage") + .WithMany() + .HasForeignKey("ChildStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "ParentStage") + .WithMany() + .HasForeignKey("ParentStageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Edges") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildStage"); + + b.Navigation("ParentStage"); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageInput", b => + { + b.HasOne("ERPCore.Domain.Entities.StageOutput", "FromOutput") + .WithMany() + .HasForeignKey("FromOutputId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Inputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromOutput"); + + b.Navigation("Item"); + + b.Navigation("Stage"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StageOutput", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.TemplateStage", "Stage") + .WithMany("Outputs") + .HasForeignKey("StageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("Stage"); + + b.Navigation("Uom"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.ReasonCode", "ReasonCode") + .WithMany() + .HasForeignKey("ReasonCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("ReasonCode"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.HasOne("ERPCore.Domain.Entities.StockAdjustment", "Adjustment") + .WithMany("Lines") + .HasForeignKey("AdjustmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockCount", "Count") + .WithMany("Lines") + .HasForeignKey("CountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Batch"); + + b.Navigation("Count"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", "Batch") + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.GrnLine", "GrnLine") + .WithMany() + .HasForeignKey("GrnLineId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("GrnLine"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLedger", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("BinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", null) + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", null) + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DestWarehouse") + .WithMany() + .HasForeignKey("DestWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "SrcWarehouse") + .WithMany() + .HasForeignKey("SrcWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + + b.Navigation("Creator"); + + b.Navigation("DestWarehouse"); + + b.Navigation("SrcWarehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("DestBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Bin", null) + .WithMany() + .HasForeignKey("SrcBinId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.StockTransfer", "Transfer") + .WithMany("Lines") + .HasForeignKey("TransferId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Transfer"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLineSerial", b => + { + b.HasOne("ERPCore.Domain.Entities.Batch", null) + .WithMany() + .HasForeignKey("BatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.StockTransferLine", "TransferLine") + .WithMany("Serials") + .HasForeignKey("TransferLineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Serial"); + + b.Navigation("TransferLine"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b => + { + b.HasOne("ERPCore.Domain.Entities.Category", "Category") + .WithMany("SubCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SubNavItem", b => + { + b.HasOne("ERPCore.Domain.Entities.NavItem", "NavItem") + .WithMany("Children") + .HasForeignKey("NavItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NavItem"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicketMessage", b => + { + b.HasOne("ERPCore.Domain.Entities.SupportTicket", "Ticket") + .WithMany("Messages") + .HasForeignKey("TicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ticket"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.HasOne("ERPCore.Domain.Entities.ProductionTemplate", "Template") + .WithMany("Stages") + .HasForeignKey("TemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Template"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Vendor", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.HasOne("ERPCore.Domain.Entities.Rfq", "Rfq") + .WithMany("Quotations") + .HasForeignKey("RfqId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Vendor", "Vendor") + .WithMany() + .HasForeignKey("VendorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Rfq"); + + b.Navigation("Vendor"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotationLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.VendorQuotation", "Quotation") + .WithMany("Lines") + .HasForeignKey("QuotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Quotation"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Branch"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WarrantyRegistration", b => + { + b.HasOne("ERPCore.Domain.Entities.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Serial", "Serial") + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Customer"); + + b.Navigation("Item"); + + b.Navigation("Serial"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Navigation("SubCategories"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeLoan", b => + { + b.Navigation("Installments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.EmployeeSalaryStructure", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Expense", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ExpenseCategory", b => + { + b.Navigation("Titles"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Navigation("Serials"); + + b.Navigation("WarrantyNumbers"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NotificationConfig", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Navigation("Lines"); + + b.Navigation("StockLines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Navigation("Components"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollRun", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PriceAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionRun", b => + { + b.Navigation("Edges"); + + b.Navigation("Events"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductionTemplate", b => + { + b.Navigation("Edges"); + + b.Navigation("Runs"); + + b.Navigation("Stages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Requisition", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Rfq", b => + { + b.Navigation("Lines"); + + b.Navigation("Quotations"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RunStage", b => + { + b.Navigation("Events"); + + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesDayEnd", b => + { + b.Navigation("BundleSales"); + + b.Navigation("Slips"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Navigation("Lines"); + + b.Navigation("Payments"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesReturnLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransfer", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockTransferLine", b => + { + b.Navigation("Serials"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SupportTicket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TemplateStage", b => + { + b.Navigation("Inputs"); + + b.Navigation("Outputs"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.cs b/Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.cs new file mode 100644 index 0000000..ca3d6cf --- /dev/null +++ b/Backend/ERPCore/Migrations/20261018070404_AddCustomerFieldSwitches.cs @@ -0,0 +1,95 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddCustomerFieldSwitches : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CustomerAddressEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "CustomerCityEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "CustomerCountryEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "CustomerCreditDaysEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "CustomerCreditLimitEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "CustomerEmailEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "CustomerPhoneEnabled", + table: "sales_config", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CustomerAddressEnabled", + table: "sales_config"); + + migrationBuilder.DropColumn( + name: "CustomerCityEnabled", + table: "sales_config"); + + migrationBuilder.DropColumn( + name: "CustomerCountryEnabled", + table: "sales_config"); + + migrationBuilder.DropColumn( + name: "CustomerCreditDaysEnabled", + table: "sales_config"); + + migrationBuilder.DropColumn( + name: "CustomerCreditLimitEnabled", + table: "sales_config"); + + migrationBuilder.DropColumn( + name: "CustomerEmailEnabled", + table: "sales_config"); + + migrationBuilder.DropColumn( + name: "CustomerPhoneEnabled", + table: "sales_config"); + } + } +} diff --git a/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs b/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs index 5e03eaf..9d0c752 100644 --- a/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs +++ b/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using ERPCore.Infra.Persistence; using Microsoft.EntityFrameworkCore; @@ -23,12 +23,42 @@ namespace ERPCore.Migrations MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("DefaultRoleId") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.HasKey("ConfigId"); + + b.HasIndex("DefaultRoleId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("access_config", null, t => + { + t.HasCheckConstraint("ck_access_config_singleton", "`ConfigId` = 1"); + }); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.ApprovalConfig", b => { b.Property("ApprovalConfigId") .HasColumnType("int"); - b.Property("Level2RoleCodes") + b.Property("Level2RoleIds") .HasMaxLength(500) .HasColumnType("varchar(500)"); @@ -3382,6 +3412,24 @@ namespace ERPCore.Migrations Label = "Production", SortOrder = 14, Status = "Active" + }, + new + { + NavItemId = 15, + Code = "hrm", + Href = "/dashboard/hrm", + Label = "Human Resources", + SortOrder = 15, + Status = "Active" + }, + new + { + NavItemId = 16, + Code = "sales-returns", + Href = "/dashboard/sales/sales-returns", + Label = "Sales Returns", + SortOrder = 16, + Status = "Active" }); }); @@ -3556,6 +3604,127 @@ namespace ERPCore.Migrations b.ToTable("number_sequences", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.Property("OpeningBalanceLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceLineId")); + + b.Property("AccountCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.HasKey("OpeningBalanceLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.ToTable("opening_balance_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Property("OpeningBalanceRunId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningBalanceRunId")); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("varchar(3)"); + + b.Property("CutoverDate") + .HasColumnType("date"); + + b.Property("GlJournalNo") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("OnceKey") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("PostedAt") + .HasColumnType("datetime(6)"); + + b.Property("PostedBy") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.HasKey("OpeningBalanceRunId"); + + b.HasIndex("Kind"); + + b.HasIndex("OnceKey") + .IsUnique(); + + b.ToTable("opening_balance_runs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.Property("OpeningStockLineId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("OpeningStockLineId")); + + b.Property("ItemId") + .HasColumnType("int"); + + b.Property("OpeningBalanceRunId") + .HasColumnType("int"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("int"); + + b.HasKey("OpeningStockLineId"); + + b.HasIndex("OpeningBalanceRunId"); + + b.HasIndex("WarehouseId"); + + b.HasIndex("ItemId", "WarehouseId"); + + b.ToTable("opening_stock_lines", (string)null); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => { b.Property("PayrollLineId") @@ -4226,6 +4395,78 @@ namespace ERPCore.Migrations PermissionId = 59, Code = "NAV:settings.approvals", SubNavItemId = 49 + }, + new + { + PermissionId = 60, + Code = "NAV:procurement.po-approvals", + SubNavItemId = 50 + }, + new + { + PermissionId = 61, + Code = "NAV:accounts.opening-balances", + SubNavItemId = 51 + }, + new + { + PermissionId = 62, + Code = "NAV:hrm", + NavItemId = 15 + }, + new + { + PermissionId = 63, + Code = "NAV:sales-returns", + NavItemId = 16 + }, + new + { + PermissionId = 64, + Code = "NAV:hrm.employees", + SubNavItemId = 52 + }, + new + { + PermissionId = 65, + Code = "NAV:hrm.attendance", + SubNavItemId = 53 + }, + new + { + PermissionId = 66, + Code = "NAV:hrm.leave", + SubNavItemId = 54 + }, + new + { + PermissionId = 67, + Code = "NAV:hrm.payroll", + SubNavItemId = 55 + }, + new + { + PermissionId = 68, + Code = "NAV:hrm.reports", + SubNavItemId = 56 + }, + new + { + PermissionId = 69, + Code = "NAV:hrm.settings", + SubNavItemId = 57 + }, + new + { + PermissionId = 70, + Code = "NAV:settings.audit-logs", + SubNavItemId = 58 + }, + new + { + PermissionId = 71, + Code = "NAV:settings.notification-config", + SubNavItemId = 24 }); }); @@ -4255,6 +4496,12 @@ namespace ERPCore.Migrations .HasPrecision(9, 4) .HasColumnType("decimal(9,4)"); + b.Property("TaxAmount") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(0m); + b.Property("UnitPrice") .HasPrecision(18, 4) .HasColumnType("decimal(18,4)"); @@ -4398,6 +4645,58 @@ namespace ERPCore.Migrations b.ToTable("price_adjustment_lines", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.Property("ConfigId") + .HasColumnType("int"); + + b.Property("PoTaxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("int unsigned"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("int"); + + b.Property("VendorAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("VendorContactMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasDefaultValue("None"); + + b.Property("VendorPaymentTermsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("VendorTaxRegEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.HasKey("ConfigId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("procurement_config", null, t => + { + t.HasCheckConstraint("ck_procurement_config_singleton", "`ConfigId` = 1"); + }); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => { b.Property("ConfigId") @@ -5121,7 +5420,7 @@ namespace ERPCore.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("RoleId")); - b.Property("AuthRoleId") + b.Property("AuthRoleId") .HasColumnType("char(36)") .HasColumnName("auth_role_id"); @@ -5133,6 +5432,11 @@ namespace ERPCore.Migrations b.Property("CreatedAt") .HasColumnType("datetime(6)"); + b.Property("IsAdmin") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + b.Property("IsSystemRole") .ValueGeneratedOnAdd() .HasColumnType("tinyint(1)") @@ -5530,6 +5834,41 @@ namespace ERPCore.Migrations b.Property("CashCustomerId") .HasColumnType("int"); + b.Property("CustomerAddressEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCityEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCountryEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCreditDaysEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerCreditLimitEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerEmailEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CustomerPhoneEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + b.Property("DefaultWarehouseId") .HasColumnType("int"); @@ -7403,6 +7742,16 @@ namespace ERPCore.Migrations Status = "Active" }, new + { + SubNavItemId = 50, + Code = "procurement.po-approvals", + Href = "/dashboard/procurement/purchase-orders/approvals", + Label = "PO Approval Queue", + NavItemId = 4, + SortOrder = 6, + Status = "Active" + }, + new { SubNavItemId = 20, Code = "procurement.purchase-returns", @@ -7553,6 +7902,16 @@ namespace ERPCore.Migrations Status = "Active" }, new + { + SubNavItemId = 51, + Code = "accounts.opening-balances", + Href = "/dashboard/accounts/opening-balances", + Label = "Opening Balances", + NavItemId = 12, + SortOrder = 7, + Status = "Active" + }, + new { SubNavItemId = 27, Code = "production.templates", @@ -7661,6 +8020,76 @@ namespace ERPCore.Migrations NavItemId = 6, SortOrder = 8, Status = "Active" + }, + new + { + SubNavItemId = 52, + Code = "hrm.employees", + Href = "/dashboard/hrm/employees", + Label = "Employees", + NavItemId = 15, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 53, + Code = "hrm.attendance", + Href = "/dashboard/hrm/attendance", + Label = "Attendance", + NavItemId = 15, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 54, + Code = "hrm.leave", + Href = "/dashboard/hrm/leave", + Label = "Leave", + NavItemId = 15, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 55, + Code = "hrm.payroll", + Href = "/dashboard/hrm/payroll", + Label = "Payroll", + NavItemId = 15, + SortOrder = 4, + Status = "Active" + }, + new + { + SubNavItemId = 56, + Code = "hrm.reports", + Href = "/dashboard/hrm/reports", + Label = "Reports", + NavItemId = 15, + SortOrder = 5, + Status = "Active" + }, + new + { + SubNavItemId = 57, + Code = "hrm.settings", + Href = "/dashboard/hrm/settings", + Label = "HR Settings", + NavItemId = 15, + SortOrder = 6, + Status = "Active" + }, + new + { + SubNavItemId = 58, + Code = "settings.audit-logs", + Href = "/dashboard/settings/audit-logs", + Label = "Audit Log", + NavItemId = 9, + SortOrder = 5, + Status = "Active" }); }); @@ -8005,6 +8434,10 @@ namespace ERPCore.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("VendorId")); + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + b.Property("BranchId") .HasColumnType("int"); @@ -8023,6 +8456,14 @@ namespace ERPCore.Migrations .HasColumnType("varchar(3)") .HasDefaultValue("LKR"); + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -8334,6 +8775,23 @@ namespace ERPCore.Migrations b.ToTable("hr_work_shifts", (string)null); }); + modelBuilder.Entity("ERPCore.Domain.Entities.AccessConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.Role", "DefaultRole") + .WithMany() + .HasForeignKey("DefaultRoleId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("DefaultRole"); + + b.Navigation("UpdatedByUser"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => { b.HasOne("ERPCore.Domain.Entities.AttendanceUploadBatch", "AttendanceUploadBatch") @@ -9127,6 +9585,44 @@ namespace ERPCore.Migrations b.Navigation("Branch"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceLine", b => + { + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("Lines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningStockLine", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.OpeningBalanceRun", "Run") + .WithMany("StockLines") + .HasForeignKey("OpeningBalanceRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Run"); + + b.Navigation("Warehouse"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => { b.HasOne("ERPCore.Domain.Entities.Employee", "Employee") @@ -9259,6 +9755,16 @@ namespace ERPCore.Migrations b.Navigation("PriceAdjustment"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.ProcurementConfig", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => { b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") @@ -10693,6 +11199,13 @@ namespace ERPCore.Migrations b.Navigation("Modules"); }); + modelBuilder.Entity("ERPCore.Domain.Entities.OpeningBalanceRun", b => + { + b.Navigation("Lines"); + + b.Navigation("StockLines"); + }); + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => { b.Navigation("Components"); diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index 9a74631..b58ed8e 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -28,7 +28,12 @@ builder.Host.UseSerilog((ctx, cfg) => cfg .WriteTo.File("logs/erpcore-.log", rollingInterval: RollingInterval.Day)); // Controllers + JSON: serialize enums as their string names (docs/11 §8, camelCase). -builder.Services.AddControllers(o => o.Filters.Add()) +builder.Services.AddControllers(o => + { + o.Filters.Add(); + // CSRF double-submit check on every mutating, cookie-authenticated endpoint (docs/02-SECURITY.md §B.2). + o.Filters.Add(); + }) .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); // EF Core + MariaDB (Pomelo). The server version is pinned explicitly rather than using @@ -119,6 +124,10 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/Backend/ERPCore/Services/AccessConfigService.cs b/Backend/ERPCore/Services/AccessConfigService.cs new file mode 100644 index 0000000..d32cbd8 --- /dev/null +++ b/Backend/ERPCore/Services/AccessConfigService.cs @@ -0,0 +1,79 @@ +using ERPCore.Common.Http; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Config; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Access configuration over the singleton row seeded by DataSeeder. The default role is what every first sign-in +/// (and every role-less person) receives, so it is held to a hard rule: it must exist, be active, and NEVER be an +/// administrator role — otherwise anyone who can register would become an administrator. +/// +public sealed class AccessConfigService : IAccessConfigService +{ + private readonly IRepository _config; + private readonly IRepository _roles; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public AccessConfigService(IRepository config, IRepository roles, ICurrentUser currentUser, IUnitOfWork uow) + { + _config = config; + _roles = roles; + _currentUser = currentUser; + _uow = uow; + } + + public async Task> GetAsync(CancellationToken ct = default) + { + var config = await _config.Query().AsNoTracking().Include(c => c.DefaultRole) + .FirstOrDefaultAsync(c => c.ConfigId == AccessConfig.SingletonId, ct) + ?? throw new NotFoundException("Access configuration has not been seeded."); + + return new ETagged(Map(config), config.RowVersion); + } + + public async Task> UpdateAsync( + UpdateAccessConfigRequest request, uint expectedRowVersion, CancellationToken ct = default) + { + var config = await _config.GetByIdAsync(AccessConfig.SingletonId, ct) + ?? throw new NotFoundException("Access configuration has not been seeded."); + + if (config.RowVersion != expectedRowVersion) + throw new DomainException(ErrorCodes.ConcurrencyConflict, "The configuration was modified by another request.", 412); + + if (request.DefaultRoleId is int roleId) + { + var role = await _roles.GetByIdAsync(roleId, ct) + ?? throw new NotFoundException($"Role {roleId} was not found."); + if (role.Status != EntityStatus.Active) + throw new DomainException(ErrorCodes.Validation, $"Role '{role.Name}' is inactive and cannot be the default.", 422); + if (role.IsAdmin) + throw new DomainException(ErrorCodes.Validation, "The administrator role cannot be the default role — everyone who signs in would become an administrator.", 422); + } + + config.DefaultRoleId = request.DefaultRoleId; + config.UpdatedAt = DateTime.UtcNow; + config.UpdatedBy = _currentUser.AuditUserId; + + try + { + await _uow.SaveChangesAsync(ct); + } + catch (DbUpdateConcurrencyException) + { + throw new DomainException(ErrorCodes.ConcurrencyConflict, "The configuration was modified by another request.", 412); + } + + return await GetAsync(ct); + } + + private static AccessConfigDto Map(AccessConfig c) => new(c.DefaultRoleId, c.DefaultRole?.Name, c.UpdatedAt, c.UpdatedBy); +} diff --git a/Backend/ERPCore/Services/ApprovalInboxService.cs b/Backend/ERPCore/Services/ApprovalInboxService.cs index 89884ff..c5819d9 100644 --- a/Backend/ERPCore/Services/ApprovalInboxService.cs +++ b/Backend/ERPCore/Services/ApprovalInboxService.cs @@ -89,5 +89,5 @@ public sealed class ApprovalInboxService } private async Task CanApproveScreenAsync(string navCode, CancellationToken ct) - => _currentUser.IsAdmin || await _nav.HasActionAsync(_currentUser.RoleCode, navCode, RolePermissionAction.Approve, ct); + => _currentUser.IsAdmin || await _nav.HasActionAsync(_currentUser.RoleId, navCode, RolePermissionAction.Approve, ct); } diff --git a/Backend/ERPCore/Services/ApprovalService.cs b/Backend/ERPCore/Services/ApprovalService.cs index de121c1..18ac1e9 100644 --- a/Backend/ERPCore/Services/ApprovalService.cs +++ b/Backend/ERPCore/Services/ApprovalService.cs @@ -18,20 +18,21 @@ namespace ERPCore.Services; /// public sealed class ApprovalService : IApprovalService { - private const string AdminRoleCode = "Admin"; - private readonly IRepository _config; private readonly IRepository _approvals; private readonly IRepository _users; private readonly IRepository _rolePermissions; + private readonly IRepository _roles; private readonly ICurrentUser _currentUser; private readonly INotificationService _notifications; private readonly IUnitOfWork _uow; public ApprovalService( IRepository config, IRepository approvals, IRepository users, - IRepository rolePermissions, ICurrentUser currentUser, INotificationService notifications, IUnitOfWork uow) + IRepository rolePermissions, IRepository roles, ICurrentUser currentUser, + INotificationService notifications, IUnitOfWork uow) { + _roles = roles; _config = config; _approvals = approvals; _users = users; @@ -56,8 +57,15 @@ public sealed class ApprovalService : IApprovalService config.StockAdjustmentLevel2ThresholdValue = Math.Max(0m, request.StockAdjustmentLevel2ThresholdValue); config.PriceAdjustmentLevels = Math.Clamp(request.PriceAdjustmentLevels, 0, 2); config.PriceAdjustmentLevel2ThresholdPercent = Math.Max(0m, request.PriceAdjustmentLevel2ThresholdPercent); - var roles = request.Level2RoleCodes.Select(r => r.Trim()).Where(r => r.Length > 0).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - config.Level2RoleCodes = roles.Count == 0 ? null : string.Join(",", roles); + var roleIds = request.Level2RoleIds.Where(id => id > 0).Distinct().ToList(); + if (roleIds.Count > 0) + { + var known = await _roles.Query().AsNoTracking().Where(r => roleIds.Contains(r.RoleId)).Select(r => r.RoleId).ToListAsync(ct); + var unknown = roleIds.Except(known).ToList(); + if (unknown.Count > 0) + throw new DomainException(ErrorCodes.Validation, $"Role(s) not found: {string.Join(", ", unknown)}.", 422); + } + config.Level2RoleIds = roleIds.Count == 0 ? null : string.Join(",", roleIds); config.UpdatedAt = DateTime.UtcNow; config.UpdatedBy = _currentUser.AuditUserId; @@ -117,9 +125,13 @@ public sealed class ApprovalService : IApprovalService return ("You gave the first approval — a different person has to give the second.", ErrorCodes.ApprovalSamePerson); if (target.CurrentLevel >= 2) { - var roles = Level2Roles(await LoadConfigAsync(ct)); - if (roles.Count > 0 && !_currentUser.IsAdmin && !roles.Contains(_currentUser.RoleCode ?? string.Empty)) - return ($"Only {string.Join(", ", roles)} can give the second approval.", ErrorCodes.ApprovalRoleRequired); + var allowed = Level2RoleIds(await LoadConfigAsync(ct)); + if (allowed.Count > 0 && !_currentUser.IsAdmin && !(_currentUser.RoleId is int roleId && allowed.Contains(roleId))) + { + var names = await _roles.Query().AsNoTracking().Where(r => allowed.Contains(r.RoleId)).OrderBy(r => r.Name) + .Select(r => r.Name).ToListAsync(ct); + return ($"Only {string.Join(", ", names)} can give the second approval.", ErrorCodes.ApprovalRoleRequired); + } } return (null, null); } @@ -141,15 +153,15 @@ public sealed class ApprovalService : IApprovalService var approverRoleIds = grants.Where(g => g.Actions.HasFlag(RolePermissionAction.Approve)).Select(g => g.RoleId).ToHashSet(); var candidates = await _users.Query().AsNoTracking() - .Where(u => u.Status == EntityStatus.Active && u.RoleId != null) - .Select(u => new { u.UserId, RoleId = u.RoleId!.Value, RoleCode = u.Role!.Code }) + .Where(u => u.Status == EntityStatus.Active && u.RoleId != null && u.Role!.Status == EntityStatus.Active) + .Select(u => new { u.UserId, RoleId = u.RoleId!.Value, RoleIsAdmin = u.Role!.IsAdmin }) .ToListAsync(ct); - var level2Roles = target.CurrentLevel >= 2 ? Level2Roles(await LoadConfigAsync(ct)) : []; + var level2Roles = target.CurrentLevel >= 2 ? Level2RoleIds(await LoadConfigAsync(ct)) : []; return candidates - .Where(u => string.Equals(u.RoleCode, AdminRoleCode, StringComparison.OrdinalIgnoreCase) || approverRoleIds.Contains(u.RoleId)) + .Where(u => u.RoleIsAdmin || approverRoleIds.Contains(u.RoleId)) .Where(u => u.UserId != target.RequestedBy && u.UserId != target.Level1ApprovedBy) - .Where(u => level2Roles.Count == 0 || level2Roles.Contains(u.RoleCode) || string.Equals(u.RoleCode, AdminRoleCode, StringComparison.OrdinalIgnoreCase)) + .Where(u => level2Roles.Count == 0 || level2Roles.Contains(u.RoleId) || u.RoleIsAdmin) .Select(u => u.UserId) .ToList(); } @@ -166,13 +178,15 @@ public sealed class ApprovalService : IApprovalService => await _config.Query().AsNoTracking().FirstOrDefaultAsync(c => c.ApprovalConfigId == ApprovalConfig.SingletonId, ct) ?? new ApprovalConfig { ApprovalConfigId = ApprovalConfig.SingletonId }; - private static HashSet Level2Roles(ApprovalConfig c) - => (c.Level2RoleCodes ?? string.Empty) + private static HashSet Level2RoleIds(ApprovalConfig c) + => (c.Level2RoleIds ?? string.Empty) .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .ToHashSet(StringComparer.OrdinalIgnoreCase); + .Select(s => int.TryParse(s, out var id) ? id : 0) + .Where(id => id > 0) + .ToHashSet(); private static ApprovalConfigDto Map(ApprovalConfig c) => new( c.StockAdjustmentLevels, c.StockAdjustmentLevel2ThresholdValue, c.PriceAdjustmentLevels, c.PriceAdjustmentLevel2ThresholdPercent, - Level2Roles(c).ToList(), c.UpdatedAt); + Level2RoleIds(c).Order().ToList(), c.UpdatedAt); } diff --git a/Backend/ERPCore/Services/Auth/AuthUserService.cs b/Backend/ERPCore/Services/Auth/AuthUserService.cs index a6ce0de..a5dad80 100644 --- a/Backend/ERPCore/Services/Auth/AuthUserService.cs +++ b/Backend/ERPCore/Services/Auth/AuthUserService.cs @@ -15,6 +15,9 @@ public sealed class AuthUserService : IAuthUserService public async Task RegisterAsync(RegisterRequest request, CancellationToken ct = default) { request.UserId ??= Guid.NewGuid(); + // A self-registering person never chooses their role (this used to accept any AuthHex role id, including an + // admin one). They sign in with the ERP's default role until an administrator assigns a real one. + request.RoleId = null; var result = await _authHex.RegisterAsync(request, ct); return ToSessionResult(result); } diff --git a/Backend/ERPCore/Services/BankDepositService.cs b/Backend/ERPCore/Services/BankDepositService.cs index 73a75ad..46cc224 100644 --- a/Backend/ERPCore/Services/BankDepositService.cs +++ b/Backend/ERPCore/Services/BankDepositService.cs @@ -209,7 +209,12 @@ public sealed class BankDepositService : IBankDepositService private async Task<(int BranchId, int? CashScope)> ResolveBranchAsync(int requested, CancellationToken ct) { if (_currentBranch.IsActive && _currentBranch.BranchId is { } own) + { + // A branch-less person in a branching deployment has no branch to deposit for. + if (own == CurrentBranchContext.NoBranchId) + throw new DomainException(ErrorCodes.Validation, "Your account is not linked to a branch, so you cannot record bank deposits. Ask an administrator to link your employee record to a branch.", 403); return (own, own); + } var branchId = await _branchConfig.ResolveBranchIdAsync(requested, ct); return await _branchConfig.IsBranchingEnabledAsync(ct) ? (branchId, branchId) : (branchId, null); diff --git a/Backend/ERPCore/Services/CustomerService.cs b/Backend/ERPCore/Services/CustomerService.cs index 35931c9..e1a376f 100644 --- a/Backend/ERPCore/Services/CustomerService.cs +++ b/Backend/ERPCore/Services/CustomerService.cs @@ -139,6 +139,7 @@ public sealed class CustomerService : ICustomerService if (!exists) throw new NotFoundException($"Warehouse {request.DefaultWarehouseId} was not found."); } + var fields = await GetFieldSwitchesAsync(ct); var branchId = await _branchConfig.ResolveBranchIdAsync(request.BranchId, ct); var customer = new Customer { @@ -146,15 +147,15 @@ public sealed class CustomerService : ICustomerService CustomerType = request.CustomerType, Name = name, DisplayName = Normalize(request.DisplayName), - Phone = Normalize(request.Phone), - Email = Normalize(request.Email), - AddressLine1 = Normalize(request.AddressLine1), - AddressLine2 = Normalize(request.AddressLine2), - City = Normalize(request.City), - Country = Normalize(request.Country), + Phone = Gate(fields.Phone, request.Phone, null, "phone numbers"), + Email = Gate(fields.Email, request.Email, null, "email addresses"), + AddressLine1 = Gate(fields.Address, request.AddressLine1, null, "addresses"), + AddressLine2 = Gate(fields.Address, request.AddressLine2, null, "addresses"), + City = Gate(fields.City, request.City, null, "cities"), + Country = Gate(fields.Country, request.Country, null, "countries"), TaxRegistrationNo = Normalize(request.TaxRegistrationNo), - CreditLimit = request.CreditLimit, - CreditDays = request.CreditDays, + CreditLimit = Gate(fields.CreditLimit, request.CreditLimit, 0m, "credit limits"), + CreditDays = Gate(fields.CreditDays, request.CreditDays, 0, "credit days"), DefaultWarehouseId = request.DefaultWarehouseId, Status = EntityStatus.Active, BranchId = branchId, @@ -197,15 +198,17 @@ public sealed class CustomerService : ICustomerService customer.CustomerType = request.CustomerType; customer.Name = name; customer.DisplayName = Normalize(request.DisplayName); - customer.Phone = Normalize(request.Phone); - customer.Email = Normalize(request.Email); - customer.AddressLine1 = Normalize(request.AddressLine1); - customer.AddressLine2 = Normalize(request.AddressLine2); - customer.City = Normalize(request.City); - customer.Country = Normalize(request.Country); + // A field switched off in Sales Configuration keeps whatever is stored; only a *different* value is refused. + var fields = await GetFieldSwitchesAsync(ct); + customer.Phone = Gate(fields.Phone, request.Phone, customer.Phone, "phone numbers"); + customer.Email = Gate(fields.Email, request.Email, customer.Email, "email addresses"); + customer.AddressLine1 = Gate(fields.Address, request.AddressLine1, customer.AddressLine1, "addresses"); + customer.AddressLine2 = Gate(fields.Address, request.AddressLine2, customer.AddressLine2, "addresses"); + customer.City = Gate(fields.City, request.City, customer.City, "cities"); + customer.Country = Gate(fields.Country, request.Country, customer.Country, "countries"); customer.TaxRegistrationNo = Normalize(request.TaxRegistrationNo); - customer.CreditLimit = request.CreditLimit; - customer.CreditDays = request.CreditDays; + customer.CreditLimit = Gate(fields.CreditLimit, request.CreditLimit, customer.CreditLimit, "credit limits"); + customer.CreditDays = Gate(fields.CreditDays, request.CreditDays, customer.CreditDays, "credit days"); customer.DefaultWarehouseId = request.DefaultWarehouseId; customer.BranchId = await _branchConfig.ResolveBranchIdAsync(request.BranchId, ct); customer.UpdatedAt = DateTime.UtcNow; @@ -238,4 +241,38 @@ public sealed class CustomerService : ICustomerService c.CreditLimit, c.CreditDays, c.DefaultWarehouseId, c.Status, c.BranchId, c.CreatedAt, c.UpdatedAt); private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + + /// Which optional customer fields Sales Configuration has switched on (all on when the row is missing). + private sealed record FieldSwitches(bool Phone, bool Email, bool Address, bool City, bool Country, bool CreditLimit, bool CreditDays); + + private async Task GetFieldSwitchesAsync(CancellationToken ct) => + await _salesConfig.Query().AsNoTracking() + .Where(c => c.ConfigId == SalesConfig.SingletonId) + .Select(c => new FieldSwitches( + c.CustomerPhoneEnabled, c.CustomerEmailEnabled, c.CustomerAddressEnabled, c.CustomerCityEnabled, + c.CustomerCountryEnabled, c.CustomerCreditLimitEnabled, c.CustomerCreditDaysEnabled)) + .FirstOrDefaultAsync(ct) + ?? new FieldSwitches(true, true, true, true, true, true, true); + + /// On: the (trimmed) requested value. Off: the stored value is kept; a different value is a 422 CONFIG_DISABLED + /// (the UI hides the field, so only a stale or hand-built request gets here). + private static string? Gate(bool enabled, string? requested, string? stored, string what) + { + var value = Normalize(requested); + if (enabled) return value; + if (value is null || string.Equals(value, stored, StringComparison.Ordinal)) return stored; + throw new DomainException(ErrorCodes.ConfigDisabled, $"Customer {what} are turned off in Sales configuration.", 422); + } + + private static decimal Gate(bool enabled, decimal requested, decimal stored, string what) + { + if (enabled || requested == 0m || requested == stored) return enabled ? requested : stored; + throw new DomainException(ErrorCodes.ConfigDisabled, $"Customer {what} are turned off in Sales configuration.", 422); + } + + private static int Gate(bool enabled, int requested, int stored, string what) + { + if (enabled || requested == 0 || requested == stored) return enabled ? requested : stored; + throw new DomainException(ErrorCodes.ConfigDisabled, $"Customer {what} are turned off in Sales configuration.", 422); + } } diff --git a/Backend/ERPCore/Services/GeneralLedgerService.cs b/Backend/ERPCore/Services/GeneralLedgerService.cs index 3e1be51..51af597 100644 --- a/Backend/ERPCore/Services/GeneralLedgerService.cs +++ b/Backend/ERPCore/Services/GeneralLedgerService.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using ERPCore.Infra.Gl; using ERPCore.Services.Gl; using ERPCore.Services.Interfaces; @@ -13,7 +14,11 @@ public sealed class GeneralLedgerService : IGeneralLedgerService private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true + PropertyNameCaseInsensitive = true, + // GL's request DTOs have non-nullable strings (e.g. JournalType) that its [ApiController] validation treats as + // required: an explicit `"journalType": null` is a 400, while an omitted property just takes the default. + // So never send nulls. + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; private readonly IGeneralLedgerClient _client; @@ -82,7 +87,9 @@ public sealed class GeneralLedgerService : IGeneralLedgerService if (response.StatusCode is < 200 or >= 300 || envelope is null || !envelope.Success || envelope.Data is null) { - var message = envelope?.Message ?? "The General Ledger service rejected the request."; + var message = (string.IsNullOrWhiteSpace(envelope?.Message) ? null : envelope!.Message) + ?? DescribeProblem(response.Body) + ?? "The General Ledger service rejected the request."; var status = response.StatusCode is >= 400 and < 500 ? response.StatusCode : 502; throw new DomainException(ErrorCodes.GlRequestFailed, message, status); } @@ -90,6 +97,43 @@ public sealed class GeneralLedgerService : IGeneralLedgerService return envelope.Data; } + /// + /// GL answers some failures (request-validation errors) with an ASP.NET ProblemDetails body rather than its own + /// envelope; turn that into a readable sentence instead of a generic "rejected the request". + /// + private static string? DescribeProblem(string body) + { + try + { + using var doc = JsonDocument.Parse(body); + var root = doc.RootElement; + if (root.ValueKind != JsonValueKind.Object) return null; + + var details = new List(); + foreach (var prop in root.EnumerateObject()) + { + if (!string.Equals(prop.Name, "errors", StringComparison.OrdinalIgnoreCase) || prop.Value.ValueKind != JsonValueKind.Object) continue; + foreach (var field in prop.Value.EnumerateObject()) + { + var text = field.Value.ValueKind == JsonValueKind.Array + ? string.Join(" ", field.Value.EnumerateArray().Select(v => v.GetString())) + : field.Value.ToString(); + details.Add($"{field.Name}: {text}"); + } + } + + if (details.Count > 0) return "The General Ledger service rejected the request — " + string.Join("; ", details); + if (root.TryGetProperty("title", out var title) && title.ValueKind == JsonValueKind.String) + return "The General Ledger service rejected the request — " + title.GetString(); + } + catch (JsonException) + { + // Not JSON at all (a proxy page, say): fall through to the generic message. + } + + return null; + } + private sealed class GlEnvelope { public int StatusCode { get; set; } diff --git a/Backend/ERPCore/Services/Gl/GlModels.cs b/Backend/ERPCore/Services/Gl/GlModels.cs index c5f0175..50ada45 100644 --- a/Backend/ERPCore/Services/Gl/GlModels.cs +++ b/Backend/ERPCore/Services/Gl/GlModels.cs @@ -12,6 +12,8 @@ public sealed class GlJournalEntryRequest { public int PeriodId { get; set; } public DateOnly EntryDate { get; set; } + /// Free-text journal type (max 30) — e.g. OPENING_BALANCE. Left null by the flows that never set one. + public string? JournalType { get; set; } public string? SourceModule { get; set; } public string? Reference { get; set; } public string? Narration { get; set; } diff --git a/Backend/ERPCore/Services/GlobalSearchService.cs b/Backend/ERPCore/Services/GlobalSearchService.cs index 5b2bddf..669ae4c 100644 --- a/Backend/ERPCore/Services/GlobalSearchService.cs +++ b/Backend/ERPCore/Services/GlobalSearchService.cs @@ -1,5 +1,6 @@ using ERPCore.Domain.Entities; using ERPCore.Dtos.Search; +using ERPCore.Infra.Auth; using ERPCore.Repositories.Interfaces; using ERPCore.Services.Interfaces; using Microsoft.EntityFrameworkCore; @@ -9,7 +10,9 @@ namespace ERPCore.Services; /// /// Backs the global command palette (⌘K). Fans a single free-text term out across /// Items, Purchase Orders, Sales Slips (invoices) and Employees in parallel and -/// returns a combined, capped result set — no persistence, read-only. +/// returns a combined, capped result set — no persistence, read-only. Each kind is searched only when +/// the caller's role may open that area (an administrator may open all of them), so the palette can't be used to read +/// staff names, invoices or purchase orders the person has no menu entry for. /// public sealed class GlobalSearchService : IGlobalSearchService { @@ -17,19 +20,28 @@ public sealed class GlobalSearchService : IGlobalSearchService private readonly IRepository _purchaseOrders; private readonly IRepository _salesSlips; private readonly IRepository _employees; + private readonly ICurrentUser _currentUser; + private readonly INavPermissionService _navPermissions; public GlobalSearchService( IRepository items, IRepository purchaseOrders, IRepository salesSlips, - IRepository employees) + IRepository employees, + ICurrentUser currentUser, + INavPermissionService navPermissions) { _items = items; _purchaseOrders = purchaseOrders; _salesSlips = salesSlips; _employees = employees; + _currentUser = currentUser; + _navPermissions = navPermissions; } + private async Task MayOpenAsync(string navCode, CancellationToken ct) + => _currentUser.IsAdmin || await _navPermissions.HasAccessAsync(_currentUser.RoleId, navCode, ct); + public async Task> SearchAsync(string term, int takePerType = 6, CancellationToken ct = default) { var q = term?.Trim() ?? string.Empty; @@ -39,28 +51,28 @@ public sealed class GlobalSearchService : IGlobalSearchService // Sequential, not parallel: these share one scoped DbContext, which EF Core // does not allow concurrent operations against. - var items = await _items.Query().AsNoTracking() + var items = !await MayOpenAsync("products", ct) ? new List() : await _items.Query().AsNoTracking() .Where(i => EF.Functions.Like(i.Sku, pattern) || EF.Functions.Like(i.Name, pattern)) .OrderBy(i => i.Name) .Take(takePerType) .Select(i => new GlobalSearchResultDto(GlobalSearchResultType.Item, i.ItemId, i.Sku, i.Name, i.Sku)) .ToListAsync(ct); - var pos = await _purchaseOrders.Query().AsNoTracking() + var pos = !await MayOpenAsync("procurement", ct) ? new List() : await _purchaseOrders.Query().AsNoTracking() .Where(p => EF.Functions.Like(p.DocNo, pattern)) .OrderByDescending(p => p.PoId) .Take(takePerType) .Select(p => new GlobalSearchResultDto(GlobalSearchResultType.PurchaseOrder, p.PoId, p.DocNo, p.DocNo, p.Status.ToString())) .ToListAsync(ct); - var invoices = await _salesSlips.Query().AsNoTracking() + var invoices = !await MayOpenAsync("sales", ct) ? new List() : await _salesSlips.Query().AsNoTracking() .Where(s => EF.Functions.Like(s.SlipNo, pattern) || EF.Functions.Like(s.CustomerSnapshotName, pattern)) .OrderByDescending(s => s.SalesSlipId) .Take(takePerType) .Select(s => new GlobalSearchResultDto(GlobalSearchResultType.Invoice, s.SalesSlipId, s.SlipNo, s.SlipNo, s.CustomerSnapshotName)) .ToListAsync(ct); - var employees = await _employees.Query().AsNoTracking() + var employees = !await MayOpenAsync("hrm.employees", ct) ? new List() : await _employees.Query().AsNoTracking() .Where(e => EF.Functions.Like(e.FullName, pattern) || EF.Functions.Like(e.EmployeeCode, pattern)) .OrderBy(e => e.FullName) .Take(takePerType) diff --git a/Backend/ERPCore/Services/GrnService.cs b/Backend/ERPCore/Services/GrnService.cs index 4e8bf23..0f563d1 100644 --- a/Backend/ERPCore/Services/GrnService.cs +++ b/Backend/ERPCore/Services/GrnService.cs @@ -49,6 +49,7 @@ public sealed class GrnService : IGrnService private readonly IGeneralLedgerService _gl; private readonly INotificationService _notifications; private readonly IGlAccountConfigService _glAccounts; + private readonly IProcurementConfigService _procurementConfig; public GrnService( IRepository grns, IRepository pos, IRepository poLines, @@ -56,7 +57,8 @@ public sealed class GrnService : IGrnService IRepository bins, IRepository vendors, IRepository batches, IRepository serials, IRepository serialMovements, ISerialTrackingService serialTracking, IRepository layers, IRepository ledger, IRepository taxRates, IFifoCostingService fifo, INumberSequenceService numbers, ICurrentUser currentUser, IBranchConfigService branchConfig, IGrnConfigService grnConfig, - IUnitOfWork uow, IGeneralLedgerService gl, INotificationService notifications, IGlAccountConfigService glAccounts) + IUnitOfWork uow, IGeneralLedgerService gl, INotificationService notifications, IGlAccountConfigService glAccounts, + IProcurementConfigService procurementConfig) { _grns = grns; _pos = pos; @@ -81,17 +83,28 @@ public sealed class GrnService : IGrnService _gl = gl; _notifications = notifications; _glAccounts = glAccounts; + _procurementConfig = procurementConfig; } public async Task> ListAsync( - PageQuery query, GrnStatus? status, int? poId, int? vendorId, int? warehouseId, int? branchId, CancellationToken ct = default) + PageQuery query, GrnStatus? status, int? poId, int? vendorId, int? warehouseId, int? branchId, + DateOnly? from = null, DateOnly? to = null, CancellationToken ct = default) { var q = _grns.Query().AsNoTracking(); if (!string.IsNullOrWhiteSpace(query.Q)) { var term = query.Q.Trim(); - q = q.Where(g => EF.Functions.Like(g.DocNo, $"%{term}%")); + // Doc no., the PO's doc no., or the vendor's name / code (the vendor is the table's first column). + var vendorIdsMatching = _vendors.Query() + .Where(v => EF.Functions.Like(v.Name, $"%{term}%") || EF.Functions.Like(v.Code, $"%{term}%")) + .Select(v => v.VendorId); + var poIdsMatching = _pos.Query() + .Where(p => EF.Functions.Like(p.DocNo, $"%{term}%")) + .Select(p => p.PoId); + q = q.Where(g => EF.Functions.Like(g.DocNo, $"%{term}%") + || vendorIdsMatching.Contains(g.VendorId) + || (g.PoId != null && poIdsMatching.Contains(g.PoId.Value))); } if (status is not null) q = q.Where(g => g.Status == status); if (poId is not null) q = q.Where(g => g.PoId == poId); @@ -99,17 +112,76 @@ public sealed class GrnService : IGrnService if (warehouseId is not null) q = q.Where(g => g.WarehouseId == warehouseId); // Warehouses are shared across branches — GRN carries its own BranchId, set at creation. if (branchId is not null) q = q.Where(g => g.BranchId == branchId); + // Created date, as a business (Colombo) day: [start of `from`, start of the day after `to`). + if (from is DateOnly fromDay) + { + var start = BusinessClock.StartOfDayUtc(fromDay); + q = q.Where(g => g.CreatedAt >= start); + } + if (to is DateOnly toDay) + { + var end = BusinessClock.StartOfDayUtc(toDay.AddDays(1)); + q = q.Where(g => g.CreatedAt < end); + } var total = await q.CountAsync(ct); - var rows = await q.OrderByDescending(g => g.GrnId) + var rows = await ApplySort(q, query.Sort) .Skip(query.Skip).Take(query.PageSize) - .Select(g => new GrnSummaryDto( + .Select(g => new + { g.GrnId, g.DocNo, g.PoId, g.VendorId, g.WarehouseId, g.BranchId, g.Status, - g.CreatedBy, g.CreatedAt, g.PostedAt, g.Lines.Count, g.Lines.Sum(l => l.LineTotal), - g.PurchaseInvoiceId, g.PurchaseInvoice != null ? g.PurchaseInvoice.DocNo : null)) + g.CreatedBy, g.CreatedAt, g.PostedAt, LineCount = g.Lines.Count, TotalValue = g.Lines.Sum(l => l.LineTotal), + g.PurchaseInvoiceId, PurchaseInvoiceNo = g.PurchaseInvoice != null ? g.PurchaseInvoice.DocNo : null + }) .ToListAsync(ct); - return PagedResponse.Create(rows, query.Page, query.PageSize, total); + var flags = await _procurementConfig.GetFlagsAsync(ct); + var vendorIds = rows.Select(r => r.VendorId).Distinct().ToList(); + var vendorMap = await _vendors.Query().AsNoTracking() + .Where(v => vendorIds.Contains(v.VendorId)) + .ToDictionaryAsync(v => v.VendorId, ct); + var poIds = rows.Where(r => r.PoId != null).Select(r => r.PoId!.Value).Distinct().ToList(); + var poDocNos = await _pos.Query().AsNoTracking() + .Where(p => poIds.Contains(p.PoId)) + .ToDictionaryAsync(p => p.PoId, p => p.DocNo, ct); + + var dtos = rows.Select(r => new GrnSummaryDto( + r.GrnId, r.DocNo, r.PoId, r.VendorId, r.WarehouseId, r.BranchId, r.Status, + r.CreatedBy, r.CreatedAt, r.PostedAt, r.LineCount, r.TotalValue, + r.PurchaseInvoiceId, r.PurchaseInvoiceNo, + r.PoId is int pid && poDocNos.TryGetValue(pid, out var poDoc) ? poDoc : null, + vendorMap.TryGetValue(r.VendorId, out var v) + ? new Dtos.Procurement.PoVendorDto( + v.VendorId, v.Code, v.Name, + flags.VendorContactMode == VendorContactMode.Mobile ? v.Mobile : null, + flags.VendorContactMode == VendorContactMode.Email ? v.Email : null, + flags.VendorAddressEnabled ? v.Address : null) + : null)).ToList(); + + return PagedResponse.Create(dtos, query.Page, query.PageSize, total); + } + + /// + /// Server-side sort so paging stays correct: created / vendor / total / status, a leading + /// - for descending. Default (and any unknown key) is newest first; GrnId is the tiebreaker. + /// + private IQueryable ApplySort(IQueryable q, string? sort) + { + var key = (sort ?? "-created").Trim(); + var desc = key.StartsWith('-'); + key = key.TrimStart('-', '+').ToLowerInvariant(); + + IOrderedQueryable ordered = key switch + { + "vendor" => desc + ? q.OrderByDescending(g => _vendors.Query().Where(v => v.VendorId == g.VendorId).Select(v => v.Name).FirstOrDefault()) + : q.OrderBy(g => _vendors.Query().Where(v => v.VendorId == g.VendorId).Select(v => v.Name).FirstOrDefault()), + "total" => desc ? q.OrderByDescending(g => g.Lines.Sum(l => l.LineTotal)) : q.OrderBy(g => g.Lines.Sum(l => l.LineTotal)), + "status" => desc ? q.OrderByDescending(g => g.Status) : q.OrderBy(g => g.Status), + "created" => desc ? q.OrderByDescending(g => g.CreatedAt) : q.OrderBy(g => g.CreatedAt), + _ => q.OrderByDescending(g => g.CreatedAt) + }; + return ordered.ThenByDescending(g => g.GrnId); } public async Task GetAsync(int grnId, CancellationToken ct = default) @@ -132,8 +204,6 @@ public sealed class GrnService : IGrnService if (!await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.WarehouseId, ct)) throw new DomainException(ErrorCodes.Validation, $"Warehouse {request.WarehouseId} does not exist.", 422); - var branchId = await _branchConfig.ResolveBranchIdAsync(request.BranchId, ct); - // Resolve vendor + PO context. PurchaseOrder? po = null; int vendorId; @@ -155,6 +225,10 @@ public sealed class GrnService : IGrnService vendorId = request.VendorId.Value; } + // The branch is the purchase order's own (it was fixed when the PO was raised), so receiving against a PO never + // asks again; only a direct receipt names its branch. + var branchId = po?.BranchId ?? await _branchConfig.ResolveBranchIdAsync(request.BranchId, ct); + var (allowDiscounts, allowVat) = await _grnConfig.GetLineOptionsAsync(ct); if (!allowDiscounts && request.Lines.Any(l => l.DiscountPct > 0m)) throw new DomainException(ErrorCodes.Validation, "Discounts are turned off in GRN Configuration.", 422); @@ -314,7 +388,8 @@ public sealed class GrnService : IGrnService var entry = await _fifo.PostLedgerAsync( line.ItemId, grn.WarehouseId, line.BinId, line.BatchId, null, actor, - Direction.In, qtyBase, unitCostBase, bal, DocumentTypes.Grn, grn.GrnId, now, token); + Direction.In, qtyBase, unitCostBase, bal, DocumentTypes.Grn, grn.GrnId, now, token, + postGlJournal: false); // the GRN posts its own balanced journal below (with VAT) ledgerRefs.Add(entry); if (line.PoLineId is not null) diff --git a/Backend/ERPCore/Services/Interfaces/IAccessConfigService.cs b/Backend/ERPCore/Services/Interfaces/IAccessConfigService.cs new file mode 100644 index 0000000..5449923 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IAccessConfigService.cs @@ -0,0 +1,12 @@ +using ERPCore.Common.Http; +using ERPCore.Dtos.Config; + +namespace ERPCore.Services.Interfaces; + +/// Access configuration (the default role for people who have none). Singleton. +public interface IAccessConfigService +{ + Task> GetAsync(CancellationToken ct = default); + + Task> UpdateAsync(UpdateAccessConfigRequest request, uint expectedRowVersion, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs b/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs index be04655..b57f059 100644 --- a/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs +++ b/Backend/ERPCore/Services/Interfaces/IFifoCostingService.cs @@ -63,11 +63,17 @@ public interface IFifoCostingService /// would drift from the pool (FR-MFG-13). Also used by leftover and cancel returns, whose /// value is the exact consumed residual. Omit for every rate-driven movement. /// + /// + /// Also post the generic per-movement "STOCK" journal (Dr Inventory / Cr Clearing, or the reverse for an issue). + /// Pass false when the calling document posts its own complete journal (GRN, sales invoice, purchase + /// return, adjustment, sales return): otherwise the inventory movement reaches the General Ledger twice. + /// Production runs and transfers have no document journal of their own and keep the default. + /// Task PostLedgerAsync( int itemId, int warehouseId, int? binId, int? batchId, int? serialId, int userId, Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, string sourceDocType, int sourceDocId, DateTime createdAt, CancellationToken ct = default, - decimal? valueOverride = null); + decimal? valueOverride = null, bool postGlJournal = true); /// Current on-hand (Σ open-layer qtyRemaining) for an item at a warehouse. Task GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default); diff --git a/Backend/ERPCore/Services/Interfaces/IGrnService.cs b/Backend/ERPCore/Services/Interfaces/IGrnService.cs index 9198843..24e03f8 100644 --- a/Backend/ERPCore/Services/Interfaces/IGrnService.cs +++ b/Backend/ERPCore/Services/Interfaces/IGrnService.cs @@ -8,7 +8,8 @@ namespace ERPCore.Services.Interfaces; public interface IGrnService { Task> ListAsync( - PageQuery query, GrnStatus? status, int? poId, int? vendorId, int? warehouseId, int? branchId, CancellationToken ct = default); + PageQuery query, GrnStatus? status, int? poId, int? vendorId, int? warehouseId, int? branchId, + DateOnly? from = null, DateOnly? to = null, CancellationToken ct = default); Task GetAsync(int grnId, CancellationToken ct = default); Task CreateAsync(CreateGrnRequest request, CancellationToken ct = default); diff --git a/Backend/ERPCore/Services/Interfaces/INavPermissionService.cs b/Backend/ERPCore/Services/Interfaces/INavPermissionService.cs index e5f9b70..53df857 100644 --- a/Backend/ERPCore/Services/Interfaces/INavPermissionService.cs +++ b/Backend/ERPCore/Services/Interfaces/INavPermissionService.cs @@ -3,28 +3,28 @@ using ERPCore.Domain.Enums; namespace ERPCore.Services.Interfaces; /// -/// Resolves whether a role can reach a given nav module — the same -/// RolePermission data /auth/me already reduces to navCodes -/// (), reused here to gate the API itself instead -/// of only the sidebar (docs/02-SECURITY.md AR-01). +/// "What may this role do?" — the permission data the sidebar, /auth/me and the API policies all share. +/// Keyed by the role's ID (stable under renames), read from the ERP's own tables, and cached per role until it +/// is explicitly invalidated: whoever edits a role's permissions, status or deletes it calls +/// so the change applies on the next request. /// public interface INavPermissionService { /// - /// True when is granted itself, - /// or any sub-nav item nested under it (code prefixed "{navCode}."). Does not - /// special-case Admin — callers (e.g. ) - /// decide whether Admin bypasses this entirely. + /// Whether the role may reach : it holds that nav item, or any child of it + /// ("procurement" matches a grant of "procurement.vendors"). False for a missing, unknown or inactive role. /// - Task HasAccessAsync(string? roleCode, string navCode, CancellationToken ct = default); + Task HasAccessAsync(int? roleId, string navCode, CancellationToken ct = default); /// - /// True when 's grant on (exact - /// code match — no sub-nav prefix expansion, unlike , since - /// an action applies to one specific module's permission row) includes - /// in its RolePermission.Actions bitmask. False if the - /// role has no permission row for that exact nav code at all. Does not special-case - /// Admin — see . + /// Whether the role holds a specific (Create, Delete, Approve, …) on exactly that nav code + /// — no parent/child expansion, unlike . The caller handles Admin before asking. /// - Task HasActionAsync(string? roleCode, string navCode, RolePermissionAction action, CancellationToken ct = default); + Task HasActionAsync(int? roleId, string navCode, RolePermissionAction action, CancellationToken ct = default); + + /// Every nav code the role holds, with its granted actions. Empty for a missing, unknown or inactive role. + Task> GetPermissionsAsync(int roleId, CancellationToken ct = default); + + /// Forgets what is cached for one role, so its next lookup re-reads the database. + void Invalidate(int roleId); } diff --git a/Backend/ERPCore/Services/Interfaces/IOpeningBalanceService.cs b/Backend/ERPCore/Services/Interfaces/IOpeningBalanceService.cs new file mode 100644 index 0000000..4485b12 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IOpeningBalanceService.cs @@ -0,0 +1,17 @@ +using ERPCore.Dtos.OpeningBalances; + +namespace ERPCore.Services.Interfaces; + +/// +/// Opening balances. Each kind is posted once: one balanced journal in the General Ledger +/// (journal type OPENING_BALANCE) against the "Opening Balance Equity" account, plus a local +/// that makes a second posting impossible. +/// +public interface IOpeningBalanceService +{ + /// The cash and bank accounts to enter amounts for, or the posted record if already done. + Task GetCashBankAsync(CancellationToken ct = default); + + /// Posts the cash and bank opening balances. 409 OPENING_BALANCE_ALREADY_POSTED if it was done before. + Task PostCashBankAsync(PostCashBankOpeningRequest request, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IOpeningStockService.cs b/Backend/ERPCore/Services/Interfaces/IOpeningStockService.cs new file mode 100644 index 0000000..68b0e74 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IOpeningStockService.cs @@ -0,0 +1,17 @@ +using ERPCore.Dtos.OpeningBalances; + +namespace ERPCore.Services.Interfaces; + +/// +/// Opening stock: items already on the shelf when the system starts. Each line becomes a FIFO layer +/// at the given unit cost, and ONE General Ledger journal debits Inventory against Opening Balance Equity. +/// Stock can be loaded in several runs, but an item in a warehouse can be opened only once. +/// +public interface IOpeningStockService +{ + Task> ListAsync(CancellationToken ct = default); + + Task GetAsync(int runId, CancellationToken ct = default); + + Task PostAsync(PostOpeningStockRequest request, CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IProcurementConfigService.cs b/Backend/ERPCore/Services/Interfaces/IProcurementConfigService.cs new file mode 100644 index 0000000..6c54cc0 --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/IProcurementConfigService.cs @@ -0,0 +1,16 @@ +using ERPCore.Common.Http; +using ERPCore.Dtos.Config; + +namespace ERPCore.Services.Interfaces; + +/// Procurement configuration business logic. Singleton. +public interface IProcurementConfigService +{ + Task> GetAsync(CancellationToken ct = default); + + Task> UpdateAsync( + UpdateProcurementConfigRequest request, uint expectedRowVersion, CancellationToken ct = default); + + /// The current flags for server-side gating; defaults (all off) when the row is missing. + Task GetFlagsAsync(CancellationToken ct = default); +} diff --git a/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs b/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs index 32f1c7d..030bce8 100644 --- a/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs +++ b/Backend/ERPCore/Services/Interfaces/IPurchaseOrderService.cs @@ -8,8 +8,13 @@ namespace ERPCore.Services.Interfaces; /// Purchase-order business logic (docs/11 §3.3; FR-PROC-03..07). public interface IPurchaseOrderService { + /// Created on or after this business day. + /// Created on or before this business day. + /// query.Sort: created (default, newest first), vendor, total or status; + /// a leading - flips the direction (-created is the default's own direction, created is oldest first). Task> ListAsync( - PageQuery query, PurchaseOrderStatus? status, int? vendorId, CancellationToken ct = default); + PageQuery query, PurchaseOrderStatus? status, int? vendorId, DateOnly? from = null, DateOnly? to = null, + CancellationToken ct = default); Task?> GetAsync(int poId, CancellationToken ct = default); Task> CreateAsync(CreatePurchaseOrderRequest request, CancellationToken ct = default); diff --git a/Backend/ERPCore/Services/Interfaces/IRoleService.cs b/Backend/ERPCore/Services/Interfaces/IRoleService.cs index c561850..4ede2f7 100644 --- a/Backend/ERPCore/Services/Interfaces/IRoleService.cs +++ b/Backend/ERPCore/Services/Interfaces/IRoleService.cs @@ -24,5 +24,6 @@ public interface IRoleService Task AssignPermissionsAsync(int roleId, AssignRolePermissionsRequest request, CancellationToken ct = default); /// Resolves the nav codes a role (by AuthHex `RoleCode` claim) may see. Used by `GET /auth/me`. - Task GetMeAsync(string? roleCode, CancellationToken ct = default); + /// The signed-in person's role, permissions (nav code -> granted actions bitmask) and branch, from the ERP's own tables. + Task GetMeAsync(CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/Interfaces/IUserManagementService.cs b/Backend/ERPCore/Services/Interfaces/IUserManagementService.cs index a43a112..282a2ad 100644 --- a/Backend/ERPCore/Services/Interfaces/IUserManagementService.cs +++ b/Backend/ERPCore/Services/Interfaces/IUserManagementService.cs @@ -12,7 +12,8 @@ namespace ERPCore.Services.Interfaces; /// public interface IUserManagementService { - Task> ListAsync(PageQuery query, EntityStatus? status, string? roleCode, CancellationToken ct = default); + /// True = only users whose role is the administrator role (by flag, not by name); false = everyone else. + Task> ListAsync(PageQuery query, EntityStatus? status, string? roleCode, bool? isAdmin = null, CancellationToken ct = default); Task GetAsync(int userId, CancellationToken ct = default); Task CreateAsync(CreateUserRequest request, int actorUserId, CancellationToken ct = default); Task UpdateRoleAsync(int userId, UpdateUserRoleRequest request, CancellationToken ct = default); @@ -48,4 +49,11 @@ public interface IUserManagementService /// touch ERPCore's local `users` table at all — only AuthHex's separate identity store. /// Task PurgeAuthHexIdentityAsync(PurgeAuthHexIdentityRequest request, CancellationToken ct = default); + + /// + /// One-time migration helper: for every user that has no role yet, reads the role AuthHex still holds for them and + /// stores the matching ERP role in users.RoleId. Run it BEFORE AuthHex stops returning roles; users it cannot + /// map are reported, never guessed. + /// + Task BackfillRolesFromAuthHexAsync(CancellationToken ct = default); } diff --git a/Backend/ERPCore/Services/NavPermissionService.cs b/Backend/ERPCore/Services/NavPermissionService.cs index 4bf1d0e..5a89f7f 100644 --- a/Backend/ERPCore/Services/NavPermissionService.cs +++ b/Backend/ERPCore/Services/NavPermissionService.cs @@ -8,15 +8,15 @@ using Microsoft.Extensions.Caching.Memory; namespace ERPCore.Services; /// -/// See . A role's permitted nav-code set changes -/// rarely (an admin editing Roles → Permissions) but is looked up on every gated -/// request, so it's cached per role code for a short window rather than re-querying -/// RolePermission every call — a Roles→Permissions edit takes up to -/// to take effect for already-issued tokens of that role. +/// See . A role's permission set changes rarely (an admin editing +/// Roles → Permissions) but is read on every gated request, so it is cached per role ID. Edits evict the entry +/// explicitly (), so a change is live on the next request; the short +/// is only a backstop for a change made some other way (a direct database edit, another +/// server instance). /// public sealed class NavPermissionService : INavPermissionService { - private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(2); + private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(1); private readonly IRepository _roles; private readonly IRepository _rolePermissions; @@ -29,85 +29,56 @@ public sealed class NavPermissionService : INavPermissionService _cache = cache; } - public async Task HasAccessAsync(string? roleCode, string navCode, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(roleCode)) return false; + private static string Key(int roleId) => $"nav-permissions:{roleId}"; - var codes = await GetNavCodesAsync(roleCode.Trim(), ct); - return codes.Contains(navCode) || codes.Any(c => c.StartsWith(navCode + ".", StringComparison.OrdinalIgnoreCase)); + public async Task HasAccessAsync(int? roleId, string navCode, CancellationToken ct = default) + { + if (roleId is null) return false; + + var granted = await GetPermissionsAsync(roleId.Value, ct); + return granted.ContainsKey(navCode) + || granted.Keys.Any(c => c.StartsWith(navCode + ".", StringComparison.OrdinalIgnoreCase)); } - public async Task HasActionAsync(string? roleCode, string navCode, RolePermissionAction action, CancellationToken ct = default) + public async Task HasActionAsync(int? roleId, string navCode, RolePermissionAction action, CancellationToken ct = default) { - if (string.IsNullOrWhiteSpace(roleCode)) return false; + if (roleId is null) return false; - var actionsByCode = await GetActionsByNavCodeAsync(roleCode.Trim(), ct); - return actionsByCode.TryGetValue(navCode, out var granted) && granted.HasFlag(action); + var granted = await GetPermissionsAsync(roleId.Value, ct); + return granted.TryGetValue(navCode, out var actions) && actions.HasFlag(action); } - private async Task> GetNavCodesAsync(string roleCode, CancellationToken ct) + public async Task> GetPermissionsAsync(int roleId, CancellationToken ct = default) { - var cacheKey = $"nav-permission-codes:{roleCode.ToLowerInvariant()}"; - if (_cache.TryGetValue(cacheKey, out HashSet? cached) && cached is not null) + if (_cache.TryGetValue(Key(roleId), out Dictionary? cached) && cached is not null) return cached; - // Same shape as RoleService.GetMeAsync's navCodes computation — mirrored rather - // than shared because that method also carries the unauthenticated/no-role - // early-return branches this lookup doesn't need. - var role = await _roles.Query().AsNoTracking() - .FirstOrDefaultAsync(r => r.Code.ToLower() == roleCode.ToLower(), ct); + var byCode = new Dictionary(StringComparer.OrdinalIgnoreCase); - var codes = role is null - ? new HashSet(StringComparer.OrdinalIgnoreCase) - : (await _rolePermissions.Query().AsNoTracking() - .Where(rp => rp.RoleId == role.RoleId) - .Include(rp => rp.Permission!).ThenInclude(p => p.NavItem) - .Include(rp => rp.Permission!).ThenInclude(p => p.SubNavItem) - .Select(rp => rp.Permission!) - .ToListAsync(ct)) - .Select(p => p.NavItem?.Code ?? p.SubNavItem?.Code) - .Where(c => c is not null) - .Select(c => c!) - .ToHashSet(StringComparer.OrdinalIgnoreCase); - - _cache.Set(cacheKey, codes, CacheTtl); - return codes; - } - - private async Task> GetActionsByNavCodeAsync(string roleCode, CancellationToken ct) - { - var cacheKey = $"nav-permission-actions:{roleCode.ToLowerInvariant()}"; - if (_cache.TryGetValue(cacheKey, out Dictionary? cached) && cached is not null) - return cached; - - var role = await _roles.Query().AsNoTracking() - .FirstOrDefaultAsync(r => r.Code.ToLower() == roleCode.ToLower(), ct); - - var actionsByCode = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (role is not null) + // An inactive (or missing) role grants nothing. + var active = await _roles.Query().AsNoTracking() + .AnyAsync(r => r.RoleId == roleId && r.Status == EntityStatus.Active, ct); + if (active) { var rows = await _rolePermissions.Query().AsNoTracking() - .Where(rp => rp.RoleId == role.RoleId) - .Include(rp => rp.Permission!).ThenInclude(p => p.NavItem) - .Include(rp => rp.Permission!).ThenInclude(p => p.SubNavItem) - .Select(rp => new { rp.Actions, rp.Permission!.NavItem, rp.Permission!.SubNavItem }) + .Where(rp => rp.RoleId == roleId) + .Select(rp => new { rp.Actions, NavCode = rp.Permission!.NavItem!.Code, SubCode = rp.Permission!.SubNavItem!.Code }) .ToListAsync(ct); foreach (var row in rows) { - var code = row.NavItem?.Code ?? row.SubNavItem?.Code; + var code = row.NavCode ?? row.SubCode; if (code is null) continue; - // A role could in principle hold two permission rows resolving to the same - // code (shouldn't happen given one Permission per NavItem/SubNavItem, but - // OR rather than overwrite so a duplicate never silently narrows access). - actionsByCode[code] = actionsByCode.TryGetValue(code, out var existing) - ? existing | row.Actions - : row.Actions; + // A role could in principle hold two permission rows resolving to the same code (not expected, since + // there is one Permission per nav item). OR rather than overwrite, so a duplicate never narrows access. + byCode[code] = byCode.TryGetValue(code, out var existing) ? existing | row.Actions : row.Actions; } } - _cache.Set(cacheKey, actionsByCode, CacheTtl); - return actionsByCode; + _cache.Set(Key(roleId), byCode, CacheTtl); + return byCode; } + + public void Invalidate(int roleId) => _cache.Remove(Key(roleId)); } diff --git a/Backend/ERPCore/Services/OpeningBalanceService.cs b/Backend/ERPCore/Services/OpeningBalanceService.cs new file mode 100644 index 0000000..862e643 --- /dev/null +++ b/Backend/ERPCore/Services/OpeningBalanceService.cs @@ -0,0 +1,178 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.OpeningBalances; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Gl; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Opening balances for cash and bank accounts: ONE balanced General Ledger journal (journal type +/// OPENING_BALANCE) — a debit per account with an amount, and the offsetting credit to the +/// "Opening Balance Equity" account — plus a local row. +/// +/// The General Ledger does not stop a second opening entry, so the run row is the guard. It is inserted +/// (and saved) BEFORE the GL is called, inside the transaction: its unique index on the kind makes two +/// concurrent attempts queue up, and the second fails before it can post anything. The accepted +/// failure mode is the same as the other posting services (BankDepositService): if the GL post +/// succeeds and the local commit then fails, a journal exists that the run row does not reference. +/// +/// +public sealed class OpeningBalanceService : IOpeningBalanceService +{ + private const string BaseCurrency = "LKR"; + private const string SourceModule = "OPENING"; + private const string JournalType = "OPENING_BALANCE"; + + private readonly IRepository _runs; + private readonly IGeneralLedgerService _gl; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public OpeningBalanceService( + IRepository runs, IGeneralLedgerService gl, ICurrentUser currentUser, IUnitOfWork uow) + { + _runs = runs; + _gl = gl; + _currentUser = currentUser; + _uow = uow; + } + + public async Task GetCashBankAsync(CancellationToken ct = default) + { + var run = await _runs.Query().AsNoTracking().Include(r => r.Lines) + .FirstOrDefaultAsync(r => r.Kind == OpeningBalanceKind.CashBank, ct); + var accounts = await _gl.ListBankAccountsAsync(ct); + + var posted = run?.Lines.ToDictionary(l => l.AccountCode, l => l.Amount) ?? new Dictionary(); + var rows = accounts + .OrderBy(a => a.AccountType).ThenBy(a => a.AccountName) + .Select(a => new OpeningCashBankAccountDto( + a.AccountType, a.AccountName, a.BankName, a.AccountNumber, a.GlAccountCode, a.CurrencyCode, + posted.TryGetValue(a.GlAccountCode, out var amount) ? amount : null)) + .ToList(); + + return new CashBankOpeningStateDto(run is null ? null : Map(run), rows, BaseCurrency); + } + + public async Task PostCashBankAsync(PostCashBankOpeningRequest request, CancellationToken ct = default) + { + var cutover = request.CutoverDate!.Value; + if (cutover > DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1)) + throw new DomainException(ErrorCodes.Validation, "The opening date cannot be in the future.", 400); + + if (await AlreadyPostedAsync(ct)) + throw AlreadyPostedError(); + + // ---- validate the lines against the real cash/bank accounts + var accounts = (await _gl.ListBankAccountsAsync(ct)).ToDictionary(a => a.GlAccountCode); + var seen = new HashSet(); + var lines = new List<(GlBankAccount Account, decimal Amount)>(); + foreach (var input in request.Lines) + { + var code = input.GlAccountCode.Trim(); + if (!seen.Add(code)) + throw new DomainException(ErrorCodes.Validation, $"Account {code} appears more than once.", 400); + if (!accounts.TryGetValue(code, out var account)) + throw new DomainException(ErrorCodes.Validation, $"{code} is not a cash or bank account.", 422); + if (decimal.Round(input.Amount, 2) != input.Amount) + throw new DomainException(ErrorCodes.Validation, $"The amount for {account.AccountName} can have at most 2 decimal places.", 400); + if (input.Amount == 0) continue; + if (!string.Equals(account.CurrencyCode, BaseCurrency, StringComparison.OrdinalIgnoreCase)) + throw new DomainException(ErrorCodes.Validation, + $"{account.AccountName} is in {account.CurrencyCode}; only {BaseCurrency} accounts can take an opening balance here.", 422); + lines.Add((account, input.Amount)); + } + if (lines.Count == 0) + throw new DomainException(ErrorCodes.Validation, "Enter an amount for at least one cash or bank account.", 400); + + var net = lines.Sum(l => l.Amount); + + // ---- everything the GL needs, resolved before the transaction opens + var equityCode = await OpeningEquityAccount.EnsureAsync(_gl, ct); + var period = await _gl.GetPeriodByDateAsync(cutover, ct); + var narration = $"Opening balances — cash and bank as at {cutover:yyyy-MM-dd}"; + + var journalLines = new List(); + foreach (var (account, amount) in lines) + { + var memo = $"Opening balance — {account.AccountName}"; + journalLines.Add(amount > 0 + ? new GlJournalEntryLineRequest(account.GlAccountCode, amount, 0m, memo) + : new GlJournalEntryLineRequest(account.GlAccountCode, 0m, -amount, memo)); + } + if (net != 0) + { + const string equityMemo = "Opening balance equity"; + journalLines.Add(net > 0 + ? new GlJournalEntryLineRequest(equityCode, 0m, net, equityMemo) + : new GlJournalEntryLineRequest(equityCode, -net, 0m, equityMemo)); + } + + try + { + var run = await _uow.ExecuteInTransactionAsync(async token => + { + var entity = new OpeningBalanceRun + { + Kind = OpeningBalanceKind.CashBank, + OnceKey = nameof(OpeningBalanceKind.CashBank), + CutoverDate = cutover, + TotalAmount = net, + Currency = BaseCurrency, + PostedBy = _currentUser.AuditUserId, + PostedAt = DateTime.UtcNow, + Lines = lines.Select(l => new OpeningBalanceLine + { + AccountCode = l.Account.GlAccountCode, + AccountName = l.Account.AccountName, + Amount = l.Amount + }).ToList() + }; + await _runs.AddAsync(entity, token); + // Claim the once-only slot first: a concurrent second attempt blocks on the unique index here + // and then fails, before it can reach the GL. + await _uow.SaveChangesAsync(token); + + var posted = await _gl.PostJournalEntryAsync(new GlJournalEntryRequest + { + PeriodId = period.PeriodId, + EntryDate = cutover, + JournalType = JournalType, + SourceModule = SourceModule, + Reference = "OPENING-CASH-BANK", + Narration = narration, + Lines = journalLines + }, token); + + entity.GlJournalNo = posted.JournalNo; + return entity; + }, ct); + + return Map(run); + } + catch (DbUpdateException) + { + // The unique index on Kind refused a concurrent second attempt. Anything else is a real failure. + if (await AlreadyPostedAsync(ct)) throw AlreadyPostedError(); + throw; + } + } + + private Task AlreadyPostedAsync(CancellationToken ct) => + _runs.Query().AsNoTracking().AnyAsync(r => r.OnceKey == nameof(OpeningBalanceKind.CashBank), ct); + + private static DomainException AlreadyPostedError() => new( + ErrorCodes.OpeningBalanceAlreadyPosted, + "The cash and bank opening balances have already been posted. Correct them with a journal entry instead.", 409); + + private static OpeningBalanceRunDto Map(OpeningBalanceRun r) => new( + r.OpeningBalanceRunId, r.Kind.ToString(), r.CutoverDate, r.GlJournalNo, r.TotalAmount, r.Currency, + r.PostedAt, r.PostedBy, + r.Lines.OrderBy(l => l.AccountCode).Select(l => new OpeningBalanceLineDto(l.AccountCode, l.AccountName, l.Amount)).ToList()); +} diff --git a/Backend/ERPCore/Services/OpeningEquityAccount.cs b/Backend/ERPCore/Services/OpeningEquityAccount.cs new file mode 100644 index 0000000..1d79c27 --- /dev/null +++ b/Backend/ERPCore/Services/OpeningEquityAccount.cs @@ -0,0 +1,61 @@ +using System.Text.Json; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; + +namespace ERPCore.Services; + +/// +/// The postable "Opening Balance Equity" account both opening balances (cash/bank and stock) are offset +/// against. Looked up in the GL's chart by name and created on first use; account codes are assigned by the +/// GL (never chosen by the caller), so the code is read back from the response. +/// +internal static class OpeningEquityAccount +{ + private const string AccountName = "Opening Balance Equity"; + private const short EquityAccountTypeId = 3; // GL account type 3 = Equity + private const string BaseCurrency = "LKR"; + + public static async Task EnsureAsync(IGeneralLedgerService gl, CancellationToken ct) + { + var accounts = await gl.ListAccountsAsync(ct); + var existing = accounts.FirstOrDefault(a => + a.AccountTypeId == EquityAccountTypeId && a.IsPostable && a.IsActive + && string.Equals(a.AccountName, AccountName, StringComparison.OrdinalIgnoreCase)); + if (existing is not null) return existing.AccountCode; + + var payload = JsonSerializer.SerializeToUtf8Bytes(new + { + accountName = AccountName, + accountTypeId = EquityAccountTypeId, + isControlAccount = false, + isPostable = true, + currencyCode = BaseCurrency + }); + using var body = new MemoryStream(payload); + var response = await gl.ForwardAsync(HttpMethod.Post, "accounts", null, "application/json", body, ct); + + string? code = null; + string? message = null; + try + { + using var doc = JsonDocument.Parse(response.Body); + foreach (var prop in doc.RootElement.EnumerateObject()) + { + if (prop.Name.Equals("message", StringComparison.OrdinalIgnoreCase)) message = prop.Value.GetString(); + if (prop.Name.Equals("data", StringComparison.OrdinalIgnoreCase) && prop.Value.ValueKind == JsonValueKind.Object) + foreach (var d in prop.Value.EnumerateObject()) + if (d.Name.Equals("accountCode", StringComparison.OrdinalIgnoreCase)) code = d.Value.GetString(); + } + } + catch (JsonException) + { + // fall through to the failure below + } + + if (response.StatusCode is < 200 or >= 300 || string.IsNullOrWhiteSpace(code)) + throw new DomainException(ErrorCodes.GlRequestFailed, + message ?? $"Could not create the \"{AccountName}\" account in the General Ledger.", + response.StatusCode is >= 400 and < 500 ? response.StatusCode : 502); + return code; + } +} diff --git a/Backend/ERPCore/Services/OpeningStockService.cs b/Backend/ERPCore/Services/OpeningStockService.cs new file mode 100644 index 0000000..6280252 --- /dev/null +++ b/Backend/ERPCore/Services/OpeningStockService.cs @@ -0,0 +1,238 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.OpeningBalances; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Gl; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using ERPCore.System.Time; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// +/// Opening stock. Each line becomes an inbound FIFO layer at the given unit cost plus a stock-ledger row +/// (source OPN), and ONE General Ledger journal debits Inventory for the total against +/// "Opening Balance Equity". +/// +/// Why this does not go through StockMutator / FifoCostingService.PostLedgerAsync. That path +/// posts its own STOCK journal (Dr Inventory / Cr Clearing) for every ledger row. Opening stock +/// must credit Opening Balance Equity instead, and posting both would debit Inventory twice. So the layer and the +/// ledger row are written directly here (the same two writes, minus the journal) and this service posts the one +/// journal itself. The local JournalEntryStub rows the journal-listing screen reads are still written. +/// +/// +/// One opening per item and warehouse. An item that already has any stock movement in that warehouse is +/// refused — opening stock on top of real movements would be a hidden adjustment. Stock may be loaded in +/// several runs (a warehouse at a time, say). Two simultaneous runs naming the same item could both pass that +/// check; this is an admin-only, rare operation and is not serialised. +/// +/// Untracked items only: serial- and batch-tracked items need per-unit identities, which a plain quantity cannot give. +/// +public sealed class OpeningStockService : IOpeningStockService +{ + private const string BaseCurrency = "LKR"; + private const string SourceDocType = "OPN"; + private const string SourceModule = "OPENING"; + private const string JournalType = "OPENING_BALANCE"; + + private readonly IRepository _runs; + private readonly IRepository _items; + private readonly IRepository _warehouses; + private readonly IRepository _ledger; + private readonly IRepository _journal; + private readonly IFifoCostingService _fifo; + private readonly IGeneralLedgerService _gl; + private readonly IGlAccountConfigService _glAccounts; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public OpeningStockService( + IRepository runs, IRepository items, IRepository warehouses, + IRepository ledger, IRepository journal, IFifoCostingService fifo, + IGeneralLedgerService gl, IGlAccountConfigService glAccounts, ICurrentUser currentUser, IUnitOfWork uow) + { + _runs = runs; + _items = items; + _warehouses = warehouses; + _ledger = ledger; + _journal = journal; + _fifo = fifo; + _gl = gl; + _glAccounts = glAccounts; + _currentUser = currentUser; + _uow = uow; + } + + public async Task> ListAsync(CancellationToken ct = default) + { + var rows = await _runs.Query().AsNoTracking() + .Where(r => r.Kind == OpeningBalanceKind.Stock) + .OrderByDescending(r => r.OpeningBalanceRunId) + .Select(r => new + { + r.OpeningBalanceRunId, r.CutoverDate, r.GlJournalNo, r.TotalAmount, r.PostedAt, r.PostedBy, + LineCount = r.StockLines.Count + }) + .ToListAsync(ct); + return rows.Select(r => new OpeningStockRunSummaryDto( + r.OpeningBalanceRunId, r.CutoverDate, r.GlJournalNo, r.TotalAmount, r.LineCount, r.PostedAt, r.PostedBy)).ToList(); + } + + public async Task GetAsync(int runId, CancellationToken ct = default) + { + var run = await _runs.Query().AsNoTracking() + .Include(r => r.StockLines).ThenInclude(l => l.Item) + .Include(r => r.StockLines).ThenInclude(l => l.Warehouse) + .FirstOrDefaultAsync(r => r.OpeningBalanceRunId == runId && r.Kind == OpeningBalanceKind.Stock, ct); + return run is null ? null : Map(run); + } + + public async Task PostAsync(PostOpeningStockRequest request, CancellationToken ct = default) + { + var cutover = request.CutoverDate!.Value; + if (cutover > BusinessClock.Today.AddDays(1)) + throw new DomainException(ErrorCodes.Validation, "The opening date cannot be in the future.", 400); + + // ---- validate every line before anything is written + var itemIds = request.Lines.Select(l => l.ItemId).Distinct().ToList(); + var warehouseIds = request.Lines.Select(l => l.WarehouseId).Distinct().ToList(); + var items = await _items.Query().AsNoTracking().Where(i => itemIds.Contains(i.ItemId)).ToDictionaryAsync(i => i.ItemId, ct); + var warehouses = await _warehouses.Query().AsNoTracking().Where(w => warehouseIds.Contains(w.WarehouseId)).ToDictionaryAsync(w => w.WarehouseId, ct); + + var seen = new HashSet<(int, int)>(); + foreach (var line in request.Lines) + { + if (!items.TryGetValue(line.ItemId, out var item)) + throw new DomainException(ErrorCodes.Validation, $"Item {line.ItemId} does not exist.", 422); + if (!warehouses.ContainsKey(line.WarehouseId)) + throw new DomainException(ErrorCodes.Validation, $"Warehouse {line.WarehouseId} does not exist.", 422); + if (!seen.Add((line.ItemId, line.WarehouseId))) + throw new DomainException(ErrorCodes.Validation, $"{item.Sku} appears more than once for the same warehouse.", 400); + if (item.Status != EntityStatus.Active) + throw new DomainException(ErrorCodes.Validation, $"{item.Sku} is inactive.", 422); + if (item.StockNature != StockNature.Stocked) + throw new DomainException(ErrorCodes.Validation, $"{item.Sku} is not a stocked item.", 422); + if (item.TrackingMode != TrackingMode.None) + throw new DomainException(ErrorCodes.Validation, + $"{item.Sku} is {item.TrackingMode.ToString().ToLowerInvariant()}-tracked; opening stock supports untracked items only.", 422); + if (line.Qty <= 0 || decimal.Round(line.Qty, 4) != line.Qty) + throw new DomainException(ErrorCodes.Validation, $"The quantity for {item.Sku} must be more than 0, with at most 4 decimals.", 400); + if (line.UnitCost < 0 || decimal.Round(line.UnitCost, 4) != line.UnitCost) + throw new DomainException(ErrorCodes.Validation, $"The unit cost for {item.Sku} must be 0 or more, with at most 4 decimals.", 400); + } + + // ---- an item/warehouse that already has movements is not "opening" any more + var existing = await _ledger.Query().AsNoTracking() + .Where(l => itemIds.Contains(l.ItemId) && warehouseIds.Contains(l.WarehouseId)) + .Select(l => new { l.ItemId, l.WarehouseId }) + .Distinct() + .ToListAsync(ct); + var clashes = existing.Where(e => seen.Contains((e.ItemId, e.WarehouseId))).ToList(); + if (clashes.Count > 0) + { + var names = string.Join(", ", clashes.Take(5).Select(c => $"{items[c.ItemId].Sku} @ {warehouses[c.WarehouseId].Code}")); + var more = clashes.Count > 5 ? $" and {clashes.Count - 5} more" : ""; + throw new DomainException(ErrorCodes.Conflict, + $"Already has stock movements, so it can't be opened again: {names}{more}. Use a stock adjustment instead.", 409); + } + + // ---- everything the GL needs, resolved before the transaction opens + var glCodes = await _glAccounts.GetCodesAsync(ct); + var inventoryCode = glCodes[GlAccountRole.Inventory]; + var equityCode = await OpeningEquityAccount.EnsureAsync(_gl, ct); + var period = await _gl.GetPeriodByDateAsync(cutover, ct); + + // An instant whose business (Colombo) date is the cutover date, so the layers sort as the oldest stock. + var receiptDate = DateTime.SpecifyKind(cutover.ToDateTime(new TimeOnly(6, 30)), DateTimeKind.Utc); + var actor = _currentUser.AuditUserId; + + var run = await _uow.ExecuteInTransactionAsync(async token => + { + var entity = new OpeningBalanceRun + { + Kind = OpeningBalanceKind.Stock, + CutoverDate = cutover, + Currency = BaseCurrency, + PostedBy = actor, + PostedAt = DateTime.UtcNow + }; + await _runs.AddAsync(entity, token); + await _uow.SaveChangesAsync(token); // the run id is the ledger rows' source document id + + decimal total = 0m; + foreach (var line in request.Lines) + { + var value = Math.Round(line.Qty * line.UnitCost, 4, MidpointRounding.AwayFromZero); + total += value; + + await _fifo.CreateInboundLayerAsync(line.ItemId, line.WarehouseId, null, null, null, line.Qty, line.UnitCost, receiptDate, token); + await _ledger.AddAsync(new StockLedger + { + ItemId = line.ItemId, + WarehouseId = line.WarehouseId, + UserId = actor, + Direction = Direction.In, + QtyBase = line.Qty, + UnitCost = line.UnitCost, + Value = value, + RunningBalance = line.Qty, // nothing was there before (checked above) + SourceDocType = SourceDocType, + SourceDocId = entity.OpeningBalanceRunId, + CreatedAt = receiptDate + }, token); + await _journal.AddAsync(new JournalEntryStub + { + SourceDocType = SourceDocType, + SourceDocId = entity.OpeningBalanceRunId, + DebitAccount = inventoryCode, + CreditAccount = equityCode, + Amount = value + }, token); + entity.StockLines.Add(new OpeningStockLine + { + ItemId = line.ItemId, + WarehouseId = line.WarehouseId, + Qty = line.Qty, + UnitCost = line.UnitCost, + Value = value + }); + } + + entity.TotalAmount = total; + // A run of zero-cost items has nothing to post: a journal needs a non-zero amount. + if (total > 0) + { + var posted = await _gl.PostJournalEntryAsync(new GlJournalEntryRequest + { + PeriodId = period.PeriodId, + EntryDate = cutover, + JournalType = JournalType, + SourceModule = SourceModule, + Reference = $"OPENING-STOCK-{entity.OpeningBalanceRunId}", + Narration = $"Opening stock as at {cutover:yyyy-MM-dd} ({request.Lines.Count} line(s))", + Lines = + [ + new GlJournalEntryLineRequest(inventoryCode, total, 0m, "Opening stock"), + new GlJournalEntryLineRequest(equityCode, 0m, total, "Opening balance equity") + ] + }, token); + entity.GlJournalNo = posted.JournalNo; + } + return entity; + }, ct); + + return (await GetAsync(run.OpeningBalanceRunId, ct))!; + } + + private static OpeningStockRunDto Map(OpeningBalanceRun r) => new( + r.OpeningBalanceRunId, r.CutoverDate, r.GlJournalNo, r.TotalAmount, r.StockLines.Count, r.PostedAt, r.PostedBy, + r.StockLines + .OrderBy(l => l.Item?.Sku).ThenBy(l => l.Warehouse?.Code) + .Select(l => new OpeningStockLineDto( + l.ItemId, l.Item?.Sku ?? $"#{l.ItemId}", l.Item?.Name ?? string.Empty, + l.WarehouseId, l.Warehouse?.Code ?? $"#{l.WarehouseId}", l.Qty, l.UnitCost, l.Value)) + .ToList()); +} diff --git a/Backend/ERPCore/Services/ProcurementConfigService.cs b/Backend/ERPCore/Services/ProcurementConfigService.cs new file mode 100644 index 0000000..6b83ca6 --- /dev/null +++ b/Backend/ERPCore/Services/ProcurementConfigService.cs @@ -0,0 +1,78 @@ +using ERPCore.Common.Http; +using ERPCore.Domain; +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Config; +using ERPCore.Infra.Auth; +using ERPCore.Infra.UoW; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +/// Procurement configuration service over the singleton row seeded by DataSeeder. +public sealed class ProcurementConfigService : IProcurementConfigService +{ + private readonly IRepository _config; + private readonly ICurrentUser _currentUser; + private readonly IUnitOfWork _uow; + + public ProcurementConfigService(IRepository config, ICurrentUser currentUser, IUnitOfWork uow) + { + _config = config; + _currentUser = currentUser; + _uow = uow; + } + + public async Task> GetAsync(CancellationToken ct = default) + { + var config = await _config.Query().AsNoTracking() + .FirstOrDefaultAsync(c => c.ConfigId == ProcurementConfig.SingletonId, ct) + ?? throw new NotFoundException("Procurement configuration has not been seeded."); + + return new ETagged(Map(config), config.RowVersion); + } + + public async Task> UpdateAsync( + UpdateProcurementConfigRequest request, uint expectedRowVersion, CancellationToken ct = default) + { + var config = await _config.GetByIdAsync(ProcurementConfig.SingletonId, ct) + ?? throw new NotFoundException("Procurement configuration has not been seeded."); + + if (config.RowVersion != expectedRowVersion) + throw new DomainException(ErrorCodes.ConcurrencyConflict, "The configuration was modified by another request.", 412); + + config.VendorContactMode = request.VendorContactMode!.Value; + config.VendorAddressEnabled = request.VendorAddressEnabled!.Value; + config.PoTaxEnabled = request.PoTaxEnabled!.Value; + config.VendorPaymentTermsEnabled = request.VendorPaymentTermsEnabled!.Value; + config.VendorTaxRegEnabled = request.VendorTaxRegEnabled!.Value; + config.UpdatedAt = DateTime.UtcNow; + config.UpdatedBy = _currentUser.AuditUserId; + + try + { + await _uow.SaveChangesAsync(ct); + } + catch (DbUpdateConcurrencyException) + { + throw new DomainException(ErrorCodes.ConcurrencyConflict, "The configuration was modified by another request.", 412); + } + + return await GetAsync(ct); + } + + public async Task GetFlagsAsync(CancellationToken ct = default) + { + var config = await _config.Query().AsNoTracking() + .FirstOrDefaultAsync(c => c.ConfigId == ProcurementConfig.SingletonId, ct); + // No row (not seeded yet): contact/address/tax off, the long-standing terms and tax-reg fields on. + return config is null ? new ProcurementConfigDto(VendorContactMode.None, false, false, null, null, true, true) : Map(config); + } + + private static ProcurementConfigDto Map(ProcurementConfig c) => new( + c.VendorContactMode, c.VendorAddressEnabled, c.PoTaxEnabled, c.UpdatedAt, c.UpdatedBy, + c.VendorPaymentTermsEnabled, c.VendorTaxRegEnabled); +} diff --git a/Backend/ERPCore/Services/PurchaseOrderService.cs b/Backend/ERPCore/Services/PurchaseOrderService.cs index ca0f881..39e93ad 100644 --- a/Backend/ERPCore/Services/PurchaseOrderService.cs +++ b/Backend/ERPCore/Services/PurchaseOrderService.cs @@ -9,6 +9,7 @@ using ERPCore.Infra.UoW; using ERPCore.Repositories.Interfaces; using ERPCore.Services.Interfaces; using ERPCore.System.Errors; +using ERPCore.System.Time; using Microsoft.EntityFrameworkCore; namespace ERPCore.Services; @@ -33,14 +34,16 @@ public sealed class PurchaseOrderService : IPurchaseOrderService private readonly ICurrentUser _currentUser; private readonly INotificationService _notifications; private readonly IBranchConfigService _branchConfig; + private readonly IProcurementConfigService _procurementConfig; private readonly IUnitOfWork _uow; public PurchaseOrderService( IRepository pos, IRepository vendors, IRepository requisitions, IRepository items, IRepository uoms, IRepository warehouses, INumberSequenceService numbers, ICurrentUser currentUser, INotificationService notifications, - IBranchConfigService branchConfig, IUnitOfWork uow) + IBranchConfigService branchConfig, IProcurementConfigService procurementConfig, IUnitOfWork uow) { + _procurementConfig = procurementConfig; _pos = pos; _vendors = vendors; _requisitions = requisitions; @@ -55,34 +58,84 @@ public sealed class PurchaseOrderService : IPurchaseOrderService } public async Task> ListAsync( - PageQuery query, PurchaseOrderStatus? status, int? vendorId, CancellationToken ct = default) + PageQuery query, PurchaseOrderStatus? status, int? vendorId, DateOnly? from = null, DateOnly? to = null, + CancellationToken ct = default) { var q = _pos.Query().AsNoTracking().Include(p => p.Lines).AsQueryable(); if (!string.IsNullOrWhiteSpace(query.Q)) { var term = query.Q.Trim(); - q = q.Where(p => EF.Functions.Like(p.DocNo, $"%{term}%")); + // Doc no., or the vendor's name / code (the vendor is the table's first column). + var vendorIdsMatching = _vendors.Query() + .Where(v => EF.Functions.Like(v.Name, $"%{term}%") || EF.Functions.Like(v.Code, $"%{term}%")) + .Select(v => v.VendorId); + q = q.Where(p => EF.Functions.Like(p.DocNo, $"%{term}%") || vendorIdsMatching.Contains(p.VendorId)); } if (status is not null) q = q.Where(p => p.Status == status); if (vendorId is not null) q = q.Where(p => p.VendorId == vendorId); + // Created date, as a business (Colombo) day: [start of `from`, start of the day after `to`). + if (from is DateOnly fromDay) + { + var start = BusinessClock.StartOfDayUtc(fromDay); + q = q.Where(p => p.CreatedAt >= start); + } + if (to is DateOnly toDay) + { + var end = BusinessClock.StartOfDayUtc(toDay.AddDays(1)); + q = q.Where(p => p.CreatedAt < end); + } var total = await q.CountAsync(ct); - var rows = await q.OrderByDescending(p => p.PoId) + var rows = await ApplySort(q, query.Sort) .Skip(query.Skip).Take(query.PageSize) .ToListAsync(ct); + var flags = await _procurementConfig.GetFlagsAsync(ct); + var vendorIds = rows.Select(p => p.VendorId).Distinct().ToList(); + var vendorMap = await _vendors.Query().AsNoTracking() + .Where(v => vendorIds.Contains(v.VendorId)) + .ToDictionaryAsync(v => v.VendorId, ct); + var summaries = rows.Select(p => new PurchaseOrderSummaryDto( - p.PoId, p.DocNo, p.VendorId, p.BranchId, p.Status, p.ApprovalRequired, p.CreatedBy, p.CreatedAt, ComputeTotals(p.Lines))).ToList(); + p.PoId, p.DocNo, p.VendorId, p.BranchId, p.Status, p.ApprovalRequired, p.CreatedBy, p.CreatedAt, ComputeTotals(p.Lines), + vendorMap.TryGetValue(p.VendorId, out var v) ? ToVendorDto(v, flags) : null)).ToList(); return PagedResponse.Create(summaries, query.Page, query.PageSize, total); } + /// + /// Server-side sort so paging stays correct: created / vendor / total / status, a leading + /// - for descending. Default (and any unknown key) is newest first. PoId is the tiebreaker, so a page + /// boundary never repeats or skips a row. + /// + private IQueryable ApplySort(IQueryable q, string? sort) + { + var key = (sort ?? "-created").Trim(); + var desc = key.StartsWith('-'); + key = key.TrimStart('-', '+').ToLowerInvariant(); + + IOrderedQueryable ordered = key switch + { + "vendor" => desc + ? q.OrderByDescending(p => _vendors.Query().Where(v => v.VendorId == p.VendorId).Select(v => v.Name).FirstOrDefault()) + : q.OrderBy(p => _vendors.Query().Where(v => v.VendorId == p.VendorId).Select(v => v.Name).FirstOrDefault()), + "total" => desc + ? q.OrderByDescending(p => p.Lines.Sum(l => l.Qty * l.UnitPrice + l.TaxAmount)) + : q.OrderBy(p => p.Lines.Sum(l => l.Qty * l.UnitPrice + l.TaxAmount)), + "status" => desc ? q.OrderByDescending(p => p.Status) : q.OrderBy(p => p.Status), + "created" => desc ? q.OrderByDescending(p => p.CreatedAt) : q.OrderBy(p => p.CreatedAt), + _ => q.OrderByDescending(p => p.CreatedAt) + }; + return ordered.ThenByDescending(p => p.PoId); + } + public async Task?> GetAsync(int poId, CancellationToken ct = default) { var po = await _pos.Query().AsNoTracking() .Include(p => p.Lines) .FirstOrDefaultAsync(p => p.PoId == poId, ct); - return po is null ? null : new ETagged(Map(po), po.RowVersion); + if (po is null) return null; + return new ETagged(await ToDtoAsync(po, ct), po.RowVersion); } public async Task> CreateAsync(CreatePurchaseOrderRequest request, CancellationToken ct = default) @@ -116,7 +169,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService if (po.Status == PurchaseOrderStatus.PendingApproval) await _notifications.RaiseAsync("purchase.order.pending_approval", "Purchase Order Awaiting Approval", $"Purchase order {po.DocNo} is awaiting approval.", nameof(PurchaseOrder), po.PoId, ct); - return new ETagged(Map(po), po.RowVersion); + return new ETagged(await ToDtoAsync(po, ct), po.RowVersion); } public async Task> UpdateAsync( @@ -154,7 +207,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService throw new DomainException(ErrorCodes.ConcurrencyConflict, "The purchase order was modified by another request.", 412); } - return new ETagged(Map(po), po.RowVersion); + return new ETagged(await ToDtoAsync(po, ct), po.RowVersion); } public async Task ApproveAsync(int poId, CancellationToken ct = default) @@ -168,21 +221,22 @@ public sealed class PurchaseOrderService : IPurchaseOrderService await _uow.SaveChangesAsync(ct); await _notifications.RaiseAsync("purchase.order.approved", "Purchase Order Approved", $"Purchase order {po.DocNo} was approved.", nameof(PurchaseOrder), po.PoId, ct); - return Map(po); + return await ToDtoAsync(po, ct); } public async Task RejectAsync(int poId, string? reason, CancellationToken ct = default) { var po = await LoadForApprovalDecisionAsync(poId, ct); - // Back to Draft, not Cancelled — a reject is "revise and resubmit", not "abandon". - po.Status = PurchaseOrderStatus.Draft; + // Rejected, not Cancelled — a reject is "revise and resubmit", not "abandon" - and not Draft either, so the + // list and the creator can tell it was turned down. + po.Status = PurchaseOrderStatus.Rejected; po.UpdatedAt = DateTime.UtcNow; await _uow.SaveChangesAsync(ct); var suffix = string.IsNullOrWhiteSpace(reason) ? "." : $": {reason}"; await _notifications.RaiseAsync("purchase.order.rejected", "Purchase Order Rejected", $"Purchase order {po.DocNo} was rejected{suffix}", nameof(PurchaseOrder), po.PoId, ct); - return Map(po); + return await ToDtoAsync(po, ct); } /// Shared load + gate for Approve/Reject: must be PendingApproval, and the @@ -197,9 +251,9 @@ public sealed class PurchaseOrderService : IPurchaseOrderService if (po.Status != PurchaseOrderStatus.PendingApproval) throw new DomainException(ErrorCodes.PoNotPendingApproval, $"Purchase order {poId} is {po.Status} and is not awaiting approval.", 409); - if (!_currentUser.IsAdmin) - throw new DomainException(ErrorCodes.PoApprovalForbidden, "Only an Admin may approve or reject a purchase order.", 403); - + // Who may approve is the role's "Approve" permission on Procurement (an administrator always has it): the + // approve/reject endpoints require policy `Nav:procurement:Approve`. This used to be a second, hard-coded + // "Admin only" check that ignored the permission; what stays here is the maker-checker rule. if (po.CreatedBy == _currentUser.AuditUserId) throw new DomainException(ErrorCodes.PoApprovalForbidden, "You cannot approve or reject your own purchase order.", 403); @@ -223,7 +277,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService await _uow.SaveChangesAsync(ct); } - return Map(po); + return await ToDtoAsync(po, ct); } public async Task SubmitAsync(int poId, CancellationToken ct = default) @@ -233,7 +287,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService .FirstOrDefaultAsync(p => p.PoId == poId, ct) ?? throw new NotFoundException($"Purchase order {poId} was not found."); - if (po.Status != PurchaseOrderStatus.Draft) + if (!IsEditable(po.Status)) throw new DomainException(ErrorCodes.PoNotEditable, $"Purchase order {poId} is {po.Status} and cannot be submitted.", 409); po.Status = PurchaseOrderStatus.PendingApproval; @@ -242,7 +296,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService await _uow.SaveChangesAsync(ct); await _notifications.RaiseAsync("purchase.order.pending_approval", "Purchase Order Awaiting Approval", $"Purchase order {po.DocNo} is awaiting approval.", nameof(PurchaseOrder), po.PoId, ct); - return Map(po); + return await ToDtoAsync(po, ct); } public async Task DeleteAsync(int poId, CancellationToken ct = default) @@ -252,8 +306,8 @@ public sealed class PurchaseOrderService : IPurchaseOrderService .FirstOrDefaultAsync(p => p.PoId == poId, ct) ?? throw new NotFoundException($"Purchase order {poId} was not found."); - if (po.Status != PurchaseOrderStatus.Draft) - throw new DomainException(ErrorCodes.PoNotEditable, $"Purchase order {poId} is {po.Status} and cannot be deleted; only a Draft can be deleted.", 409); + if (!IsEditable(po.Status)) + throw new DomainException(ErrorCodes.PoNotEditable, $"Purchase order {poId} is {po.Status} and cannot be deleted; only a Draft or Rejected order can be deleted.", 409); _pos.Remove(po); await _uow.SaveChangesAsync(ct); @@ -261,7 +315,7 @@ public sealed class PurchaseOrderService : IPurchaseOrderService // FR-PROC-05 (revised): a PO is editable/deletable only while Draft. Submitting locks it. // Supersedes Phase-1 Option B "freely editable while open" — see docs/10 FR-PROC-05. - private static bool IsEditable(PurchaseOrderStatus status) => status is PurchaseOrderStatus.Draft; + private static bool IsEditable(PurchaseOrderStatus status) => status is PurchaseOrderStatus.Draft or PurchaseOrderStatus.Rejected; private static PoLine ToLine(CreatePoLineInput l) => new() { @@ -270,17 +324,21 @@ public sealed class PurchaseOrderService : IPurchaseOrderService Qty = l.Qty, UnitPrice = l.UnitPrice, Tax = l.Tax, + TaxAmount = TaxAmountFor(l.Qty, l.UnitPrice, l.Tax), QtyReceived = 0 }; + /// The line's tax amount, rounded once here and then stored (see ). + private static decimal TaxAmountFor(decimal qty, decimal unitPrice, decimal rate) => + Math.Round(qty * unitPrice * rate, 2, MidpointRounding.AwayFromZero); + private static PoTotalsDto ComputeTotals(IEnumerable lines) { decimal sub = 0, tax = 0; foreach (var l in lines) { - var net = l.Qty * l.UnitPrice; - sub += net; - tax += net * l.Tax; + sub += l.Qty * l.UnitPrice; + tax += l.TaxAmount; } sub = Math.Round(sub, 2, MidpointRounding.AwayFromZero); tax = Math.Round(tax, 2, MidpointRounding.AwayFromZero); @@ -300,6 +358,11 @@ public sealed class PurchaseOrderService : IPurchaseOrderService && !await _requisitions.Query().AnyAsync(r => r.RequisitionId == requisitionId, ct)) throw new DomainException(ErrorCodes.Validation, $"Requisition {requisitionId} does not exist.", 422); + // Tax is a Procurement-configuration option: with it off, the form hides the field, so a + // non-zero rate can only come from a stale or hand-built request. + if (!(await _procurementConfig.GetFlagsAsync(ct)).PoTaxEnabled && lines.Any(l => l.Tax != 0)) + throw new DomainException(ErrorCodes.ConfigDisabled, "Tax on purchase orders is turned off in Procurement configuration.", 422); + await EnsureAllExistAsync(_items.Query().Select(i => i.ItemId), lines.Select(l => l.ItemId), "Item", ct); await EnsureAllExistAsync(_warehouses.Query().Select(w => w.WarehouseId), lines.Select(l => l.WarehouseId), "Warehouse", ct); } @@ -328,9 +391,24 @@ public sealed class PurchaseOrderService : IPurchaseOrderService throw new DomainException(ErrorCodes.Validation, $"{label}(s) not found: {string.Join(", ", missing)}.", 422); } - private static PurchaseOrderDto Map(PurchaseOrder p) => new( + /// Vendor block for a PO — the optional contact/address fields only when their switch is on. + private static PoVendorDto ToVendorDto(Vendor v, Dtos.Config.ProcurementConfigDto flags) => new( + v.VendorId, v.Code, v.Name, + flags.VendorContactMode == VendorContactMode.Mobile ? v.Mobile : null, + flags.VendorContactMode == VendorContactMode.Email ? v.Email : null, + flags.VendorAddressEnabled ? v.Address : null); + + private async Task ToDtoAsync(PurchaseOrder p, CancellationToken ct) + { + var vendor = await _vendors.Query().AsNoTracking().FirstOrDefaultAsync(v => v.VendorId == p.VendorId, ct); + var flags = await _procurementConfig.GetFlagsAsync(ct); + return Map(p, vendor is null ? null : ToVendorDto(vendor, flags)); + } + + private static PurchaseOrderDto Map(PurchaseOrder p, PoVendorDto? vendor) => new( p.PoId, p.DocNo, p.VendorId, p.BranchId, p.RequisitionId, p.Status, p.ApprovalRequired, p.CreatedBy, p.ApprovedBy, p.ApprovedAt, p.CreatedAt, p.UpdatedAt, ComputeTotals(p.Lines), p.Lines.OrderBy(l => l.PoLineId).Select(l => new PoLineDto( - l.PoLineId, l.ItemId, l.WarehouseId, l.Qty, l.UnitPrice, l.Tax, l.QtyReceived)).ToList()); + l.PoLineId, l.ItemId, l.WarehouseId, l.Qty, l.UnitPrice, l.Tax, l.QtyReceived, l.TaxAmount)).ToList(), + vendor); } diff --git a/Backend/ERPCore/Services/PurchaseReturnService.cs b/Backend/ERPCore/Services/PurchaseReturnService.cs index 1dff8b2..f9aea5f 100644 --- a/Backend/ERPCore/Services/PurchaseReturnService.cs +++ b/Backend/ERPCore/Services/PurchaseReturnService.cs @@ -251,7 +251,8 @@ public sealed class PurchaseReturnService : IPurchaseReturnService var entry = await _fifo.PostLedgerAsync( layer.ItemId, layer.WarehouseId, null, layer.BatchId, null, actor, - Direction.Out, take, layer.UnitCost, bal, DocumentTypes.PurchaseReturn, ret.ReturnId, now, token); + Direction.Out, take, layer.UnitCost, bal, DocumentTypes.PurchaseReturn, ret.ReturnId, now, token, + postGlJournal: false); // the return posts its own journal refs.Add(entry); } balances[line.ItemId] = bal; diff --git a/Backend/ERPCore/Services/RoleService.cs b/Backend/ERPCore/Services/RoleService.cs index 16257ab..647f7bc 100644 --- a/Backend/ERPCore/Services/RoleService.cs +++ b/Backend/ERPCore/Services/RoleService.cs @@ -1,11 +1,9 @@ using ERPCore.Common.Http; using ERPCore.Domain.Entities; using ERPCore.Domain.Enums; -using ERPCore.Dtos.Auth; using ERPCore.Dtos.Common; using ERPCore.Dtos.Rbac; using ERPCore.Infra.Auth; -using ERPCore.Infra.Auth.AuthHex; using ERPCore.Infra.UoW; using ERPCore.Repositories.Interfaces; using ERPCore.Services.Interfaces; @@ -14,32 +12,37 @@ using Microsoft.EntityFrameworkCore; namespace ERPCore.Services; +/// +/// Roles and their permissions. The ERP owns these completely — nothing here talks to AuthHex any more. +/// Every change that can alter what someone may do evicts that role's cached permissions +/// (), so it applies on the person's very next request. +/// public sealed class RoleService : IRoleService { - private const string AdminRoleCode = "Admin"; - private readonly IRepository _roles; private readonly IRepository _rolePermissions; private readonly IRepository _permissions; private readonly IRepository _employees; private readonly IRepository _users; + private readonly IRepository _accessConfig; private readonly ICurrentUser _currentUser; - private readonly IAuthHexClient _authHex; + private readonly INavPermissionService _navPermissions; private readonly IBranchConfigService _branchConfig; private readonly IUnitOfWork _uow; public RoleService( IRepository roles, IRepository rolePermissions, IRepository permissions, - IRepository employees, IRepository users, ICurrentUser currentUser, IAuthHexClient authHex, - IBranchConfigService branchConfig, IUnitOfWork uow) + IRepository employees, IRepository users, IRepository accessConfig, + ICurrentUser currentUser, INavPermissionService navPermissions, IBranchConfigService branchConfig, IUnitOfWork uow) { _roles = roles; _rolePermissions = rolePermissions; _permissions = permissions; _employees = employees; _users = users; + _accessConfig = accessConfig; _currentUser = currentUser; - _authHex = authHex; + _navPermissions = navPermissions; _branchConfig = branchConfig; _uow = uow; } @@ -74,15 +77,13 @@ public sealed class RoleService : IRoleService if (await _roles.Query().AnyAsync(r => r.Code == code, ct)) throw new ConflictException($"A role with code '{code}' already exists."); - var authRole = await _authHex.CreateRoleAsync( - new CreateAuthHexRoleRequest { Code = code, Name = request.Name.Trim() }, ct); - + // IsAdmin is never settable here: an administrator role is created by the seeder, not through the API. var role = new Role { - AuthRoleId = authRole.RoleId, Code = code, Name = request.Name.Trim(), - IsSystemRole = authRole.IsSystemRole ?? false, + IsSystemRole = false, + IsAdmin = false, Status = EntityStatus.Active, CreatedAt = DateTime.UtcNow }; @@ -107,9 +108,7 @@ public sealed class RoleService : IRoleService && await _roles.Query().AnyAsync(r => r.Code == code && r.RoleId != roleId, ct)) throw new ConflictException($"A role with code '{code}' already exists."); - await _authHex.UpdateRoleAsync( - new UpdateAuthHexRoleRequest { RoleId = role.AuthRoleId, Code = code, Name = request.Name.Trim() }, ct); - + // Safe to rename any role, the administrator role included: nothing joins on the code any more. role.Code = code; role.Name = request.Name.Trim(); role.UpdatedAt = DateTime.UtcNow; @@ -132,11 +131,15 @@ public sealed class RoleService : IRoleService ?? throw new NotFoundException($"Role {roleId} was not found."); if (status == EntityStatus.Inactive) + { + await EnsureRemovableAsync(role, "deactivated", ct); await EnsureNotAssignedToAnyUserAsync(roleId, "deactivated", ct); + } role.Status = status; role.UpdatedAt = DateTime.UtcNow; await _uow.SaveChangesAsync(ct); + _navPermissions.Invalidate(roleId); } public async Task DeleteAsync(int roleId, CancellationToken ct = default) @@ -144,19 +147,31 @@ public sealed class RoleService : IRoleService var role = await _roles.GetByIdAsync(roleId, ct) ?? throw new NotFoundException($"Role {roleId} was not found."); + await EnsureRemovableAsync(role, "deleted", ct); await EnsureNotAssignedToAnyUserAsync(roleId, "deleted", ct); - try - { - await _authHex.DeleteRoleAsync(role.AuthRoleId, ct); - } - catch (DomainException ex) when (ex.Message.Contains("ROLE_IN_USE", StringComparison.OrdinalIgnoreCase)) - { - throw new DomainException(ErrorCodes.RoleInUse, "This role is assigned to one or more users.", 409); - } - + // Its permission rows go with it (cascade); drop the cached copy too. _roles.Remove(role); await _uow.SaveChangesAsync(ct); + _navPermissions.Invalidate(roleId); + } + + /// + /// The administrator role, system roles and the configured default role can't be deactivated or deleted: + /// losing them would lock everyone out, or leave new sign-ins with nowhere to land. + /// + private async Task EnsureRemovableAsync(Role role, string action, CancellationToken ct) + { + if (role.IsAdmin) + throw new DomainException(ErrorCodes.RoleInUse, $"The administrator role cannot be {action}.", 409); + if (role.IsSystemRole) + throw new DomainException(ErrorCodes.RoleInUse, $"'{role.Name}' is a system role and cannot be {action}.", 409); + + var isDefault = await _accessConfig.Query().AsNoTracking() + .AnyAsync(c => c.ConfigId == AccessConfig.SingletonId && c.DefaultRoleId == role.RoleId, ct); + if (isDefault) + throw new DomainException(ErrorCodes.RoleInUse, + $"'{role.Name}' is the default role for new sign-ins and cannot be {action}. Choose a different default role first (Settings → Access).", 409); } /// @@ -220,11 +235,13 @@ public sealed class RoleService : IRoleService } await _uow.SaveChangesAsync(ct); + // The change is live on the next request of everyone holding this role. + _navPermissions.Invalidate(roleId); return await GetPermissionsAsync(roleId, ct); } - public async Task GetMeAsync(string? roleCode, CancellationToken ct = default) + public async Task GetMeAsync(CancellationToken ct = default) { // The caller's branch, via their optional Employee link — independent of role, // since even an Admin's own "home" branch may be worth surfacing later. Null @@ -239,36 +256,40 @@ public sealed class RoleService : IRoleService var branchingEnabled = await _branchConfig.IsBranchingEnabledAsync(ct); var warehouseBranchingEnabled = await _branchConfig.IsWarehouseBranchingEnabledAsync(ct); - if (string.IsNullOrWhiteSpace(roleCode)) - return new MeResponseDto(null, null, Array.Empty(), false, branchId, branchName, branchingEnabled, warehouseBranchingEnabled); - - // AuthHex mints the RoleCode claim independently of ERPCore's stored casing - // (e.g. token "ADMIN" vs seeded "Admin"), so match case-insensitively — an - // identity code differing only by case must not lock the user out of the nav. - var normalized = roleCode.Trim(); - var isAdmin = string.Equals(normalized, AdminRoleCode, StringComparison.OrdinalIgnoreCase); - var role = await _roles.Query().AsNoTracking() - .FirstOrDefaultAsync(r => r.Code.ToLower() == normalized.ToLower(), ct); + // The role was resolved from the ERP's own tables when the request was authenticated. No role claim means + // the person has no usable role (none assigned and no default, or the role is inactive): say so explicitly, + // so the UI can show "waiting for access" instead of an empty, unexplained menu. + var roleId = _currentUser.RoleId; + var role = roleId is null + ? null + : await _roles.Query().AsNoTracking().FirstOrDefaultAsync(r => r.RoleId == roleId && r.Status == EntityStatus.Active, ct); if (role is null) - return new MeResponseDto(roleCode, null, Array.Empty(), isAdmin, branchId, branchName, branchingEnabled, warehouseBranchingEnabled); + return new MeResponseDto(null, null, Array.Empty(), false, branchId, branchName, branchingEnabled, + warehouseBranchingEnabled, null, false, new Dictionary()); - var permissions = await _rolePermissions.Query().AsNoTracking() - .Where(rp => rp.RoleId == role.RoleId) - .Include(rp => rp.Permission!).ThenInclude(p => p.NavItem) - .Include(rp => rp.Permission!).ThenInclude(p => p.SubNavItem) - .Select(rp => rp.Permission!) - .ToListAsync(ct); + // An administrator holds everything, always — including nav items added after the role was last edited — + // so the UI never depends on grant rows being kept in step with new screens. + Dictionary permissions; + if (role.IsAdmin) + { + var codes = await _permissions.Query().AsNoTracking() + .Select(p => p.NavItem!.Code ?? p.SubNavItem!.Code) + .ToListAsync(ct); + permissions = codes.Where(c => !string.IsNullOrEmpty(c)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToDictionary(c => c, _ => (int)RolePermissionAction.All, StringComparer.OrdinalIgnoreCase); + } + else + { + permissions = (await _navPermissions.GetPermissionsAsync(role.RoleId, ct)) + .ToDictionary(kv => kv.Key, kv => (int)kv.Value, StringComparer.OrdinalIgnoreCase); + } - var navCodes = permissions - .Select(p => p.NavItem?.Code ?? p.SubNavItem?.Code) - .Where(code => code is not null) - .Select(code => code!) - .Distinct() - .ToList(); - - return new MeResponseDto(role.Code, role.Name, navCodes, isAdmin, branchId, branchName, branchingEnabled, warehouseBranchingEnabled); + return new MeResponseDto( + role.Code, role.Name, permissions.Keys.ToList(), role.IsAdmin, branchId, branchName, + branchingEnabled, warehouseBranchingEnabled, role.RoleId, true, permissions); } private static RoleDto Map(Role r) => new( - r.RoleId, r.Code, r.Name, r.IsSystemRole, r.Status, r.CreatedAt, r.UpdatedAt); + r.RoleId, r.Code, r.Name, r.IsSystemRole, r.Status, r.CreatedAt, r.UpdatedAt, r.IsAdmin); } diff --git a/Backend/ERPCore/Services/SalesConfigService.cs b/Backend/ERPCore/Services/SalesConfigService.cs index 002ad03..7de250b 100644 --- a/Backend/ERPCore/Services/SalesConfigService.cs +++ b/Backend/ERPCore/Services/SalesConfigService.cs @@ -98,6 +98,13 @@ public sealed class SalesConfigService : ISalesConfigService config.AllowOverStockSale = request.AllowOverStockSale!.Value; config.AllowWarehouseSelection = allowWarehouseSelection; config.DefaultWarehouseId = request.DefaultWarehouseId; + config.CustomerPhoneEnabled = request.CustomerPhoneEnabled!.Value; + config.CustomerEmailEnabled = request.CustomerEmailEnabled!.Value; + config.CustomerAddressEnabled = request.CustomerAddressEnabled!.Value; + config.CustomerCityEnabled = request.CustomerCityEnabled!.Value; + config.CustomerCountryEnabled = request.CustomerCountryEnabled!.Value; + config.CustomerCreditLimitEnabled = request.CustomerCreditLimitEnabled!.Value; + config.CustomerCreditDaysEnabled = request.CustomerCreditDaysEnabled!.Value; config.UpdatedAt = DateTime.UtcNow; config.UpdatedBy = _currentUser.AuditUserId; @@ -140,5 +147,7 @@ public sealed class SalesConfigService : ISalesConfigService private static SalesConfigDto Map(SalesConfig c) => new( c.AllowCashCustomer, c.CashCustomerId, c.CashCustomer?.DisplayName ?? c.CashCustomer?.Name, c.AllowFreeItems, c.AllowDiscounts, c.AllowOverStockSale, - c.AllowWarehouseSelection, c.DefaultWarehouseId, c.DefaultWarehouse?.Name, c.UpdatedAt, c.UpdatedBy); + c.AllowWarehouseSelection, c.DefaultWarehouseId, c.DefaultWarehouse?.Name, c.UpdatedAt, c.UpdatedBy, + c.CustomerPhoneEnabled, c.CustomerEmailEnabled, c.CustomerAddressEnabled, c.CustomerCityEnabled, c.CustomerCountryEnabled, + c.CustomerCreditLimitEnabled, c.CustomerCreditDaysEnabled); } diff --git a/Backend/ERPCore/Services/SalesPostingService.cs b/Backend/ERPCore/Services/SalesPostingService.cs index 4385931..afdd97e 100644 --- a/Backend/ERPCore/Services/SalesPostingService.cs +++ b/Backend/ERPCore/Services/SalesPostingService.cs @@ -311,7 +311,8 @@ public sealed class SalesPostingService : ISalesPostingService var cost = qty == 0 ? 0m : group.Sum(x => x.Qty * x.UnitCost) / qty; await _fifo.PostLedgerAsync(line.ItemId, line.WarehouseId, null, group.Key, serials.Count == 1 ? serials[0].SerialId : null, _currentUser.AuditUserId, - Direction.Out, qty, cost, 0m, sourceDocType, docId, now, token); + Direction.Out, qty, cost, 0m, sourceDocType, docId, now, token, + postGlJournal: false); // the invoice posts its own journal (revenue, COGS, inventory) } foreach (var segment in consumed) diff --git a/Backend/ERPCore/Services/Stock/FifoCostingService.cs b/Backend/ERPCore/Services/Stock/FifoCostingService.cs index 418a2e4..c5beb25 100644 --- a/Backend/ERPCore/Services/Stock/FifoCostingService.cs +++ b/Backend/ERPCore/Services/Stock/FifoCostingService.cs @@ -361,7 +361,7 @@ public sealed class FifoCostingService : IFifoCostingService int itemId, int warehouseId, int? binId, int? batchId, int? serialId, int userId, Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance, string sourceDocType, int sourceDocId, DateTime createdAt, CancellationToken ct = default, - decimal? valueOverride = null) + decimal? valueOverride = null, bool postGlJournal = true) { var entry = new StockLedger { @@ -401,7 +401,7 @@ public sealed class FifoCostingService : IFifoCostingService Amount = entry.Value }, ct); - if (entry.Value != 0) + if (postGlJournal && entry.Value != 0) { var period = await _gl.GetPeriodByDateAsync(BusinessClock.ToBusinessDate(createdAt), ct); await _gl.PostJournalEntryAsync(new GlJournalEntryRequest diff --git a/Backend/ERPCore/Services/Stock/StockMutator.cs b/Backend/ERPCore/Services/Stock/StockMutator.cs index 4f4fae5..73bba74 100644 --- a/Backend/ERPCore/Services/Stock/StockMutator.cs +++ b/Backend/ERPCore/Services/Stock/StockMutator.cs @@ -58,7 +58,8 @@ public sealed class StockMutator : IStockMutator bal -= qty; entry = await _fifo.PostLedgerAsync( d.ItemId, warehouseId, d.BinId, d.BatchId, ledgerSerialId, actor, - Direction.Out, qty, unitCost, bal, sourceDocType, sourceDocId, now, ct); + Direction.Out, qty, unitCost, bal, sourceDocType, sourceDocId, now, ct, + postGlJournal: false); // every caller (adjustment, returns) posts its own document journal } else { @@ -75,7 +76,8 @@ public sealed class StockMutator : IStockMutator bal += d.QtyDelta; entry = await _fifo.PostLedgerAsync( d.ItemId, warehouseId, d.BinId, d.BatchId, ledgerSerialId, actor, - Direction.In, d.QtyDelta, unitCost, bal, sourceDocType, sourceDocId, now, ct); + Direction.In, d.QtyDelta, unitCost, bal, sourceDocType, sourceDocId, now, ct, + postGlJournal: false); } balances[d.ItemId] = bal; diff --git a/Backend/ERPCore/Services/UserManagementService.cs b/Backend/ERPCore/Services/UserManagementService.cs index fd605b1..216d63e 100644 --- a/Backend/ERPCore/Services/UserManagementService.cs +++ b/Backend/ERPCore/Services/UserManagementService.cs @@ -4,6 +4,7 @@ using ERPCore.Dtos.Auth; using ERPCore.Dtos.Common; using ERPCore.Dtos.Hrm; using ERPCore.Dtos.Users; +using ERPCore.Infra.Auth; using ERPCore.Infra.Auth.AuthHex; using ERPCore.Infra.UoW; using ERPCore.Repositories.Interfaces; @@ -21,12 +22,15 @@ public sealed class UserManagementService : IUserManagementService private readonly IAuthHexClient _authHex; private readonly IEmployeeUserLinkService _links; private readonly IEmployeeService _employeeService; + private readonly ICurrentUser _currentUser; private readonly IUnitOfWork _uow; public UserManagementService( IRepository users, IRepository roles, IRepository employees, - IAuthHexClient authHex, IEmployeeUserLinkService links, IEmployeeService employeeService, IUnitOfWork uow) + IAuthHexClient authHex, IEmployeeUserLinkService links, IEmployeeService employeeService, + ICurrentUser currentUser, IUnitOfWork uow) { + _currentUser = currentUser; _users = users; _roles = roles; _employees = employees; @@ -36,7 +40,7 @@ public sealed class UserManagementService : IUserManagementService _uow = uow; } - public async Task> ListAsync(PageQuery query, EntityStatus? status, string? roleCode, CancellationToken ct = default) + public async Task> ListAsync(PageQuery query, EntityStatus? status, string? roleCode, bool? isAdmin = null, CancellationToken ct = default) { IQueryable q = _users.Query().AsNoTracking().Include(u => u.Role) .Where(u => !u.Username.StartsWith(User.DeletedUsernamePrefix)); @@ -47,6 +51,8 @@ public sealed class UserManagementService : IUserManagementService } if (status is not null) q = q.Where(u => u.Status == status); if (!string.IsNullOrWhiteSpace(roleCode)) q = q.Where(u => u.Role != null && u.Role.Code == roleCode); + if (isAdmin is true) q = q.Where(u => u.Role != null && u.Role.IsAdmin); + else if (isAdmin is false) q = q.Where(u => u.Role == null || !u.Role.IsAdmin); var total = await q.CountAsync(ct); var rows = await q.OrderBy(u => u.Username) @@ -67,11 +73,13 @@ public sealed class UserManagementService : IUserManagementService { var role = await _roles.GetByIdAsync(request.RoleId, ct) ?? throw new NotFoundException($"Role {request.RoleId} was not found."); + if (role.Status != EntityStatus.Active) + throw new DomainException(ErrorCodes.Validation, $"Role '{role.Name}' is inactive.", 422); // Non-admin roles are branch-scoped (BranchAccessService.EnsureBranchVisibleAsync // resolves the caller's branch via their linked Employee) — either link an existing, // already branch-scoped Employee, or supply enough to create a new one. - var isAdminRole = string.Equals(role.Code, "Admin", StringComparison.OrdinalIgnoreCase); + var isAdminRole = role.IsAdmin; string? employeeCode = null; if (!isAdminRole && request.LinkEmployeeId is null) { @@ -103,7 +111,8 @@ public sealed class UserManagementService : IUserManagementService await _authHex.RegisterAsync(new RegisterRequest { UserId = authUserId, - RoleId = role.AuthRoleId, + // No AuthHex role: the person's role lives in the ERP (users.RoleId, set below). The client fills in + // AuthHex's legacy placeholder only while the old AuthHex, which still demands one, is deployed. UserTypeId = request.UserTypeId, Fullname = request.FullName.Trim(), UserName = username, @@ -165,7 +174,22 @@ public sealed class UserManagementService : IUserManagementService ?? throw new NotFoundException($"User {userId} was not found."); var role = await _roles.GetByIdAsync(request.RoleId, ct) ?? throw new NotFoundException($"Role {request.RoleId} was not found."); + if (role.Status != EntityStatus.Active) + throw new DomainException(ErrorCodes.Validation, $"Role '{role.Name}' is inactive.", 422); + // Never leave the system without an administrator, and never let someone lock themselves out. + if (!role.IsAdmin) + { + var currentRole = user.RoleId is null ? null : await _roles.GetByIdAsync(user.RoleId.Value, ct); + if (currentRole?.IsAdmin == true) + { + if (user.UserId == _currentUser.AuditUserId) + throw new DomainException(ErrorCodes.Conflict, "You cannot remove your own administrator access. Ask another administrator.", 409); + await EnsureAnotherActiveAdminAsync(userId, "change this user's role", ct); + } + } + + // The very next request of this person reads the new role from the database — nothing else to refresh. user.RoleId = role.RoleId; await _uow.SaveChangesAsync(ct); @@ -205,6 +229,14 @@ public sealed class UserManagementService : IUserManagementService var user = await _users.Query().Include(u => u.Role).FirstOrDefaultAsync(u => u.UserId == userId, ct) ?? throw new NotFoundException($"User {userId} was not found."); + if (status == EntityStatus.Inactive) + { + if (user.UserId == _currentUser.AuditUserId) + throw new DomainException(ErrorCodes.Conflict, "You cannot deactivate your own account.", 409); + if (user.Role?.IsAdmin == true) + await EnsureAnotherActiveAdminAsync(userId, "deactivate this user", ct); + } + // The local flag alone is cosmetic — without this, a user shown "Inactive" here // could still log in directly against AuthHex. if (user.AuthUserId is Guid authUserId) @@ -247,6 +279,67 @@ public sealed class UserManagementService : IUserManagementService return _authHex.DeleteUserAsync(request.Email, request.MobileNumber, request.Nic, request.UserName, ct); } + /// Refuses an action that would leave no other active administrator. + private async Task EnsureAnotherActiveAdminAsync(int exceptUserId, string action, CancellationToken ct) + { + var others = await _users.Query().AsNoTracking().CountAsync(u => + u.UserId != exceptUserId && u.Status == EntityStatus.Active + && u.Role != null && u.Role.IsAdmin && u.Role.Status == EntityStatus.Active, ct); + if (others == 0) + throw new DomainException(ErrorCodes.Conflict, + $"You cannot {action}: it is the only active administrator. Make another user an administrator first.", 409); + } + + public async Task BackfillRolesFromAuthHexAsync(CancellationToken ct = default) + { + var candidates = await _users.Query() + .Where(u => u.RoleId == null && u.AuthUserId != null && !u.Username.StartsWith(User.DeletedUsernamePrefix)) + .ToListAsync(ct); + var alreadyHad = await _users.Query().AsNoTracking().CountAsync(u => u.RoleId != null, ct); + + var rolesByCode = (await _roles.Query().AsNoTracking().ToListAsync(ct)) + .ToDictionary(r => r.Code, StringComparer.OrdinalIgnoreCase); + + var issues = new List(); + var assigned = 0; + foreach (var user in candidates) + { + try + { + var details = await _authHex.GetUserDetailsAsync(user.AuthUserId!.Value, ct); + var code = ReadRoleCode(details.Role); + if (code is null) + issues.Add(new BackfillRolesIssueDto(user.UserId, user.Username, "AuthHex returned no role for this user.")); + else if (!rolesByCode.TryGetValue(code, out var role)) + issues.Add(new BackfillRolesIssueDto(user.UserId, user.Username, $"AuthHex role '{code}' has no matching ERP role.")); + else if (role.Status != EntityStatus.Active) + issues.Add(new BackfillRolesIssueDto(user.UserId, user.Username, $"ERP role '{role.Name}' is inactive.")); + else + { + user.RoleId = role.RoleId; + assigned++; + } + } + catch (DomainException ex) + { + issues.Add(new BackfillRolesIssueDto(user.UserId, user.Username, $"AuthHex lookup failed: {ex.Message}")); + } + } + + await _uow.SaveChangesAsync(ct); + return new BackfillRolesResultDto(candidates.Count, assigned, alreadyHad, issues); + } + + /// AuthHex's role comes back as a loose JSON object; its code may be cased either way. + private static string? ReadRoleCode(global::System.Text.Json.JsonElement? role) + { + if (role is not { ValueKind: global::System.Text.Json.JsonValueKind.Object } obj) return null; + foreach (var property in obj.EnumerateObject()) + if (property.Name.Equals("code", StringComparison.OrdinalIgnoreCase) && property.Value.ValueKind == global::System.Text.Json.JsonValueKind.String) + return property.Value.GetString(); + return null; + } + private static ManagedUserDto Map(User u) => new( u.UserId, u.Username, u.DisplayName, u.Email, u.Status, u.RoleId, u.Role?.Code, u.Role?.Name); } diff --git a/Backend/ERPCore/Services/VendorService.cs b/Backend/ERPCore/Services/VendorService.cs index ca17045..dda8ba1 100644 --- a/Backend/ERPCore/Services/VendorService.cs +++ b/Backend/ERPCore/Services/VendorService.cs @@ -1,5 +1,7 @@ using ERPCore.Common.Http; using ERPCore.Domain.Entities; +using System.Text.RegularExpressions; +using ERPCore.Domain; using ERPCore.Domain.Enums; using ERPCore.Dtos.Common; using ERPCore.Dtos.Vendors; @@ -15,12 +17,16 @@ public sealed class VendorService : IVendorService { private readonly IRepository _vendors; private readonly IBranchConfigService _branchConfig; + private readonly IProcurementConfigService _procurementConfig; private readonly IUnitOfWork _uow; - public VendorService(IRepository vendors, IBranchConfigService branchConfig, IUnitOfWork uow) + public VendorService( + IRepository vendors, IBranchConfigService branchConfig, + IProcurementConfigService procurementConfig, IUnitOfWork uow) { _vendors = vendors; _branchConfig = branchConfig; + _procurementConfig = procurementConfig; _uow = uow; } @@ -56,13 +62,19 @@ public sealed class VendorService : IVendorService throw new ConflictException($"A vendor with code '{code}' already exists."); var branchId = await _branchConfig.ResolveBranchIdAsync(request.BranchId, ct); + var flags = await _procurementConfig.GetFlagsAsync(ct); + var (mobile, email, address, terms, taxReg) = + ValidateOptionalFields(flags, request.Mobile, request.Email, request.Address, request.Terms, request.TaxReg); var vendor = new Vendor { Code = code, Name = request.Name.Trim(), - Terms = request.Terms, - TaxReg = request.TaxReg, + Terms = terms, + TaxReg = taxReg, Currency = request.Currency.Trim().ToUpperInvariant(), + Mobile = mobile, + Email = email, + Address = address, Status = EntityStatus.Active, BranchId = branchId, CreatedAt = DateTime.UtcNow @@ -89,12 +101,20 @@ public sealed class VendorService : IVendorService throw new ConflictException($"A vendor with code '{code}' already exists."); var branchId = await _branchConfig.ResolveBranchIdAsync(request.BranchId, ct); + var flags = await _procurementConfig.GetFlagsAsync(ct); + var (mobile, email, address, terms, taxReg) = + ValidateOptionalFields(flags, request.Mobile, request.Email, request.Address, request.Terms, request.TaxReg); vendor.Code = code; vendor.Name = request.Name.Trim(); - vendor.Terms = request.Terms; - vendor.TaxReg = request.TaxReg; vendor.Currency = request.Currency.Trim().ToUpperInvariant(); + // A field that is switched off in Procurement configuration is left as stored (not erased), + // so re-enabling it later brings the old values back. + if (flags.VendorContactMode == VendorContactMode.Mobile) vendor.Mobile = mobile; + if (flags.VendorContactMode == VendorContactMode.Email) vendor.Email = email; + if (flags.VendorAddressEnabled) vendor.Address = address; + if (flags.VendorPaymentTermsEnabled) vendor.Terms = terms; + if (flags.VendorTaxRegEnabled) vendor.TaxReg = taxReg; vendor.BranchId = branchId; vendor.UpdatedAt = DateTime.UtcNow; @@ -140,6 +160,55 @@ public sealed class VendorService : IVendorService } } + private static readonly Regex MobilePattern = new(@"^\+?[0-9][0-9 \-()]{5,18}$", RegexOptions.Compiled); + + /// + /// Trims the optional fields, rejects any that Procurement configuration has switched off + /// (422 CONFIG_DISABLED — the UI hides them, so only a stale or hand-built request gets here) + /// and format-checks the ones that are on. Blank means "not provided". + /// + private static (string? Mobile, string? Email, string? Address, string? Terms, string? TaxReg) ValidateOptionalFields( + Dtos.Config.ProcurementConfigDto flags, string? mobile, string? email, string? address, string? terms, string? taxReg) + { + mobile = string.IsNullOrWhiteSpace(mobile) ? null : mobile.Trim(); + email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(); + address = string.IsNullOrWhiteSpace(address) ? null : address.Trim(); + terms = string.IsNullOrWhiteSpace(terms) ? null : terms.Trim(); + taxReg = string.IsNullOrWhiteSpace(taxReg) ? null : taxReg.Trim(); + + if (mobile is not null && flags.VendorContactMode != VendorContactMode.Mobile) + throw new DomainException(ErrorCodes.ConfigDisabled, "Vendor mobile numbers are turned off in Procurement configuration.", 422); + if (email is not null && flags.VendorContactMode != VendorContactMode.Email) + throw new DomainException(ErrorCodes.ConfigDisabled, "Vendor email addresses are turned off in Procurement configuration.", 422); + if (address is not null && !flags.VendorAddressEnabled) + throw new DomainException(ErrorCodes.ConfigDisabled, "Vendor addresses are turned off in Procurement configuration.", 422); + if (terms is not null && !flags.VendorPaymentTermsEnabled) + throw new DomainException(ErrorCodes.ConfigDisabled, "Vendor payment terms are turned off in Procurement configuration.", 422); + if (taxReg is not null && !flags.VendorTaxRegEnabled) + throw new DomainException(ErrorCodes.ConfigDisabled, "Vendor tax registration is turned off in Procurement configuration.", 422); + + if (mobile is not null && !MobilePattern.IsMatch(mobile)) + throw new DomainException(ErrorCodes.Validation, "Enter a valid mobile number (digits, optional leading +).", 422); + if (email is not null && !IsValidEmail(email)) + throw new DomainException(ErrorCodes.Validation, "Enter a valid email address.", 422); + + return (mobile, email, address, terms, taxReg); + } + + private static bool IsValidEmail(string value) + { + try + { + var parsed = new global::System.Net.Mail.MailAddress(value); + return parsed.Address == value && parsed.Host.Contains('.'); + } + catch (FormatException) + { + return false; + } + } + private static VendorDto Map(Vendor v) => new( - v.VendorId, v.Code, v.Name, v.Terms, v.TaxReg, v.Currency, v.Status, v.BranchId, v.CreatedAt, v.UpdatedAt); + v.VendorId, v.Code, v.Name, v.Terms, v.TaxReg, v.Currency, v.Status, v.BranchId, v.CreatedAt, v.UpdatedAt, + v.Mobile, v.Email, v.Address); } diff --git a/Backend/ERPCore/System/Errors/ErrorCodes.cs b/Backend/ERPCore/System/Errors/ErrorCodes.cs index 3c29e09..47623c3 100644 --- a/Backend/ERPCore/System/Errors/ErrorCodes.cs +++ b/Backend/ERPCore/System/Errors/ErrorCodes.cs @@ -87,6 +87,9 @@ public static class ErrorCodes // A submitted GL account mapping names an account GL does not have, or cannot post to. public const string GlAccountInvalid = "GL_ACCOUNT_INVALID"; + // Opening balances: each kind can be posted only once. + public const string OpeningBalanceAlreadyPosted = "OPENING_BALANCE_ALREADY_POSTED"; + // Sales invoice payments public const string SalesInvoiceNotPayable = "SALES_INVOICE_NOT_PAYABLE"; public const string SalesInvoicePaymentExceedsBalance = "SALES_INVOICE_PAYMENT_EXCEEDS_BALANCE"; diff --git a/Backend/ERPCore/System/Time/BusinessClock.cs b/Backend/ERPCore/System/Time/BusinessClock.cs index 21db664..27250d8 100644 --- a/Backend/ERPCore/System/Time/BusinessClock.cs +++ b/Backend/ERPCore/System/Time/BusinessClock.cs @@ -53,6 +53,10 @@ public static class BusinessClock /// Today's business day. public static DateOnly Today => DateOnly.FromDateTime(Now); + /// The UTC instant a business day starts at (its local midnight) — the inclusive lower bound of "that day". + public static DateTime StartOfDayUtc(DateOnly day) => + TimeZoneInfo.ConvertTimeToUtc(day.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified), Zone); + /// /// The business day a UTC instant falls on. Prefer this over when a /// flow already captured a now/createdAt: it keeps the period lookup and the diff --git a/Backend/PROGRESS.md b/Backend/PROGRESS.md index 7216d65..80d2588 100644 --- a/Backend/PROGRESS.md +++ b/Backend/PROGRESS.md @@ -591,3 +591,55 @@ Spec: `docs/30-BACKEND-PHASE2.md` (model + rules **and** API — one doc, unlike 1. **The password was never stored — FIXED 2026-07-17.** `Services/UserManager/UserManagerService.cs:97` computes `var PasswordHash = PasswordHasher.Hash(...)`, but the assignment in the `new User { … }` initializer at **line 116 was commented out** (`//PasswordHash = PasswordHash`). Every registered user landed in MySQL with a null `PasswordHash`, so `loginUser`'s `if (string.IsNullOrEmpty(user.PasswordHash) || !PasswordHasher.Verify(...))` (line 230) always threw `"Invalid credentials"`. **Uncommenting that line fixed login outright** — verified end-to-end. Pre-fix users are unrecoverable (their hashes were never written) and need re-registration or a password reset (`ChangeUserPassword`/`UpdateUser` do persist the hash correctly, and `UpdateUser` even handles the null-hash case at line 836). 2. **Username is not a valid login identifier.** `Repos/UserManageRepository.cs:46` `GetUserByIdentifierAndType` matches only `Email`/`MobileNumber`/`Nic` — **not `UserName`** — and ignores its `userTypeId` argument entirely (that filtering sits commented out at lines 56–65, so the "AndType" half of the method name is currently a lie). Even with bug 1 fixed, `identifier: ""` will not resolve a user; only email/mobile/NIC will. - **Workaround meanwhile: `POST /api/v1/auth/register` issues a working `erp_at` session cookie directly**, which authenticates every v1 controller via the handler's cookie fallback. That is how this session's Master-Data smoke test (§1) was run — no login needed. + +### 2026-10-17 — Procurement configuration, PO tax/PDF/approval queue, item edit, opening balances +- **Procurement configuration (`procurement_config`, singleton):** `VendorContactMode` (None/Mobile/Email), `VendorAddressEnabled`, `PoTaxEnabled`. `GET /api/v1/procurement-config` is open to any signed-in user (the vendor and PO forms need it); `PUT` needs the `settings` nav policy (ETag/If-Match like the other configs). UI: Settings → Configuration → Procurement Configuration. +- **Vendors:** nullable `Mobile`/`Email`/`Address`. A value for a switched-off field is `422 CONFIG_DISABLED`; format-checked when on; an update leaves a switched-off field as stored. +- **Purchase orders:** `po_lines.TaxAmount` stores round(qty × price × rate, 2) (half away from zero); totals sum the stored amounts. A non-zero rate with `PoTaxEnabled` off is `422 CONFIG_DISABLED`. List/detail DTOs carry a `vendor` block (contact/address only when enabled). PO table: vendor name / contact / code / address first, PDF download per PO (`lib/pdf/purchase-order-pdf.ts`, jsPDF). Create / submit / approve / reject / cancel return to the PO table. List search also matches vendor name/code. +- **Sidebar (DB):** `SubNavItem` 50 + `Permission` 60 `procurement.po-approvals` (PO Approval Queue); `SubNavItem` 51 + `Permission` 61 `accounts.opening-balances`. Admin is granted both. Note `procurement` is still in the sidebar's `bypassCodes`, so the UI shows the queue entry to everyone; the API still enforces approval rules. +- **Price adjustment:** no new flag — `ApprovalConfig.PriceAdjustmentLevels` (0/1/2) already is the dual-approval switch; only the wording and the "Confirm and apply" button at level 0 were added. **Item edit** now has the Warranty section and gates the selling-price fields on `sellingPricesEnabled`. +- **Opening balances (`opening_balance_runs`, `opening_balance_lines`, `opening_stock_lines`):** `POST /api/v1/opening-balances/cash-bank` posts ONE journal (journal type `OPENING_BALANCE`): Dr each cash/bank account, Cr "Opening Balance Equity" (created in the GL on first use). Once only — `OnceKey` unique index; the GL itself would accept a second one. `POST /opening-balances/stock` creates FIFO layers + `OPN` ledger rows and one Dr Inventory / Cr Opening Balance Equity journal; an item/warehouse with any prior stock movement is refused; untracked stocked items only. +- **Opening stock deliberately bypasses `StockMutator`/`FifoCostingService.PostLedgerAsync`**, because that path posts its own `STOCK` journal (Dr Inventory / Cr Clearing) per ledger row. **OPEN QUESTION (not verified against live data):** GRN and stock adjustments call that path *and* post their own journal, which looks like Inventory is debited twice per receipt. Check a posted GRN's journals in the GL before trusting inventory balances. +- **Migrations (additive, NOT yet applied anywhere):** `AddProcurementConfigAndVendorContact`, `AddPoLineTaxAmount` (backfills `TaxAmount` for lines that already had a rate), `AddPoApprovalsNav`, `AddOpeningBalances`. They apply on the next start of the backend (`MigrateAsync`) — back up the production DB first. +- **Tests:** `ProcurementAndOpeningBalanceTests` (22 tests). `DualApprovalTests.cs` does not compile (stale `PriceAdjustmentService` constructor call) — pre-existing, untouched. + +### 2026-10-17 (2) — Roles and permissions owned by the ERP; AuthHex = authentication only (Phase A) +- **Why:** the role behind every permission check came from the AuthHex token's `RoleCode`; `users.RoleId` was a decorative copy, so changing a user's role changed nothing (and a stale token kept old rights for up to ~1000 min). +- **New model:** `users.RoleId` -> `roles` (now with `IsAdmin`, `AuthRoleId` nullable) -> `role_permissions`. "Admin" is the `IsAdmin` role flag, never the role's code/name. `access_config` (singleton) holds the **default role** for first sign-ins and role-less people; the seeder creates an `Admin` role and a permission-free `NoAccess` role and points the default at it (first time only). +- **Per request:** `ShadowUserClaimsTransformation` loads user + role + branch in ONE query and injects `erp:RoleId/RoleCode/IsAdmin` (and `erp:Blocked` for an inactive user, refused by the `ErpAccess` policy). `ICurrentUser` reads those; **no AuthHex role claim is trusted**. A role-less user is assigned (and saved) the role named by a still-present legacy token claim (`Auth:LegacyRoleClaimFallback`, default on, turn off after the backfill), else the default role. +- **Permissions:** `NavPermissionService` is keyed by role id and evicted on any role/permission change (`Invalidate`), so edits apply on the next request. `/auth/me` returns `roleId`, `hasRole` and `permissions` (nav code -> actions bitmask); an administrator always receives every code. The UI uses `can(navCode, action)`; people with no role see "Waiting for access". +- **Role/user management:** role CRUD no longer calls AuthHex; the Admin/system/default roles can't be deactivated or deleted; the last active administrator can't be demoted/deactivated, nobody can remove their own admin access or deactivate themselves; the default role can never be an admin role; user detail page can now change the role. `Level2RoleCodes` became `Level2RoleIds` (migration converts); the PO approve/reject rule follows the `procurement:Approve` permission instead of a hard-coded Admin check. Self-registration no longer accepts a role id. +- **One-time backfill:** admin-only `POST /api/v1/users/backfill-roles` copies each role-less user's role from AuthHex (reports the ones it cannot map). +- **AuthHex (`ERP_Auth_Service`):** `/api/role`, the role entity/repo/service, the token's `RoleId`/`RoleCode` claims and every `roleId` in its payloads are gone; `registerUser` ignores a sent `roleId`. Run `Docs/migrations/2026-10-remove-roles.sql` (PART 1) BEFORE deploying it. Not touched: its anonymous `registerUser`/`getUserDetails`/`LogoutUser`, and `alt/VerifyOTP` trusting a caller-supplied identifier (pre-existing; recommended follow-ups). +- **ROLLOUT ORDER:** (1) back up both DBs; (2) deploy ERPCore (applies migration `RolesOwnedByErp`, seeds roles) with AuthHex still old; (3) as an admin run the backfill and fix anything it reports; (4) run AuthHex SQL PART 1, deploy new AuthHex; (5) set `Auth:LegacyRoleClaimFallback=false`. New roles created in the ERP have no AuthHex role, so until step 4 set `AuthHex:LegacyRoleId` to any existing AuthHex role GUID so registration keeps working. +- **Tests:** `AccessControlTests` (29), `DualApprovalTests` repaired (it did not compile) and moved to role ids. Phase B follows below. + +### 2026-10-18 — Permission coverage (Phase B) +- **Permissions seeded** (migration `PermissionCoverage`, **not applied**): nav `hrm` (15) with `hrm.employees/attendance/leave/payroll/reports/settings` (52–57), nav `sales-returns` (16), `settings.audit-logs` (58) and the missing permission row for `settings.notification-config` (24); permissions 62–71. Grants are idempotent `INSERT IGNORE`, keyed on `roles.IsAdmin`: administrator roles get all ten; roles that could reach Sales keep Sales Returns (same actions). **Nobody else gets HRM or the audit log** — HRM used to be open to every signed-in user, so non-admin HR staff need a grant under Settings → Roles after deploy. +- **Controllers:** all 16 HRM controllers gated (masters: read = any HR role, write = `hrm.settings`; employees = `hrm.employees` + Create/Edit/Delete; salary structure, loans, bank details, payslips, payroll runs and the payroll/salary-history reports need `hrm.payroll`; attendance confirm/unlock, leave approve/reject and payroll approve/lock/unlock need Approve). `AuditLogsController` -> `settings.audit-logs`; `SalesReturnsController` -> `sales-returns`; `ReasonCodesController` writes -> `settings`; `BranchesController` writes -> `settings.branches` (reads stay open for the pickers); `VendorsController` fixed (`Nav:vendors` matched nothing, so only admins reached it): read = `procurement`, create/edit/delete = `procurement.vendors`. `Edit` is enforced on HRM and vendors; `View` is not enforced (stored only). +- **Global search** (`GlobalSearchService`) searches items / purchase orders / invoices / employees only when the role may open `products` / `procurement` / `sales` / `hrm.employees`. +- **CSRF:** `CsrfProtectionFilter` is registered globally; every POST/PUT/PATCH/DELETE that rides on the `erp_at` cookie must carry `X-XSRF-TOKEN` (skipped for Bearer callers, `[AllowAnonymous]` actions, actions with their own `[ValidateCsrf]`, and requests with no session cookie). The frontend api-client now sends the header on every state-changing call and, on a `CSRF_TOKEN_MISMATCH`, renews the session once and retries (old sessions have no XSRF cookie). +- **Branch hole closed:** a signed-in non-admin with no linked employee used to fall through the branch query filters and read every branch (writes already refused them). In a branching deployment they are now scoped to `CurrentBranchContext.NoBranchId` (0, matches nothing); with branching off nobody is scoped. `erp:BranchingEnabled` rides on the claims query (still one round trip). Bank deposits refuse a branch-less user with a clear message. +- **Frontend:** `bypassCodes` removed from the sidebar; `Audit Log` menu entry + page; route guard (`lib/nav-routes.ts`, built from the sidebar list, enforced in `AccessGate`); `api-client` dispatches `erp:forbidden` on any 403 and `AuthProvider` re-reads permissions; employee detail hides the Bank Details / Salary & Loans tabs without `hrm.payroll`. +- **Tests:** `PermissionCoverageTests` (29 cases; suite 196/196): every data-changing endpoint has a Nav policy (allow-list for personal endpoints), every named code is seeded, no duplicate codes / every sub item has a permission, HR pay data needs payroll, policy decisions per role, search per role, CSRF filter matrix, branch context and query filters. +- **Not done:** `Edit`/`View` on the older modules; reading masters still needs only module access; `TrackingController`, notifications and support tickets stay login-only; AuthHex hardening (anonymous `registerUser`/`getUserDetails`/`LogoutUser`, `alt/VerifyOTP`) unchanged; Playwright/smoke suites not re-run; the migration SQL was not run against a real MariaDB. + +### 2026-10-18 (2) — GRN confirm fix, GRN table/actions, PO "Rejected" status +- **GRN confirm failed with `GL_REQUEST_FAILED` (my regression):** the opening-balance work added `JournalType` to the GL journal request and it was serialised as `"journalType": null` for every other posting. GL's `[ApiController]` model validation treats its non-nullable `JournalType` as required, so every GRN / sales / expense post got a 400 with a ProblemDetails body (and ERPCore then showed only the generic message). Now nulls are never sent, and a GL validation error is reported with its reason. **Restart ERPCore**, then confirm the draft GRN again. Opening-balance posts were unaffected. +- **GRN against a PO never asks for a branch:** the GRN takes the PO's branch (server-side too: `CreateAsync` ignores the request branch when a PO is given); the form shows it read-only. Direct receipts still pick a branch. +- **Create GRN:** two buttons - *Save as draft* and *Confirm now* (creates, then confirms; if confirming fails the GRN stays a draft and the message says so). Both land on the GRN table. +- **GRN table:** same layout as Purchase Orders - vendor-first row (contact/address per Procurement configuration), GRN no. with PO + warehouse, created date, status, total, invoice, branch (admin + branching), date-range filter, server-side sort, search on GRN no. / PO / vendor. Actions: View, **Receipt** (A4 / 80mm / 58mm print preview, `GrnReceiptPrint.tsx`), **Approve** for Drafts (confirms the receipt, with a confirmation prompt and an idempotency key). +- **PO rejection:** a rejected PO now has its own status `Rejected` (was sent back to `Draft`, which looked like a new draft / pending). It is editable, deletable and can be resubmitted like a draft. No migration (status is stored as a string). +- **Tests:** +6 (`Po_Reject...`, GL payload/ProblemDetails). Suite 202/202. Not covered by tests: the GRN list query and GRN-from-PO branch (needs the full GRN service fixture). + +### 2026-10-18 (3) — Inventory counted twice in the General Ledger (fixed) +- **Bug:** `FifoCostingService.PostLedgerAsync` always posted a generic "STOCK" journal (Dr Inventory / Cr Clearing, reversed for issues) per ledger row, and GRN, sales invoice, purchase return, stock adjustment and sales return ALSO post their own complete document journal. Each movement therefore reached the GL twice (a 100,000 GRN showed 200,000 of Inventory). Seen on GRN 2. +- **Fix:** new `postGlJournal` parameter (default true). Passed `false` by GRN, sales posting, purchase return and `StockMutator` (adjustments, sales returns, purchase-return deltas). Production runs and transfers have no journal of their own and keep the STOCK journal. Local `JournalEntryStub` rows are unchanged. +- **Existing data is NOT corrected:** journals already in the GL stay. For each affected document the "STOCK" journals (Reference like `GRN#2`) need a reversal in the GL (GRN 2: three, 20,000 + 30,000 + 50,000). Not done automatically. +- **Test:** `StockLedgerPost_AddsTheGenericStockJournal_OnlyWhenTheDocumentHasNoJournalOfItsOwn`. Suite 203/203. + +### 2026-10-18 (4) — Configurable customer fields (Sales Configuration) +- **What:** Settings -> Sales Configuration -> *Customer fields* has seven switches: Phone, Email, Address (lines 1 and 2), City, Country, Credit limit, Credit days (the customer's "age"). All default **on**, so nothing changes until one is turned off. The New customer dialog, Edit customer dialog, customer profile page, and the customers table / cards (Phone, Credit limit columns) follow the switches. +- **Server rules (`CustomerService`):** a new value for a switched-off field is refused (`422 CONFIG_DISABLED`); on update a switched-off field **keeps its stored value** (a blank/zero from a form that no longer has the field can't wipe it). Tax registration and display name are not configurable. +- **Where stored:** seven `bool` columns on `sales_config` (migration `AddCustomerFieldSwitches`, additive, default true, **not applied**). `useCustomerConfig` reads them from `GET /sales-config`; until it loads, the UI assumes all on. +- **Tests:** `CustomerFieldSwitchTests` (12 cases). Suite 214/214. diff --git a/Backend/smoke/smoke_common.py b/Backend/smoke/smoke_common.py index f8e5175..17a317a 100644 --- a/Backend/smoke/smoke_common.py +++ b/Backend/smoke/smoke_common.py @@ -34,6 +34,9 @@ for _stream in (sys.stdout, sys.stderr): except (AttributeError, ValueError): pass +# NOTE (roles moved into the ERP, 2026-10): permissions no longer come from the AuthHex token. The smoke user must be an +# ADMINISTRATOR IN THE ERP (users.RoleId pointing at the role flagged IsAdmin), e.g. assigned under Settings -> Users, or the +# Nav-policy endpoints these scripts hit will answer 403. The ports below are also stale (ERPCore 6701, AuthHex 6703). DEFAULTS = { "api": "http://localhost:5224/api/v1", "auth": "http://localhost:5602", diff --git a/Frontend/erp-system/app/dashboard/accounts/opening-balances/page.tsx b/Frontend/erp-system/app/dashboard/accounts/opening-balances/page.tsx new file mode 100644 index 0000000..75e3de6 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/accounts/opening-balances/page.tsx @@ -0,0 +1,49 @@ +"use client" + +import { useState } from "react" + +import { cn } from "@/lib/utils" +import { OpeningCashBankPanel } from "@/components/accounts/OpeningCashBankPanel" +import { OpeningStockPanel } from "@/components/accounts/OpeningStockPanel" + +const TABS = [ + { key: "cash", label: "Cash & bank" }, + { key: "stock", label: "Stock" }, +] as const + +/** + * Opening balances — what the business already had when it started using the system. Each tab posts one + * balanced General Ledger journal against "Opening Balance Equity" (cash and bank once; stock a warehouse at a time). + */ +export default function OpeningBalancesPage() { + const [tab, setTab] = useState<(typeof TABS)[number]["key"]>("cash") + + return ( +
+
+

Opening Balances

+

Bring in the cash, bank and stock you already had on the day you started.

+
+ +
+ {TABS.map((t) => ( + + ))} +
+ + {tab === "cash" ? : } +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/accounts/page.tsx b/Frontend/erp-system/app/dashboard/accounts/page.tsx index 34f53a0..09a6828 100644 --- a/Frontend/erp-system/app/dashboard/accounts/page.tsx +++ b/Frontend/erp-system/app/dashboard/accounts/page.tsx @@ -1,5 +1,5 @@ import Link from "next/link" -import { BookText, Inbox, PiggyBank, Receipt, Tags, Wallet, type LucideIcon } from "lucide-react" +import { BookText, Inbox, PiggyBank, Receipt, Scale, Tags, Wallet, type LucideIcon } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" @@ -40,6 +40,12 @@ const areas: { title: string; description: string; href: string; icon: LucideIco href: "/dashboard/accounts/bank-deposits", icon: PiggyBank, }, + { + title: "Opening Balances", + description: "Enter what each cash and bank account held when you started — posted once as a single ledger journal.", + href: "/dashboard/accounts/opening-balances", + icon: Scale, + }, ] export default function AccountsHubPage() { diff --git a/Frontend/erp-system/app/dashboard/customers/[id]/page.tsx b/Frontend/erp-system/app/dashboard/customers/[id]/page.tsx index a7abbee..42af49e 100644 --- a/Frontend/erp-system/app/dashboard/customers/[id]/page.tsx +++ b/Frontend/erp-system/app/dashboard/customers/[id]/page.tsx @@ -5,6 +5,7 @@ import { useParams } from "next/navigation" import Link from "next/link" import { AlertTriangle, ArrowLeft, BadgeDollarSign, Pencil, Receipt, Save, Users } from "lucide-react" +import { useCustomerConfig } from "@/hooks/use-customer-config" import { customersApi } from "@/lib/api/customers" import { salesApi } from "@/lib/api/sales" import { errorMessage, fieldErrors } from "@/lib/error-map" @@ -61,6 +62,8 @@ export default function CustomerDetailPage() { const [city, setCity] = useState("") const [country, setCountry] = useState("") const [taxRegistrationNo, setTaxRegistrationNo] = useState("") + // A field switched off in Sales Configuration is hidden; its stored value is sent back unchanged. + const { fields } = useCustomerConfig() const [creditLimit, setCreditLimit] = useState("0") const [creditDays, setCreditDays] = useState("0") @@ -502,42 +505,58 @@ export default function CustomerDetailPage() { setDisplayName(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
- - setPhone(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
-
- - setEmail(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
-
- - setAddressLine1(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
-
- - setAddressLine2(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
-
- - setCity(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
-
- - setCountry(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
+ {fields.phone && ( +
+ + setPhone(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )} + {fields.email && ( +
+ + setEmail(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )} + {fields.address && ( + <> +
+ + setAddressLine1(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+
+ + setAddressLine2(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ + )} + {fields.city && ( +
+ + setCity(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )} + {fields.country && ( +
+ + setCountry(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )}
setTaxRegistrationNo(e.target.value)} className="h-12 text-base" disabled={conflict} />
-
- - setCreditLimit(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
-
- - setCreditDays(e.target.value)} className="h-12 text-base" disabled={conflict} /> -
+ {fields.creditLimit && ( +
+ + setCreditLimit(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )} + {fields.creditDays && ( +
+ + setCreditDays(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )} + {(po.status === "Draft" || po.status === "Rejected") && ( <> @@ -472,8 +523,11 @@ export default function PurchaseOrderDetailPage() {

{line.qty} / {line.qtyReceived}

-

Unit price / Tax

-

{Number(line.unitPrice).toFixed(2)} / {(Number(line.tax) * 100).toFixed(0)}%

+

{showTax ? "Unit price / Tax" : "Unit price"}

+

+ {Number(line.unitPrice).toFixed(2)} + {showTax ? ` / ${line.taxPct}% (${formatAmount(line.taxAmount)})` : ""} +

@@ -485,7 +539,7 @@ export default function PurchaseOrderDetailPage() {
Item - value={line.itemId} onValueChange={(v) => updateLine(line.key, { itemId: v })}> + value={line.itemId} onValueChange={(v) => handleItemChange(line.key, v)}> @@ -569,20 +623,31 @@ export default function PurchaseOrderDetailPage() {
-
- Tax - updateLine(line.key, { tax: e.target.value })} - className="h-11 w-full text-base sm:w-32" - /> - -
+ {showTax && ( +
+
+ Tax % + updateLine(line.key, { taxPct: e.target.value })} + className="h-11 text-base" + /> + +
+
+ Tax amount +
+ {formatAmount(lineTaxAmount(Number(line.qty), Number(line.unitPrice), Number(line.taxPct)))} +
+
+
+ )} ) })} @@ -599,7 +664,8 @@ export default function PurchaseOrderDetailPage() { Qty Received Unit price - Tax + {showTax && Tax %} + {showTax && Tax amount} {editable && } @@ -616,14 +682,15 @@ export default function PurchaseOrderDetailPage() { {line.qty} {line.qtyReceived} {Number(line.unitPrice).toFixed(2)} - {(Number(line.tax) * 100).toFixed(0)}% + {showTax && {line.taxPct}%} + {showTax && {formatAmount(line.taxAmount)}} ) } return ( - value={line.itemId} onValueChange={(v) => updateLine(line.key, { itemId: v })}> + value={line.itemId} onValueChange={(v) => handleItemChange(line.key, v)}> @@ -682,18 +749,26 @@ export default function PurchaseOrderDetailPage() { /> - - updateLine(line.key, { tax: e.target.value })} - className="h-11 text-base" - /> - - + {showTax && ( + + updateLine(line.key, { taxPct: e.target.value })} + className="h-11 text-base" + /> + + + )} + {showTax && ( + + {formatAmount(lineTaxAmount(Number(line.qty), Number(line.unitPrice), Number(line.taxPct)))} + + )} + + + )} + /> { if (!v) setRejectTarget(null) }}> - Reject {rejectTarget?.docNo}? - Sends it back to Draft so the requester can revise and resubmit. + Reject {rejectTarget?.po.docNo}? + Marks it Rejected so the requester can revise and resubmit it. Reason (optional) diff --git a/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx b/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx index f8d9f76..4aff6c6 100644 --- a/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx +++ b/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx @@ -1,9 +1,9 @@ "use client" -import { Suspense, useEffect, useState } from "react" +import { Suspense, useEffect, useMemo, useState } from "react" import { useRouter, useSearchParams } from "next/navigation" import Link from "next/link" -import { ArrowLeft, ExternalLink, Plus, Trash2 } from "lucide-react" +import { ChevronDown, ExternalLink, Plus, Trash2 } from "lucide-react" import { purchaseOrdersApi } from "@/lib/api/purchase-orders" import { requisitionsApi } from "@/lib/api/requisitions" @@ -16,9 +16,20 @@ import { uomsApi } from "@/lib/api/uoms" import { baseUomLabel } from "@/lib/uom-label" import { errorMessage } from "@/lib/error-map" import { validatePoLine } from "@/lib/validations/procurement" +import { formatAmount } from "@/lib/format" +import { lineTaxAmount, pctText, pctToRate } from "@/lib/po-tax" import { cn } from "@/lib/utils" import { generateVendorCode } from "@/lib/vendor-code" import { CreatePoLineInput } from "@/types/procurement" +import { useProcurementConfig } from "@/hooks/use-procurement-config" +import { + EMPTY_VENDOR_OPTIONAL, + VendorOptionalFields, + VendorOptionalValues, + validateVendorOptional, + vendorOptionalPayload, + vendorTermsTaxPayload, +} from "@/components/vendors/VendorOptionalFields" import { ItemListItem, Uom, Vendor, Warehouse } from "@/types/master-data" import { Branch } from "@/types/hrm" @@ -36,6 +47,7 @@ import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { SuggestInput, SuggestOption } from "@/components/ui/suggest-input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Skeleton } from "@/components/ui/skeleton" import { toast } from "@/components/ui/toast" @@ -46,7 +58,10 @@ interface DraftLine { warehouseId: number | null qty: string unitPrice: string - tax: string + /** Tax as a percent (18 = 18%) — prefilled from the item's tax rate, editable. Only used while PO tax is on. */ + taxPct: string + /** True once the user types a tax %, so a later item change or reload of items never overwrites it. */ + taxTouched?: boolean } let keySeq = 0 @@ -55,12 +70,55 @@ function newKey() { return `poline-${keySeq}` } -// Tax is still not entered at PO creation — it's captured at GRN receipt (with discount/VAT -// there) and stays off this form, though it remains on the payload since the backend line -// DTO still requires it. Unit price *is* entered here; a PO prefilled from an RFQ starts -// from its negotiated price (below) but stays editable. +// Tax is entered here only while Procurement configuration has "Apply tax on purchase orders" on: +// each line then starts from its item's tax rate and stays editable. With it off the field is hidden +// and every line is sent at 0 (the server rejects anything else). Unit price *is* entered here; a PO +// prefilled from an RFQ starts from its negotiated price (below) but stays editable. function emptyLine(): DraftLine { - return { key: newKey(), itemId: null, warehouseId: null, qty: "", unitPrice: "0", tax: "0" } + return { key: newKey(), itemId: null, warehouseId: null, qty: "", unitPrice: "0", taxPct: "0" } +} + +function ItemSearchPicker({ + item, + options, + onSelect, +}: { + item: ItemListItem | null + options: SuggestOption[] + onSelect: (itemId: number) => void +}) { + const [searching, setSearching] = useState(!item) + + if (item && !searching) { + return ( + + ) + } + + return ( + { + onSelect(Number(value)) + setSearching(false) + }} + browsable + autoFocus + placeholder="Search item — SKU or name…" + emptyText="No items match." + listClassName="min-w-[min(32rem,calc(100vw-2rem))]" + aria-label="Search item" + /> + ) } function NewPurchaseOrderContent() { @@ -93,6 +151,8 @@ function NewPurchaseOrderContent() { const [vTerms, setVTerms] = useState("") const [vTaxReg, setVTaxReg] = useState("") const [vCurrency, setVCurrency] = useState("LKR") + const { config: procConfig } = useProcurementConfig() + const [vOptional, setVOptional] = useState(EMPTY_VENDOR_OPTIONAL) const [vErrors, setVErrors] = useState>({}) const [vSubmitting, setVSubmitting] = useState(false) @@ -132,6 +192,7 @@ function NewPurchaseOrderContent() { setVTerms("") setVTaxReg("") setVCurrency("LKR") + setVOptional(EMPTY_VENDOR_OPTIONAL) setVErrors({}) } @@ -139,6 +200,7 @@ function NewPurchaseOrderContent() { const nextErrors: Record = {} if (!vName.trim()) nextErrors.name = "Vendor name is required" if (!vCurrency.trim()) nextErrors.currency = "Currency is required" + Object.assign(nextErrors, validateVendorOptional(procConfig, vOptional)) setVErrors(nextErrors) if (Object.keys(nextErrors).length > 0) return @@ -147,9 +209,9 @@ function NewPurchaseOrderContent() { const result = await vendorsApi.create({ code: generatedVendorCode, name: vName, - terms: vTerms || null, - taxReg: vTaxReg || null, + ...vendorTermsTaxPayload(procConfig, vTerms, vTaxReg), currency: vCurrency, + ...vendorOptionalPayload(procConfig, vOptional), }) await loadVendors() setVendorId(result.data.vendorId) @@ -178,7 +240,7 @@ function NewPurchaseOrderContent() { warehouseId: null, qty: String(l.qty), unitPrice: "0", - tax: "0", + taxPct: "0", }) ) ) @@ -203,7 +265,7 @@ function NewPurchaseOrderContent() { warehouseId: null, qty: String(l.qty), unitPrice: cell ? String(cell.unitPrice) : "0", - tax: "0", + taxPct: "0", } }) ) @@ -244,6 +306,32 @@ function NewPurchaseOrderContent() { return items?.find((i) => i.itemId === itemId) ?? null } + // PO tax on: every line the user has not typed a tax % into follows its item's tax rate. Keyed on the + // item ids (not on `lines`), so typing in a line never re-triggers it. Covers picking an item, a + // requisition/RFQ prefill and the item list finishing its load. + const showTax = procConfig.poTaxEnabled + const lineItemIds = lines.map((l) => l.itemId ?? 0).join(",") + useEffect(() => { + if (!showTax || !items) return + setLines((prev) => + prev.map((l) => (l.taxTouched ? l : { ...l, taxPct: pctText(items.find((i) => i.itemId === l.itemId)?.taxRatePercentage ?? 0) })), + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showTax, items, lineItemIds]) + + const itemOptions = useMemo( + () => + (items ?? []).map((i) => { + const uom = (uoms ?? []).find((u) => u.uomId === i.baseUomId)?.name + return { + value: String(i.itemId), + label: `${i.sku} — ${i.name}`, + detail: uom ? `UOM: ${uom}` : undefined, + } + }), + [items, uoms] + ) + async function handleSubmit(saveAsDraft: boolean) { setHeaderError(null) setSubmitError(null) @@ -268,8 +356,13 @@ function NewPurchaseOrderContent() { warehouseId: line.warehouseId, qty: line.qty, unitPrice: line.unitPrice, - tax: line.tax, + tax: "0", }) + if (showTax) { + const pct = Number(line.taxPct) + if (line.taxPct.trim() === "" || Number.isNaN(pct) || pct < 0) errors.tax = "Tax % cannot be negative" + else if (pct > 100) errors.tax = "Tax cannot exceed 100%" + } if (Object.keys(errors).length > 0) nextLineErrors[line.key] = errors } setLineErrors(nextLineErrors) @@ -283,7 +376,7 @@ function NewPurchaseOrderContent() { warehouseId: l.warehouseId as number, qty: Number(l.qty), unitPrice: Number(l.unitPrice), - tax: Number(l.tax), + tax: showTax ? pctToRate(Number(l.taxPct)) : 0, })) setSubmitting(true) @@ -297,9 +390,9 @@ function NewPurchaseOrderContent() { }) toast.success( "Purchase order created", - saveAsDraft ? `${po.docNo} — saved as draft.` : `${po.docNo} — auto-approved (FR-PROC-04).` + saveAsDraft ? `${po.docNo} — saved as draft.` : `${po.docNo} — submitted for approval.` ) - router.push(`/dashboard/procurement/purchase-orders/${po.poId}`) + router.push("/dashboard/procurement/purchase-orders") } catch (err) { setSubmitError(errorMessage(err)) toast.error("Could not create purchase order", errorMessage(err)) @@ -310,12 +403,17 @@ function NewPurchaseOrderContent() { const loading = !items || !uoms || !warehouses || !vendors || prefillLoading + const subTotal = lines.reduce((sum, l) => sum + (Number(l.qty) || 0) * (Number(l.unitPrice) || 0), 0) + const taxTotal = showTax + ? lines.reduce((sum, l) => sum + lineTaxAmount(Number(l.qty), Number(l.unitPrice), Number(l.taxPct)), 0) + : 0 + return (

New Purchase Order

-

Auto-approved on creation; freely editable while open (FR-PROC-03..05).

+

Save as a draft to keep editing, or create and submit it for approval (FR-PROC-03..05).

@@ -369,14 +467,25 @@ function NewPurchaseOrderContent() { Code (auto-generated) - - Payment terms (optional) - setVTerms(e.target.value)} placeholder="NET30" /> - - - Tax registration (optional) - setVTaxReg(e.target.value)} placeholder="134567890-7000" /> - + {procConfig.vendorPaymentTermsEnabled && ( + + Payment terms (optional) + setVTerms(e.target.value)} placeholder="NET30" /> + + )} + {procConfig.vendorTaxRegEnabled && ( + + Tax registration (optional) + setVTaxReg(e.target.value)} placeholder="134567890-7000" /> + + )} + setVOptional((prev) => ({ ...prev, ...patch }))} + idPrefix="po-v" + errors={vErrors} + /> Currency setVCurrency(e.target.value)} placeholder="LKR" maxLength={3} aria-invalid={!!vErrors.currency} /> @@ -468,22 +577,13 @@ function NewPurchaseOrderContent() {
) : ( <> - value={line.itemId} onValueChange={(v) => updateLine(line.key, { itemId: v })}> - - - - - {(items ?? []).map((i) => ( - - {i.sku} — {i.name} - - ))} - - +
+ updateLine(line.key, { itemId })} + /> +
)} @@ -555,6 +655,32 @@ function NewPurchaseOrderContent() { + + {showTax && ( +
+
+ + updateLine(line.key, { taxPct: e.target.value, taxTouched: true })} + className="h-11 text-base" + /> + +
+
+ +
+ {formatAmount(lineTaxAmount(Number(line.qty), Number(line.unitPrice), Number(line.taxPct)))} +
+
+
+ )} ) })} @@ -570,6 +696,8 @@ function NewPurchaseOrderContent() { Warehouse Qty Unit price + {showTax && Tax %} + {showTax && Tax amount}
@@ -584,22 +712,13 @@ function NewPurchaseOrderContent() {
{item ? `${item.sku} — ${item.name}` : `Item #${line.itemId}`}
) : ( <> - value={line.itemId} onValueChange={(v) => updateLine(line.key, { itemId: v })}> - - - - - {(items ?? []).map((i) => ( - - {i.sku} — {i.name} - - ))} - - +
+ updateLine(line.key, { itemId })} + /> +
)} @@ -648,6 +767,26 @@ function NewPurchaseOrderContent() { /> + {showTax && ( + + updateLine(line.key, { taxPct: e.target.value, taxTouched: true })} + className="h-11 text-base" + /> + + + )} + {showTax && ( + + {formatAmount(lineTaxAmount(Number(line.qty), Number(line.unitPrice), Number(line.taxPct)))} + + )} - )} - - - {error && ( -
{error}
- )} - - {!error && pos === null && ( -
-
- - -
- {Array.from({ length: 5 }).map((_, i) => ( -
- -
- - -
- - -
- ))} -
- )} - - {!error && visiblePos !== null && visiblePos.length === 0 && ( -
- -

{hasFilters ? "No purchase orders match your search/filter." : "No purchase orders yet."}

-
- )} - - {!error && visiblePos !== null && visiblePos.length > 0 && ( - <> - {/* Mobile: stacked cards. */} -
- {visiblePos.map((po) => { - const editable = isPoEditable(po.status) - return ( -
{ - if ((e.target as HTMLElement).closest("[data-no-row-nav]")) return - router.push(`/dashboard/procurement/purchase-orders/${po.poId}`) - }} - onKeyDown={(e) => { - if (e.key === "Enter") router.push(`/dashboard/procurement/purchase-orders/${po.poId}`) - }} - className="flex flex-col gap-3 rounded-xl border bg-card p-4 shadow-sm" + { + const editable = isPoEditable(po.status) + return ( + <> + + + + {editable && ( + -
-
-

{po.docNo}

-

{vendorCode(po.vendorId)}

-
- -
- -
-
-

Grand total

-

{po.totals.currency} {po.totals.grandTotal.toFixed(2)}

-
-
-

Created

-

{new Date(po.createdAt).toLocaleDateString()}

-
- {isAdmin && branchingEnabled && ( -
-

Branch

- - {branchName(po.branchId)} - -
- )} -
- - {editable && ( -
- - - - - - } - > - - - handleSubmit(po)} - /> - - - - } - > - - - handleDelete(po)} - /> - -
- )} -
- ) - })} - - {pagination && pos !== null && pos.length < pagination.totalItems && ( - - )} -
- - {/* Desktop: table. */} -
-
- - - - Doc No - Vendor - Status - Grand total - {isAdmin && branchingEnabled && Branch} - Created - Actions - - - - {visiblePos.map((po) => { - const editable = isPoEditable(po.status) - return ( - { - if ((e.target as HTMLElement).closest("[data-no-row-nav]")) return - router.push(`/dashboard/procurement/purchase-orders/${po.poId}`) - }} - > - - - {po.docNo} - - - {vendorCode(po.vendorId)} - - - - {po.totals.currency} {po.totals.grandTotal.toFixed(2)} - {isAdmin && branchingEnabled && ( - - {branchName(po.branchId)} - - )} - {new Date(po.createdAt).toLocaleString()} - -
- - - - {editable && ( - <> - - - - - - } - > - - - handleSubmit(po)} - /> - - - - } - > - - - handleDelete(po)} - /> - - - )} -
-
-
- ) - })} -
-
-
-
- - {pagination && ( -
- { setPage(1); setPageSize(size) }} - /> -
- )} - - )} + {editable && ( + <> + + + } + > + + + handleSubmit(po, reload)} + /> + + + + } + > + + + handleDelete(po, reload)} + /> + + + )} + + ) + }} + /> ) } diff --git a/Frontend/erp-system/app/dashboard/products/[id]/edit/page.tsx b/Frontend/erp-system/app/dashboard/products/[id]/edit/page.tsx new file mode 100644 index 0000000..d986f2d --- /dev/null +++ b/Frontend/erp-system/app/dashboard/products/[id]/edit/page.tsx @@ -0,0 +1,7 @@ +"use client" + +import ItemDetailRoute from "../page" + +export default function EditItemRoute() { + return +} diff --git a/Frontend/erp-system/app/dashboard/products/[id]/page.tsx b/Frontend/erp-system/app/dashboard/products/[id]/page.tsx index 55f3b6e..3499b6e 100644 --- a/Frontend/erp-system/app/dashboard/products/[id]/page.tsx +++ b/Frontend/erp-system/app/dashboard/products/[id]/page.tsx @@ -1,7 +1,7 @@ "use client" import { useEffect, useMemo, useState } from "react" -import { useParams } from "next/navigation" +import { useParams, usePathname, useRouter } from "next/navigation" import Link from "next/link" import { AlertTriangle, ArrowLeft, BadgeDollarSign, Boxes, Package, PackageCheck, Pencil, Save, ShoppingCart, TrendingUp } from "lucide-react" @@ -9,6 +9,7 @@ import { approvalsApi, priceAdjustmentsApi } from "@/lib/api/approvals" import { itemsApi } from "@/lib/api/items" import { categoriesApi } from "@/lib/api/categories" import { productConfig } from "@/lib/api/product-config" +import { warrantyPeriodsApi } from "@/lib/api/warranty-periods" import { uomsApi } from "@/lib/api/uoms" import { taxRatesApi } from "@/lib/api/tax-rates" import { warehousesApi } from "@/lib/api/warehouses" @@ -16,7 +17,7 @@ import { stockApi } from "@/lib/api/stock" import { errorMessage, fieldErrors } from "@/lib/error-map" import { validateItemForm } from "@/lib/validations/master-data" import { cn } from "@/lib/utils" -import { Item, ItemReorderSetting, MeasureUnit, StockNature, TaxRate, TrackingMode, Warranty, WarrantyPeriodMonths } from "@/types/master-data" +import { Item, ItemReorderSetting, MeasureUnit, ProductConfig, StockNature, TaxRate, TrackingMode, Warranty, WarrantyPeriodMonths } from "@/types/master-data" import { LedgerEntry } from "@/types/stock" import { ItemPriceHistory } from "@/types/approvals" @@ -28,7 +29,6 @@ const SALES_DOC_TYPES = new Set(["SI", "SSL", "BND"]) import { Badge } from "@/components/ui/badge" import { Button, buttonVariants } from "@/components/ui/button" -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { FieldError } from "@/components/ui/field" @@ -40,6 +40,7 @@ import LineChart from "@/components/ui/line-chart" import BarChart from "@/components/ui/bar-chart" import { ItemImagesPanel } from "@/components/products/ItemImagesPanel" import { ItemReorderPanel } from "@/components/products/ItemReorderPanel" +import { WarrantyFields } from "@/components/products/WarrantyFields" /** * The Edit dialog's simple low-stock alert, read back from the item's reorder rules: one level and @@ -88,7 +89,9 @@ function StatChip({ icon: Icon, label, value }: { icon: typeof Package; label: s ) } -export default function ItemDetailPage() { +function ItemDetailPage() { + const router = useRouter() + const editMode = usePathname().endsWith("/edit") const params = useParams<{ id: string }>() const itemId = Number(params.id) @@ -120,7 +123,7 @@ export default function ItemDetailPage() { // carried through unchanged (from the loaded item) so a save doesn't silently clear them. const [defaultVendorId, setDefaultVendorId] = useState(null) const [trackingMode, setTrackingMode] = useState("None") - // Not editable here — carried through unchanged so a save doesn't silently reset it. + // Warranty: edited in the Warranty section below (options follow Product configuration). const [warranty, setWarranty] = useState("NonWarranty") const [warrantyPeriodMonths, setWarrantyPeriodMonths] = useState(null) const [taxRateId, setTaxRateId] = useState(null) @@ -145,8 +148,11 @@ export default function ItemDetailPage() { const [saveError, setSaveError] = useState(null) const [saving, setSaving] = useState(false) const [togglingStatus, setTogglingStatus] = useState(false) - const [editOpen, setEditOpen] = useState(false) - const [imagesEnabled, setImagesEnabled] = useState(false) + // Product configuration drives which edit fields exist: selling prices and warranty only when switched on. + const [config, setConfig] = useState(null) + const imagesEnabled = config?.itemImagesEnabled ?? false + // Active rows of the Warranty Periods master (months), offered when custom warranty periods are on. + const [customWarrantyMonths, setCustomWarrantyMonths] = useState([]) const [imagesVersion, setImagesVersion] = useState(0) function applyItem(data: Item) { @@ -199,7 +205,8 @@ export default function ItemDetailPage() { setTaxRates(tr.items) }) .catch(() => {}) - productConfig().then((cfg) => setImagesEnabled(cfg.itemImagesEnabled)).catch(() => {}) + productConfig().then(setConfig).catch(() => {}) + warrantyPeriodsApi.list({ status: "Active" }).then((rows) => setCustomWarrantyMonths(rows.map((r) => r.months))).catch(() => {}) // Locked unless Settings → Approvals turned price approval off; the server enforces it either way. approvalsApi.getConfig().then((c) => setPricesLocked(c.priceAdjustmentLevels > 0)).catch(() => {}) priceAdjustmentsApi.itemHistory(itemId).then(setPriceHistory).catch(() => setPriceHistory([])) @@ -283,25 +290,17 @@ export default function ItemDetailPage() { ) const currentStockQty = stockTrend.length > 0 ? stockTrend[stockTrend.length - 1].qty : 0 - /** Opens the edit dialog fresh from the last-loaded item, discarding any unsaved - * edits left over from a previous cancelled session. */ function openEdit() { - if (item) applyItem(item) - setErrors({}) - setSaveError(null) - setEditOpen(true) - } - - function closeEdit() { - if (item) applyItem(item) - setEditOpen(false) - setImagesVersion((v) => v + 1) + router.push(`/dashboard/products/${itemId}/edit`) } async function handleSave() { if (!item || !etag) return setSaveError(null) const nextErrors: Record = validateItemForm({ sku, name, categoryId, baseUomId, contentQty, contentUnit }) + if (config?.warrantyPeriodsEnabled && warranty === "Warranty" && warrantyPeriodMonths === null) { + nextErrors.warrantyPeriodMonths = "Select a warranty period" + } if (lowStockAt.trim()) { const point = Number(lowStockAt) const qty = Number(reorderQty) @@ -350,16 +349,15 @@ export default function ItemDetailPage() { } applyItem(saved) setEtag(result.etag) - setEditOpen(false) setImagesVersion((v) => v + 1) toast.success("Item saved", `${result.data.sku} — ${result.data.name}`) + if (editMode) router.push(`/dashboard/products/${item.itemId}`) } catch (err) { const code = (err as { code?: string })?.code if (code === "CONCURRENCY_CONFLICT") { setConflict(true) setSaveError(errorMessage(err)) setSaving(false) - setEditOpen(false) return } const fe = fieldErrors(err) @@ -417,6 +415,177 @@ export default function ItemDetailPage() { ) } + if (editMode) { + return ( +
+ + + Back to item + +
+

Edit {item.sku}

+

Update this item's basic info, pricing and classification.

+
+ {conflict && ( +
+ +
+

{saveError ?? "This item was changed by someone else."} Reload before retrying.

+ +
+
+ )} + {saveError && !conflict && ( +
{saveError}
+ )} +
+
+
+ + setSku(e.target.value)} aria-invalid={!!errors.sku} className="h-12 text-base" disabled={conflict} /> + +
+
+ + setBarcode(e.target.value)} placeholder="Scan or type a barcode" aria-invalid={!!errors.barcode} className="h-12 text-base" disabled={conflict} /> + +

Used for scan lookup on GRN, stock count, and sales lines — separate from SKU.

+
+
+ + setName(e.target.value)} aria-invalid={!!errors.name} className="h-12 text-base" disabled={conflict} /> + +
+ {config?.sellingPricesEnabled && ( + <> +
+ + setSellingMinPrice(e.target.value)} placeholder="Optional" className="h-12 text-base" disabled={conflict || pricesLocked} /> +
+
+ + setSellingMaxPrice(e.target.value)} placeholder="Optional" className="h-12 text-base" disabled={conflict || pricesLocked} /> +
+
+ + setSellingFlatPrice(e.target.value)} placeholder="Optional" className="h-12 text-base" disabled={conflict || pricesLocked} /> + {pricesLocked && ( +

+ Selling prices need approval (Settings → Approvals).{" "} + + Request a price change + +

+ )} +
+ + )} +
+ + value={taxRateId === null ? NO_TAX_RATE : String(taxRateId)} onValueChange={(v) => setTaxRateId(!v || v === NO_TAX_RATE ? null : Number(v))} disabled={conflict}> + + + No default (0%) + {taxRates.map((t) => {t.name} ({t.percentage}%))} + + +

Sales/purchase lines for this item default to this rate — see Products → Tax Rates.

+
+
+ + value={categoryId} onValueChange={setCategoryId} disabled={conflict} items={categories.map((c) => ({ label: c.name, value: c.categoryId }))}> + + {categories.map((c) => {c.name})} + + +
+
+ + value={baseUomId} onValueChange={setBaseUomId} disabled={conflict} items={uoms.map((u) => ({ label: u.name, value: u.uomId }))}> + + {uoms.map((u) => {u.name})} + + +
+
+ +
+ setContentQty(e.target.value)} placeholder="e.g. 500" aria-invalid={!!errors.contentQty} className="h-12! text-base" /> + value={contentUnit} onValueChange={setContentUnit} disabled={conflict} items={CONTENT_UNITS.map((u) => ({ label: u, value: u }))}> + + {CONTENT_UNITS.map((u) => {u.toLowerCase()})} + +
+ +

+ {item.contentBaseQty !== null && item.contentBaseUnit ? `Stored as ${item.contentBaseQty} ${item.contentBaseUnit.toLowerCase()} per ${uomName(item.baseUomId)}.` : "No content size — leave blank for items with nothing measurable to hold."} +

+
+
+ + value={stockNature} onValueChange={(v) => v && setStockNature(v)} disabled={conflict}> + + + Stocked + Non-stocked + Service + + +
+
+ +
+ setLowStockAt(e.target.value)} placeholder="Alert when stock ≤" aria-label="Alert when stock is at or below" aria-invalid={!!errors.lowStockAt} className="h-12! text-base" disabled={conflict} /> + setReorderQty(e.target.value)} placeholder="Reorder qty" aria-label="Reorder quantity" aria-invalid={!!errors.reorderQty} className="h-12! text-base" disabled={conflict} /> +
+ +

+ {alertIsCustom ? "This item has its own per-warehouse levels (see Reorder settings). Changing these fields replaces them all with one level." : lowStockAt.trim() ? `When stock at ${warehouseId ? (warehouses.find((w) => w.warehouseId === warehouseId)?.code ?? "the warehouse") : "any warehouse"} drops to ${lowStockAt} or less, a low-stock alert is sent.` : "When stock drops to this count or less, you get a low-stock alert. Clear it to stop alerts."} +

+
+
+ + value={warehouseId} onValueChange={setWarehouseId} disabled={conflict} items={warehouses.map((w) => ({ label: `${w.code} — ${w.name}`, value: w.warehouseId }))}> + + + Every warehouse + {warehouses.map((w) => {w.code} — {w.name})} + + +

Where the low-stock alert watches stock.

+
+
+ {config?.warrantyPeriodsEnabled && ( + { + setWarranty(w) + setWarrantyPeriodMonths(m) + setErrors((prev) => ({ ...prev, warrantyPeriodMonths: "" })) + }} + error={errors.warrantyPeriodMonths || undefined} + disabled={conflict} + /> + )} + +
+ + +
+
+
+ ) + } + return (
- (open ? openEdit() : closeEdit())}> - - - Edit {item.sku} - Update this item's basic info, pricing and classification. - -
-
- - setSku(e.target.value)} aria-invalid={!!errors.sku} className="h-12 text-base" disabled={conflict} /> - -
-
- - setBarcode(e.target.value)} - placeholder="Scan or type a barcode" - aria-invalid={!!errors.barcode} - className="h-12 text-base" - disabled={conflict} - /> - -

Used for scan lookup on GRN, stock count, and sales lines — separate from SKU.

-
-
- - setName(e.target.value)} aria-invalid={!!errors.name} className="h-12 text-base" disabled={conflict} /> - -
-
- - setSellingMinPrice(e.target.value)} - placeholder="Optional" - className="h-12 text-base" - disabled={conflict || pricesLocked} - /> -
-
- - setSellingMaxPrice(e.target.value)} - placeholder="Optional" - className="h-12 text-base" - disabled={conflict || pricesLocked} - /> -
-
- - setSellingFlatPrice(e.target.value)} - placeholder="Optional" - className="h-12 text-base" - disabled={conflict || pricesLocked} - /> - {pricesLocked && ( -

- Selling prices need approval.{" "} - - Request a price change - -

- )} -
-
- - - value={taxRateId === null ? NO_TAX_RATE : String(taxRateId)} - onValueChange={(v) => setTaxRateId(!v || v === NO_TAX_RATE ? null : Number(v))} - disabled={conflict} - > - - - - - No default (0%) - {taxRates.map((t) => ( - - {t.name} ({t.percentage}%) - - ))} - - -

- Sales/purchase lines for this item default to this rate — see Products → Tax Rates. -

-
-
- - - value={categoryId} - onValueChange={setCategoryId} - disabled={conflict} - items={categories.map((c) => ({ label: c.name, value: c.categoryId }))} - > - - - - - {categories.map((c) => ( - - {c.name} - - ))} - - - -
-
- - - value={baseUomId} - onValueChange={setBaseUomId} - disabled={conflict} - items={uoms.map((u) => ({ label: u.name, value: u.uomId }))} - > - - - - - {uoms.map((u) => ( - - {u.name} - - ))} - - - -
- {/* Content size (FR-MD-02): how much one pack holds. Optional, and independent of the - base UOM above — stock is counted in packs either way. */} -
- -
- setContentQty(e.target.value)} - placeholder="e.g. 500" - aria-invalid={!!errors.contentQty} - className="h-12! text-base" - /> - - value={contentUnit} - onValueChange={setContentUnit} - disabled={conflict} - items={CONTENT_UNITS.map((u) => ({ label: u, value: u }))} - > - - - - - {CONTENT_UNITS.map((u) => ( - - {u.toLowerCase()} - - ))} - - -
- - {/* The stored value, so the ×1000 normalisation is never a surprise. */} -

- {item.contentBaseQty !== null && item.contentBaseUnit - ? `Stored as ${item.contentBaseQty} ${item.contentBaseUnit.toLowerCase()} per ${uomName(item.baseUomId)}.` - : "No content size — leave blank for items with nothing measurable to hold."} -

-
-
- {/* "Item type" now means a Color/Size dimension master — this field is the - stock-nature one it used to be confused with (docs/11 §8). */} - - value={stockNature} onValueChange={(v) => v && setStockNature(v)} disabled={conflict}> - - - - - Stocked - Non-stocked - Service - - -
-
- -
- setLowStockAt(e.target.value)} - placeholder="Alert when stock ≤" - aria-label="Alert when stock is at or below" - aria-invalid={!!errors.lowStockAt} - className="h-12! text-base" - disabled={conflict} - /> - setReorderQty(e.target.value)} - placeholder="Reorder qty" - aria-label="Reorder quantity" - aria-invalid={!!errors.reorderQty} - className="h-12! text-base" - disabled={conflict} - /> -
- -

- {alertIsCustom - ? "This item has its own per-warehouse levels (see Reorder settings). Changing these fields replaces them all with one level." - : lowStockAt.trim() - ? `When stock at ${warehouseId ? (warehouses.find((w) => w.warehouseId === warehouseId)?.code ?? "the warehouse") : "any warehouse"} drops to ${lowStockAt} or less, a low-stock alert is sent.` - : "When stock drops to this count or less, you get a low-stock alert. Clear it to stop alerts."} -

-
-
- - - value={warehouseId} - onValueChange={setWarehouseId} - disabled={conflict} - items={warehouses.map((w) => ({ label: `${w.code} — ${w.name}`, value: w.warehouseId }))} - > - - - - - Every warehouse - {warehouses.map((w) => ( - - {w.code} — {w.name} - - ))} - - -

Where the low-stock alert watches stock.

-
-
- - - - - -
-
+

{uomName(item.baseUomId)} is the base UOM — every transaction records quantities as a count of it (FR-MD-03). @@ -1042,3 +926,7 @@ export default function ItemDetailPage() { ) } + +export default function ItemDetailRoute() { + return +} diff --git a/Frontend/erp-system/app/dashboard/products/new/page.tsx b/Frontend/erp-system/app/dashboard/products/new/page.tsx index c501021..8b3f288 100644 --- a/Frontend/erp-system/app/dashboard/products/new/page.tsx +++ b/Frontend/erp-system/app/dashboard/products/new/page.tsx @@ -208,7 +208,7 @@ export default function NewItemPage() { brandsApi.list({ pageSize: 200, status: "Active" }), itemTypesApi.list({ pageSize: 200, status: "Active" }), productConfig(), - uomsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200, status: "Active" }), warehousesApi.list({ pageSize: 200 }), ]) .then(([cat, br, types, cfg, uoms, wh]) => { diff --git a/Frontend/erp-system/app/dashboard/products/page.tsx b/Frontend/erp-system/app/dashboard/products/page.tsx index 25335bc..300e3dc 100644 --- a/Frontend/erp-system/app/dashboard/products/page.tsx +++ b/Frontend/erp-system/app/dashboard/products/page.tsx @@ -579,7 +579,7 @@ export default function ItemsPage() { >({}) const [saving, setSaving] = useState(false) + // Settings → Approvals: 0 means no approval — the change is typed, confirmed and applied at once. + const [approvalLevels, setApprovalLevels] = useState(null) + const appliesImmediately = approvalLevels === 0 + useEffect(() => { + approvalsApi.getConfig().then((c) => setApprovalLevels(c.priceAdjustmentLevels)).catch(() => setApprovalLevels(null)) + }, []) // The item's latest GRN purchase prices, to price against (null while loading). const [purchases, setPurchases] = useState(null) @@ -322,14 +328,20 @@ function NewPriceAdjustmentForm() { - {/* 5. Send for approval */} + {/* 5. Send for approval, or confirm and apply when approval is switched off */}

-

Approvers are notified. The price changes only after approval.

+

+ {appliesImmediately + ? "Approval is switched off in Settings → Approvals, so the new price is applied as soon as you confirm." + : "Approvers are notified. The price changes only after approval."} +

Cancel
diff --git a/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx b/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx index 1d1579a..c72dd78 100644 --- a/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx +++ b/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx @@ -236,6 +236,8 @@ export default function NewGrnPage() { } const [vendorId, setVendorId] = useState(null) const [poId, setPoId] = useState(null) + // A GRN against a purchase order belongs to that order's branch - fixed when the PO was raised - so it is never asked again. + const [poBranchId, setPoBranchId] = useState(null) const [poLoading, setPoLoading] = useState(false) const [lines, setLines] = useState([]) const [lineTab, setLineTab] = useState("lines") @@ -265,7 +267,7 @@ export default function NewGrnPage() { const [headerError, setHeaderError] = useState(null) const [lineErrors, setLineErrors] = useState>>({}) const [submitError, setSubmitError] = useState(null) - const [submitting, setSubmitting] = useState(false) + const [submitting, setSubmitting] = useState<"draft" | "confirm" | null>(null) const [refreshingItems, setRefreshingItems] = useState(false) const [scanError, setScanError] = useState(null) const [scanning, setScanning] = useState(false) @@ -318,7 +320,11 @@ export default function NewGrnPage() { const showBranchPicker = branchingEnabled && isAdmin && !lockedBranchId // The branch this GRN is being raised for — no longer used to filter warehouses // (warehouses are shared infrastructure across branches), only to scope the document itself. - const effectiveBranchId = branchingEnabled ? (isAdmin ? (activeBranchId ?? branchFilterId) : myBranchId) : null + const effectiveBranchId = !branchingEnabled + ? null + : mode === "po" + ? poBranchId + : isAdmin ? (activeBranchId ?? branchFilterId) : myBranchId const visibleWarehouses = warehouses ?? [] // Each keeps its inline message (it marks the field) and mirrors it as a toast so no error goes unseen. @@ -334,7 +340,7 @@ export default function NewGrnPage() { function failSubmit(message: string) { setSubmitError(message) - toast.error("Could not create GRN", message) + toast.error("Could not save the GRN", message) } function failWarranty(lineKey: string, message: string) { @@ -345,6 +351,7 @@ export default function NewGrnPage() { function switchMode(next: Mode) { setMode(next) setPoId(null) + setPoBranchId(null) setVendorId(null) setLines([]) setHeaderDiscountPct("") @@ -355,6 +362,7 @@ export default function NewGrnPage() { async function handleSelectPo(nextPoId: number | null) { setPoId(nextPoId) + setPoBranchId(null) if (!nextPoId) { setLines([]) return @@ -364,6 +372,7 @@ export default function NewGrnPage() { setHeaderError(null) try { const po: PurchaseOrder = await purchaseOrdersApi.get(nextPoId) + setPoBranchId(po.branchId) const openLines = po.lines.filter((l) => l.qtyReceived < l.qty) if (openLines.length === 0) { failHeader("This purchase order has no open (unreceived) lines.") @@ -647,7 +656,7 @@ export default function NewGrnPage() { } } - async function handleSubmit() { + async function handleSubmit(confirmNow: boolean) { setSubmitError(null) setHeaderError(null) @@ -733,7 +742,7 @@ export default function NewGrnPage() { } }) - setSubmitting(true) + setSubmitting(confirmNow ? "confirm" : "draft") try { const grn = await grnsApi.create({ poId: mode === "po" ? poId : null, @@ -742,13 +751,26 @@ export default function NewGrnPage() { branchId: effectiveBranchId ?? 0, lines: payloadLines, }) - toast.success("GRN created", `${grn.docNo} is ready to confirm.`) - router.push(`/dashboard/receiving/grn/${grn.grnId}`) + if (!confirmNow) { + toast.success("GRN saved as draft", `${grn.docNo} is waiting to be confirmed.`) + router.push("/dashboard/receiving/grn") + return + } + + // Confirming posts stock and the ledger entry. If that part fails the GRN already exists as a draft, so say so + // and land on the table, where it can be confirmed again once the cause is fixed. + try { + const result = await grnsApi.confirm(grn.grnId, `grn-new-${grn.grnId}`) + toast.success("GRN confirmed", `${grn.docNo} — ${result.createdLayers.length} stock layer(s) posted. Journal ${result.glJournalNo}.`) + } catch (err) { + toast.error(`${grn.docNo} saved as draft, but could not be confirmed`, errorMessage(err)) + } + router.push("/dashboard/receiving/grn") } catch (err) { setSubmitError(errorMessage(err)) toast.error("Could not create GRN", errorMessage(err)) } finally { - setSubmitting(false) + setSubmitting(null) } } @@ -759,7 +781,7 @@ export default function NewGrnPage() {

New GRN

-

Receive goods against a purchase order, or record a direct receipt (FR-GRN-01/02).

+

Receive goods against a purchase order, or record a direct receipt (FR-GRN-01/02). Save it as a draft, or confirm it right away to post the stock.

@@ -829,7 +851,18 @@ export default function NewGrnPage() { )} - {branchingEnabled && isAdmin && ( + {branchingEnabled && mode === "po" && ( +
+ +
+ {poBranchId + ? (branches.find((b) => b.branchId === poBranchId)?.name ?? (poBranchId === activeBranchId ? activeBranchName : null) ?? `Branch #${poBranchId}`) + : "Taken from the purchase order"} +
+
+ )} + + {branchingEnabled && isAdmin && mode === "direct" && (
{showBranchPicker ? ( @@ -1638,8 +1671,11 @@ export default function NewGrnPage() { Cancel - +
)} diff --git a/Frontend/erp-system/app/dashboard/receiving/grn/page.tsx b/Frontend/erp-system/app/dashboard/receiving/grn/page.tsx index 84ff24c..1b80570 100644 --- a/Frontend/erp-system/app/dashboard/receiving/grn/page.tsx +++ b/Frontend/erp-system/app/dashboard/receiving/grn/page.tsx @@ -1,77 +1,115 @@ "use client" -import { useEffect, useState } from "react" +import { useEffect, useRef, useState } from "react" import Link from "next/link" import { useRouter } from "next/navigation" -import { ChevronLeft, ChevronRight, Clock, Eye, PackageSearch, Plus, Search } from "lucide-react" +import { ArrowDown, ArrowUp, ArrowUpDown, Check, Clock, Eye, PackageSearch, Plus, Printer, Truck } from "lucide-react" import { grnsApi } from "@/lib/api/grns" -import { vendorsApi } from "@/lib/api/vendors" +import { branchesHrmApi } from "@/lib/api/hrm-masters" import { warehousesApi } from "@/lib/api/warehouses" import { errorMessage } from "@/lib/error-map" -import { GrnStatus, GrnSummary } from "@/types/grn" -import { Vendor, Warehouse } from "@/types/master-data" -import { PaginationMeta } from "@/types/common" +import { formatAmount, formatReportDate } from "@/lib/format" import { cn } from "@/lib/utils" -import { useAuth } from "@/components/auth/AuthProvider" +import { useProcurementConfig } from "@/hooks/use-procurement-config" import { useDashboardStats } from "@/hooks/use-dashboard-stats" +import { PaginationMeta } from "@/types/common" +import { GrnStatus, GrnSummary } from "@/types/grn" +import { Branch } from "@/types/hrm" +import { Warehouse } from "@/types/master-data" + +import { useAuth } from "@/components/auth/AuthProvider" +import { GrnReceiptDialog } from "@/components/receiving/GrnReceiptPrint" +import { GrnStatusBadge } from "@/components/receiving/status-badges" +import { AlertDialog, AlertDialogContent, AlertDialogTrigger } from "@/components/ui/alert-dialog" +import { Badge } from "@/components/ui/badge" import { Button, buttonVariants } from "@/components/ui/button" -import { toast } from "@/components/ui/toast" -import { Input } from "@/components/ui/input" +import { DateRangeFilter, DateRangeValue, dateRangeBounds } from "@/components/ui/date-range-filter" +import { FilterBar } from "@/components/ui/filter-bar" import { KpiRow } from "@/components/ui/kpi-row" import { PaginationFooter } from "@/components/ui/pagination-footer" -import { Skeleton } from "@/components/ui/skeleton" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { - Table, - TableBody, - - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table" -import { GrnStatusBadge } from "@/components/receiving/status-badges" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { toast } from "@/components/ui/toast" type StatusFilter = GrnStatus | "All" +type SortKey = "created" | "vendor" | "total" | "status" +type SortOrder = "asc" | "desc" const PAGE_SIZE_OPTIONS = [5, 10, 15, 20] // Mobile has no page-size selector — tapping "Show more" just grows the page size and // re-fetches from page 1, so previously loaded cards stay put and more append below. const MOBILE_PAGE_SIZE_STEP = 10 +const STATUS_OPTIONS: { value: StatusFilter; label: string }[] = [ + { value: "All", label: "All statuses" }, + { value: "Draft", label: "Draft" }, + { value: "Confirmed", label: "Confirmed" }, + { value: "Closed", label: "Closed" }, +] + +function SortableHeader({ label, active, order, onClick }: { label: string; active: boolean; order: SortOrder; onClick: () => void }) { + return ( + + ) +} + +/** + * Goods Received Notes — the same table as Purchase Orders: a vendor-first row (name, code · contact, address — the + * contact and address only when switched on in Procurement configuration), created DATE, status, total, branch (admin + * with branching on), and actions. A Draft can be approved (confirmed) right from its row; any GRN can print its receipt. + */ export default function GrnListPage() { const router = useRouter() - const { activeBranchId, branchingEnabled } = useAuth() + const { isAdmin, activeBranchId, branchingEnabled } = useAuth() const stats = useDashboardStats() + const { config: procConfig } = useProcurementConfig() + const showContact = procConfig.vendorContactMode !== "None" + const showAddress = procConfig.vendorAddressEnabled + const showBranch = isAdmin && branchingEnabled const [grns, setGrns] = useState(null) - const [vendors, setVendors] = useState([]) const [warehouses, setWarehouses] = useState([]) + const [branches, setBranches] = useState([]) const [pagination, setPagination] = useState(null) const [error, setError] = useState(null) const [searchInput, setSearchInput] = useState("") const [query, setQuery] = useState("") const [status, setStatus] = useState("All") + const [dateRange, setDateRange] = useState({ preset: "All" }) + const [sortKey, setSortKey] = useState("created") + const [sortOrder, setSortOrder] = useState("desc") const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(PAGE_SIZE_OPTIONS[1]) const [showMoreLoading, setShowMoreLoading] = useState(false) + const [approvingId, setApprovingId] = useState(null) + const [receipt, setReceipt] = useState(null) + // One idempotency key per GRN per page session, so a double click or a retry can't post the stock twice. + const approveKeys = useRef(new Map()) + const approveSeq = useRef(0) + + const { from, to } = dateRangeBounds(dateRange) + // The effect depends on these primitives rather than the range object, which is a new object every render. + const customFrom = dateRange.custom?.from?.getTime() + const customTo = dateRange.custom?.to?.getTime() + useEffect(() => { - vendorsApi.list({ pageSize: 200 }).then((res) => setVendors(res.items)).catch(() => {}) warehousesApi.list({ pageSize: 200 }).then((res) => setWarehouses(res.items)).catch(() => {}) }, []) - function vendorName(id: number) { - const vendor = vendors.find((v) => v.vendorId === id) - return vendor ? `${vendor.code} — ${vendor.name}` : `#${id}` - } - function warehouseName(id: number) { - const warehouse = warehouses.find((w) => w.warehouseId === id) - return warehouse ? `${warehouse.code} — ${warehouse.name}` : `#${id}` - } - + useEffect(() => { + if (showBranch) branchesHrmApi.list({ pageSize: 200, status: "Active" }).then((r) => setBranches(r.items)).catch(() => setBranches([])) + }, [showBranch]) // Debounce the search box so typing doesn't refetch on every keystroke. useEffect(() => { @@ -80,9 +118,7 @@ export default function GrnListPage() { }, [searchInput]) // Any filter change starts back at page 1. - useEffect(() => { - setPage(1) - }, [query, status, activeBranchId]) + useEffect(() => setPage(1), [query, status, activeBranchId, dateRange.preset, customFrom, customTo, sortKey, sortOrder]) function load() { setError(null) @@ -93,6 +129,9 @@ export default function GrnListPage() { q: query || undefined, status: status === "All" ? undefined : status, branchId: branchingEnabled ? (activeBranchId ?? undefined) : undefined, + from, + to, + sort: `${sortOrder === "desc" ? "-" : ""}${sortKey}`, }) .then((res) => { setGrns(res.items) @@ -105,14 +144,142 @@ export default function GrnListPage() { .finally(() => setShowMoreLoading(false)) } - function handleShowMore() { - setShowMoreLoading(true) - setPageSize((s) => s + MOBILE_PAGE_SIZE_STEP) + useEffect(load, [page, pageSize, query, status, activeBranchId, branchingEnabled, from, to, sortKey, sortOrder]) + + function toggleSort(key: SortKey) { + if (key === sortKey) setSortOrder((o) => (o === "asc" ? "desc" : "asc")) + else { + setSortKey(key) + // Dates and totals read best newest/largest first; names and statuses A→Z. + setSortOrder(key === "created" || key === "total" ? "desc" : "asc") + } } - useEffect(load, [page, pageSize, query, status, activeBranchId, branchingEnabled]) + async function handleApprove(grn: GrnSummary) { + let key = approveKeys.current.get(grn.grnId) + if (!key) { + approveSeq.current += 1 + key = `grn-list-${grn.grnId}-${approveSeq.current}` + approveKeys.current.set(grn.grnId, key) + } + setApprovingId(grn.grnId) + try { + const result = await grnsApi.confirm(grn.grnId, key) + toast.success("GRN approved", `${grn.docNo} — ${result.createdLayers.length} stock layer(s) posted. Journal ${result.glJournalNo}.`) + load() + } catch (err) { + // A failed attempt rolled back, so a later click is a fresh attempt. + approveKeys.current.delete(grn.grnId) + toast.error("Could not approve GRN", errorMessage(err)) + } finally { + setApprovingId(null) + } + } - const hasFilters = query.length > 0 || status !== "All" + const warehouseName = (id: number) => { + const w = warehouses.find((x) => x.warehouseId === id) + return w ? `${w.code} — ${w.name}` : `#${id}` + } + const branchName = (id: number) => branches.find((b) => b.branchId === id)?.name ?? `#${id}` + const hasFilters = query.length > 0 || status !== "All" || dateRange.preset !== "All" + const open = (grnId: number) => router.push(`/dashboard/receiving/grn/${grnId}`) + + function clearFilters() { + setSearchInput("") + setStatus("All") + setDateRange({ preset: "All" }) + } + + /** Vendor name, then "code · contact" and (when enabled) the address — the same lines in the table and the cards. */ + function vendorBlock(grn: GrnSummary) { + const contact = procConfig.vendorContactMode === "Mobile" ? grn.vendor?.mobile : grn.vendor?.email + const sub = [grn.vendor?.code, showContact ? contact : ""].filter(Boolean).join(" · ") + return ( +
+
+ +
+
+

{grn.vendor?.name ?? `Vendor #${grn.vendorId}`}

+ {sub &&

{sub}

} + {showAddress && grn.vendor?.address && ( +

{grn.vendor.address}

+ )} +
+
+ ) + } + + /** Where the goods came from: "PO-… · Warehouse" under the GRN number. */ + const docBlock = (grn: GrnSummary) => ( +
+

{grn.docNo}

+

+ {grn.poId ? ( + + {grn.poDocNo ?? `PO ${grn.poId}`} + + ) : ( + "Direct receipt" + )} + {" · "} + {warehouseName(grn.warehouseId)} +

+
+ ) + + const invoiceCell = (grn: GrnSummary) => + grn.status !== "Confirmed" ? ( + — + ) : grn.purchaseInvoiceId ? ( + + {grn.purchaseInvoiceNo ?? `#${grn.purchaseInvoiceId}`} + + ) : ( + Not invoiced + ) + + /** View · Receipt · Approve (Draft only). Clicks inside don't open the row. */ + function actions(grn: GrnSummary) { + return ( + <> + + + + + {grn.status === "Draft" && ( + + }> + + {approvingId === grn.grnId ? "Approving…" : "Approve"} + + handleApprove(grn)} + /> + + )} + + ) + } + + const totalCell = (grn: GrnSummary) =>

{formatAmount(grn.totalValue)}

return (
@@ -131,43 +298,32 @@ export default function GrnListPage() { -
-
- - setSearchInput(e.target.value)} - placeholder="Search doc no., PO, vendor, warehouse…" - className="h-14 w-full pl-11 text-base" - aria-label="Search GRNs" - /> + +
+ value={status} onValueChange={(v) => setStatus(v ?? "All")} items={STATUS_OPTIONS}> + + + + + {STATUS_OPTIONS.map((o) => ( + {o.label} + ))} + + + + {hasFilters && ( + + )}
- - - - {hasFilters && ( - - )} -
+ {error && (
@@ -199,18 +355,10 @@ export default function GrnListPage() {

- {hasFilters ? "No GRNs match your search/filter." : "No GRNs yet. Create one from an approved purchase order."} + {hasFilters ? "No GRNs match your search or filters." : "No GRNs yet. Create one from an approved purchase order."}

{hasFilters ? ( - ) : ( @@ -233,146 +381,116 @@ export default function GrnListPage() { tabIndex={0} onClick={(e) => { if ((e.target as HTMLElement).closest("[data-no-row-nav]")) return - router.push(`/dashboard/receiving/grn/${grn.grnId}`) + open(grn.grnId) }} onKeyDown={(e) => { - if (e.key === "Enter") router.push(`/dashboard/receiving/grn/${grn.grnId}`) + if (e.key === "Enter" && !(e.target as HTMLElement).closest("[data-no-row-nav]")) open(grn.grnId) }} className="flex flex-col gap-3 rounded-xl border bg-card p-4 shadow-sm" >
-
-

{grn.docNo}

-

{vendorName(grn.vendorId)}

-
+ {vendorBlock(grn)}
-
-
-

PO

-

- {grn.poId ? ( - - PO {String(grn.poId).padStart(2, "0")} - - ) : ( - "Direct" - )} -

+
+

GRN

+ {docBlock(grn)}
-

Warehouse

-

{warehouseName(grn.warehouseId)}

-
-
-

Invoice

-

- {grn.status !== "Confirmed" ? ( - "—" - ) : grn.purchaseInvoiceId ? ( - - {grn.purchaseInvoiceNo ?? `#${grn.purchaseInvoiceId}`} - - ) : ( - "Not invoiced" - )} -

+

Total

+ {totalCell(grn)}

Created

-

{new Date(grn.createdAt).toLocaleDateString()}

+

{formatReportDate(grn.createdAt)}

+
+

Invoice

+

{invoiceCell(grn)}

+
+ {showBranch && ( +
+

Branch

+ + {branchName(grn.branchId)} + +
+ )} +
+
+ {actions(grn)}
))} {pagination && grns.length < pagination.totalItems && ( - )}
- {/* Desktop: table. */} -
-
- - - - Doc No - PO - Vendor - Warehouse - Status - Invoice - Created - Actions + {/* Desktop: table, styled like the Purchase Orders table. */} +
+
+ + + + toggleSort("vendor")} /> + + GRN + + toggleSort("created")} /> + + + toggleSort("status")} /> + + + toggleSort("total")} /> + + Invoice + {showBranch && Branch} + Actions + + + + {grns.map((grn) => ( + { + if ((e.target as HTMLElement).closest("[data-no-row-nav]")) return + open(grn.grnId) + }} + > + {vendorBlock(grn)} + {docBlock(grn)} + {formatReportDate(grn.createdAt)} + + {totalCell(grn)} + {invoiceCell(grn)} + {showBranch && ( + + + {branchName(grn.branchId)} + + + )} + +
{actions(grn)}
+
- - - {grns.map((grn) => { - return ( - { - if ((e.target as HTMLElement).closest("[data-no-row-nav]")) return - router.push(`/dashboard/receiving/grn/${grn.grnId}`) - }} - > - - - {grn.docNo} - - - - {grn.poId ? ( - - PO {String(grn.poId).padStart(2, "0")} - - ) : ( - Direct - )} - - {vendorName(grn.vendorId)} - {warehouseName(grn.warehouseId)} - - - - - {grn.status !== "Confirmed" ? ( - — - ) : grn.purchaseInvoiceId ? ( - - {grn.purchaseInvoiceNo ?? `#${grn.purchaseInvoiceId}`} - - ) : ( - Not invoiced - )} - - {new Date(grn.createdAt).toLocaleString()} - -
- - - -
-
-
- ) - })} -
-
-
+ ))} + +
{pagination && ( @@ -387,6 +505,13 @@ export default function GrnListPage() { )} )} + + { if (!o) setReceipt(null) }} + />
) } diff --git a/Frontend/erp-system/app/dashboard/sales/day-end/page.tsx b/Frontend/erp-system/app/dashboard/sales/day-end/page.tsx index 81b1fcb..9095fd0 100644 --- a/Frontend/erp-system/app/dashboard/sales/day-end/page.tsx +++ b/Frontend/erp-system/app/dashboard/sales/day-end/page.tsx @@ -1,7 +1,8 @@ "use client" -import { useEffect, useState } from "react" +import { Suspense, useEffect, useState } from "react" import Link from "next/link" +import { useSearchParams } from "next/navigation" import { AlertTriangle, ArrowLeft, CalendarDays, CheckCircle2, CircleDollarSign, FileText, Hourglass, Wallet } from "lucide-react" import { salesApi } from "@/lib/api/sales" @@ -73,6 +74,17 @@ const paymentFilters: { value: PaymentFilter; label: string }[] = [ ] export default function ApprovedInvoicesPage() { + // useSearchParams needs a Suspense boundary above it. + return ( + + + + ) +} + +function ApprovedInvoices() { + // The Sales list links here with ?q= so the invoice is already searched for. + const initialQuery = useSearchParams().get("q")?.trim() ?? "" const [cashiers, setCashiers] = useState([]) // Approved (Posted) sales invoices with their own filters: a cashier ("All" shows every @@ -82,8 +94,8 @@ export default function ApprovedInvoicesPage() { const [salesDateRange, setSalesDateRange] = useState("All") const { from: salesFrom, to: salesTo } = salesDateBounds(salesDateRange) const [salesPayment, setSalesPayment] = useState("All") - const [salesSearchInput, setSalesSearchInput] = useState("") - const [salesQuery, setSalesQuery] = useState("") + const [salesSearchInput, setSalesSearchInput] = useState(initialQuery) + const [salesQuery, setSalesQuery] = useState(initialQuery) // Server-side paging — every filter above is applied by the API, so pages and counts are exact. const [salesPage, setSalesPage] = useState(1) const [salesPageSize, setSalesPageSize] = useState(SALES_PAGE_SIZE_OPTIONS[1]) diff --git a/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx b/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx index 0164c58..14927fe 100644 --- a/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx +++ b/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx @@ -11,10 +11,12 @@ import { CheckCircle2, CircleDollarSign, FileText, + ExternalLink, Loader2, Plus, Receipt, Tag, + Wallet, } from "lucide-react" import { salesApi } from "@/lib/api/sales" @@ -33,9 +35,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Skeleton } from "@/components/ui/skeleton" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { toast } from "@/components/ui/toast" +import { SalesInvoicePaymentDialog } from "@/components/sales/SalesInvoicePaymentDialog" import { SalesInvoiceStatusBadge, SalesPaymentStatusBadge } from "@/components/sales/status-badges" import { PaginationMeta } from "@/types/common" -import { SalesInvoiceSummary } from "@/types/sales" +import { SalesInvoiceStatus, SalesInvoiceSummary } from "@/types/sales" import { Customer } from "@/types/customers" import { Warehouse } from "@/types/master-data" import { cn } from "@/lib/utils" @@ -61,6 +64,15 @@ function formatAmount(n: number) { return n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } +type StatusFilter = "All" | SalesInvoiceStatus + +const statusFilters: { value: StatusFilter; label: string }[] = [ + { value: "All", label: "All sales" }, + { value: "Draft", label: "Draft (pending)" }, + { value: "Posted", label: "Approved" }, + { value: "Cancelled", label: "Cancelled" }, +] + type DateRange = "All" | "Today" | "7d" | "30d" | "Custom" type CustomRange = { from: Date | undefined; to?: Date | undefined } @@ -125,8 +137,13 @@ export default function SalesInvoicesPage() { const [pageSize, setPageSize] = useState(PAGE_SIZE_OPTIONS[1]) const [showMoreLoading, setShowMoreLoading] = useState(false) const [confirmingId, setConfirmingId] = useState(null) - // This screen lists drafts only; the row, its invoice no. and the invoice icon all open the - // invoice page, where a draft is editable. + const [statusFilter, setStatusFilter] = useState("All") + // Bumped after an approval or a payment, so the page is re-read from the server. + const [reloadKey, setReloadKey] = useState(0) + const [payInvoice, setPayInvoice] = useState(null) + const [payOpen, setPayOpen] = useState(false) + // This screen lists every sale (drafts, approved, cancelled); the row and its invoice no. open the + // invoice page, where a draft is editable and an approved sale is read-only with its payments. const router = useRouter() function openDraft(row: SalesInvoiceSummary) { @@ -151,7 +168,7 @@ export default function SalesInvoicesPage() { setPage(1) setPageSize(rowsPerPage) setSelected(new Set()) - }, [query, dateFrom, dateTo, activeBranchId, customerId, warehouseId, rowsPerPage]) + }, [query, dateFrom, dateTo, activeBranchId, customerId, warehouseId, rowsPerPage, statusFilter]) useEffect(() => { // Includes the cash customer so its (walk-in) invoices can be filtered too. @@ -165,8 +182,9 @@ export default function SalesInvoicesPage() { .listInvoices({ page, pageSize, - // Drafts only — approved invoices are worked (and paid) from the Day End screen. - status: "Draft", + // Every sale by default; approved ones can be paid here or worked from Approved Invoices. + status: statusFilter === "All" ? undefined : statusFilter, + q: query || undefined, branchId: branchingEnabled ? (activeBranchId ?? undefined) : undefined, customerId, warehouseId, @@ -179,7 +197,7 @@ export default function SalesInvoicesPage() { }) .catch((err) => setError(errorMessage(err))) .finally(() => setShowMoreLoading(false)) - }, [page, pageSize, dateFrom, dateTo, activeBranchId, branchingEnabled, customerId, warehouseId]) + }, [page, pageSize, dateFrom, dateTo, activeBranchId, branchingEnabled, customerId, warehouseId, statusFilter, query, reloadKey]) function handleShowMore() { setShowMoreLoading(true) @@ -190,13 +208,13 @@ export default function SalesInvoicesPage() { setConfirmingId(row.salesInvoiceId) try { const posted = await salesApi.postInvoice(row.salesInvoiceId) - // No longer a draft, so it leaves this list — it's now on Day End. - setRows((prev) => prev?.filter((r) => r.salesInvoiceId !== row.salesInvoiceId) ?? prev) + // Stays in the list, now Approved (and payable): re-read it from the server. setSelected((prev) => { const next = new Set(prev) next.delete(row.salesInvoiceId) return next }) + setReloadKey((k) => k + 1) toast.success("Invoice confirmed", posted.invoiceNo) } catch (err) { toast.error("Could not confirm invoice", errorMessage(err)) @@ -221,9 +239,9 @@ export default function SalesInvoicesPage() { failed.push(`${row?.invoiceNo ?? id}: ${errorMessage(err)}`) } } - setRows((prev) => prev?.filter((r) => !approved.includes(r.salesInvoiceId)) ?? prev) setSelected((prev) => new Set([...prev].filter((id) => !approved.includes(id)))) setBulkApproving(false) + if (approved.length > 0) setReloadKey((k) => k + 1) if (approved.length > 0) toast.success("Invoices approved", `${approved.length} invoice(s) approved.`) if (failed.length > 0) toast.error(`${failed.length} invoice(s) could not be approved`, failed.join("\n")) } @@ -249,13 +267,8 @@ export default function SalesInvoicesPage() { } } - const visibleRows = useMemo( - () => - rows?.filter((row) => - `${row.invoiceNo} ${row.customerSnapshotName}`.toLowerCase().includes(query.toLowerCase()) - ) ?? [], - [rows, query] - ) + // Search, status and dates are applied by the API, so the loaded page is shown as it comes. + const visibleRows = useMemo(() => rows ?? [], [rows]) const sortedRows = useMemo(() => { if (!sortKey) return visibleRows const value = (r: SalesInvoiceSummary): string | number => { @@ -276,12 +289,13 @@ export default function SalesInvoicesPage() { }) }, [visibleRows, sortKey, sortOrder]) - // Header "select all" ticks every draft on screen (the loaded page, after search). - const allTicked = sortedRows.length > 0 && sortedRows.every((r) => selected.has(r.salesInvoiceId)) + // Header "select all" ticks every draft on screen (approved and cancelled sales can't be approved again). + const draftRows = sortedRows.filter((r) => r.status === "Draft") + const allTicked = draftRows.length > 0 && draftRows.every((r) => selected.has(r.salesInvoiceId)) function toggleAll(checked: boolean) { setSelected((prev) => { const next = new Set(prev) - sortedRows.forEach((r) => (checked ? next.add(r.salesInvoiceId) : next.delete(r.salesInvoiceId))) + draftRows.forEach((r) => (checked ? next.add(r.salesInvoiceId) : next.delete(r.salesInvoiceId))) return next }) } @@ -290,14 +304,71 @@ export default function SalesInvoicesPage() { return ( toggleSelected(row.salesInvoiceId)} /> ) } - const hasFilters = dateFrom !== undefined || query.length > 0 || customerId !== undefined || warehouseId !== undefined + const hasFilters = dateFrom !== undefined || query.length > 0 || customerId !== undefined || warehouseId !== undefined || statusFilter !== "All" + + /** Open · Approve (drafts) · Pay and "Open in Approved Invoices" (approved) — the same on cards and table rows. */ + function renderRowActions(row: SalesInvoiceSummary, buttonVariant: "outline" | "ghost", size: "icon" | "icon-sm") { + const confirming = confirmingId === row.salesInvoiceId + return ( + <> + + {row.status === "Draft" && ( + + )} + {row.status === "Posted" && row.totals.balanceAmount > 0 && ( + + )} + {row.status === "Posted" && ( + + + + )} + + ) + } const grossTotal = visibleRows.reduce((sum, row) => sum + row.totals.subtotal, 0) const discountTotal = visibleRows.reduce((sum, row) => sum + row.totals.discountTotal, 0) const netTotal = visibleRows.reduce((sum, row) => sum + row.totals.grandTotal, 0) @@ -307,9 +378,13 @@ export default function SalesInvoicesPage() {

Sales

-

Invoice register with filters, posting flow, and settlement tracking.

+

Every sale — pending drafts and approved invoices — with filters, approval and payment.

+ + + Approved invoices + New Sale @@ -317,13 +392,13 @@ export default function SalesInvoicesPage() {
- {/* Same order as the table: how many drafts, then Gross → Discount → Net. Draft count is every - matching draft; the amounts are over the invoices currently listed (filters + loaded page), + {/* Same order as the table: how many sales, then Gross → Discount → Net. The count is every + matching sale; the amounts are over the invoices currently listed (filters + loaded page), passed as strings so StatCard shows full 2dp amounts rather than its compact "1.2K" form. */}
{rows !== null ? ( <> - + @@ -371,6 +446,21 @@ export default function SalesInvoicesPage() { } >
+ + value={statusFilter} + onValueChange={(v) => setStatusFilter(v ?? "All")} + items={statusFilters.map((s) => ({ label: s.label, value: s.value }))} + > + + + + + {statusFilters.map((s) => ( + {s.label} + ))} + + + value={dateRange} onValueChange={(v) => setDateRange(v ?? "All")} @@ -496,30 +586,8 @@ export default function SalesInvoicesPage() {
-
- - {row.status === "Draft" && ( - - )} +
+ {renderRowActions(row, "outline", "icon")}
))} @@ -603,31 +671,7 @@ export default function SalesInvoicesPage() {
-
- - {row.status === "Draft" && ( - - )} -
+
{renderRowActions(row, "ghost", "icon-sm")}
))} @@ -648,6 +692,14 @@ export default function SalesInvoicesPage() { )}
+ + {/* After a payment the page is re-read, so Paid / Remaining and the badges come from the server. */} + setReloadKey((k) => k + 1)} + /> ) } diff --git a/Frontend/erp-system/app/dashboard/settings/access/page.tsx b/Frontend/erp-system/app/dashboard/settings/access/page.tsx new file mode 100644 index 0000000..9e675bd --- /dev/null +++ b/Frontend/erp-system/app/dashboard/settings/access/page.tsx @@ -0,0 +1,123 @@ +"use client" + +import { useEffect, useState } from "react" +import { ShieldCheck } from "lucide-react" + +import { AccessConfig, accessConfigApi } from "@/lib/api/access-config" +import { rolesApi } from "@/lib/api/roles" +import { errorMessage } from "@/lib/error-map" +import { Role } from "@/types/rbac" + +import { Button } from "@/components/ui/button" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Skeleton } from "@/components/ui/skeleton" +import { toast } from "@/components/ui/toast" + +const NONE = "__none__" + +/** + * Settings → Access — the default role. Every first sign-in, and every person with no role, receives it. Keep it + * least-privilege (the seeded "No access" role has no permissions at all): whoever can register a login gets it. + * The administrator role is never offered, and the server refuses it. + */ +export default function AccessSettingsPage() { + const [config, setConfig] = useState(null) + const [etag, setEtag] = useState(null) + const [roles, setRoles] = useState([]) + const [error, setError] = useState(null) + const [saving, setSaving] = useState(false) + + function load() { + setError(null) + accessConfigApi + .get() + .then((res) => { + setConfig(res.data) + setEtag(res.etag) + }) + .catch((err) => setError(errorMessage(err))) + } + + useEffect(load, []) + useEffect(() => { + rolesApi.list({ pageSize: 200, status: "Active" }).then((r) => setRoles(r.items.filter((x) => !x.isAdmin))).catch(() => setRoles([])) + }, []) + + async function save(value: string) { + const roleId = value === NONE ? null : Number(value) + setSaving(true) + try { + const res = await accessConfigApi.update(roleId, etag ?? "") + setConfig(res.data) + setEtag(res.etag) + toast.success("Default role saved", res.data.defaultRoleName ? `New sign-ins get “${res.data.defaultRoleName}”.` : "New sign-ins get no role.") + } catch (err) { + toast.error("Could not save", errorMessage(err)) + load() + } finally { + setSaving(false) + } + } + + return ( +
+
+

Access

+

What a person can do when they have no role of their own.

+
+ + {error && ( +
{error}
+ )} + {!error && !config && } + + {!error && config && ( +
+
+ +

Default role

+
+
+ +
+ + value={config.defaultRoleId ? String(config.defaultRoleId) : NONE} + onValueChange={(v) => v && save(v)} + items={[{ label: "No default role", value: NONE }, ...roles.map((r) => ({ label: r.name, value: String(r.roleId) }))]} + disabled={saving} + > + + + + + No default role + {roles.map((r) => ( + {r.name} + ))} + + +

+ Given to anyone who signs in for the first time, and to anyone without a role. Choose a role with few or no permissions — + an administrator then assigns the real one under Settings → Users. With no default, such people see “Waiting for access”. +

+
+ + {config.defaultRoleId && ( +
+ +
+ )} + + {config.updatedAt && ( +

+ Last changed {new Date(config.updatedAt).toLocaleString()} + {config.updatedBy ? ` by user #${config.updatedBy}` : ""}. +

+ )} +
+ )} +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/settings/approvals/page.tsx b/Frontend/erp-system/app/dashboard/settings/approvals/page.tsx index 6e2c2b0..0de90fd 100644 --- a/Frontend/erp-system/app/dashboard/settings/approvals/page.tsx +++ b/Frontend/erp-system/app/dashboard/settings/approvals/page.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from "react" import { Save } from "lucide-react" import { approvalsApi } from "@/lib/api/approvals" +import { rolesApi } from "@/lib/api/roles" +import { Role } from "@/types/rbac" import { errorMessage } from "@/lib/error-map" import { ApprovalConfig } from "@/types/approvals" @@ -17,7 +19,7 @@ import { toast } from "@/components/ui/toast" const levelOptions = [ { value: "2", label: "Two approvals — two different people" }, { value: "1", label: "One approval" }, - { value: "0", label: "No approval — applies immediately" }, + { value: "0", label: "No approval — type, confirm and it applies immediately" }, ] /** Settings → Approvals (docs/DUAL_APPROVAL_PLAN.md §7): how many approvals stock and price adjustments need. */ @@ -28,7 +30,8 @@ export default function ApprovalSettingsPage() { const [stockThreshold, setStockThreshold] = useState("0") const [priceLevels, setPriceLevels] = useState("2") const [priceThreshold, setPriceThreshold] = useState("0") - const [roles, setRoles] = useState("") + const [level2Roles, setLevel2Roles] = useState([]) + const [allRoles, setAllRoles] = useState([]) const [saving, setSaving] = useState(false) function apply(c: ApprovalConfig) { @@ -37,11 +40,12 @@ export default function ApprovalSettingsPage() { setStockThreshold(String(c.stockAdjustmentLevel2ThresholdValue)) setPriceLevels(String(c.priceAdjustmentLevels)) setPriceThreshold(String(c.priceAdjustmentLevel2ThresholdPercent)) - setRoles(c.level2RoleCodes.join(", ")) + setLevel2Roles(c.level2RoleIds) } useEffect(() => { approvalsApi.getConfig().then(apply).catch((err) => setError(errorMessage(err))) + rolesApi.list({ pageSize: 200, status: "Active" }).then((r) => setAllRoles(r.items)).catch(() => setAllRoles([])) }, []) async function save() { @@ -58,7 +62,7 @@ export default function ApprovalSettingsPage() { stockAdjustmentLevel2ThresholdValue: sv, priceAdjustmentLevels: Number(priceLevels), priceAdjustmentLevel2ThresholdPercent: pv, - level2RoleCodes: roles.split(",").map((r) => r.trim()).filter(Boolean), + level2RoleIds: level2Roles, }) apply(saved) toast.success("Approval settings saved", "They apply to requests made from now on.") @@ -104,7 +108,10 @@ export default function ApprovalSettingsPage() {

Price adjustments

-

Selling-price changes. While this is not “No approval”, prices can’t be edited directly on an item.

+

+ Selling-price changes. With “No approval” the new price is typed on the item (or the price-change form), confirmed and applied at once. + With one or two approvals, prices can’t be edited directly on an item — they go through a price change request. +

Approvals required value={priceLevels} onValueChange={(v) => v && setPriceLevels(v)}> @@ -124,11 +131,29 @@ export default function ApprovalSettingsPage() {
- Who can give the second approval (role codes) - setRoles(e.target.value)} placeholder="e.g. Manager, Accountant — leave empty for anyone with the Approve permission" className="h-11 text-base" /> + Who can give the second approval +
+ {allRoles.filter((r) => !r.isAdmin).map((r) => { + const on = level2Roles.includes(r.roleId) + return ( + + ) + })} +
- Comma-separated. Anyone giving an approval also needs the Approve permission on the screen (Settings → Roles). - Make sure at least two people per branch can approve, or requests will wait forever. + Pick the roles allowed to give the second approval; pick none to allow anyone with the Approve permission (administrators always can). + Anyone giving an approval also needs the Approve permission on the screen (Settings → Roles). Make sure at least two people can approve, + or requests will wait forever.
diff --git a/Frontend/erp-system/app/dashboard/settings/audit-logs/page.tsx b/Frontend/erp-system/app/dashboard/settings/audit-logs/page.tsx new file mode 100644 index 0000000..27cd9d3 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/settings/audit-logs/page.tsx @@ -0,0 +1,139 @@ +"use client" + +import { useEffect, useState } from "react" +import { ScrollText } from "lucide-react" + +import { auditLogsApi } from "@/lib/api/audit-logs" +import { errorMessage } from "@/lib/error-map" +import { PaginationMeta } from "@/types/common" +import { AuditLog } from "@/types/audit" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" + +const PAGE_SIZE = 20 + +/** + * Settings → Audit Log — who created, changed or deleted what (and who signed in). Read-only; the API requires the + * Audit Log permission, because the trail includes pay and personal-data changes. + */ +export default function AuditLogsPage() { + const [rows, setRows] = useState([]) + const [pagination, setPagination] = useState(null) + const [page, setPage] = useState(1) + const [entityType, setEntityType] = useState("") + const [userId, setUserId] = useState("") + const [from, setFrom] = useState("") + const [to, setTo] = useState("") + const [error, setError] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let cancelled = false + auditLogsApi + .list({ + page, + pageSize: PAGE_SIZE, + entityType: entityType.trim() || undefined, + userId: userId.trim() ? Number(userId) : undefined, + from: from || undefined, + to: to || undefined, + }) + .then((res) => { + if (cancelled) return + setRows(res.items) + setPagination(res.pagination) + setError(null) + }) + .catch((err) => !cancelled && setError(errorMessage(err))) + .finally(() => !cancelled && setLoading(false)) + return () => { + cancelled = true + } + }, [page, entityType, userId, from, to]) + + // Any filter change goes back to the first page. + function onFilter(setter: (v: string) => void) { + return (e: React.ChangeEvent) => { + setLoading(true) + setter(e.target.value) + setPage(1) + } + } + + return ( +
+
+ +
+

Audit Log

+

Who created, changed or deleted what, and when.

+
+
+ +
+ + + + +
+ + {error &&
{error}
} + + {!error && ( +
+ + + + When + User + Action + Record + Changes + + + + {loading && + Array.from({ length: 6 }).map((_, i) => ( + + + + ))} + {!loading && rows.length === 0 && ( + + Nothing recorded for these filters. + + )} + {!loading && + rows.map((r) => ( + + {new Date(r.createdAt).toLocaleString()} + #{r.userId} + {r.action} + {r.entityType} #{r.entityId} + + {JSON.stringify(r.changeSet)} + + + ))} + +
+
+ )} + + {pagination && pagination.totalPages > 1 && ( +
+ + Page {pagination.page} of {pagination.totalPages} · {pagination.totalItems} entries + +
+ + +
+
+ )} +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/settings/configuration/page.tsx b/Frontend/erp-system/app/dashboard/settings/configuration/page.tsx index 14bbb97..6b99e0f 100644 --- a/Frontend/erp-system/app/dashboard/settings/configuration/page.tsx +++ b/Frontend/erp-system/app/dashboard/settings/configuration/page.tsx @@ -1,5 +1,5 @@ import Link from "next/link" -import { BookOpen, Building2, LayoutTemplate, MapPin, PackageCheck, ShoppingCart, SlidersHorizontal } from "lucide-react" +import { BookOpen, Building2, ClipboardList, LayoutTemplate, MapPin, PackageCheck, ShieldCheck, ShoppingCart, SlidersHorizontal } from "lucide-react" const cards = [ { @@ -32,6 +32,18 @@ const cards = [ icon: PackageCheck, description: "Warehouse selection and the default receiving warehouse", }, + { + title: "Procurement Configuration", + href: "/dashboard/settings/procurement-configuration", + icon: ClipboardList, + description: "Vendor contact and address fields, and tax on purchase orders", + }, + { + title: "Access", + href: "/dashboard/settings/access", + icon: ShieldCheck, + description: "The default role for people who sign in without one", + }, { title: "GL Accounts", href: "/dashboard/settings/gl-accounts", diff --git a/Frontend/erp-system/app/dashboard/settings/procurement-configuration/page.tsx b/Frontend/erp-system/app/dashboard/settings/procurement-configuration/page.tsx new file mode 100644 index 0000000..f696160 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/settings/procurement-configuration/page.tsx @@ -0,0 +1,213 @@ +"use client" + +import { useEffect, useState } from "react" +import { Settings } from "lucide-react" + +import { procurementConfigApi } from "@/lib/api/procurement-config" +import { errorMessage } from "@/lib/error-map" +import { ProcurementConfig, UpdateProcurementConfigRequest, VendorContactMode } from "@/types/procurement" + +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Skeleton } from "@/components/ui/skeleton" +import { Switch } from "@/components/ui/switch" +import { toast } from "@/components/ui/toast" + +const CONTACT_LABEL: Record = { + None: "Off — no contact field", + Mobile: "Mobile number", + Email: "Email address", +} + +/** + * Procurement Configuration — singleton options for vendors and purchase orders (same pattern as + * GRN Configuration). Every flag is enforced server-side as well: a value sent for a switched-off + * field is rejected with 422 CONFIG_DISABLED, so turning something off here hides it everywhere. + */ +export default function ProcurementConfigurationPage() { + const [config, setConfig] = useState(null) + const [etag, setEtag] = useState(null) + const [error, setError] = useState(null) + const [saving, setSaving] = useState(false) + + function load() { + setError(null) + procurementConfigApi + .get() + .then((res) => { + setConfig(res.data) + setEtag(res.etag) + }) + .catch((err) => setError(errorMessage(err))) + } + + useEffect(load, []) + + // PUT is a full replacement — send every flag, overriding just the one changed. + async function saveFlag(patch: Partial, message: string) { + if (!config) return + setSaving(true) + try { + const res = await procurementConfigApi.update( + { + vendorContactMode: config.vendorContactMode, + vendorAddressEnabled: config.vendorAddressEnabled, + vendorPaymentTermsEnabled: config.vendorPaymentTermsEnabled, + vendorTaxRegEnabled: config.vendorTaxRegEnabled, + poTaxEnabled: config.poTaxEnabled, + ...patch, + }, + etag ?? "", + ) + setConfig(res.data) + setEtag(res.etag) + toast.success("Configuration saved", message) + } catch (err) { + toast.error("Could not save configuration", errorMessage(err)) + load() + } finally { + setSaving(false) + } + } + + return ( +
+
+

Procurement Configuration

+

Choose which optional details vendors and purchase orders carry. Contact, address, payment terms and tax registration are all optional fields you can switch on or off.

+
+ + {error && ( +
{error}
+ )} + + {!error && !config && } + + {!error && config && ( +
+
+ +

Vendors

+
+
+ +
+ Vendor contact + + value={config.vendorContactMode} + onValueChange={(v) => { + if (!v || v === config.vendorContactMode) return + saveFlag({ vendorContactMode: v }, `Vendor contact: ${CONTACT_LABEL[v]}.`) + }} + items={(Object.keys(CONTACT_LABEL) as VendorContactMode[]).map((m) => ({ label: CONTACT_LABEL[m], value: m }))} + disabled={saving} + > + + + + + {(Object.keys(CONTACT_LABEL) as VendorContactMode[]).map((m) => ( + + {CONTACT_LABEL[m]} + + ))} + + + + Pick one contact detail to record for every vendor. It appears on the vendor forms, the + purchase-order table and the purchase-order PDF. Details already saved are kept if you + switch it off. + +
+ +
+ + saveFlag({ vendorAddressEnabled: next }, `Vendor address ${next ? "enabled" : "disabled"}.`) + } + disabled={saving} + aria-label="Vendor company address" + className="mt-1 shrink-0" + /> +
+ Vendor company address + + On: vendors can have an optional company address, shown on the purchase-order table and + PDF. Off: the address field is hidden. + +
+
+ +
+ + saveFlag({ vendorPaymentTermsEnabled: next }, `Vendor payment terms ${next ? "enabled" : "disabled"}.`) + } + disabled={saving} + aria-label="Vendor payment terms" + className="mt-1 shrink-0" + /> +
+ Vendor payment terms + + On: vendors can have optional payment terms (e.g. NET30). Off: the field is hidden and can't be set; + values already saved are kept. + +
+
+ +
+ + saveFlag({ vendorTaxRegEnabled: next }, `Vendor tax registration ${next ? "enabled" : "disabled"}.`) + } + disabled={saving} + aria-label="Vendor tax registration" + className="mt-1 shrink-0" + /> +
+ Vendor tax registration + + On: vendors can have an optional tax registration number. Off: the field is hidden and can't be set; + numbers already saved are kept (and still copied onto purchase invoices). + +
+
+ +
+ +

Purchase orders

+
+
+ +
+ saveFlag({ poTaxEnabled: next }, `PO tax ${next ? "enabled" : "disabled"}.`)} + disabled={saving} + aria-label="Apply tax on purchase orders" + className="mt-1 shrink-0" + /> +
+ Apply tax on purchase orders + + On: each PO line gets a tax rate (pre-filled from the item's tax rate, editable per line) + and a tax amount, included in the PO total and PDF. Off: tax is hidden everywhere on + purchase orders and every line is at 0%. + +
+
+ + {config.updatedAt && ( +

+ Last changed {new Date(config.updatedAt).toLocaleString()} + {config.updatedBy ? ` by user #${config.updatedBy}` : ""}. +

+ )} +
+ )} +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/settings/sales-configuration/page.tsx b/Frontend/erp-system/app/dashboard/settings/sales-configuration/page.tsx index cf794bd..d5e3afb 100644 --- a/Frontend/erp-system/app/dashboard/settings/sales-configuration/page.tsx +++ b/Frontend/erp-system/app/dashboard/settings/sales-configuration/page.tsx @@ -15,6 +15,21 @@ import { Skeleton } from "@/components/ui/skeleton" import { Switch } from "@/components/ui/switch" import { toast } from "@/components/ui/toast" +/** The optional customer fields, in the order the customer form shows them. */ +const customerFieldSwitches: { + key: "customerPhoneEnabled" | "customerEmailEnabled" | "customerAddressEnabled" | "customerCityEnabled" | "customerCountryEnabled" | "customerCreditLimitEnabled" | "customerCreditDaysEnabled" + label: string + help: string +}[] = [ + { key: "customerPhoneEnabled", label: "Phone", help: "On: customers have an optional phone number. Off: the field is hidden." }, + { key: "customerEmailEnabled", label: "Email", help: "On: customers have an optional email address. Off: the field is hidden." }, + { key: "customerAddressEnabled", label: "Address", help: "On: customers have optional address lines 1 and 2. Off: both are hidden." }, + { key: "customerCityEnabled", label: "City", help: "On: customers have an optional city. Off: the field is hidden." }, + { key: "customerCountryEnabled", label: "Country", help: "On: customers have an optional country. Off: the field is hidden." }, + { key: "customerCreditLimitEnabled", label: "Credit limit", help: "On: customers have a credit limit (LKR). Off: the field and its column are hidden." }, + { key: "customerCreditDaysEnabled", label: "Credit days (age)", help: "On: customers have credit days — how long an invoice may stay unpaid. Off: the field is hidden." }, +] + /** * Sales Configuration — singleton sales toggles. "Allow cash customer": on, New Sale offers a * generic "Cash Customer" (and defaults to it) alongside the customer list; off, a real customer @@ -67,6 +82,13 @@ export default function SalesConfigurationPage() { allowOverStockSale: config.allowOverStockSale, allowWarehouseSelection: config.allowWarehouseSelection, defaultWarehouseId: config.defaultWarehouseId, + customerPhoneEnabled: config.customerPhoneEnabled, + customerEmailEnabled: config.customerEmailEnabled, + customerAddressEnabled: config.customerAddressEnabled, + customerCityEnabled: config.customerCityEnabled, + customerCountryEnabled: config.customerCountryEnabled, + customerCreditLimitEnabled: config.customerCreditLimitEnabled, + customerCreditDaysEnabled: config.customerCreditDaysEnabled, ...patch, }, etag ?? "", @@ -125,6 +147,32 @@ export default function SalesConfigurationPage() {
+
+ +

Customer fields

+
+
+

+ Choose which optional details a customer carries. A field that is off is hidden on the New customer, Edit + customer and profile screens, and new values for it are refused; details already saved are kept. +

+ + {customerFieldSwitches.map((f) => ( +
+ saveFlag({ [f.key]: next }, `${f.label} ${next ? "enabled" : "disabled"} for customers.`)} + disabled={saving} + aria-label={f.label} + className="mt-1 shrink-0" + /> +
+ {f.label} + {f.help} +
+
+ ))} +

Item lines

diff --git a/Frontend/erp-system/app/dashboard/settings/users/[id]/page.tsx b/Frontend/erp-system/app/dashboard/settings/users/[id]/page.tsx index ea8a43b..04ca87a 100644 --- a/Frontend/erp-system/app/dashboard/settings/users/[id]/page.tsx +++ b/Frontend/erp-system/app/dashboard/settings/users/[id]/page.tsx @@ -7,6 +7,10 @@ import { Clock, IdCard, KeyRound, Laptop, Pencil, ShieldCheck } from "lucide-rea import { auditLogsApi } from "@/lib/api/audit-logs" import { usersApi } from "@/lib/api/users" +import { rolesApi } from "@/lib/api/roles" +import { useAuth } from "@/components/auth/AuthProvider" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Role } from "@/types/rbac" import { errorMessage } from "@/lib/error-map" import { cn } from "@/lib/utils" import { AuditLog, LoginActivity } from "@/types/audit" @@ -48,12 +52,42 @@ export default function UserDetailPage() { const [sendingResetLink, setSendingResetLink] = useState(false) + // Role assignment: the role is the ERP's own (users.RoleId); a change applies on the person's very next request. + const { refresh: refreshMyAccess } = useAuth() + const [roles, setRoles] = useState([]) + // The role chosen in the dropdown; until the person picks one it shows their current role. + const [pickedRoleIdState, setPickedRoleId] = useState(undefined) + const pickedRoleId = pickedRoleIdState === undefined ? (user?.roleId ?? null) : pickedRoleIdState + const [savingRole, setSavingRole] = useState(false) + useEffect(() => { if (!Number.isFinite(userId)) return setLoadError(null) usersApi.get(userId).then(setUser).catch((err) => setLoadError(errorMessage(err))) }, [userId]) + useEffect(() => { + rolesApi.list({ pageSize: 200, status: "Active" }).then((r) => setRoles(r.items)).catch(() => setRoles([])) + }, []) + + async function handleSaveRole() { + if (!user || pickedRoleId === null || pickedRoleId === user.roleId) return + setSavingRole(true) + try { + const updated = await usersApi.updateRole(user.userId, { roleId: pickedRoleId }) + setUser(updated) + setPickedRoleId(undefined) + toast.success("Role changed", `${updated.displayName} is now ${updated.roleName}. It applies on their next request.`) + // It may be your own role (or a role you hold changed): re-read what you can do. + void refreshMyAccess() + } catch (err) { + toast.error("Could not change the role", errorMessage(err)) + setPickedRoleId(undefined) + } finally { + setSavingRole(false) + } + } + useEffect(() => { if (!Number.isFinite(userId)) return auditLogsApi @@ -238,10 +272,32 @@ export default function UserDetailPage() {

Controls what this user can see and do.

-
+
Role - {user.roleName ?? "—"} - {user.roleCode &&

Role code: {user.roleCode}

} +
+ + value={pickedRoleId} + onValueChange={setPickedRoleId} + items={roles.map((r) => ({ label: r.name, value: r.roleId }))} + disabled={savingRole} + > + + + + + {roles.map((r) => ( + {r.name} + ))} + + + +
+

+ {user.roleCode ? `Current role code: ${user.roleCode}. ` : "No role assigned yet. "} + The change applies on this person's very next click — they don't need to sign in again. +

diff --git a/Frontend/erp-system/app/dashboard/settings/users/page.tsx b/Frontend/erp-system/app/dashboard/settings/users/page.tsx index cab838a..9e0e446 100644 --- a/Frontend/erp-system/app/dashboard/settings/users/page.tsx +++ b/Frontend/erp-system/app/dashboard/settings/users/page.tsx @@ -71,7 +71,7 @@ export default function UsersPage() { Promise.all([ usersApi.list({ page: 1, pageSize: 1 }), usersApi.list({ page: 1, pageSize: 1, status: "Active" }), - usersApi.list({ page: 1, pageSize: 1, roleCode: "Admin" }), + usersApi.list({ page: 1, pageSize: 1, isAdmin: true }), ]) .then(([totalRes, activeRes, adminsRes]) => setStats({ total: totalRes.pagination.totalItems, active: activeRes.pagination.totalItems, admins: adminsRes.pagination.totalItems }) diff --git a/Frontend/erp-system/app/dashboard/vendors/[id]/page.tsx b/Frontend/erp-system/app/dashboard/vendors/[id]/page.tsx index 80b8989..767be17 100644 --- a/Frontend/erp-system/app/dashboard/vendors/[id]/page.tsx +++ b/Frontend/erp-system/app/dashboard/vendors/[id]/page.tsx @@ -11,6 +11,15 @@ import { errorMessage, fieldErrors } from "@/lib/error-map" import { cn } from "@/lib/utils" import { generateVendorCode } from "@/lib/vendor-code" import { Vendor } from "@/types/master-data" +import { useProcurementConfig } from "@/hooks/use-procurement-config" +import { + EMPTY_VENDOR_OPTIONAL, + VendorOptionalFields, + VendorOptionalValues, + validateVendorOptional, + vendorOptionalPayload, + vendorTermsTaxPayload, +} from "@/components/vendors/VendorOptionalFields" import { GrnSummary } from "@/types/grn" import { Button, buttonVariants } from "@/components/ui/button" @@ -41,6 +50,8 @@ export default function VendorDetailPage() { const [terms, setTerms] = useState("") const [taxReg, setTaxReg] = useState("") const [currency, setCurrency] = useState("") + const { config: procConfig } = useProcurementConfig() + const [optional, setOptional] = useState(EMPTY_VENDOR_OPTIONAL) const [errors, setErrors] = useState>({}) const [conflict, setConflict] = useState(false) @@ -87,6 +98,7 @@ export default function VendorDetailPage() { setTerms(data.terms ?? "") setTaxReg(data.taxReg ?? "") setCurrency(data.currency) + setOptional({ mobile: data.mobile ?? "", email: data.email ?? "", address: data.address ?? "" }) } function load() { @@ -169,13 +181,14 @@ export default function VendorDetailPage() { if (!code.trim()) nextErrors.code = "Vendor code is required" if (!name.trim()) nextErrors.name = "Vendor name is required" if (!currency.trim()) nextErrors.currency = "Currency is required" + Object.assign(nextErrors, validateVendorOptional(procConfig, optional)) setErrors(nextErrors) if (Object.keys(nextErrors).length > 0 || !etag) return if (!vendor) return setSaving(true) try { - const result = await vendorsApi.update(vendorId, { code, name, terms: terms || null, taxReg: taxReg || null, currency, branchId: vendor.branchId }, etag) + const result = await vendorsApi.update(vendorId, { code, name, ...vendorTermsTaxPayload(procConfig, terms, taxReg), currency, ...vendorOptionalPayload(procConfig, optional), branchId: vendor.branchId }, etag) setVendor(result.data) setEtag(result.etag) setEditOpen(false) @@ -461,13 +474,29 @@ export default function VendorDetailPage() { handleNameChange(e.target.value)} aria-invalid={!!errors.name} className="h-12 text-base" disabled={conflict} />
-
- - setTerms(e.target.value)} placeholder="NET30" className="h-12 text-base" disabled={conflict} /> -
-
- - setTaxReg(e.target.value)} className="h-12 text-base" disabled={conflict} /> + {procConfig.vendorPaymentTermsEnabled && ( +
+ + setTerms(e.target.value)} placeholder="NET30" className="h-12 text-base" disabled={conflict} /> +
+ )} + {procConfig.vendorTaxRegEnabled && ( +
+ + setTaxReg(e.target.value)} className="h-12 text-base" disabled={conflict} /> +
+ )} +
+ { + setOptional((prev) => ({ ...prev, ...patch })) + clearErrors("mobile", "email") + }} + idPrefix="vd" + errors={errors} + />
diff --git a/Frontend/erp-system/app/dashboard/vendors/page.tsx b/Frontend/erp-system/app/dashboard/vendors/page.tsx index 7f742c1..620ba98 100644 --- a/Frontend/erp-system/app/dashboard/vendors/page.tsx +++ b/Frontend/erp-system/app/dashboard/vendors/page.tsx @@ -34,6 +34,15 @@ import { Skeleton } from "@/components/ui/skeleton" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { toast } from "@/components/ui/toast" import { EditVendorDialog } from "@/components/vendors/EditVendorDialog" +import { useProcurementConfig } from "@/hooks/use-procurement-config" +import { + EMPTY_VENDOR_OPTIONAL, + VendorOptionalFields, + VendorOptionalValues, + validateVendorOptional, + vendorOptionalPayload, + vendorTermsTaxPayload, +} from "@/components/vendors/VendorOptionalFields" type StatusFilter = EntityStatus | "All" @@ -62,6 +71,8 @@ export default function VendorsPage() { const [terms, setTerms] = useState("") const [taxReg, setTaxReg] = useState("") const [currency, setCurrency] = useState("LKR") + const { config: procConfig } = useProcurementConfig() + const [optional, setOptional] = useState(EMPTY_VENDOR_OPTIONAL) const [errors, setErrors] = useState>({}) const [submitting, setSubmitting] = useState(false) @@ -129,6 +140,7 @@ export default function VendorsPage() { setTerms("") setTaxReg("") setCurrency("LKR") + setOptional(EMPTY_VENDOR_OPTIONAL) setBranchFilterId(null) setErrors({}) } @@ -148,12 +160,13 @@ export default function VendorsPage() { if (!name.trim()) nextErrors.name = "Vendor name is required" if (!currency.trim()) nextErrors.currency = "Currency is required" if (branchingEnabled && !effectiveBranchId) nextErrors.branch = "Select a branch" + Object.assign(nextErrors, validateVendorOptional(procConfig, optional)) setErrors(nextErrors) if (Object.keys(nextErrors).length > 0) return setSubmitting(true) try { - const result = await vendorsApi.create({ code: generatedCode, name, terms: terms || null, taxReg: taxReg || null, currency, branchId: effectiveBranchId ?? 0 }) + const result = await vendorsApi.create({ code: generatedCode, name, ...vendorTermsTaxPayload(procConfig, terms, taxReg), currency, ...vendorOptionalPayload(procConfig, optional), branchId: effectiveBranchId ?? 0 }) toast.success("Vendor created", `${result.data.code} — ${result.data.name}`) setOpen(false) resetForm() @@ -202,7 +215,7 @@ export default function VendorsPage() {

Vendors

-

Supplier master data — code, terms, tax registration, currency (FR-MD-06).

+

Supplier master data — code, currency and the optional details switched on in Procurement Configuration (FR-MD-06).

@@ -229,14 +242,29 @@ export default function VendorsPage() { Code (auto-generated) - - Payment terms (optional) - setTerms(e.target.value)} placeholder="NET30" /> - - - Tax registration (optional) - setTaxReg(e.target.value)} placeholder="134567890-7000" /> - + {procConfig.vendorPaymentTermsEnabled && ( + + Payment terms (optional) + setTerms(e.target.value)} placeholder="NET30" /> + + )} + {procConfig.vendorTaxRegEnabled && ( + + Tax registration (optional) + setTaxReg(e.target.value)} placeholder="134567890-7000" /> + + )} + { + setOptional((prev) => ({ ...prev, ...patch })) + clearError("mobile") + clearError("email") + }} + idPrefix="v" + errors={errors} + /> Currency { setCurrency(e.target.value); clearError("currency") }} placeholder="LKR" maxLength={3} aria-invalid={!!errors.currency} /> @@ -359,10 +387,12 @@ export default function VendorsPage() {
-
-

Terms

-

{v.terms ?? "—"}

-
+ {procConfig.vendorPaymentTermsEnabled && ( +
+

Terms

+

{v.terms ?? "—"}

+
+ )}

Currency

{v.currency}

@@ -429,7 +459,7 @@ export default function VendorsPage() { Code Name - Terms + {procConfig.vendorPaymentTermsEnabled && Terms} Currency {isAdmin && branchingEnabled && Branch} Status @@ -449,7 +479,7 @@ export default function VendorsPage() { {v.name} - {v.terms ?? "—"} + {procConfig.vendorPaymentTermsEnabled && {v.terms ?? "—"}} {v.currency} {isAdmin && branchingEnabled && ( diff --git a/Frontend/erp-system/components/Layouts/AppSidebar.tsx b/Frontend/erp-system/components/Layouts/AppSidebar.tsx index 8caae37..7b33bc6 100644 --- a/Frontend/erp-system/components/Layouts/AppSidebar.tsx +++ b/Frontend/erp-system/components/Layouts/AppSidebar.tsx @@ -67,7 +67,8 @@ import { useAuth } from "@/components/auth/AuthProvider" // `code` must match the seeded NavItem/SubNavItem codes in ERPCore // (Infra/Persistence/Configurations/NavItemConfiguration.cs / SubNavItemConfiguration.cs) // so role-based filtering (via GET /auth/me's navCodes) can match entries here. -const navItems: { +// Exported: lib/nav-routes.ts derives the page -> permission map (the route guard) from this same list. +export const navItems: { title: string code: string href: string @@ -114,6 +115,7 @@ const navItems: { icon: ClipboardList, children: [ { title: "Purchase Orders", code: "procurement.purchase-orders", href: "/dashboard/procurement/purchase-orders", icon: ShoppingCart }, + { title: "PO Approval Queue", code: "procurement.po-approvals", href: "/dashboard/procurement/purchase-orders/approvals", icon: ClipboardCheck }, { title: "Purchase Returns", code: "procurement.purchase-returns", href: "/dashboard/procurement/purchase-returns", icon: PackageX }, { title: "Vendors", code: "procurement.vendors", href: "/dashboard/vendors", icon: Truck }, ], @@ -215,6 +217,7 @@ const navItems: { { title: "Expenses", code: "accounts.expenses", href: "/dashboard/accounts/expenses", icon: ReceiptText }, { title: "Expense Setup", code: "accounts.expense-setup", href: "/dashboard/accounts/expense-setup", icon: Tags }, { title: "Bank Deposits", code: "accounts.bank-deposits", href: "/dashboard/accounts/bank-deposits", icon: PiggyBank }, + { title: "Opening Balances", code: "accounts.opening-balances", href: "/dashboard/accounts/opening-balances", icon: Scale }, ], }, { @@ -227,6 +230,7 @@ const navItems: { { title: "Roles", code: "settings.roles", href: "/dashboard/settings/roles", icon: ShieldCheck }, { title: "Users", code: "settings.users", href: "/dashboard/settings/users", icon: Users }, { title: "Approvals", code: "settings.approvals", href: "/dashboard/settings/approvals", icon: ClipboardCheck }, + { title: "Audit Log", code: "settings.audit-logs", href: "/dashboard/settings/audit-logs", icon: ScrollText }, { title: "Configuration", code: "products.configuration", href: "/dashboard/settings/configuration", icon: SlidersHorizontal }, ], }, @@ -439,32 +443,19 @@ export function AppSidebar() { // While /auth/me hasn't resolved yet, show nothing rather than briefly // flashing the full menu to a restricted role. Once resolved, a nav item // is visible if its own code is granted, or (for parents) if any child is. - // - // "procurement", "hrm", "sales", "production" and "stock" are exempted from that check - // (frontend-only): no role is currently seeded with NAV:procurement/NAV:hrm/NAV:production - // or their children server-side (stock's children specifically have no SubNavItem rows at - // all yet), which would hide the whole section for everyone. Remove each bypass once roles - // are granted the permission properly (Settings → Roles → Sidebar permissions) or a backend - // seed grants it. This is also flagged in 02-SECURITY.md as the AR-09 sidebar-visibility - // stopgap for HRM's salary/PII data — it hides HRM from the UI but doesn't enforce - // anything server-side. - const bypassCodes = new Set(["procurement", "sales", "sales-returns", "hrm", "production", "stock"]) - + // Every section now has seeded nav rows (HRM and Sales Returns were the last gaps), so there is no + // exemption list any more: what you see is exactly what your role grants (an administrator holds every code). const visibleItems = loading ? [] : navItems .filter( (item) => - bypassCodes.has(item.code) || navCodes.includes(item.code) || item.children?.some((c) => navCodes.includes(c.code) || c.altCodes?.some((ac) => navCodes.includes(ac))) ) .map((item) => ({ ...item, - children: (bypassCodes.has(item.code) - ? item.children - : item.children?.filter((c) => navCodes.includes(c.code) || c.altCodes?.some((ac) => navCodes.includes(ac))) - ), + children: item.children?.filter((c) => navCodes.includes(c.code) || c.altCodes?.some((ac) => navCodes.includes(ac))), })) // Close mobile menu on route change diff --git a/Frontend/erp-system/components/Layouts/Header.tsx b/Frontend/erp-system/components/Layouts/Header.tsx index e7b9a1b..72488f3 100644 --- a/Frontend/erp-system/components/Layouts/Header.tsx +++ b/Frontend/erp-system/components/Layouts/Header.tsx @@ -49,6 +49,7 @@ const PROCUREMENT_TITLES: Record = { "/dashboard/procurement/rfqs/new": "New RFQ", "/dashboard/procurement/purchase-orders": "Purchase Orders", "/dashboard/procurement/purchase-orders/new": "New Purchase Order", + "/dashboard/procurement/purchase-orders/approvals": "PO Approval Queue", "/dashboard/procurement/purchase-returns": "Purchase Returns", "/dashboard/procurement/purchase-returns/new": "New Purchase Return", } diff --git a/Frontend/erp-system/components/accounts/OpeningCashBankPanel.tsx b/Frontend/erp-system/components/accounts/OpeningCashBankPanel.tsx new file mode 100644 index 0000000..82126c4 --- /dev/null +++ b/Frontend/erp-system/components/accounts/OpeningCashBankPanel.tsx @@ -0,0 +1,278 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import { CheckCircle2, Landmark, Wallet } from "lucide-react" + +import { openingBalancesApi } from "@/lib/api/opening-balances" +import { errorMessage } from "@/lib/error-map" +import { formatAmount, formatReportDate, todayIso } from "@/lib/format" +import { CashBankOpeningState, OpeningCashBankAccount } from "@/types/opening-balances" + +import { AlertDialog, AlertDialogContent, AlertDialogTrigger } from "@/components/ui/alert-dialog" +import { Button } from "@/components/ui/button" +import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { toast } from "@/components/ui/toast" + +/** Amounts with at most 2 decimals; blank means "nothing to enter for this account". */ +function parseAmount(raw: string): { value: number | null; error?: string } { + const text = raw.trim() + if (text === "") return { value: null } + const n = Number(text) + if (!Number.isFinite(n)) return { value: null, error: "Enter a number" } + if (Math.round(n * 100) / 100 !== n) return { value: null, error: "At most 2 decimals" } + return { value: n } +} + +function AccountIcon({ type }: { type: string }) { + const Icon = type === "Bank" ? Landmark : Wallet + return +} + +/** + * Opening balances — cash and bank. The amounts become ONE journal in the General Ledger (a debit to each + * account, the offsetting credit to "Opening Balance Equity"), posted once. Once posted this panel shows the + * record instead of the form; later corrections are made with an ordinary journal entry. + */ +export function OpeningCashBankPanel() { + const [state, setState] = useState(null) + const [error, setError] = useState(null) + + const [date, setDate] = useState(todayIso()) + const [amounts, setAmounts] = useState>({}) + const [errors, setErrors] = useState>({}) + const [posting, setPosting] = useState(false) + + function load() { + setError(null) + openingBalancesApi + .cashBank() + .then(setState) + .catch((err) => setError(errorMessage(err))) + } + + useEffect(load, []) + + // Accounts in another currency are shown but cannot take an amount: the journal is in the base currency. + const usable = (a: OpeningCashBankAccount) => !state || a.currencyCode.toUpperCase() === state.baseCurrency.toUpperCase() + + const total = useMemo(() => { + let sum = 0 + for (const a of state?.accounts ?? []) { + const { value } = parseAmount(amounts[a.glAccountCode] ?? "") + if (value !== null && usable(a)) sum += value + } + return Math.round(sum * 100) / 100 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [amounts, state]) + + function validate(): { lines: { glAccountCode: string; amount: number }[]; ok: boolean } { + const next: Record = {} + const lines: { glAccountCode: string; amount: number }[] = [] + for (const a of state?.accounts ?? []) { + if (!usable(a)) continue + const { value, error: err } = parseAmount(amounts[a.glAccountCode] ?? "") + if (err) next[a.glAccountCode] = err + else if (value !== null && value !== 0) lines.push({ glAccountCode: a.glAccountCode, amount: value }) + } + if (!date) next.date = "Choose the opening date" + else if (date > todayIso()) next.date = "The opening date cannot be in the future" + if (lines.length === 0 && Object.keys(next).length === 0) next.lines = "Enter an amount for at least one account" + setErrors(next) + return { lines, ok: Object.keys(next).length === 0 } + } + + async function post() { + const { lines, ok } = validate() + if (!ok) return + setPosting(true) + try { + const run = await openingBalancesApi.postCashBank({ cutoverDate: date, lines }) + toast.success("Opening balances posted", `Journal ${run.glJournalNo ?? ""} — ${run.currency} ${formatAmount(run.totalAmount)}`) + load() + } catch (err) { + const code = (err as { code?: string })?.code + toast.error("Could not post the opening balances", errorMessage(err)) + // Already done by someone else: show the posted record instead of an empty form. + if (code === "OPENING_BALANCE_ALREADY_POSTED") load() + } finally { + setPosting(false) + } + } + + if (error) { + return
{error}
+ } + if (!state) return + + const posted = state.posted + + return ( +
+

+ Enter what each cash and bank account held when you started using the system. It is posted once, as a single + General Ledger journal against Opening Balance Equity. +

+ + {posted ? ( +
+
+ +
+

Cash and bank opening balances are posted

+

+ As at {formatReportDate(posted.cutoverDate)} · journal {posted.glJournalNo ?? "—"} · + posted {new Date(posted.postedAt).toLocaleString()}. To change an amount, post a correcting journal entry in the General Ledger. +

+
+
+ +
+ + + + Account + Code + Opening balance ({posted.currency}) + + + + {posted.lines.map((l) => ( + + {l.accountName} + {l.accountCode} + {formatAmount(l.amount)} + + ))} + + Credited to Opening Balance Equity + {formatAmount(posted.totalAmount)} + + +
+
+
+ ) : ( + <> +
+ + Opening date + { + setDate(e.target.value) + setErrors((p) => ({ ...p, date: "" })) + }} + aria-invalid={!!errors.date} + className="h-11 text-base" + /> + + + Must fall in an open accounting period. Reports that start after this date show these amounts as opening + balances, so use the day you started — or the first day of the financial year. + + +
+ + {state.accounts.length === 0 ? ( +
+ There are no cash or bank accounts yet. Create them under Accounts → Cash / Bank Accounts first. +
+ ) : ( +
+ + + + Account + Code + Opening balance ({state.baseCurrency}) + + + + {state.accounts.map((a) => { + const ok = usable(a) + const err = errors[a.glAccountCode] + return ( + + +
+ +
+

{a.accountName}

+

+ {a.accountType} + {a.bankName ? ` · ${a.bankName}` : ""} + {a.accountNumber ? ` · ${a.accountNumber}` : ""} +

+
+
+
+ {a.glAccountCode} + + {ok ? ( + <> + { + setAmounts((p) => ({ ...p, [a.glAccountCode]: e.target.value })) + setErrors((p) => ({ ...p, [a.glAccountCode]: "", lines: "" })) + }} + placeholder="0.00" + aria-label={`Opening balance for ${a.accountName}`} + aria-invalid={!!err} + className="h-11 text-right text-base" + /> + + + ) : ( + {a.currencyCode} account — not supported here + )} + +
+ ) + })} + + Credited to Opening Balance Equity + Opening Balance Equity (credit) + {formatAmount(total)} + +
+
+
+ )} + + {errors.lines && ( +
{errors.lines}
+ )} + +

+ A negative amount records an overdraft (a credit balance). Leave an account blank if it opened at zero. +

+ +
+ + }> + {posting ? "Posting…" : "Post opening balances"} + + + +
+ + )} +
+ ) +} diff --git a/Frontend/erp-system/components/accounts/OpeningStockPanel.tsx b/Frontend/erp-system/components/accounts/OpeningStockPanel.tsx new file mode 100644 index 0000000..6e19978 --- /dev/null +++ b/Frontend/erp-system/components/accounts/OpeningStockPanel.tsx @@ -0,0 +1,449 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import { ClipboardPaste, Plus, Trash2 } from "lucide-react" + +import { itemsApi } from "@/lib/api/items" +import { openingBalancesApi } from "@/lib/api/opening-balances" +import { warehousesApi } from "@/lib/api/warehouses" +import { errorMessage } from "@/lib/error-map" +import { formatAmount, formatReportDate, todayIso } from "@/lib/format" +import { ItemListItem, Warehouse } from "@/types/master-data" +import { OpeningStockRun, OpeningStockRunSummary } from "@/types/opening-balances" + +import { AlertDialog, AlertDialogContent, AlertDialogTrigger } from "@/components/ui/alert-dialog" +import { Button } from "@/components/ui/button" +import { Combobox, ComboboxOption } from "@/components/ui/combobox" +import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Textarea } from "@/components/ui/textarea" +import { toast } from "@/components/ui/toast" + +interface DraftLine { + key: string + itemId: number | null + qty: string + unitCost: string +} + +let keySeq = 0 +const newLine = (patch: Partial = {}): DraftLine => ({ key: `ol-${++keySeq}`, itemId: null, qty: "", unitCost: "", ...patch }) + +/** A positive number with at most `dp` decimals; null otherwise. */ +function num(raw: string, dp: number, allowZero: boolean): number | null { + const text = raw.trim() + if (text === "") return null + const n = Number(text) + if (!Number.isFinite(n) || n < 0 || (!allowZero && n === 0)) return null + const f = 10 ** dp + return Math.round(n * f) / f === n ? n : null +} + +/** + * Opening stock: what is already on the shelf when the system starts. One warehouse per run (load them one at a + * time); each line becomes a FIFO layer at the unit cost entered, and ONE journal debits Inventory against + * Opening Balance Equity. An item that already has stock movements in the warehouse is refused by the server. + * Only stocked, untracked items are offered — serial/batch items need per-unit identities. + */ +export function OpeningStockPanel() { + const [items, setItems] = useState(null) + const [warehouses, setWarehouses] = useState([]) + const [runs, setRuns] = useState(null) + const [error, setError] = useState(null) + + const [date, setDate] = useState(todayIso()) + const [warehouseId, setWarehouseId] = useState(null) + const [lines, setLines] = useState([newLine()]) + const [errors, setErrors] = useState>({}) + const [posting, setPosting] = useState(false) + + const [pasteOpen, setPasteOpen] = useState(false) + const [pasteText, setPasteText] = useState("") + const [pasteNotes, setPasteNotes] = useState([]) + + const [openRun, setOpenRun] = useState(null) + const [openRunLoading, setOpenRunLoading] = useState(false) + + function loadRuns() { + openingBalancesApi.stockRuns().then(setRuns).catch((err) => setError(errorMessage(err))) + } + + useEffect(() => { + loadRuns() + warehousesApi.list({ pageSize: 200 }).then((r) => setWarehouses(r.items)).catch(() => setWarehouses([])) + let cancelled = false + ;(async () => { + const all: ItemListItem[] = [] + for (let page = 1; page <= 25; page++) { + const res = await itemsApi.list({ status: "Active", page, pageSize: 200 }) + all.push(...res.items) + if (page >= res.pagination.totalPages) break + } + if (!cancelled) setItems(all) + })().catch((err) => { + if (!cancelled) { + setItems([]) + toast.error("Could not load items", errorMessage(err)) + } + }) + return () => { + cancelled = true + } + }, []) + + // Only what the server accepts: stocked items with no serial/batch tracking. + const eligible = useMemo(() => (items ?? []).filter((i) => i.stockNature === "Stocked" && i.trackingMode === "None"), [items]) + const options = useMemo[]>( + () => eligible.map((i) => ({ value: i.itemId, label: `${i.sku} — ${i.name}` })).sort((a, b) => a.label.localeCompare(b.label)), + [eligible], + ) + + const total = useMemo( + () => + Math.round( + lines.reduce((sum, l) => { + const q = num(l.qty, 4, false) + const c = num(l.unitCost, 4, true) + return sum + (q !== null && c !== null ? q * c : 0) + }, 0) * 10000, + ) / 10000, + [lines], + ) + + function updateLine(key: string, patch: Partial) { + setLines((prev) => prev.map((l) => (l.key === key ? { ...l, ...patch } : l))) + setErrors((prev) => ({ ...prev, [key]: "", general: "" })) + } + + /** "SKU qty cost" per row, tab- or comma-separated — what copying three columns out of a spreadsheet gives. */ + function applyPaste() { + const notes: string[] = [] + const next: DraftLine[] = [] + const bySku = new Map(eligible.map((i) => [i.sku.trim().toLowerCase(), i])) + pasteText.split(/\r?\n/).forEach((row, idx) => { + if (!row.trim()) return + const cells = row.split(/\t|,/).map((c) => c.trim()) + if (idx === 0 && Number.isNaN(Number(cells[1]))) return // a header row + const [sku, qty, cost] = cells + const item = bySku.get((sku ?? "").toLowerCase()) + if (!item) notes.push(`Row ${idx + 1}: "${sku}" is not an active, stocked, untracked item — skipped.`) + else next.push(newLine({ itemId: item.itemId, qty: qty ?? "", unitCost: cost ?? "" })) + }) + if (next.length > 0) setLines((prev) => [...prev.filter((l) => l.itemId !== null || l.qty || l.unitCost), ...next]) + setPasteNotes(notes) + if (notes.length === 0) { + setPasteOpen(false) + setPasteText("") + } + toast.success("Rows added", `${next.length} line${next.length === 1 ? "" : "s"} imported${notes.length ? `, ${notes.length} skipped` : ""}.`) + } + + function validate() { + const next: Record = {} + const payload: { itemId: number; warehouseId: number; qty: number; unitCost: number }[] = [] + const used = new Set() + for (const l of lines) { + if (l.itemId === null && !l.qty && !l.unitCost) continue // an untouched blank row + const q = num(l.qty, 4, false) + const c = num(l.unitCost, 4, true) + if (l.itemId === null) next[l.key] = "Select an item" + else if (used.has(l.itemId)) next[l.key] = "This item is already on another line" + else if (q === null) next[l.key] = "Quantity must be more than 0 (up to 4 decimals)" + else if (c === null) next[l.key] = "Unit cost must be 0 or more (up to 4 decimals)" + else { + used.add(l.itemId) + payload.push({ itemId: l.itemId, warehouseId: warehouseId ?? 0, qty: q, unitCost: c }) + } + } + if (warehouseId === null) next.warehouse = "Choose the warehouse" + if (!date) next.date = "Choose the opening date" + else if (date > todayIso()) next.date = "The opening date cannot be in the future" + if (payload.length === 0 && Object.keys(next).length === 0) next.general = "Add at least one item with a quantity" + setErrors(next) + return { payload, ok: Object.keys(next).length === 0 } + } + + async function post() { + const { payload, ok } = validate() + if (!ok) return + setPosting(true) + try { + const run = await openingBalancesApi.postStock({ cutoverDate: date, lines: payload }) + toast.success( + "Opening stock posted", + `${run.lineCount} line${run.lineCount === 1 ? "" : "s"} · ${formatAmount(run.totalValue)}${run.glJournalNo ? ` · ${run.glJournalNo}` : ""}`, + ) + setLines([newLine()]) + setOpenRun(run) + loadRuns() + } catch (err) { + toast.error("Could not post the opening stock", errorMessage(err)) + } finally { + setPosting(false) + } + } + + async function showRun(id: number) { + setOpenRunLoading(true) + try { + setOpenRun(await openingBalancesApi.stockRun(id)) + } catch (err) { + toast.error("Could not load that run", errorMessage(err)) + } finally { + setOpenRunLoading(false) + } + } + + if (error) return
{error}
+ if (items === null || runs === null) return + + return ( +
+

+ Enter the stock you already have on the shelf, one warehouse at a time. Each line becomes stock at the unit cost you give, and one + General Ledger journal debits Inventory against Opening Balance Equity. + An item can be opened once per warehouse — after that, use a stock adjustment. +

+ +
+ + Opening date + { + setDate(e.target.value) + setErrors((p) => ({ ...p, date: "" })) + }} + aria-invalid={!!errors.date} + className="h-11 text-base" + /> + + + + Warehouse + + value={warehouseId} + onValueChange={(v) => { + setWarehouseId(v) + setErrors((p) => ({ ...p, warehouse: "" })) + }} + items={warehouses.map((w) => ({ label: `${w.code} — ${w.name}`, value: w.warehouseId }))} + > + + + + + {warehouses.map((w) => ( + + {w.code} — {w.name} + + ))} + + + + Quantities are counted in each item's base unit (bottle, can, pack…). + +
+ +
+
+

Items

+
+ + +
+
+ + {pasteOpen && ( +
+

+ Paste rows of SKU, quantity, unit cost (tab- or comma-separated — three columns + copied from a spreadsheet work). A header row is skipped. +

+