From 6258ebd8dec0d7911398ede22129e4753376a2fb Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Mon, 27 Jul 2026 14:17:00 +0530 Subject: [PATCH 1/5] implemnet customer master used for both B2B and B2C. --- Backend/ERPCore/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index 84dbae8..dc4cb9f 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -9,6 +9,7 @@ using ERPCore.Repositories; using ERPCore.Repositories.Interfaces; using ERPCore.Services; using ERPCore.Services.Interfaces; +using ERPCore.Services.Interfaces; using ERPCore.Services.Auth; using ERPCore.Services.Hrm; using ERPCore.Services.Interfaces; From 0b9d64f91187b893e3fa6381aaaa7223a6170dee Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Mon, 27 Jul 2026 14:32:09 +0530 Subject: [PATCH 2/5] Add sales invoice document --- .../20260728053233_InitialCreate.Designer.cs | 5651 ++++++++++++++++ .../20260728053233_InitialCreate.cs | 22 + .../20260728054611_InitialCreate1.Designer.cs | 5651 ++++++++++++++++ .../20260728054611_InitialCreate1.cs | 3829 +++++++++++ ...260801000000_AddCompanyProfile.Designer.cs | 23 + .../20260801000000_AddCompanyProfile.cs | 60 + .../Migrations/ErpDbContextModelSnapshot.cs | 5754 +++++++++++++++++ 7 files changed, 20990 insertions(+) create mode 100644 Backend/ERPCore/Migrations/20260728053233_InitialCreate.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20260728053233_InitialCreate.cs create mode 100644 Backend/ERPCore/Migrations/20260728054611_InitialCreate1.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20260728054611_InitialCreate1.cs create mode 100644 Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.Designer.cs create mode 100644 Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.cs create mode 100644 Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs diff --git a/Backend/ERPCore/Migrations/20260728053233_InitialCreate.Designer.cs b/Backend/ERPCore/Migrations/20260728053233_InitialCreate.Designer.cs new file mode 100644 index 0000000..f11cbe0 --- /dev/null +++ b/Backend/ERPCore/Migrations/20260728053233_InitialCreate.Designer.cs @@ -0,0 +1,5651 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260728053233_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("integer"); + + b.Property("CheckIn") + .HasColumnType("interval"); + + b.Property("CheckOut") + .HasColumnType("interval"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("integer"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("integer"); + + b.Property("EditedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EditedBy") + .HasColumnType("integer"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("IsManualOverride") + .HasColumnType("boolean"); + + b.Property("LateMinutes") + .HasColumnType("integer"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("integer"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("WorkShiftId") + .HasColumnType("integer"); + + b.Property("WorkingMinutes") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ConfirmedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("PeriodEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("PeriodStart") + .HasColumnType("timestamp with time zone"); + + b.Property("RowCountDuplicate") + .HasColumnType("integer"); + + b.Property("RowCountError") + .HasColumnType("integer"); + + b.Property("RowCountTotal") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityId") + .HasColumnType("integer"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreditDays") + .HasColumnType("integer"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("integer"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("CustomerId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HeadEmployeeId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DateOfBirth") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartmentId") + .HasColumnType("integer"); + + b.Property("DesignationId") + .HasColumnType("integer"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("integer"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HireDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastWorkingDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WorkShiftId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HrDocumentTypeId") + .HasColumnType("integer"); + + b.Property("IssueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedBy") + .HasColumnType("integer"); + + b.Property("VerifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VerifiedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("integer"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StartMonth") + .HasColumnType("integer"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("integer"); + + b.Property("SalaryComponentId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("integer"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("GrnId") + .HasColumnType("integer"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("PoLineId") + .HasColumnType("integer"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiryTracked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("integer"); + + b.Property("BrandId") + .HasColumnType("integer"); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SubCategoryId") + .HasColumnType("integer"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SourceDocId") + .HasColumnType("integer"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Year") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaveTypeId") + .HasColumnType("integer"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("boolean"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPaid") + .HasColumnType("boolean"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequiresApproval") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("integer"); + + b.Property("DueYear") + .HasColumnType("integer"); + + b.Property("EmployeeLoanId") + .HasColumnType("integer"); + + b.Property("InstallmentNumber") + .HasColumnType("integer"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(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 = 3, + Code = "vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + SortOrder = 3, + 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" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("integer") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("integer"); + + b.Property("BasicSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EpfEmployeeAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("EpfEmployerAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("EtfEmployerAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("GrossSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("LateDeductionAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("integer"); + + b.Property("LeaveDays") + .HasColumnType("integer"); + + b.Property("LoanDeductionAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("NetSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("NoPayAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("integer"); + + b.Property("OtherDeductionsAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("OvertimeAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("integer"); + + b.Property("PresentDays") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("TaxAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("TotalAllowances") + .HasColumnType("numeric(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PayrollLineId") + .HasColumnType("integer"); + + b.Property("SalaryComponentId") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GeneratedBy") + .HasColumnType("integer"); + + b.Property("LockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockedBy") + .HasColumnType("integer"); + + b.Property("PeriodMonth") + .HasColumnType("integer"); + + b.Property("PeriodYear") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UnlockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UnlockedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PayrollLineId") + .HasColumnType("integer"); + + b.Property("ReleasedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReleasedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("NavItemId") + .HasColumnType("integer"); + + b.Property("SubNavItemId") + .HasColumnType("integer"); + + 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:vendors", + NavItemId = 3 + }, + 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 = 19, + Code = "NAV:procurement.requisitions", + SubNavItemId = 9 + }, + new + { + PermissionId = 20, + Code = "NAV:procurement.rfqs", + SubNavItemId = 10 + }, + new + { + PermissionId = 21, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 11 + }, + new + { + PermissionId = 22, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 12 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("PoId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("integer"); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + 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.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.HasKey("PoId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("uuid") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + b.Property("PermissionId") + .HasColumnType("integer"); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("boolean"); + + b.Property("IsTaxable") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("CreatorUserId") + .HasColumnType("integer"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesInvoiceId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("boolean"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("integer"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalesInvoiceId") + .HasColumnType("integer"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SlipDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("boolean"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("integer"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalesSlipId") + .HasColumnType("integer"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("integer"); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("CountId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("CountId") + .HasColumnType("integer"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("GrnLineId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("SourceDocId") + .HasColumnType("integer"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DestWarehouseId") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("DestBinId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("SrcBinId") + .HasColumnType("integer"); + + b.Property("TransferId") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(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.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NavItemId") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(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 = 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 = 9, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ToUomId") + .HasColumnType("integer"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("uuid") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("RoleId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("integer"); + + b.Property("VendorId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("integer"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("GraceMinutes") + .HasColumnType("integer"); + + b.Property("IsOvernight") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkingDaysMask") + .HasColumnType("integer"); + + 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.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.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + 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.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + 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.Grn", b => + { + 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.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("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + 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.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + 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.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + }); + + 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.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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + 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.PurchaseOrder", b => + { + 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("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.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.SalesInvoice", b => + { + 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("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + 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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + 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.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + 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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", 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.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + 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("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + 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.Navigation("Count"); + + b.Navigation("Item"); + }); + + 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.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("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.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.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + 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.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.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.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + 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.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.SalesInvoice", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + 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.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/20260728053233_InitialCreate.cs b/Backend/ERPCore/Migrations/20260728053233_InitialCreate.cs new file mode 100644 index 0000000..33fd144 --- /dev/null +++ b/Backend/ERPCore/Migrations/20260728053233_InitialCreate.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Backend/ERPCore/Migrations/20260728054611_InitialCreate1.Designer.cs b/Backend/ERPCore/Migrations/20260728054611_InitialCreate1.Designer.cs new file mode 100644 index 0000000..f8c9318 --- /dev/null +++ b/Backend/ERPCore/Migrations/20260728054611_InitialCreate1.Designer.cs @@ -0,0 +1,5651 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260728054611_InitialCreate1")] + partial class InitialCreate1 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("integer"); + + b.Property("CheckIn") + .HasColumnType("interval"); + + b.Property("CheckOut") + .HasColumnType("interval"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("integer"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("integer"); + + b.Property("EditedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EditedBy") + .HasColumnType("integer"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("IsManualOverride") + .HasColumnType("boolean"); + + b.Property("LateMinutes") + .HasColumnType("integer"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("integer"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("WorkShiftId") + .HasColumnType("integer"); + + b.Property("WorkingMinutes") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ConfirmedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("PeriodEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("PeriodStart") + .HasColumnType("timestamp with time zone"); + + b.Property("RowCountDuplicate") + .HasColumnType("integer"); + + b.Property("RowCountError") + .HasColumnType("integer"); + + b.Property("RowCountTotal") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityId") + .HasColumnType("integer"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreditDays") + .HasColumnType("integer"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("integer"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("CustomerId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HeadEmployeeId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DateOfBirth") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartmentId") + .HasColumnType("integer"); + + b.Property("DesignationId") + .HasColumnType("integer"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("integer"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HireDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastWorkingDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WorkShiftId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HrDocumentTypeId") + .HasColumnType("integer"); + + b.Property("IssueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedBy") + .HasColumnType("integer"); + + b.Property("VerifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VerifiedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("integer"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StartMonth") + .HasColumnType("integer"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("integer"); + + b.Property("SalaryComponentId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("integer"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("GrnId") + .HasColumnType("integer"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("PoLineId") + .HasColumnType("integer"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiryTracked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("integer"); + + b.Property("BrandId") + .HasColumnType("integer"); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SubCategoryId") + .HasColumnType("integer"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SourceDocId") + .HasColumnType("integer"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Year") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaveTypeId") + .HasColumnType("integer"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("boolean"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPaid") + .HasColumnType("boolean"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequiresApproval") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("integer"); + + b.Property("DueYear") + .HasColumnType("integer"); + + b.Property("EmployeeLoanId") + .HasColumnType("integer"); + + b.Property("InstallmentNumber") + .HasColumnType("integer"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(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 = 3, + Code = "vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + SortOrder = 3, + 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" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("integer") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("integer"); + + b.Property("BasicSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EpfEmployeeAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("EpfEmployerAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("EtfEmployerAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("GrossSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("LateDeductionAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("integer"); + + b.Property("LeaveDays") + .HasColumnType("integer"); + + b.Property("LoanDeductionAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("NetSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("NoPayAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("integer"); + + b.Property("OtherDeductionsAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("OvertimeAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("integer"); + + b.Property("PresentDays") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("TaxAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("TotalAllowances") + .HasColumnType("numeric(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PayrollLineId") + .HasColumnType("integer"); + + b.Property("SalaryComponentId") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GeneratedBy") + .HasColumnType("integer"); + + b.Property("LockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockedBy") + .HasColumnType("integer"); + + b.Property("PeriodMonth") + .HasColumnType("integer"); + + b.Property("PeriodYear") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UnlockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UnlockedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PayrollLineId") + .HasColumnType("integer"); + + b.Property("ReleasedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReleasedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("NavItemId") + .HasColumnType("integer"); + + b.Property("SubNavItemId") + .HasColumnType("integer"); + + 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:vendors", + NavItemId = 3 + }, + 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 = 19, + Code = "NAV:procurement.requisitions", + SubNavItemId = 9 + }, + new + { + PermissionId = 20, + Code = "NAV:procurement.rfqs", + SubNavItemId = 10 + }, + new + { + PermissionId = 21, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 11 + }, + new + { + PermissionId = 22, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 12 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("PoId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("integer"); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + 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.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.HasKey("PoId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("uuid") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + b.Property("PermissionId") + .HasColumnType("integer"); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("boolean"); + + b.Property("IsTaxable") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("CreatorUserId") + .HasColumnType("integer"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesInvoiceId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("boolean"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("integer"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalesInvoiceId") + .HasColumnType("integer"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SlipDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("boolean"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("integer"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalesSlipId") + .HasColumnType("integer"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("integer"); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("CountId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("CountId") + .HasColumnType("integer"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("GrnLineId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("SourceDocId") + .HasColumnType("integer"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DestWarehouseId") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("DestBinId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("SrcBinId") + .HasColumnType("integer"); + + b.Property("TransferId") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(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.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NavItemId") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(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 = 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 = 9, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ToUomId") + .HasColumnType("integer"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("uuid") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("RoleId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("integer"); + + b.Property("VendorId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("integer"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("GraceMinutes") + .HasColumnType("integer"); + + b.Property("IsOvernight") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkingDaysMask") + .HasColumnType("integer"); + + 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.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.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + 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.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + 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.Grn", b => + { + 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.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("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + 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.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + 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.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + }); + + 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.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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + 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.PurchaseOrder", b => + { + 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("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.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.SalesInvoice", b => + { + 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("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + 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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + 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.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + 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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", 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.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + 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("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + 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.Navigation("Count"); + + b.Navigation("Item"); + }); + + 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.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("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.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.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + 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.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.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.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + 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.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.SalesInvoice", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + 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.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/20260728054611_InitialCreate1.cs b/Backend/ERPCore/Migrations/20260728054611_InitialCreate1.cs new file mode 100644 index 0000000..9f7d032 --- /dev/null +++ b/Backend/ERPCore/Migrations/20260728054611_InitialCreate1.cs @@ -0,0 +1,3829 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace ERPCore.Migrations +{ + /// + public partial class InitialCreate1 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "brands", + columns: table => new + { + BrandId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_brands", x => x.BrandId); + }); + + migrationBuilder.CreateTable( + name: "categories", + columns: table => new + { + CategoryId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_categories", x => x.CategoryId); + }); + + migrationBuilder.CreateTable( + name: "hr_attendance_upload_batches", + columns: table => new + { + AttendanceUploadBatchId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + PeriodStart = table.Column(type: "timestamp with time zone", nullable: false), + PeriodEnd = table.Column(type: "timestamp with time zone", nullable: false), + SourceType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + OriginalFileName = table.Column(type: "character varying(260)", maxLength: 260, nullable: true), + UploadedBy = table.Column(type: "integer", nullable: false), + UploadedAt = table.Column(type: "timestamp with time zone", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ConfirmedBy = table.Column(type: "integer", nullable: true), + ConfirmedAt = table.Column(type: "timestamp with time zone", nullable: true), + RowCountTotal = table.Column(type: "integer", nullable: false), + RowCountDuplicate = table.Column(type: "integer", nullable: false), + RowCountError = table.Column(type: "integer", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_attendance_upload_batches", x => x.AttendanceUploadBatchId); + }); + + migrationBuilder.CreateTable( + name: "hr_branches", + columns: table => new + { + BranchId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Address = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_branches", x => x.BranchId); + }); + + migrationBuilder.CreateTable( + name: "hr_designations", + columns: table => new + { + DesignationId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_designations", x => x.DesignationId); + }); + + migrationBuilder.CreateTable( + name: "hr_document_types", + columns: table => new + { + HrDocumentTypeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Category = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + RequiredAtOnboarding = table.Column(type: "boolean", nullable: false), + ExpiryTracked = table.Column(type: "boolean", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_document_types", x => x.HrDocumentTypeId); + }); + + migrationBuilder.CreateTable( + name: "hr_employment_types", + columns: table => new + { + EmploymentTypeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employment_types", x => x.EmploymentTypeId); + }); + + migrationBuilder.CreateTable( + name: "hr_leave_types", + columns: table => new + { + LeaveTypeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + IsPaid = table.Column(type: "boolean", nullable: false), + CountsAsNoPay = table.Column(type: "boolean", nullable: false), + AccrualPerYear = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + CarryForwardAllowed = table.Column(type: "boolean", nullable: false), + MaxCarryForwardDays = table.Column(type: "integer", nullable: true), + RequiresApproval = table.Column(type: "boolean", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_leave_types", x => x.LeaveTypeId); + }); + + migrationBuilder.CreateTable( + name: "hr_payroll_statutory_settings", + columns: table => new + { + PayrollStatutorySettingId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EpfEmployeeRate = table.Column(type: "numeric(6,4)", precision: 6, scale: 4, nullable: false), + EpfEmployerRate = table.Column(type: "numeric(6,4)", precision: 6, scale: 4, nullable: false), + EtfEmployerRate = table.Column(type: "numeric(6,4)", precision: 6, scale: 4, nullable: false), + OtMultiplierDefault = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + EffectiveFrom = table.Column(type: "timestamp with time zone", nullable: false), + EffectiveTo = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_payroll_statutory_settings", x => x.PayrollStatutorySettingId); + }); + + migrationBuilder.CreateTable( + name: "hr_salary_components", + columns: table => new + { + SalaryComponentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ComponentType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + IsTaxable = table.Column(type: "boolean", nullable: false), + IsEpfEtfApplicable = table.Column(type: "boolean", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_salary_components", x => x.SalaryComponentId); + }); + + migrationBuilder.CreateTable( + name: "hr_tax_slabs", + columns: table => new + { + TaxSlabId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EffectiveFrom = table.Column(type: "timestamp with time zone", nullable: false), + EffectiveTo = table.Column(type: "timestamp with time zone", nullable: true), + LowerBound = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + UpperBound = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + Rate = table.Column(type: "numeric(6,4)", precision: 6, scale: 4, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_tax_slabs", x => x.TaxSlabId); + }); + + migrationBuilder.CreateTable( + name: "hr_work_shifts", + columns: table => new + { + WorkShiftId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + StartTime = table.Column(type: "interval", nullable: false), + EndTime = table.Column(type: "interval", nullable: false), + IsOvernight = table.Column(type: "boolean", nullable: false), + GraceMinutes = table.Column(type: "integer", nullable: false), + BreakMinutes = table.Column(type: "integer", nullable: false), + StandardWorkingMinutes = table.Column(type: "integer", nullable: false), + OtMultiplier = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + WorkingDaysMask = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_work_shifts", x => x.WorkShiftId); + }); + + migrationBuilder.CreateTable( + name: "item_types", + columns: table => new + { + ItemTypeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_item_types", x => x.ItemTypeId); + }); + + migrationBuilder.CreateTable( + name: "journal_entry_stubs", + columns: table => new + { + JournalId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + SourceDocId = table.Column(type: "integer", nullable: false), + DebitAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreditAccount = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Amount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_journal_entry_stubs", x => x.JournalId); + }); + + migrationBuilder.CreateTable( + name: "nav_items", + columns: table => new + { + NavItemId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Label = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Icon = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Href = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + SortOrder = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active") + }, + constraints: table => + { + table.PrimaryKey("PK_nav_items", x => x.NavItemId); + }); + + migrationBuilder.CreateTable( + name: "number_sequences", + columns: table => new + { + SequenceId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + doc_type = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + year = table.Column(type: "integer", nullable: false), + last_number = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_number_sequences", x => x.SequenceId); + }); + + migrationBuilder.CreateTable( + name: "reason_codes", + columns: table => new + { + ReasonCodeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Context = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_reason_codes", x => x.ReasonCodeId); + }); + + migrationBuilder.CreateTable( + name: "roles", + columns: table => new + { + RoleId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + auth_role_id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + IsSystemRole = table.Column(type: "boolean", nullable: false, defaultValue: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_roles", x => x.RoleId); + }); + + migrationBuilder.CreateTable( + name: "uoms", + columns: table => new + { + UomId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_uoms", x => x.UomId); + }); + + migrationBuilder.CreateTable( + name: "vendors", + columns: table => new + { + VendorId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Terms = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + TaxReg = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Currency = table.Column(type: "character varying(3)", maxLength: 3, nullable: false, defaultValue: "LKR"), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vendors", x => x.VendorId); + }); + + migrationBuilder.CreateTable( + name: "warehouses", + columns: table => new + { + WarehouseId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_warehouses", x => x.WarehouseId); + }); + + migrationBuilder.CreateTable( + name: "subcategories", + columns: table => new + { + SubCategoryId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + CategoryId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_subcategories", x => x.SubCategoryId); + table.ForeignKey( + name: "FK_subcategories_categories_CategoryId", + column: x => x.CategoryId, + principalTable: "categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_payroll_runs", + columns: table => new + { + PayrollRunId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + PeriodYear = table.Column(type: "integer", nullable: false), + PeriodMonth = table.Column(type: "integer", nullable: false), + BranchId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + GeneratedBy = table.Column(type: "integer", nullable: false), + GeneratedAt = table.Column(type: "timestamp with time zone", nullable: false), + ApprovedBy = table.Column(type: "integer", nullable: true), + ApprovedAt = table.Column(type: "timestamp with time zone", nullable: true), + LockedBy = table.Column(type: "integer", nullable: true), + LockedAt = table.Column(type: "timestamp with time zone", nullable: true), + UnlockedBy = table.Column(type: "integer", nullable: true), + UnlockedAt = table.Column(type: "timestamp with time zone", nullable: true), + UnlockReason = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_payroll_runs", x => x.PayrollRunId); + table.ForeignKey( + name: "FK_hr_payroll_runs_hr_branches_BranchId", + column: x => x.BranchId, + principalTable: "hr_branches", + principalColumn: "BranchId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "sub_nav_items", + columns: table => new + { + SubNavItemId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + NavItemId = table.Column(type: "integer", nullable: false), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Label = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Icon = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Href = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + SortOrder = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active") + }, + constraints: table => + { + table.PrimaryKey("PK_sub_nav_items", x => x.SubNavItemId); + table.ForeignKey( + name: "FK_sub_nav_items_nav_items_NavItemId", + column: x => x.NavItemId, + principalTable: "nav_items", + principalColumn: "NavItemId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + UserId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Username = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + auth_user_id = table.Column(type: "uuid", nullable: true), + Email = table.Column(type: "character varying(320)", maxLength: 320, nullable: true), + RoleId = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_users", x => x.UserId); + table.ForeignKey( + name: "FK_users_roles_RoleId", + column: x => x.RoleId, + principalTable: "roles", + principalColumn: "RoleId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "bins", + columns: table => new + { + BinId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + WarehouseId = table.Column(type: "integer", nullable: false), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + BinType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_bins", x => x.BinId); + table.ForeignKey( + name: "FK_bins_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "customers", + columns: table => new + { + CustomerId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CustomerCode = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + CustomerType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "B2C"), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Phone = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + Email = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + AddressLine1 = table.Column(type: "character varying(250)", maxLength: 250, nullable: true), + AddressLine2 = table.Column(type: "character varying(250)", maxLength: 250, nullable: true), + City = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Country = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + TaxRegistrationNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + CreditLimit = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CreditDays = table.Column(type: "integer", nullable: false), + DefaultWarehouseId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_customers", x => x.CustomerId); + table.ForeignKey( + name: "FK_customers_warehouses_DefaultWarehouseId", + column: x => x.DefaultWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "items", + columns: table => new + { + ItemId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Sku = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + CategoryId = table.Column(type: "integer", nullable: false), + SubCategoryId = table.Column(type: "integer", nullable: true), + BrandId = table.Column(type: "integer", nullable: true), + BaseUomId = table.Column(type: "integer", nullable: false), + DefaultVendorId = table.Column(type: "integer", nullable: true), + StockNature = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + TrackingMode = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + TaxClass = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + SalePrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_items", x => x.ItemId); + table.ForeignKey( + name: "FK_items_brands_BrandId", + column: x => x.BrandId, + principalTable: "brands", + principalColumn: "BrandId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_items_categories_CategoryId", + column: x => x.CategoryId, + principalTable: "categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_items_subcategories_SubCategoryId", + column: x => x.SubCategoryId, + principalTable: "subcategories", + principalColumn: "SubCategoryId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_items_uoms_BaseUomId", + column: x => x.BaseUomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_items_vendors_DefaultVendorId", + column: x => x.DefaultVendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "permissions", + columns: table => new + { + PermissionId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + NavItemId = table.Column(type: "integer", nullable: true), + SubNavItemId = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_permissions", x => x.PermissionId); + table.ForeignKey( + name: "FK_permissions_nav_items_NavItemId", + column: x => x.NavItemId, + principalTable: "nav_items", + principalColumn: "NavItemId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_permissions_sub_nav_items_SubNavItemId", + column: x => x.SubNavItemId, + principalTable: "sub_nav_items", + principalColumn: "SubNavItemId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "audit_logs", + columns: table => new + { + AuditId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + EntityType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + EntityId = table.Column(type: "integer", nullable: false), + Action = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + ChangeSet = table.Column(type: "jsonb", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_audit_logs", x => x.AuditId); + table.ForeignKey( + name: "FK_audit_logs_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "product_config", + columns: table => new + { + ConfigId = table.Column(type: "integer", nullable: false), + SubcategoriesEnabled = table.Column(type: "boolean", nullable: false, defaultValue: true), + BrandsEnabled = table.Column(type: "boolean", nullable: false, defaultValue: true), + ItemTypesEnabled = table.Column(type: "boolean", nullable: false, defaultValue: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + UpdatedBy = table.Column(type: "integer", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_product_config", x => x.ConfigId); + table.CheckConstraint("ck_product_config_singleton", "\"ConfigId\" = 1"); + table.ForeignKey( + name: "FK_product_config_users_UpdatedBy", + column: x => x.UpdatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "purchase_returns", + columns: table => new + { + ReturnId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + VendorId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + ReasonCodeId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_returns", x => x.ReturnId); + table.ForeignKey( + name: "FK_purchase_returns_reason_codes_ReasonCodeId", + column: x => x.ReasonCodeId, + principalTable: "reason_codes", + principalColumn: "ReasonCodeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_returns_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "requisitions", + columns: table => new + { + RequisitionId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + RequestedBy = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_requisitions", x => x.RequisitionId); + table.ForeignKey( + name: "FK_requisitions_users_RequestedBy", + column: x => x.RequestedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_adjustments", + columns: table => new + { + AdjustmentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + ReasonCodeId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_adjustments", x => x.AdjustmentId); + table.ForeignKey( + name: "FK_stock_adjustments_reason_codes_ReasonCodeId", + column: x => x.ReasonCodeId, + principalTable: "reason_codes", + principalColumn: "ReasonCodeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustments_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustments_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_counts", + columns: table => new + { + CountId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + CountType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_counts", x => x.CountId); + table.ForeignKey( + name: "FK_stock_counts_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_counts_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_transfers", + columns: table => new + { + TransferId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + SrcWarehouseId = table.Column(type: "integer", nullable: false), + DestWarehouseId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_transfers", x => x.TransferId); + table.ForeignKey( + name: "FK_stock_transfers_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfers_warehouses_DestWarehouseId", + column: x => x.DestWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfers_warehouses_SrcWarehouseId", + column: x => x.SrcWarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "sales_invoices", + columns: table => new + { + SalesInvoiceId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + InvoiceNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + InvoiceDate = table.Column(type: "timestamp with time zone", nullable: false), + CustomerId = table.Column(type: "integer", nullable: false), + CustomerSnapshotName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + CustomerSnapshotTaxNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + WarehouseId = table.Column(type: "integer", nullable: false), + InvoiceType = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "B2C"), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Draft"), + Subtotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + DiscountTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + TaxTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + GrandTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + RoundOff = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + NetPayable = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + PaidAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + BalanceAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatorUserId = table.Column(type: "integer", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_sales_invoices", x => x.SalesInvoiceId); + table.ForeignKey( + name: "FK_sales_invoices_customers_CustomerId", + column: x => x.CustomerId, + principalTable: "customers", + principalColumn: "CustomerId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_invoices_users_CreatorUserId", + column: x => x.CreatorUserId, + principalTable: "users", + principalColumn: "UserId"); + table.ForeignKey( + name: "FK_sales_invoices_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "sales_slips", + columns: table => new + { + SalesSlipId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SlipNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + SlipDate = table.Column(type: "timestamp with time zone", nullable: false), + CustomerId = table.Column(type: "integer", nullable: false), + CustomerSnapshotName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + CashierUserId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Draft"), + Subtotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + DiscountTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + TaxTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + GrandTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + PaidAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + BalanceAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_sales_slips", x => x.SalesSlipId); + table.ForeignKey( + name: "FK_sales_slips_customers_CustomerId", + column: x => x.CustomerId, + principalTable: "customers", + principalColumn: "CustomerId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_slips_users_CashierUserId", + column: x => x.CashierUserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_slips_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "batches", + columns: table => new + { + BatchId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + BatchNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + ExpiryDate = table.Column(type: "date", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_batches", x => x.BatchId); + table.ForeignKey( + name: "FK_batches_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "item_reorders", + columns: table => new + { + ReorderId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + ReorderPoint = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + ReorderQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_item_reorders", x => x.ReorderId); + table.ForeignKey( + name: "FK_item_reorders_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_item_reorders_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "serials", + columns: table => new + { + SerialId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + SerialNo = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_serials", x => x.SerialId); + table.ForeignKey( + name: "FK_serials_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "uom_conversions", + columns: table => new + { + ConversionId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + FromUomId = table.Column(type: "integer", nullable: false), + ToUomId = table.Column(type: "integer", nullable: false), + Factor = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_uom_conversions", x => x.ConversionId); + table.ForeignKey( + name: "FK_uom_conversions_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_uom_conversions_uoms_FromUomId", + column: x => x.FromUomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_uom_conversions_uoms_ToUomId", + column: x => x.ToUomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "role_permissions", + columns: table => new + { + RoleId = table.Column(type: "integer", nullable: false), + PermissionId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_permissions", x => new { x.RoleId, x.PermissionId }); + table.ForeignKey( + name: "FK_role_permissions_permissions_PermissionId", + column: x => x.PermissionId, + principalTable: "permissions", + principalColumn: "PermissionId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_role_permissions_roles_RoleId", + column: x => x.RoleId, + principalTable: "roles", + principalColumn: "RoleId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "purchase_orders", + columns: table => new + { + PoId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + VendorId = table.Column(type: "integer", nullable: false), + RequisitionId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ApprovalRequired = table.Column(type: "boolean", nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_orders", x => x.PoId); + table.ForeignKey( + name: "FK_purchase_orders_requisitions_RequisitionId", + column: x => x.RequisitionId, + principalTable: "requisitions", + principalColumn: "RequisitionId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_orders_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_orders_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "requisition_lines", + columns: table => new + { + ReqLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RequisitionId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + RequiredBy = table.Column(type: "date", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_requisition_lines", x => x.ReqLineId); + table.ForeignKey( + name: "FK_requisition_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_requisition_lines_requisitions_RequisitionId", + column: x => x.RequisitionId, + principalTable: "requisitions", + principalColumn: "RequisitionId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "rfqs", + columns: table => new + { + RfqId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + RequisitionId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_rfqs", x => x.RfqId); + table.ForeignKey( + name: "FK_rfqs_requisitions_RequisitionId", + column: x => x.RequisitionId, + principalTable: "requisitions", + principalColumn: "RequisitionId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_count_lines", + columns: table => new + { + CountLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CountId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + SystemQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CountedQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + Variance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_count_lines", x => x.CountLineId); + table.ForeignKey( + name: "FK_stock_count_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_count_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_count_lines_stock_counts_CountId", + column: x => x.CountId, + principalTable: "stock_counts", + principalColumn: "CountId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "sales_invoice_lines", + columns: table => new + { + SalesInvoiceLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SalesInvoiceId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + FreeQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UomId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + BaseCost = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + PriceSource = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + DiscountMode = table.Column(type: "integer", nullable: false), + DiscountPct = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + DiscountAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + NetUnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + LineTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + TaxPct = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + TaxAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + IsFreeIssue = table.Column(type: "boolean", nullable: false), + ParentLineId = table.Column(type: "integer", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_sales_invoice_lines", x => x.SalesInvoiceLineId); + table.ForeignKey( + name: "FK_sales_invoice_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_invoice_lines_sales_invoices_SalesInvoiceId", + column: x => x.SalesInvoiceId, + principalTable: "sales_invoices", + principalColumn: "SalesInvoiceId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_sales_invoice_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_invoice_lines_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "sales_slip_lines", + columns: table => new + { + SalesSlipLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SalesSlipId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + FreeQty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UomId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + BaseCost = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + PriceSource = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + DiscountMode = table.Column(type: "integer", nullable: false), + DiscountPct = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + DiscountAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + NetUnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + LineTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + TaxPct = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + TaxAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + IsFreeIssue = table.Column(type: "boolean", nullable: false), + ParentLineId = table.Column(type: "integer", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_sales_slip_lines", x => x.SalesSlipLineId); + table.ForeignKey( + name: "FK_sales_slip_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_slip_lines_sales_slips_SalesSlipId", + column: x => x.SalesSlipId, + principalTable: "sales_slips", + principalColumn: "SalesSlipId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_sales_slip_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_sales_slip_lines_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_adjustment_lines", + columns: table => new + { + AdjLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AdjustmentId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + QtyDelta = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_adjustment_lines", x => x.AdjLineId); + table.ForeignKey( + name: "FK_stock_adjustment_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_adjustment_lines_stock_adjustments_AdjustmentId", + column: x => x.AdjustmentId, + principalTable: "stock_adjustments", + principalColumn: "AdjustmentId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "stock_ledger", + columns: table => new + { + LedgerId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + UserId = table.Column(type: "integer", nullable: false), + Direction = table.Column(type: "character varying(5)", maxLength: 5, nullable: false), + QtyBase = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + Value = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + RunningBalance = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + SourceDocType = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + SourceDocId = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_ledger", x => x.LedgerId); + table.ForeignKey( + name: "FK_stock_ledger_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_ledger_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stock_transfer_lines", + columns: table => new + { + TransferLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + TransferId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + SrcBinId = table.Column(type: "integer", nullable: true), + DestBinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: true), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_transfer_lines", x => x.TransferLineId); + table.ForeignKey( + name: "FK_stock_transfer_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_bins_DestBinId", + column: x => x.DestBinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_bins_SrcBinId", + column: x => x.SrcBinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_transfer_lines_stock_transfers_TransferId", + column: x => x.TransferId, + principalTable: "stock_transfers", + principalColumn: "TransferId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "grns", + columns: table => new + { + GrnId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + PoId = table.Column(type: "integer", nullable: true), + VendorId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + PostedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_grns", x => x.GrnId); + table.ForeignKey( + name: "FK_grns_purchase_orders_PoId", + column: x => x.PoId, + principalTable: "purchase_orders", + principalColumn: "PoId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grns_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "po_lines", + columns: table => new + { + PoLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PoId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + UomId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + Tax = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_po_lines", x => x.PoLineId); + table.ForeignKey( + name: "FK_po_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_po_lines_purchase_orders_PoId", + column: x => x.PoId, + principalTable: "purchase_orders", + principalColumn: "PoId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_po_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_po_lines_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "rfq_lines", + columns: table => new + { + RfqLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RfqId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_rfq_lines", x => x.RfqLineId); + table.ForeignKey( + name: "FK_rfq_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_rfq_lines_rfqs_RfqId", + column: x => x.RfqId, + principalTable: "rfqs", + principalColumn: "RfqId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "vendor_quotations", + columns: table => new + { + QuotationId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RfqId = table.Column(type: "integer", nullable: false), + VendorId = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vendor_quotations", x => x.QuotationId); + table.ForeignKey( + name: "FK_vendor_quotations_rfqs_RfqId", + column: x => x.RfqId, + principalTable: "rfqs", + principalColumn: "RfqId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_vendor_quotations_vendors_VendorId", + column: x => x.VendorId, + principalTable: "vendors", + principalColumn: "VendorId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "grn_lines", + columns: table => new + { + GrnLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + GrnId = table.Column(type: "integer", nullable: false), + PoLineId = table.Column(type: "integer", nullable: true), + ItemId = table.Column(type: "integer", nullable: false), + UomId = table.Column(type: "integer", nullable: false), + BinId = table.Column(type: "integer", nullable: true), + BatchId = table.Column(type: "integer", nullable: true), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + PoUnitPrice = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: true), + DiscountPct = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + NetUnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + VatPct = table.Column(type: "numeric(9,4)", precision: 9, scale: 4, nullable: false), + VatAmount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + ReceivedValue = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + LineTotal = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + HoldStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_grn_lines", x => x.GrnLineId); + table.ForeignKey( + name: "FK_grn_lines_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_bins_BinId", + column: x => x.BinId, + principalTable: "bins", + principalColumn: "BinId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_grns_GrnId", + column: x => x.GrnId, + principalTable: "grns", + principalColumn: "GrnId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_grn_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_po_lines_PoLineId", + column: x => x.PoLineId, + principalTable: "po_lines", + principalColumn: "PoLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_grn_lines_uoms_UomId", + column: x => x.UomId, + principalTable: "uoms", + principalColumn: "UomId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "vendor_quotation_lines", + columns: table => new + { + QuotationLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + QuotationId = table.Column(type: "integer", nullable: false), + ItemId = table.Column(type: "integer", nullable: false), + UnitPrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + LeadDays = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vendor_quotation_lines", x => x.QuotationLineId); + table.ForeignKey( + name: "FK_vendor_quotation_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_vendor_quotation_lines_vendor_quotations_QuotationId", + column: x => x.QuotationId, + principalTable: "vendor_quotations", + principalColumn: "QuotationId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "purchase_return_lines", + columns: table => new + { + ReturnLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ReturnId = table.Column(type: "integer", nullable: false), + GrnLineId = table.Column(type: "integer", nullable: true), + ItemId = table.Column(type: "integer", nullable: false), + Qty = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_purchase_return_lines", x => x.ReturnLineId); + table.ForeignKey( + name: "FK_purchase_return_lines_grn_lines_GrnLineId", + column: x => x.GrnLineId, + principalTable: "grn_lines", + principalColumn: "GrnLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_return_lines_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_purchase_return_lines_purchase_returns_ReturnId", + column: x => x.ReturnId, + principalTable: "purchase_returns", + principalColumn: "ReturnId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "stock_layers", + columns: table => new + { + LayerId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ItemId = table.Column(type: "integer", nullable: false), + WarehouseId = table.Column(type: "integer", nullable: false), + BatchId = table.Column(type: "integer", nullable: true), + SerialId = table.Column(type: "integer", nullable: true), + GrnLineId = table.Column(type: "integer", nullable: true), + QtyReceived = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + QtyRemaining = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + UnitCost = table.Column(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false), + ReceiptDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stock_layers", x => x.LayerId); + table.ForeignKey( + name: "FK_stock_layers_batches_BatchId", + column: x => x.BatchId, + principalTable: "batches", + principalColumn: "BatchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_grn_lines_GrnLineId", + column: x => x.GrnLineId, + principalTable: "grn_lines", + principalColumn: "GrnLineId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_items_ItemId", + column: x => x.ItemId, + principalTable: "items", + principalColumn: "ItemId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_serials_SerialId", + column: x => x.SerialId, + principalTable: "serials", + principalColumn: "SerialId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_stock_layers_warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "warehouses", + principalColumn: "WarehouseId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_attendance_records", + columns: table => new + { + AttendanceRecordId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AttendanceUploadBatchId = table.Column(type: "integer", nullable: true), + EmployeeId = table.Column(type: "integer", nullable: false), + AttendanceDate = table.Column(type: "timestamp with time zone", nullable: false), + CheckIn = table.Column(type: "interval", nullable: true), + CheckOut = table.Column(type: "interval", nullable: true), + WorkShiftId = table.Column(type: "integer", nullable: false), + WorkingMinutes = table.Column(type: "integer", nullable: false), + LateMinutes = table.Column(type: "integer", nullable: false), + EarlyLeaveMinutes = table.Column(type: "integer", nullable: false), + OvertimeMinutes = table.Column(type: "integer", nullable: false), + AttendanceStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + RowValidationStatus = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + DuplicateOfAttendanceRecordId = table.Column(type: "integer", nullable: true), + Notes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + IsManualOverride = table.Column(type: "boolean", nullable: false), + EditedBy = table.Column(type: "integer", nullable: true), + EditedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_attendance_records", x => x.AttendanceRecordId); + table.ForeignKey( + name: "FK_hr_attendance_records_hr_attendance_upload_batches_Attendan~", + column: x => x.AttendanceUploadBatchId, + principalTable: "hr_attendance_upload_batches", + principalColumn: "AttendanceUploadBatchId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_hr_attendance_records_hr_work_shifts_WorkShiftId", + column: x => x.WorkShiftId, + principalTable: "hr_work_shifts", + principalColumn: "WorkShiftId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_departments", + columns: table => new + { + DepartmentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Code = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ParentDepartmentId = table.Column(type: "integer", nullable: true), + HeadEmployeeId = table.Column(type: "integer", nullable: true), + BranchId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_departments", x => x.DepartmentId); + table.ForeignKey( + name: "FK_hr_departments_hr_branches_BranchId", + column: x => x.BranchId, + principalTable: "hr_branches", + principalColumn: "BranchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_departments_hr_departments_ParentDepartmentId", + column: x => x.ParentDepartmentId, + principalTable: "hr_departments", + principalColumn: "DepartmentId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_employees", + columns: table => new + { + EmployeeId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeCode = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + FullName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Nic = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + DateOfBirth = table.Column(type: "timestamp with time zone", nullable: true), + Gender = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Nationality = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ProfilePhotoPath = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + Email = table.Column(type: "character varying(320)", maxLength: 320, nullable: true), + PersonalMobile = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + AddressLine1 = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + AddressLine2 = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + City = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + PostalCode = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Country = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + EmergencyContactName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + EmergencyContactRelationship = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + EmergencyContactPhone = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + HireDate = table.Column(type: "timestamp with time zone", nullable: false), + ConfirmationDate = table.Column(type: "timestamp with time zone", nullable: true), + LastWorkingDate = table.Column(type: "timestamp with time zone", nullable: true), + DepartmentId = table.Column(type: "integer", nullable: false), + DesignationId = table.Column(type: "integer", nullable: false), + EmploymentTypeId = table.Column(type: "integer", nullable: false), + BranchId = table.Column(type: "integer", nullable: true), + WorkShiftId = table.Column(type: "integer", nullable: false), + ReportingManagerId = table.Column(type: "integer", nullable: true), + EpfNumber = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + EtfNumber = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + TaxIdentificationNumber = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + UserId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedBy = table.Column(type: "integer", nullable: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employees", x => x.EmployeeId); + table.ForeignKey( + name: "FK_hr_employees_hr_branches_BranchId", + column: x => x.BranchId, + principalTable: "hr_branches", + principalColumn: "BranchId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employees_hr_departments_DepartmentId", + column: x => x.DepartmentId, + principalTable: "hr_departments", + principalColumn: "DepartmentId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employees_hr_designations_DesignationId", + column: x => x.DesignationId, + principalTable: "hr_designations", + principalColumn: "DesignationId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employees_hr_employees_ReportingManagerId", + column: x => x.ReportingManagerId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employees_hr_employment_types_EmploymentTypeId", + column: x => x.EmploymentTypeId, + principalTable: "hr_employment_types", + principalColumn: "EmploymentTypeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employees_hr_work_shifts_WorkShiftId", + column: x => x.WorkShiftId, + principalTable: "hr_work_shifts", + principalColumn: "WorkShiftId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employees_users_UserId", + column: x => x.UserId, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_employee_bank_details", + columns: table => new + { + EmployeeBankDetailId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeId = table.Column(type: "integer", nullable: false), + BankName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + BranchName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + AccountNumber = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + AccountHolderName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + SwiftCode = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + IsPrimary = table.Column(type: "boolean", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employee_bank_details", x => x.EmployeeBankDetailId); + table.ForeignKey( + name: "FK_hr_employee_bank_details_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "hr_employee_documents", + columns: table => new + { + EmployeeDocumentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeId = table.Column(type: "integer", nullable: false), + HrDocumentTypeId = table.Column(type: "integer", nullable: false), + OriginalFileName = table.Column(type: "character varying(260)", maxLength: 260, nullable: false), + StoredFileName = table.Column(type: "character varying(260)", maxLength: 260, nullable: false), + RelativePath = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + ContentType = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + SizeBytes = table.Column(type: "bigint", nullable: false), + IssueDate = table.Column(type: "timestamp with time zone", nullable: true), + ExpiryDate = table.Column(type: "timestamp with time zone", nullable: true), + Notes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + UploadedBy = table.Column(type: "integer", nullable: false), + UploadedAt = table.Column(type: "timestamp with time zone", nullable: false), + VerifiedBy = table.Column(type: "integer", nullable: true), + VerifiedAt = table.Column(type: "timestamp with time zone", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employee_documents", x => x.EmployeeDocumentId); + table.ForeignKey( + name: "FK_hr_employee_documents_hr_document_types_HrDocumentTypeId", + column: x => x.HrDocumentTypeId, + principalTable: "hr_document_types", + principalColumn: "HrDocumentTypeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_employee_documents_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "hr_employee_loans", + columns: table => new + { + EmployeeLoanId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + EmployeeId = table.Column(type: "integer", nullable: false), + LoanKind = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + PrincipalAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + InterestRate = table.Column(type: "numeric(6,4)", precision: 6, scale: 4, nullable: false), + InstallmentAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + NumberOfInstallments = table.Column(type: "integer", nullable: false), + StartYear = table.Column(type: "integer", nullable: false), + StartMonth = table.Column(type: "integer", nullable: false), + OutstandingBalance = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ApprovedBy = table.Column(type: "integer", nullable: false), + ApprovedAt = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employee_loans", x => x.EmployeeLoanId); + table.ForeignKey( + name: "FK_hr_employee_loans_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_employee_salary_structures", + columns: table => new + { + EmployeeSalaryStructureId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeId = table.Column(type: "integer", nullable: false), + EffectiveFrom = table.Column(type: "timestamp with time zone", nullable: false), + EffectiveTo = table.Column(type: "timestamp with time zone", nullable: true), + BasicSalary = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + Currency = table.Column(type: "character varying(3)", maxLength: 3, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ApprovedBy = table.Column(type: "integer", nullable: false), + ApprovedAt = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employee_salary_structures", x => x.EmployeeSalaryStructureId); + table.ForeignKey( + name: "FK_hr_employee_salary_structures_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_leave_balances", + columns: table => new + { + LeaveBalanceId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeId = table.Column(type: "integer", nullable: false), + LeaveTypeId = table.Column(type: "integer", nullable: false), + Year = table.Column(type: "integer", nullable: false), + EntitledDays = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + TakenDays = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + CarriedForwardDays = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + AdjustmentDays = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_leave_balances", x => x.LeaveBalanceId); + table.ForeignKey( + name: "FK_hr_leave_balances_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_leave_balances_hr_leave_types_LeaveTypeId", + column: x => x.LeaveTypeId, + principalTable: "hr_leave_types", + principalColumn: "LeaveTypeId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_leave_requests", + columns: table => new + { + LeaveRequestId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + DocNo = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + EmployeeId = table.Column(type: "integer", nullable: false), + LeaveTypeId = table.Column(type: "integer", nullable: false), + StartDate = table.Column(type: "timestamp with time zone", nullable: false), + EndDate = table.Column(type: "timestamp with time zone", nullable: false), + DaysCount = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + Reason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ApprovedBy = table.Column(type: "integer", nullable: true), + ApprovedAt = table.Column(type: "timestamp with time zone", nullable: true), + RejectionReason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + CreatedBy = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_leave_requests", x => x.LeaveRequestId); + table.ForeignKey( + name: "FK_hr_leave_requests_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_leave_requests_hr_leave_types_LeaveTypeId", + column: x => x.LeaveTypeId, + principalTable: "hr_leave_types", + principalColumn: "LeaveTypeId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_payroll_lines", + columns: table => new + { + PayrollLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PayrollRunId = table.Column(type: "integer", nullable: false), + EmployeeId = table.Column(type: "integer", nullable: false), + BasicSalary = table.Column(type: "numeric(18,2)", nullable: false), + TotalAllowances = table.Column(type: "numeric(18,2)", nullable: false), + OvertimeAmount = table.Column(type: "numeric(18,2)", nullable: false), + GrossSalary = table.Column(type: "numeric(18,2)", nullable: false), + LateDeductionAmount = table.Column(type: "numeric(18,2)", nullable: false), + NoPayAmount = table.Column(type: "numeric(18,2)", nullable: false), + LoanDeductionAmount = table.Column(type: "numeric(18,2)", nullable: false), + EpfEmployeeAmount = table.Column(type: "numeric(18,2)", nullable: false), + EpfEmployerAmount = table.Column(type: "numeric(18,2)", nullable: false), + EtfEmployerAmount = table.Column(type: "numeric(18,2)", nullable: false), + TaxAmount = table.Column(type: "numeric(18,2)", nullable: false), + OtherDeductionsAmount = table.Column(type: "numeric(18,2)", nullable: false), + NetSalary = table.Column(type: "numeric(18,2)", nullable: false), + WorkingDays = table.Column(type: "integer", nullable: false), + PresentDays = table.Column(type: "integer", nullable: false), + AbsentDays = table.Column(type: "integer", nullable: false), + LeaveDays = table.Column(type: "integer", nullable: false), + OtMinutesTotal = table.Column(type: "integer", nullable: false), + LateMinutesTotal = table.Column(type: "integer", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_payroll_lines", x => x.PayrollLineId); + table.ForeignKey( + name: "FK_hr_payroll_lines_hr_employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_hr_payroll_lines_hr_payroll_runs_PayrollRunId", + column: x => x.PayrollRunId, + principalTable: "hr_payroll_runs", + principalColumn: "PayrollRunId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "hr_loan_installments", + columns: table => new + { + LoanInstallmentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeLoanId = table.Column(type: "integer", nullable: false), + InstallmentNumber = table.Column(type: "integer", nullable: false), + DueYear = table.Column(type: "integer", nullable: false), + DueMonth = table.Column(type: "integer", nullable: false), + ScheduledAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + PaidAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + PayrollRunId = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_loan_installments", x => x.LoanInstallmentId); + table.ForeignKey( + name: "FK_hr_loan_installments_hr_employee_loans_EmployeeLoanId", + column: x => x.EmployeeLoanId, + principalTable: "hr_employee_loans", + principalColumn: "EmployeeLoanId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_hr_loan_installments_hr_payroll_runs_PayrollRunId", + column: x => x.PayrollRunId, + principalTable: "hr_payroll_runs", + principalColumn: "PayrollRunId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_employee_salary_structure_lines", + columns: table => new + { + EmployeeSalaryStructureLineId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EmployeeSalaryStructureId = table.Column(type: "integer", nullable: false), + SalaryComponentId = table.Column(type: "integer", nullable: false), + Amount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_employee_salary_structure_lines", x => x.EmployeeSalaryStructureLineId); + table.ForeignKey( + name: "FK_hr_employee_salary_structure_lines_hr_employee_salary_struc~", + column: x => x.EmployeeSalaryStructureId, + principalTable: "hr_employee_salary_structures", + principalColumn: "EmployeeSalaryStructureId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_hr_employee_salary_structure_lines_hr_salary_components_Sal~", + column: x => x.SalaryComponentId, + principalTable: "hr_salary_components", + principalColumn: "SalaryComponentId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_payroll_line_components", + columns: table => new + { + PayrollLineComponentId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PayrollLineId = table.Column(type: "integer", nullable: false), + ComponentCategory = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + SalaryComponentId = table.Column(type: "integer", nullable: true), + Label = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Amount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + SortOrder = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_payroll_line_components", x => x.PayrollLineComponentId); + table.ForeignKey( + name: "FK_hr_payroll_line_components_hr_payroll_lines_PayrollLineId", + column: x => x.PayrollLineId, + principalTable: "hr_payroll_lines", + principalColumn: "PayrollLineId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_hr_payroll_line_components_hr_salary_components_SalaryCompo~", + column: x => x.SalaryComponentId, + principalTable: "hr_salary_components", + principalColumn: "SalaryComponentId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "hr_payslips", + columns: table => new + { + PayslipId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PayrollLineId = table.Column(type: "integer", nullable: false), + GeneratedAt = table.Column(type: "timestamp with time zone", nullable: false), + ReleasedAt = table.Column(type: "timestamp with time zone", nullable: true), + ReleasedBy = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_hr_payslips", x => x.PayslipId); + table.ForeignKey( + name: "FK_hr_payslips_hr_payroll_lines_PayrollLineId", + column: x => x.PayrollLineId, + principalTable: "hr_payroll_lines", + principalColumn: "PayrollLineId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "nav_items", + columns: new[] { "NavItemId", "Code", "Href", "Icon", "Label", "SortOrder" }, + values: new object[,] + { + { 1, "dashboard", "/dashboard", null, "Dashboard", 1 }, + { 2, "products", "/dashboard/products", null, "Products", 2 }, + { 3, "vendors", "/dashboard/vendors", null, "Vendors", 3 }, + { 4, "procurement", "/dashboard/procurement", null, "Procurement", 4 }, + { 5, "receiving", "/dashboard/receiving/grn", null, "Receiving", 5 }, + { 6, "stock", "/dashboard/stock", null, "Stock", 6 }, + { 7, "warehouses", "/dashboard/warehouse", null, "Warehouses", 7 }, + { 8, "orders", "/dashboard/orders", null, "Orders", 8 }, + { 9, "settings", "/dashboard/settings", null, "Settings", 9 }, + { 10, "help", "/dashboard/help", null, "Help", 10 } + }); + + migrationBuilder.InsertData( + table: "users", + columns: new[] { "UserId", "auth_user_id", "DisplayName", "Email", "RoleId", "Status", "Username" }, + values: new object[] { 1, null, "System", null, null, "Active", "system" }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[,] + { + { 1, "NAV:dashboard", 1, null }, + { 2, "NAV:products", 2, null }, + { 3, "NAV:vendors", 3, null }, + { 4, "NAV:procurement", 4, null }, + { 5, "NAV:receiving", 5, null }, + { 6, "NAV:stock", 6, null }, + { 7, "NAV:warehouses", 7, null }, + { 8, "NAV:orders", 8, null }, + { 9, "NAV:settings", 9, null }, + { 10, "NAV:help", 10, null } + }); + + migrationBuilder.InsertData( + table: "sub_nav_items", + columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" }, + values: new object[,] + { + { 1, "products.item", "/dashboard/products", null, "Item", 2, 1 }, + { 2, "products.category", "/dashboard/products/categories", null, "Category", 2, 2 }, + { 3, "products.brand", "/dashboard/products/brands", null, "Brand", 2, 3 }, + { 4, "products.item-type", "/dashboard/products/item-types", null, "Item Type", 2, 4 }, + { 5, "products.uom", "/dashboard/products/uoms", null, "UOM", 2, 5 }, + { 6, "products.configuration", "/dashboard/products/settings", null, "Configuration", 2, 6 }, + { 7, "settings.roles", "/dashboard/settings/roles", null, "Roles", 9, 1 }, + { 8, "settings.users", "/dashboard/settings/users", null, "Users", 9, 2 }, + { 9, "procurement.requisitions", "/dashboard/procurement/requisitions", null, "Requisitions", 4, 1 }, + { 10, "procurement.rfqs", "/dashboard/procurement/rfqs", null, "RFQs", 4, 2 }, + { 11, "procurement.purchase-orders", "/dashboard/procurement/purchase-orders", null, "Purchase Orders", 4, 3 }, + { 12, "procurement.purchase-returns", "/dashboard/procurement/purchase-returns", null, "Purchase Returns", 4, 4 } + }); + + migrationBuilder.InsertData( + table: "permissions", + columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" }, + values: new object[,] + { + { 11, "NAV:products.item", null, 1 }, + { 12, "NAV:products.category", null, 2 }, + { 13, "NAV:products.brand", null, 3 }, + { 14, "NAV:products.item-type", null, 4 }, + { 15, "NAV:products.uom", null, 5 }, + { 16, "NAV:products.configuration", null, 6 }, + { 17, "NAV:settings.roles", null, 7 }, + { 18, "NAV:settings.users", null, 8 }, + { 19, "NAV:procurement.requisitions", null, 9 }, + { 20, "NAV:procurement.rfqs", null, 10 }, + { 21, "NAV:procurement.purchase-orders", null, 11 }, + { 22, "NAV:procurement.purchase-returns", null, 12 } + }); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_CreatedAt", + table: "audit_logs", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_EntityType_EntityId", + table: "audit_logs", + columns: new[] { "EntityType", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_audit_logs_UserId", + table: "audit_logs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_batches_ItemId_BatchNo", + table: "batches", + columns: new[] { "ItemId", "BatchNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_bins_WarehouseId_Code", + table: "bins", + columns: new[] { "WarehouseId", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_brands_Name", + table: "brands", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_brands_Status", + table: "brands", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_categories_Name", + table: "categories", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_categories_Status", + table: "categories", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_customers_CustomerCode", + table: "customers", + column: "CustomerCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_customers_CustomerType", + table: "customers", + column: "CustomerType"); + + migrationBuilder.CreateIndex( + name: "IX_customers_DefaultWarehouseId", + table: "customers", + column: "DefaultWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_customers_Status", + table: "customers", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_BatchId", + table: "grn_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_BinId", + table: "grn_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_GrnId", + table: "grn_lines", + column: "GrnId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_ItemId", + table: "grn_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_PoLineId", + table: "grn_lines", + column: "PoLineId"); + + migrationBuilder.CreateIndex( + name: "IX_grn_lines_UomId", + table: "grn_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_CreatedBy", + table: "grns", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_grns_DocNo", + table: "grns", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_grns_PoId", + table: "grns", + column: "PoId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_Status", + table: "grns", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_grns_VendorId", + table: "grns", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_grns_WarehouseId", + table: "grns", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_attendance_records_AttendanceUploadBatchId", + table: "hr_attendance_records", + column: "AttendanceUploadBatchId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_attendance_records_EmployeeId_AttendanceDate", + table: "hr_attendance_records", + columns: new[] { "EmployeeId", "AttendanceDate" }); + + migrationBuilder.CreateIndex( + name: "IX_hr_attendance_records_WorkShiftId", + table: "hr_attendance_records", + column: "WorkShiftId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_attendance_upload_batches_DocNo", + table: "hr_attendance_upload_batches", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_attendance_upload_batches_PeriodStart_PeriodEnd", + table: "hr_attendance_upload_batches", + columns: new[] { "PeriodStart", "PeriodEnd" }); + + migrationBuilder.CreateIndex( + name: "IX_hr_attendance_upload_batches_Status", + table: "hr_attendance_upload_batches", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_branches_Code", + table: "hr_branches", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_branches_Status", + table: "hr_branches", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_departments_BranchId", + table: "hr_departments", + column: "BranchId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_departments_Code", + table: "hr_departments", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_departments_HeadEmployeeId", + table: "hr_departments", + column: "HeadEmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_departments_ParentDepartmentId", + table: "hr_departments", + column: "ParentDepartmentId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_departments_Status", + table: "hr_departments", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_designations_Code", + table: "hr_designations", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_designations_Status", + table: "hr_designations", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_document_types_Code", + table: "hr_document_types", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_document_types_Status", + table: "hr_document_types", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_bank_details_EmployeeId", + table: "hr_employee_bank_details", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_documents_EmployeeId", + table: "hr_employee_documents", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_documents_ExpiryDate", + table: "hr_employee_documents", + column: "ExpiryDate"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_documents_HrDocumentTypeId", + table: "hr_employee_documents", + column: "HrDocumentTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_loans_DocNo", + table: "hr_employee_loans", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_loans_EmployeeId", + table: "hr_employee_loans", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_loans_Status", + table: "hr_employee_loans", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_salary_structure_lines_EmployeeSalaryStructureId", + table: "hr_employee_salary_structure_lines", + column: "EmployeeSalaryStructureId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_salary_structure_lines_SalaryComponentId", + table: "hr_employee_salary_structure_lines", + column: "SalaryComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employee_salary_structures_EmployeeId_EffectiveTo", + table: "hr_employee_salary_structures", + columns: new[] { "EmployeeId", "EffectiveTo" }); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_BranchId", + table: "hr_employees", + column: "BranchId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_DepartmentId", + table: "hr_employees", + column: "DepartmentId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_DesignationId", + table: "hr_employees", + column: "DesignationId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_Email", + table: "hr_employees", + column: "Email"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_EmployeeCode", + table: "hr_employees", + column: "EmployeeCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_EmploymentTypeId", + table: "hr_employees", + column: "EmploymentTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_ReportingManagerId", + table: "hr_employees", + column: "ReportingManagerId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_Status", + table: "hr_employees", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_UserId", + table: "hr_employees", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_employees_WorkShiftId", + table: "hr_employees", + column: "WorkShiftId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_employment_types_Code", + table: "hr_employment_types", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_employment_types_Status", + table: "hr_employment_types", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_balances_EmployeeId_LeaveTypeId_Year", + table: "hr_leave_balances", + columns: new[] { "EmployeeId", "LeaveTypeId", "Year" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_balances_LeaveTypeId", + table: "hr_leave_balances", + column: "LeaveTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_requests_DocNo", + table: "hr_leave_requests", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_requests_EmployeeId", + table: "hr_leave_requests", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_requests_LeaveTypeId", + table: "hr_leave_requests", + column: "LeaveTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_requests_StartDate_EndDate", + table: "hr_leave_requests", + columns: new[] { "StartDate", "EndDate" }); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_requests_Status", + table: "hr_leave_requests", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_types_Code", + table: "hr_leave_types", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_leave_types_Status", + table: "hr_leave_types", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_loan_installments_DueYear_DueMonth", + table: "hr_loan_installments", + columns: new[] { "DueYear", "DueMonth" }); + + migrationBuilder.CreateIndex( + name: "IX_hr_loan_installments_EmployeeLoanId", + table: "hr_loan_installments", + column: "EmployeeLoanId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_loan_installments_PayrollRunId", + table: "hr_loan_installments", + column: "PayrollRunId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_line_components_PayrollLineId", + table: "hr_payroll_line_components", + column: "PayrollLineId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_line_components_SalaryComponentId", + table: "hr_payroll_line_components", + column: "SalaryComponentId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_lines_EmployeeId", + table: "hr_payroll_lines", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_lines_PayrollRunId_EmployeeId", + table: "hr_payroll_lines", + columns: new[] { "PayrollRunId", "EmployeeId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_runs_BranchId", + table: "hr_payroll_runs", + column: "BranchId"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_runs_DocNo", + table: "hr_payroll_runs", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_runs_PeriodYear_PeriodMonth_BranchId", + table: "hr_payroll_runs", + columns: new[] { "PeriodYear", "PeriodMonth", "BranchId" }); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_runs_Status", + table: "hr_payroll_runs", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payroll_statutory_settings_EffectiveFrom", + table: "hr_payroll_statutory_settings", + column: "EffectiveFrom"); + + migrationBuilder.CreateIndex( + name: "IX_hr_payslips_PayrollLineId", + table: "hr_payslips", + column: "PayrollLineId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_salary_components_Code", + table: "hr_salary_components", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_salary_components_Status", + table: "hr_salary_components", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_hr_tax_slabs_EffectiveFrom", + table: "hr_tax_slabs", + column: "EffectiveFrom"); + + migrationBuilder.CreateIndex( + name: "IX_hr_work_shifts_Code", + table: "hr_work_shifts", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_hr_work_shifts_Status", + table: "hr_work_shifts", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_item_reorders_ItemId_WarehouseId", + table: "item_reorders", + columns: new[] { "ItemId", "WarehouseId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_item_reorders_WarehouseId", + table: "item_reorders", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_item_types_Name", + table: "item_types", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_item_types_Status", + table: "item_types", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_items_BaseUomId", + table: "items", + column: "BaseUomId"); + + migrationBuilder.CreateIndex( + name: "IX_items_BrandId", + table: "items", + column: "BrandId"); + + migrationBuilder.CreateIndex( + name: "IX_items_CategoryId", + table: "items", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_items_DefaultVendorId", + table: "items", + column: "DefaultVendorId"); + + migrationBuilder.CreateIndex( + name: "IX_items_Sku", + table: "items", + column: "Sku", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_items_Status", + table: "items", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_items_SubCategoryId", + table: "items", + column: "SubCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_journal_entry_stubs_SourceDocType_SourceDocId", + table: "journal_entry_stubs", + columns: new[] { "SourceDocType", "SourceDocId" }); + + migrationBuilder.CreateIndex( + name: "IX_nav_items_Code", + table: "nav_items", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_number_sequences_doc_type_year", + table: "number_sequences", + columns: new[] { "doc_type", "year" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_permissions_Code", + table: "permissions", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_permissions_NavItemId", + table: "permissions", + column: "NavItemId"); + + migrationBuilder.CreateIndex( + name: "IX_permissions_SubNavItemId", + table: "permissions", + column: "SubNavItemId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_ItemId", + table: "po_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_PoId", + table: "po_lines", + column: "PoId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_UomId", + table: "po_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_po_lines_WarehouseId", + table: "po_lines", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_product_config_UpdatedBy", + table: "product_config", + column: "UpdatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_CreatedBy", + table: "purchase_orders", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_DocNo", + table: "purchase_orders", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_RequisitionId", + table: "purchase_orders", + column: "RequisitionId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_Status", + table: "purchase_orders", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_orders_VendorId", + table: "purchase_orders", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_GrnLineId", + table: "purchase_return_lines", + column: "GrnLineId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_ItemId", + table: "purchase_return_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_return_lines_ReturnId", + table: "purchase_return_lines", + column: "ReturnId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_CreatedBy", + table: "purchase_returns", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_DocNo", + table: "purchase_returns", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_ReasonCodeId", + table: "purchase_returns", + column: "ReasonCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_VendorId", + table: "purchase_returns", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_purchase_returns_WarehouseId", + table: "purchase_returns", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_reason_codes_Context_Code", + table: "reason_codes", + columns: new[] { "Context", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_requisition_lines_ItemId", + table: "requisition_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_requisition_lines_RequisitionId", + table: "requisition_lines", + column: "RequisitionId"); + + migrationBuilder.CreateIndex( + name: "IX_requisitions_DocNo", + table: "requisitions", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_requisitions_RequestedBy", + table: "requisitions", + column: "RequestedBy"); + + migrationBuilder.CreateIndex( + name: "IX_requisitions_Status", + table: "requisitions", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_rfq_lines_ItemId", + table: "rfq_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_rfq_lines_RfqId", + table: "rfq_lines", + column: "RfqId"); + + migrationBuilder.CreateIndex( + name: "IX_rfqs_DocNo", + table: "rfqs", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_rfqs_RequisitionId", + table: "rfqs", + column: "RequisitionId"); + + migrationBuilder.CreateIndex( + name: "IX_role_permissions_PermissionId", + table: "role_permissions", + column: "PermissionId"); + + migrationBuilder.CreateIndex( + name: "IX_roles_auth_role_id", + table: "roles", + column: "auth_role_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_roles_Code", + table: "roles", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoice_lines_ItemId", + table: "sales_invoice_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoice_lines_SalesInvoiceId", + table: "sales_invoice_lines", + column: "SalesInvoiceId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoice_lines_UomId", + table: "sales_invoice_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoice_lines_WarehouseId", + table: "sales_invoice_lines", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoices_CreatorUserId", + table: "sales_invoices", + column: "CreatorUserId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoices_CustomerId", + table: "sales_invoices", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoices_InvoiceDate", + table: "sales_invoices", + column: "InvoiceDate"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoices_InvoiceNo", + table: "sales_invoices", + column: "InvoiceNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoices_Status", + table: "sales_invoices", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_sales_invoices_WarehouseId", + table: "sales_invoices", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slip_lines_ItemId", + table: "sales_slip_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slip_lines_SalesSlipId", + table: "sales_slip_lines", + column: "SalesSlipId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slip_lines_UomId", + table: "sales_slip_lines", + column: "UomId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slip_lines_WarehouseId", + table: "sales_slip_lines", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slips_CashierUserId", + table: "sales_slips", + column: "CashierUserId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slips_CustomerId", + table: "sales_slips", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slips_SlipDate", + table: "sales_slips", + column: "SlipDate"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slips_SlipNo", + table: "sales_slips", + column: "SlipNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_sales_slips_Status", + table: "sales_slips", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_sales_slips_WarehouseId", + table: "sales_slips", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_serials_ItemId_SerialNo", + table: "serials", + columns: new[] { "ItemId", "SerialNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_AdjustmentId", + table: "stock_adjustment_lines", + column: "AdjustmentId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_BatchId", + table: "stock_adjustment_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_BinId", + table: "stock_adjustment_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_ItemId", + table: "stock_adjustment_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustment_lines_SerialId", + table: "stock_adjustment_lines", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_CreatedBy", + table: "stock_adjustments", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_DocNo", + table: "stock_adjustments", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_ReasonCodeId", + table: "stock_adjustments", + column: "ReasonCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_adjustments_WarehouseId", + table: "stock_adjustments", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_BinId", + table: "stock_count_lines", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_CountId", + table: "stock_count_lines", + column: "CountId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_count_lines_ItemId", + table: "stock_count_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_CreatedBy", + table: "stock_counts", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_DocNo", + table: "stock_counts", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_Status", + table: "stock_counts", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_stock_counts_WarehouseId", + table: "stock_counts", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_BatchId", + table: "stock_layers", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_GrnLineId", + table: "stock_layers", + column: "GrnLineId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_ItemId_WarehouseId_ReceiptDate_LayerId", + table: "stock_layers", + columns: new[] { "ItemId", "WarehouseId", "ReceiptDate", "LayerId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_SerialId", + table: "stock_layers", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_layers_WarehouseId", + table: "stock_layers", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_BatchId", + table: "stock_ledger", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_BinId", + table: "stock_ledger", + column: "BinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_ItemId_WarehouseId_CreatedAt", + table: "stock_ledger", + columns: new[] { "ItemId", "WarehouseId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_ItemId_WarehouseId_LedgerId", + table: "stock_ledger", + columns: new[] { "ItemId", "WarehouseId", "LedgerId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_SerialId", + table: "stock_ledger", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_SourceDocType_SourceDocId", + table: "stock_ledger", + columns: new[] { "SourceDocType", "SourceDocId" }); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_UserId", + table: "stock_ledger", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_ledger_WarehouseId", + table: "stock_ledger", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_BatchId", + table: "stock_transfer_lines", + column: "BatchId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_DestBinId", + table: "stock_transfer_lines", + column: "DestBinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_ItemId", + table: "stock_transfer_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_SerialId", + table: "stock_transfer_lines", + column: "SerialId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_SrcBinId", + table: "stock_transfer_lines", + column: "SrcBinId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfer_lines_TransferId", + table: "stock_transfer_lines", + column: "TransferId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_CreatedBy", + table: "stock_transfers", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_DestWarehouseId", + table: "stock_transfers", + column: "DestWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_DocNo", + table: "stock_transfers", + column: "DocNo", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_SrcWarehouseId", + table: "stock_transfers", + column: "SrcWarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_stock_transfers_Status", + table: "stock_transfers", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_sub_nav_items_Code", + table: "sub_nav_items", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_sub_nav_items_NavItemId", + table: "sub_nav_items", + column: "NavItemId"); + + migrationBuilder.CreateIndex( + name: "IX_subcategories_CategoryId_Name", + table: "subcategories", + columns: new[] { "CategoryId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_subcategories_Status", + table: "subcategories", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_uom_conversions_FromUomId", + table: "uom_conversions", + column: "FromUomId"); + + migrationBuilder.CreateIndex( + name: "IX_uom_conversions_ItemId_FromUomId_ToUomId", + table: "uom_conversions", + columns: new[] { "ItemId", "FromUomId", "ToUomId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_uom_conversions_ToUomId", + table: "uom_conversions", + column: "ToUomId"); + + migrationBuilder.CreateIndex( + name: "IX_uoms_Name", + table: "uoms", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_users_auth_user_id", + table: "users", + column: "auth_user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_users_Email", + table: "users", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_users_RoleId", + table: "users", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_users_Username", + table: "users", + column: "Username", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotation_lines_ItemId", + table: "vendor_quotation_lines", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotation_lines_QuotationId", + table: "vendor_quotation_lines", + column: "QuotationId"); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotations_RfqId_VendorId", + table: "vendor_quotations", + columns: new[] { "RfqId", "VendorId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vendor_quotations_VendorId", + table: "vendor_quotations", + column: "VendorId"); + + migrationBuilder.CreateIndex( + name: "IX_vendors_Code", + table: "vendors", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vendors_Status", + table: "vendors", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_warehouses_Code", + table: "warehouses", + column: "Code", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_hr_attendance_records_hr_employees_EmployeeId", + table: "hr_attendance_records", + column: "EmployeeId", + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_hr_departments_hr_employees_HeadEmployeeId", + table: "hr_departments", + column: "HeadEmployeeId", + principalTable: "hr_employees", + principalColumn: "EmployeeId", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_hr_employees_users_UserId", + table: "hr_employees"); + + migrationBuilder.DropForeignKey( + name: "FK_hr_departments_hr_employees_HeadEmployeeId", + table: "hr_departments"); + + migrationBuilder.DropTable( + name: "audit_logs"); + + migrationBuilder.DropTable( + name: "hr_attendance_records"); + + migrationBuilder.DropTable( + name: "hr_employee_bank_details"); + + migrationBuilder.DropTable( + name: "hr_employee_documents"); + + migrationBuilder.DropTable( + name: "hr_employee_salary_structure_lines"); + + migrationBuilder.DropTable( + name: "hr_leave_balances"); + + migrationBuilder.DropTable( + name: "hr_leave_requests"); + + migrationBuilder.DropTable( + name: "hr_loan_installments"); + + migrationBuilder.DropTable( + name: "hr_payroll_line_components"); + + migrationBuilder.DropTable( + name: "hr_payroll_statutory_settings"); + + migrationBuilder.DropTable( + name: "hr_payslips"); + + migrationBuilder.DropTable( + name: "hr_tax_slabs"); + + migrationBuilder.DropTable( + name: "item_reorders"); + + migrationBuilder.DropTable( + name: "item_types"); + + migrationBuilder.DropTable( + name: "journal_entry_stubs"); + + migrationBuilder.DropTable( + name: "number_sequences"); + + migrationBuilder.DropTable( + name: "product_config"); + + migrationBuilder.DropTable( + name: "purchase_return_lines"); + + migrationBuilder.DropTable( + name: "requisition_lines"); + + migrationBuilder.DropTable( + name: "rfq_lines"); + + migrationBuilder.DropTable( + name: "role_permissions"); + + migrationBuilder.DropTable( + name: "sales_invoice_lines"); + + migrationBuilder.DropTable( + name: "sales_slip_lines"); + + migrationBuilder.DropTable( + name: "stock_adjustment_lines"); + + migrationBuilder.DropTable( + name: "stock_count_lines"); + + migrationBuilder.DropTable( + name: "stock_layers"); + + migrationBuilder.DropTable( + name: "stock_ledger"); + + migrationBuilder.DropTable( + name: "stock_transfer_lines"); + + migrationBuilder.DropTable( + name: "uom_conversions"); + + migrationBuilder.DropTable( + name: "vendor_quotation_lines"); + + migrationBuilder.DropTable( + name: "hr_attendance_upload_batches"); + + migrationBuilder.DropTable( + name: "hr_document_types"); + + migrationBuilder.DropTable( + name: "hr_employee_salary_structures"); + + migrationBuilder.DropTable( + name: "hr_leave_types"); + + migrationBuilder.DropTable( + name: "hr_employee_loans"); + + migrationBuilder.DropTable( + name: "hr_salary_components"); + + migrationBuilder.DropTable( + name: "hr_payroll_lines"); + + migrationBuilder.DropTable( + name: "purchase_returns"); + + migrationBuilder.DropTable( + name: "permissions"); + + migrationBuilder.DropTable( + name: "sales_invoices"); + + migrationBuilder.DropTable( + name: "sales_slips"); + + migrationBuilder.DropTable( + name: "stock_adjustments"); + + migrationBuilder.DropTable( + name: "stock_counts"); + + migrationBuilder.DropTable( + name: "grn_lines"); + + migrationBuilder.DropTable( + name: "serials"); + + migrationBuilder.DropTable( + name: "stock_transfers"); + + migrationBuilder.DropTable( + name: "vendor_quotations"); + + migrationBuilder.DropTable( + name: "hr_payroll_runs"); + + migrationBuilder.DropTable( + name: "sub_nav_items"); + + migrationBuilder.DropTable( + name: "customers"); + + migrationBuilder.DropTable( + name: "reason_codes"); + + migrationBuilder.DropTable( + name: "batches"); + + migrationBuilder.DropTable( + name: "bins"); + + migrationBuilder.DropTable( + name: "grns"); + + migrationBuilder.DropTable( + name: "po_lines"); + + migrationBuilder.DropTable( + name: "rfqs"); + + migrationBuilder.DropTable( + name: "nav_items"); + + migrationBuilder.DropTable( + name: "items"); + + migrationBuilder.DropTable( + name: "purchase_orders"); + + migrationBuilder.DropTable( + name: "warehouses"); + + migrationBuilder.DropTable( + name: "brands"); + + migrationBuilder.DropTable( + name: "subcategories"); + + migrationBuilder.DropTable( + name: "uoms"); + + migrationBuilder.DropTable( + name: "requisitions"); + + migrationBuilder.DropTable( + name: "vendors"); + + migrationBuilder.DropTable( + name: "categories"); + + migrationBuilder.DropTable( + name: "users"); + + migrationBuilder.DropTable( + name: "roles"); + + migrationBuilder.DropTable( + name: "hr_employees"); + + migrationBuilder.DropTable( + name: "hr_departments"); + + migrationBuilder.DropTable( + name: "hr_designations"); + + migrationBuilder.DropTable( + name: "hr_employment_types"); + + migrationBuilder.DropTable( + name: "hr_work_shifts"); + + migrationBuilder.DropTable( + name: "hr_branches"); + } + } +} diff --git a/Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.Designer.cs b/Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.Designer.cs new file mode 100644 index 0000000..7f21eac --- /dev/null +++ b/Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.Designer.cs @@ -0,0 +1,23 @@ +// +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + [Migration("20260801000000_AddCompanyProfile")] + partial class AddCompanyProfile + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + // This designer is kept minimal because the repo does not regenerate migrations here. + // EF tooling only needs the migration metadata to exist; the active model snapshot + // is updated separately. + } + } +} diff --git a/Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.cs b/Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.cs new file mode 100644 index 0000000..961452f --- /dev/null +++ b/Backend/ERPCore/Migrations/20260801000000_AddCompanyProfile.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ERPCore.Migrations +{ + /// + public partial class AddCompanyProfile : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "company_profile", + columns: table => new + { + CompanyProfileId = table.Column(type: "integer", nullable: false), + LegalName = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + TradeName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + LogoUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + TaxRegistrationNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + VatRegistrationNo = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + AddressLine1 = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + AddressLine2 = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + City = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Country = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Phone = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Email = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + BankName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + BankBranch = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + AccountName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + AccountNumber = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + SwiftCode = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + FooterNote = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + UpdatedBy = table.Column(type: "integer", nullable: true), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_company_profile", x => x.CompanyProfileId); + table.CheckConstraint("ck_company_profile_singleton", "\"CompanyProfileId\" = 1"); + table.ForeignKey( + name: "FK_company_profile_users_UpdatedBy", + column: x => x.UpdatedBy, + principalTable: "users", + principalColumn: "UserId", + onDelete: ReferentialAction.Restrict); + }); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "company_profile"); + } + } +} diff --git a/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs b/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs new file mode 100644 index 0000000..67ed7ed --- /dev/null +++ b/Backend/ERPCore/Migrations/ErpDbContextModelSnapshot.cs @@ -0,0 +1,5754 @@ +// +using System; +using ERPCore.Infra.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ERPCore.Migrations +{ + [DbContext(typeof(ErpDbContext))] + partial class ErpDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ERPCore.Domain.Entities.AttendanceRecord", b => + { + b.Property("AttendanceRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AttendanceRecordId")); + + b.Property("AttendanceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AttendanceStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("AttendanceUploadBatchId") + .HasColumnType("integer"); + + b.Property("CheckIn") + .HasColumnType("interval"); + + b.Property("CheckOut") + .HasColumnType("interval"); + + b.Property("DuplicateOfAttendanceRecordId") + .HasColumnType("integer"); + + b.Property("EarlyLeaveMinutes") + .HasColumnType("integer"); + + b.Property("EditedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EditedBy") + .HasColumnType("integer"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("IsManualOverride") + .HasColumnType("boolean"); + + b.Property("LateMinutes") + .HasColumnType("integer"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OvertimeMinutes") + .HasColumnType("integer"); + + b.Property("RowValidationStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("WorkShiftId") + .HasColumnType("integer"); + + b.Property("WorkingMinutes") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AttendanceUploadBatchId")); + + b.Property("ConfirmedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ConfirmedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OriginalFileName") + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("PeriodEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("PeriodStart") + .HasColumnType("timestamp with time zone"); + + b.Property("RowCountDuplicate") + .HasColumnType("integer"); + + b.Property("RowCountError") + .HasColumnType("integer"); + + b.Property("RowCountTotal") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuditId")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("ChangeSet") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EntityId") + .HasColumnType("integer"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("AuditId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_logs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Batch", b => + { + b.Property("BatchId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BatchId")); + + b.Property("BatchNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ExpiryDate") + .HasColumnType("date"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.HasKey("BatchId"); + + b.HasIndex("ItemId", "BatchNo") + .IsUnique(); + + b.ToTable("batches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Bin", b => + { + b.Property("BinId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BinId")); + + b.Property("BinType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BranchId")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BranchId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_branches", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b => + { + b.Property("BrandId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BrandId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("BrandId"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("brands", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CustomerId")); + + b.Property("AddressLine1") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("AddressLine2") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreditDays") + .HasColumnType("integer"); + + b.Property("CreditLimit") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CustomerCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CustomerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("B2C"); + + b.Property("DefaultWarehouseId") + .HasColumnType("integer"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("CustomerId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DepartmentId")); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HeadEmployeeId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ParentDepartmentId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("DesignationId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("DesignationId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_designations", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Employee", b => + { + b.Property("EmployeeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeId")); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ConfirmationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DateOfBirth") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartmentId") + .HasColumnType("integer"); + + b.Property("DesignationId") + .HasColumnType("integer"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EmployeeCode") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmploymentTypeId") + .HasColumnType("integer"); + + b.Property("EpfNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EtfNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Gender") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("HireDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastWorkingDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Nationality") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Nic") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PersonalMobile") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PostalCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ReportingManagerId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxIdentificationNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WorkShiftId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeBankDetailId")); + + b.Property("AccountHolderName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BankName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BranchName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("SwiftCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeDocumentId")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HrDocumentTypeId") + .HasColumnType("integer"); + + b.Property("IssueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("RelativePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("StoredFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedBy") + .HasColumnType("integer"); + + b.Property("VerifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VerifiedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeLoanId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("InstallmentAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("InterestRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("LoanKind") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("NumberOfInstallments") + .HasColumnType("integer"); + + b.Property("OutstandingBalance") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PrincipalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StartMonth") + .HasColumnType("integer"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeSalaryStructureId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("BasicSalary") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmployeeSalaryStructureLineId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("EmployeeSalaryStructureId") + .HasColumnType("integer"); + + b.Property("SalaryComponentId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("EmploymentTypeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("EmploymentTypeId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_employment_types", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b => + { + b.Property("GrnId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PoId") + .HasColumnType("integer"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("GrnId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("PoId"); + + b.HasIndex("Status"); + + b.HasIndex("VendorId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("grns", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.GrnLine", b => + { + b.Property("GrnLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GrnLineId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("GrnId") + .HasColumnType("integer"); + + b.Property("HoldStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("PoLineId") + .HasColumnType("integer"); + + b.Property("PoUnitPrice") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceivedValue") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("VatAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("VatPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.HasKey("GrnLineId"); + + b.HasIndex("BatchId"); + + b.HasIndex("BinId"); + + b.HasIndex("GrnId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoLineId"); + + b.HasIndex("UomId"); + + b.ToTable("grn_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.HrDocumentType", b => + { + b.Property("HrDocumentTypeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HrDocumentTypeId")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiryTracked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequiredAtOnboarding") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemId")); + + b.Property("BaseUomId") + .HasColumnType("integer"); + + b.Property("BrandId") + .HasColumnType("integer"); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultVendorId") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Sku") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("StockNature") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SubCategoryId") + .HasColumnType("integer"); + + b.Property("TaxClass") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TrackingMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ItemId"); + + b.HasIndex("BaseUomId"); + + b.HasIndex("BrandId"); + + b.HasIndex("CategoryId"); + + b.HasIndex("DefaultVendorId"); + + b.HasIndex("Sku") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("SubCategoryId"); + + b.ToTable("items", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b => + { + b.Property("ReorderId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReorderId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ReorderPoint") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReorderQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ItemTypeId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("JournalId")); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreditAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DebitAccount") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SourceDocId") + .HasColumnType("integer"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveBalanceId")); + + b.Property("AdjustmentDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("CarriedForwardDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EntitledDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("LeaveTypeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("TakenDays") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Year") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveRequestId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DaysCount") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaveTypeId") + .HasColumnType("integer"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("RejectionReason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LeaveTypeId")); + + b.Property("AccrualPerYear") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("CarryForwardAllowed") + .HasColumnType("boolean"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CountsAsNoPay") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPaid") + .HasColumnType("boolean"); + + b.Property("MaxCarryForwardDays") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequiresApproval") + .HasColumnType("boolean"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoanInstallmentId")); + + b.Property("DueMonth") + .HasColumnType("integer"); + + b.Property("DueYear") + .HasColumnType("integer"); + + b.Property("EmployeeLoanId") + .HasColumnType("integer"); + + b.Property("InstallmentNumber") + .HasColumnType("integer"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("ScheduledAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(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.NavItem", b => + { + b.Property("NavItemId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(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 = 3, + Code = "vendors", + Href = "/dashboard/vendors", + Label = "Vendors", + SortOrder = 3, + 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" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NumberSequence", b => + { + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("DocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("doc_type"); + + b.Property("LastNumber") + .HasColumnType("integer") + .HasColumnName("last_number"); + + b.Property("Year") + .HasColumnType("integer") + .HasColumnName("year"); + + b.HasKey("SequenceId"); + + b.HasIndex("DocType", "Year") + .IsUnique(); + + b.ToTable("number_sequences", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PayrollLine", b => + { + b.Property("PayrollLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollLineId")); + + b.Property("AbsentDays") + .HasColumnType("integer"); + + b.Property("BasicSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("EmployeeId") + .HasColumnType("integer"); + + b.Property("EpfEmployeeAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("EpfEmployerAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("EtfEmployerAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("GrossSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("LateDeductionAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("LateMinutesTotal") + .HasColumnType("integer"); + + b.Property("LeaveDays") + .HasColumnType("integer"); + + b.Property("LoanDeductionAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("NetSalary") + .HasColumnType("numeric(18,2)"); + + b.Property("NoPayAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("OtMinutesTotal") + .HasColumnType("integer"); + + b.Property("OtherDeductionsAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("OvertimeAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("PayrollRunId") + .HasColumnType("integer"); + + b.Property("PresentDays") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("TaxAmount") + .HasColumnType("numeric(18,2)"); + + b.Property("TotalAllowances") + .HasColumnType("numeric(18,2)"); + + b.Property("WorkingDays") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollLineComponentId")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ComponentCategory") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PayrollLineId") + .HasColumnType("integer"); + + b.Property("SalaryComponentId") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollRunId")); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("integer"); + + b.Property("BranchId") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GeneratedBy") + .HasColumnType("integer"); + + b.Property("LockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockedBy") + .HasColumnType("integer"); + + b.Property("PeriodMonth") + .HasColumnType("integer"); + + b.Property("PeriodYear") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UnlockReason") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UnlockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UnlockedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayrollStatutorySettingId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("EpfEmployeeRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("EpfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("EtfEmployerRate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("OtMultiplierDefault") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PayslipId")); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PayrollLineId") + .HasColumnType("integer"); + + b.Property("ReleasedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReleasedBy") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PermissionId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("character varying(80)"); + + b.Property("NavItemId") + .HasColumnType("integer"); + + b.Property("SubNavItemId") + .HasColumnType("integer"); + + 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:vendors", + NavItemId = 3 + }, + 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 = 19, + Code = "NAV:procurement.requisitions", + SubNavItemId = 9 + }, + new + { + PermissionId = 20, + Code = "NAV:procurement.rfqs", + SubNavItemId = 10 + }, + new + { + PermissionId = 21, + Code = "NAV:procurement.purchase-orders", + SubNavItemId = 11 + }, + new + { + PermissionId = 22, + Code = "NAV:procurement.purchase-returns", + SubNavItemId = 12 + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PoLine", b => + { + b.Property("PoLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("PoId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Tax") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("PoLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("PoId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("po_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.CompanyProfile", b => + { + b.Property("CompanyProfileId") + .HasColumnType("integer"); + + b.Property("AccountName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AccountNumber") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("AddressLine1") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BankBranch") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BankName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("FooterNote") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("LegalName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("LogoUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SwiftCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TaxRegistrationNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TradeName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("VatRegistrationNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("CompanyProfileId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("company_profile", (string)null, t => + { + t.HasCheckConstraint("ck_company_profile_singleton", "\"CompanyProfileId\" = 1"); + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b => + { + b.Property("ConfigId") + .HasColumnType("integer"); + + b.Property("BrandsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ItemTypesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SubcategoriesEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("integer"); + + 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.PurchaseOrder", b => + { + b.Property("PoId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PoId")); + + b.Property("ApprovalRequired") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.HasKey("PoId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReturnLineId")); + + b.Property("GrnLineId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReturnId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReasonCodeId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Context") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RequisitionId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequestedBy") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RequisitionId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReqLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RequiredBy") + .HasColumnType("date"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RequisitionId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("RfqId"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("RequisitionId"); + + b.ToTable("rfqs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.RfqLine", b => + { + b.Property("RfqLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RfqLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RfqId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RoleId")); + + b.Property("AuthRoleId") + .HasColumnType("uuid") + .HasColumnName("auth_role_id"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSystemRole") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + b.Property("PermissionId") + .HasColumnType("integer"); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalaryComponent", b => + { + b.Property("SalaryComponentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalaryComponentId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ComponentType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEpfEtfApplicable") + .HasColumnType("boolean"); + + b.Property("IsTaxable") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("SalaryComponentId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("hr_salary_components", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoice", b => + { + b.Property("SalesInvoiceId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesInvoiceId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("CreatorUserId") + .HasColumnType("integer"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CustomerSnapshotTaxNo") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("InvoiceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("B2C"); + + b.Property("NetPayable") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RoundOff") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesInvoiceId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesInvoiceLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("boolean"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("integer"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalesInvoiceId") + .HasColumnType("integer"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesInvoiceLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesInvoiceId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_invoice_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Property("SalesSlipId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesSlipId")); + + b.Property("BalanceAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CashierUserId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("CustomerSnapshotName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("GrandTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SlipDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SlipNo") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Draft"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesSlipId"); + + b.HasIndex("CashierUserId"); + + b.HasIndex("CustomerId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SalesSlipLineId")); + + b.Property("BaseCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DiscountAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("DiscountMode") + .HasColumnType("integer"); + + b.Property("DiscountPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("FreeQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("IsFreeIssue") + .HasColumnType("boolean"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("NetUnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ParentLineId") + .HasColumnType("integer"); + + b.Property("PriceSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SalesSlipId") + .HasColumnType("integer"); + + b.Property("TaxAmount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TaxPct") + .HasPrecision(9, 4) + .HasColumnType("numeric(9,4)"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UomId") + .HasColumnType("integer"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("SalesSlipLineId"); + + b.HasIndex("ItemId"); + + b.HasIndex("SalesSlipId"); + + b.HasIndex("UomId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("sales_slip_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.Property("SerialId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SerialId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("SerialNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("SerialId"); + + b.HasIndex("ItemId", "SerialNo") + .IsUnique(); + + b.ToTable("serials", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", b => + { + b.Property("AdjustmentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjustmentId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("ReasonCodeId") + .HasColumnType("integer"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("AdjustmentId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("DocNo") + .IsUnique(); + + b.HasIndex("ReasonCodeId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("stock_adjustments", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustmentLine", b => + { + b.Property("AdjLineId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AdjLineId")); + + b.Property("AdjustmentId") + .HasColumnType("integer"); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyDelta") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountId")); + + b.Property("CountType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + b.HasKey("CountId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CountLineId")); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("CountId") + .HasColumnType("integer"); + + b.Property("CountedQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("SystemQty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Variance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.HasKey("CountLineId"); + + b.HasIndex("BinId"); + + b.HasIndex("CountId"); + + b.HasIndex("ItemId"); + + b.ToTable("stock_count_lines", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockLayer", b => + { + b.Property("LayerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LayerId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("GrnLineId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyRemaining") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReceiptDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LedgerId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("BinId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("QtyBase") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RunningBalance") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("SourceDocId") + .HasColumnType("integer"); + + b.Property("SourceDocType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Value") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("WarehouseId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("integer"); + + b.Property("DestWarehouseId") + .HasColumnType("integer"); + + b.Property("DocNo") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("SrcWarehouseId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("TransferId"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TransferLineId")); + + b.Property("BatchId") + .HasColumnType("integer"); + + b.Property("DestBinId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("Qty") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("QtyReceived") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("SerialId") + .HasColumnType("integer"); + + b.Property("SrcBinId") + .HasColumnType("integer"); + + b.Property("TransferId") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 6) + .HasColumnType("numeric(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.SubCategory", b => + { + b.Property("SubCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SubCategoryId")); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SubNavItemId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Href") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NavItemId") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(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 = 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 = 9, + Code = "procurement.requisitions", + Href = "/dashboard/procurement/requisitions", + Label = "Requisitions", + NavItemId = 4, + SortOrder = 1, + Status = "Active" + }, + new + { + SubNavItemId = 10, + Code = "procurement.rfqs", + Href = "/dashboard/procurement/rfqs", + Label = "RFQs", + NavItemId = 4, + SortOrder = 2, + Status = "Active" + }, + new + { + SubNavItemId = 11, + Code = "procurement.purchase-orders", + Href = "/dashboard/procurement/purchase-orders", + Label = "Purchase Orders", + NavItemId = 4, + SortOrder = 3, + Status = "Active" + }, + new + { + SubNavItemId = 12, + Code = "procurement.purchase-returns", + Href = "/dashboard/procurement/purchase-returns", + Label = "Purchase Returns", + NavItemId = 4, + SortOrder = 4, + Status = "Active" + }); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.TaxSlab", b => + { + b.Property("TaxSlabId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TaxSlabId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("LowerBound") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Rate") + .HasPrecision(6, 4) + .HasColumnType("numeric(6,4)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("UpperBound") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("TaxSlabId"); + + b.HasIndex("EffectiveFrom"); + + b.ToTable("hr_tax_slabs", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b => + { + b.Property("UomId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UomId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("UomId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("uoms", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b => + { + b.Property("ConversionId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ConversionId")); + + b.Property("Factor") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("FromUomId") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ToUomId") + .HasColumnType("integer"); + + b.HasKey("ConversionId"); + + b.HasIndex("FromUomId"); + + b.HasIndex("ToUomId"); + + b.HasIndex("ItemId", "FromUomId", "ToUomId") + .IsUnique(); + + b.ToTable("uom_conversions", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("AuthUserId") + .HasColumnType("uuid") + .HasColumnName("auth_user_id"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Email") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("RoleId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("VendorId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(3) + .HasColumnType("character varying(3)") + .HasDefaultValue("LKR"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("TaxReg") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Terms") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("VendorId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Status"); + + b.ToTable("vendors", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.VendorQuotation", b => + { + b.Property("QuotationId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationId")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RfqId") + .HasColumnType("integer"); + + b.Property("VendorId") + .HasColumnType("integer"); + + 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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuotationLineId")); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("LeadDays") + .HasColumnType("integer"); + + b.Property("QuotationId") + .HasColumnType("integer"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(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("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WarehouseId")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("WarehouseId"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("warehouses", (string)null); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.WorkShift", b => + { + b.Property("WorkShiftId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WorkShiftId")); + + b.Property("BreakMinutes") + .HasColumnType("integer"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("GraceMinutes") + .HasColumnType("integer"); + + b.Property("IsOvernight") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OtMultiplier") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("StandardWorkingMinutes") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Active"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkingDaysMask") + .HasColumnType("integer"); + + 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.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.Customer", b => + { + b.HasOne("ERPCore.Domain.Entities.Warehouse", "DefaultWarehouse") + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.SetNull); + + 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.Employee", b => + { + b.HasOne("ERPCore.Domain.Entities.Branch", "Branch") + .WithMany() + .HasForeignKey("BranchId") + .OnDelete(DeleteBehavior.Restrict); + + 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.Grn", b => + { + 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.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("PurchaseOrder"); + + b.Navigation("Vendor"); + + b.Navigation("Warehouse"); + }); + + 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.HasOne("ERPCore.Domain.Entities.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Batch"); + + b.Navigation("Bin"); + + b.Navigation("Grn"); + + b.Navigation("Item"); + + b.Navigation("PoLine"); + + b.Navigation("Uom"); + }); + + 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.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("DefaultVendor"); + + b.Navigation("SubCategory"); + }); + + 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.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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + 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.CompanyProfile", b => + { + b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b => + { + 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("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.User", "Requester") + .WithMany() + .HasForeignKey("RequestedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.Requisition", "Requisition") + .WithMany() + .HasForeignKey("RequisitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.SalesInvoice", b => + { + 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("Creator"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesInvoiceLine", b => + { + 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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SalesInvoice"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + 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.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CashierUser"); + + b.Navigation("Customer"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlipLine", b => + { + 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.Uom", "Uom") + .WithMany() + .HasForeignKey("UomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SalesSlip"); + + b.Navigation("Uom"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Serial", b => + { + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockAdjustment", 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.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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", null) + .WithMany() + .HasForeignKey("SerialId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Adjustment"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCount", b => + { + 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("Creator"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.StockCountLine", b => + { + 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.Navigation("Count"); + + b.Navigation("Item"); + }); + + 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.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("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.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.UomConversion", b => + { + b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom") + .WithMany() + .HasForeignKey("FromUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Item", "Item") + .WithMany("UomConversions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ERPCore.Domain.Entities.Uom", "ToUom") + .WithMany() + .HasForeignKey("ToUomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + 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.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.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.Grn", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Item", b => + { + b.Navigation("ReorderSettings"); + + b.Navigation("UomConversions"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.NavItem", b => + { + b.Navigation("Children"); + }); + + 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.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.SalesInvoice", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.SalesSlip", b => + { + b.Navigation("Lines"); + }); + + 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.VendorQuotation", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("ERPCore.Domain.Entities.Warehouse", b => + { + b.Navigation("Bins"); + }); +#pragma warning restore 612, 618 + } + } +} From 59af50bf11bd602cb6a0c31de637db94509cc773 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Mon, 27 Jul 2026 14:45:35 +0530 Subject: [PATCH 3/5] Add sales slip document --- Backend/ERPCore/Program.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index dc4cb9f..4df0ad6 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -9,7 +9,6 @@ using ERPCore.Repositories; using ERPCore.Repositories.Interfaces; using ERPCore.Services; using ERPCore.Services.Interfaces; -using ERPCore.Services.Interfaces; using ERPCore.Services.Auth; using ERPCore.Services.Hrm; using ERPCore.Services.Interfaces; @@ -103,8 +102,6 @@ builder.Services.AddScoped(); // Sales (Phase 1) builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); // Stock transactions + reference data (docs/11 §5–6) builder.Services.AddScoped(); From 3c5b47663559e3b432a8ffba384d6738f18cff5e Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Sun, 2 Aug 2026 01:12:09 +0530 Subject: [PATCH 4/5] sales ,invoice ,slip and free issues BE fixes --- .../Controllers/FreeIssuesController.cs | 78 ++++++++++ .../Controllers/SalesInvoicesController.cs | 6 + .../Controllers/SalesReportsController.cs | 52 +++---- .../SalesSlipPromotionsController.cs | 22 +++ .../Controllers/SalesSlipsController.cs | 6 + .../ERPCore/Domain/Enums/SalesDiscountMode.cs | 2 +- .../ERPCore/Dtos/Sales/SalesInvoiceDtos.cs | 8 + .../Sales/SalesPromotionSuggestionDtos.cs | 23 +++ .../Dtos/Sales/SalesReportQueryDtos.cs | 21 +++ Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs | 19 +++ .../Interfaces/ISalesInvoiceService.cs | 1 + .../ISalesPromotionSuggestionService.cs | 8 + .../Interfaces/ISalesReportService.cs | 3 + .../Services/Interfaces/ISalesSlipService.cs | 3 + .../ERPCore/Services/SalesInvoiceService.cs | 37 ++++- .../SalesPromotionSuggestionService.cs | 74 +++++++++ .../ERPCore/Services/SalesReportService.cs | 141 +++++++++++++----- Backend/ERPCore/Services/SalesSlipService.cs | 113 +++++++++++++- 18 files changed, 543 insertions(+), 74 deletions(-) create mode 100644 Backend/ERPCore/Controllers/FreeIssuesController.cs create mode 100644 Backend/ERPCore/Controllers/SalesSlipPromotionsController.cs create mode 100644 Backend/ERPCore/Dtos/Sales/SalesPromotionSuggestionDtos.cs create mode 100644 Backend/ERPCore/Dtos/Sales/SalesReportQueryDtos.cs create mode 100644 Backend/ERPCore/Services/Interfaces/ISalesPromotionSuggestionService.cs create mode 100644 Backend/ERPCore/Services/SalesPromotionSuggestionService.cs diff --git a/Backend/ERPCore/Controllers/FreeIssuesController.cs b/Backend/ERPCore/Controllers/FreeIssuesController.cs new file mode 100644 index 0000000..93057e8 --- /dev/null +++ b/Backend/ERPCore/Controllers/FreeIssuesController.cs @@ -0,0 +1,78 @@ +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Common; +using ERPCore.Dtos.Sales; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +/// +/// Thin API alias for free-issue handling. Free issues are modeled as sales slips +/// with line-level IsFreeIssue/FreeQty flags, so this controller reuses +/// the existing sales-slip CRUD surface under a more business-friendly route. +/// +[Route("api/v1/free-issues")] +public sealed class FreeIssuesController : ApiControllerBase +{ + private readonly ISalesSlipService _slips; + + public FreeIssuesController(ISalesSlipService slips) => _slips = slips; + + [HttpGet] + [ProducesResponseType(typeof(PagedResponse), StatusCodes.Status200OK)] + public async Task>> List( + [FromQuery] PageQuery query, + [FromQuery] SalesSlipStatus? status, + [FromQuery] int? customerId, + [FromQuery] int? warehouseId, + CancellationToken ct) + => Ok(await _slips.ListFreeIssuesAsync(query, status, customerId, warehouseId, ct)); + + [HttpGet("{freeIssueId:int}")] + [ProducesResponseType(typeof(FreeIssueDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetById(int freeIssueId, CancellationToken ct) + { + var result = await _slips.GetFreeIssueAsync(freeIssueId, ct); + if (result is null) return NotFound(); + SetETag(result.RowVersion); + return Ok(result.Value); + } + + [HttpGet("{freeIssueId:int}/posting-check")] + [ProducesResponseType(typeof(SalesSlipPostingCheckDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> PostingCheck(int freeIssueId, CancellationToken ct) + => Ok(await _slips.CheckPostingAsync(freeIssueId, ct)); + + [HttpPost] + [ProducesResponseType(typeof(FreeIssueDto), StatusCodes.Status201Created)] + public async Task> Create([FromBody] CreateSalesSlipRequest request, CancellationToken ct) + { + var result = await _slips.CreateAsync(request, ct); + SetETag(result.RowVersion); + return Created($"/api/v1/free-issues/{result.Value.SalesSlipId}", result.Value); + } + + [HttpPut("{freeIssueId:int}")] + [ProducesResponseType(typeof(FreeIssueDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] + public async Task> Update(int freeIssueId, [FromBody] UpdateSalesSlipRequest request, CancellationToken ct) + { + var expected = RequireIfMatch(); + var result = await _slips.UpdateAsync(freeIssueId, request, expected, ct); + SetETag(result.RowVersion); + return Ok(result.Value); + } + + [HttpPost("{freeIssueId:int}/post")] + [ProducesResponseType(typeof(FreeIssueDto), StatusCodes.Status200OK)] + public async Task> Post(int freeIssueId, CancellationToken ct) + => Ok(await _slips.PostAsync(freeIssueId, ct)); + + [HttpPost("{freeIssueId:int}/cancel")] + [ProducesResponseType(typeof(FreeIssueDto), StatusCodes.Status200OK)] + public async Task> Cancel(int freeIssueId, CancellationToken ct) + => Ok(await _slips.CancelAsync(freeIssueId, ct)); +} diff --git a/Backend/ERPCore/Controllers/SalesInvoicesController.cs b/Backend/ERPCore/Controllers/SalesInvoicesController.cs index e27dca0..29eab9e 100644 --- a/Backend/ERPCore/Controllers/SalesInvoicesController.cs +++ b/Backend/ERPCore/Controllers/SalesInvoicesController.cs @@ -34,6 +34,12 @@ public sealed class SalesInvoicesController : ApiControllerBase return Ok(result.Value); } + [HttpGet("{salesInvoiceId:int}/posting-check")] + [ProducesResponseType(typeof(SalesInvoicePostingCheckDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> PostingCheck(int salesInvoiceId, CancellationToken ct) + => Ok(await _invoices.CheckPostingAsync(salesInvoiceId, ct)); + [HttpPost] [ProducesResponseType(typeof(SalesInvoiceDto), StatusCodes.Status201Created)] public async Task> Create([FromBody] CreateSalesInvoiceRequest request, CancellationToken ct) diff --git a/Backend/ERPCore/Controllers/SalesReportsController.cs b/Backend/ERPCore/Controllers/SalesReportsController.cs index b2edfa8..b6f18c7 100644 --- a/Backend/ERPCore/Controllers/SalesReportsController.cs +++ b/Backend/ERPCore/Controllers/SalesReportsController.cs @@ -11,39 +11,25 @@ public sealed class SalesReportsController : ApiControllerBase public SalesReportsController(ISalesReportService reports) => _reports = reports; - [HttpGet("daily-summary")] - [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] - public async Task>> DailySummary( - [FromQuery] DateOnly from, [FromQuery] DateOnly to, CancellationToken ct) - => Ok(await _reports.DailySummaryAsync(from, to, ct)); + [HttpGet] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + public ActionResult> ListReports() + => Ok(_reports.ListReports()); - [HttpGet("item-wise")] - [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] - public async Task>> ItemWise( - [FromQuery] DateOnly from, [FromQuery] DateOnly to, [FromQuery] int? itemId, [FromQuery] int? warehouseId, CancellationToken ct) - => Ok(await _reports.ItemSummaryAsync(from, to, itemId, warehouseId, ct)); + [HttpGet("{reportId}")] + [ProducesResponseType(typeof(SalesReportDefinitionDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetReport(string reportId) + { + var report = _reports.GetReport(reportId); + return report is null ? NotFound() : Ok(report); + } - [HttpGet("customer-wise")] - [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] - public async Task>> CustomerWise( - [FromQuery] DateOnly from, [FromQuery] DateOnly to, [FromQuery] int? customerId, CancellationToken ct) - => Ok(await _reports.CustomerSummaryAsync(from, to, customerId, ct)); - - [HttpGet("warehouse-wise")] - [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] - public async Task>> WarehouseWise( - [FromQuery] DateOnly from, [FromQuery] DateOnly to, [FromQuery] int? warehouseId, CancellationToken ct) - => Ok(await _reports.WarehouseSummaryAsync(from, to, warehouseId, ct)); - - [HttpGet("discount-summary")] - [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] - public async Task>> DiscountSummary( - [FromQuery] DateOnly from, [FromQuery] DateOnly to, CancellationToken ct) - => Ok(await _reports.DiscountSummaryAsync(from, to, ct)); - - [HttpGet("free-issue-summary")] - [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] - public async Task>> FreeIssueSummary( - [FromQuery] DateOnly from, [FromQuery] DateOnly to, CancellationToken ct) - => Ok(await _reports.FreeIssueSummaryAsync(from, to, ct)); + [HttpPost("query")] + [ProducesResponseType(typeof(SalesReportQueryResponse), StatusCodes.Status200OK)] + public async Task> Query([FromBody] SalesReportQueryRequest request, CancellationToken ct) + { + var rows = await _reports.QueryAsync(request.ReportType, request.From, request.To, request.ItemId, request.CustomerId, request.WarehouseId, ct); + return Ok(new SalesReportQueryResponse(request.ReportType, request.From, request.To, rows)); + } } diff --git a/Backend/ERPCore/Controllers/SalesSlipPromotionsController.cs b/Backend/ERPCore/Controllers/SalesSlipPromotionsController.cs new file mode 100644 index 0000000..f5874dc --- /dev/null +++ b/Backend/ERPCore/Controllers/SalesSlipPromotionsController.cs @@ -0,0 +1,22 @@ +using ERPCore.Dtos.Sales; +using ERPCore.Services.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace ERPCore.Controllers; + +[Route("api/v1/sales-slips/{salesSlipId:int}/free-issue-suggestions")] +public sealed class SalesSlipPromotionsController : ApiControllerBase +{ + private readonly ISalesPromotionSuggestionService _suggestions; + + public SalesSlipPromotionsController(ISalesPromotionSuggestionService suggestions) => _suggestions = suggestions; + + [HttpGet] + [ProducesResponseType(typeof(SalesFreeIssueSuggestionDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Get(int salesSlipId, CancellationToken ct) + { + var result = await _suggestions.GetFreeIssueSuggestionsAsync(salesSlipId, ct); + return result is null ? NotFound() : Ok(result); + } +} \ No newline at end of file diff --git a/Backend/ERPCore/Controllers/SalesSlipsController.cs b/Backend/ERPCore/Controllers/SalesSlipsController.cs index 37601c8..a59f93e 100644 --- a/Backend/ERPCore/Controllers/SalesSlipsController.cs +++ b/Backend/ERPCore/Controllers/SalesSlipsController.cs @@ -34,6 +34,12 @@ public sealed class SalesSlipsController : ApiControllerBase return Ok(result.Value); } + [HttpGet("{salesSlipId:int}/posting-check")] + [ProducesResponseType(typeof(SalesSlipPostingCheckDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> PostingCheck(int salesSlipId, CancellationToken ct) + => Ok(await _slips.CheckPostingAsync(salesSlipId, ct)); + [HttpPost] [ProducesResponseType(typeof(SalesSlipDto), StatusCodes.Status201Created)] public async Task> Create([FromBody] CreateSalesSlipRequest request, CancellationToken ct) diff --git a/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs b/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs index c6d8f79..6f26fb2 100644 --- a/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs +++ b/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs @@ -3,5 +3,5 @@ namespace ERPCore.Domain.Enums; public enum SalesDiscountMode { Percentage = 1, - FixedAmount = 2 + Amount = 2 } diff --git a/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs b/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs index 511d891..c541132 100644 --- a/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs +++ b/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs @@ -24,6 +24,14 @@ public sealed record SalesInvoiceSummaryDto( string CustomerSnapshotName, int WarehouseId, SalesInvoiceType InvoiceType, SalesInvoiceStatus Status, SalesInvoiceTotalsDto Totals, DateTime CreatedAt); +public sealed record SalesInvoicePostingIssueDto( + int SalesInvoiceLineId, int ItemId, string ItemSku, string ItemName, int WarehouseId, + decimal RequestedQty, decimal AvailableQty, decimal ShortQty, bool IsFreeIssue); + +public sealed record SalesInvoicePostingCheckDto( + int SalesInvoiceId, string InvoiceNo, SalesInvoiceStatus Status, bool CanPost, + IReadOnlyList Issues); + public sealed class CreateSalesInvoiceLineRequest { [Required] public int ItemId { get; set; } diff --git a/Backend/ERPCore/Dtos/Sales/SalesPromotionSuggestionDtos.cs b/Backend/ERPCore/Dtos/Sales/SalesPromotionSuggestionDtos.cs new file mode 100644 index 0000000..dffd1c8 --- /dev/null +++ b/Backend/ERPCore/Dtos/Sales/SalesPromotionSuggestionDtos.cs @@ -0,0 +1,23 @@ +namespace ERPCore.Dtos.Sales; + +public sealed record SalesFreeIssueRewardOptionDto( + int ItemId, + string Sku, + string Name, + decimal? SalePrice); + +public sealed record SalesFreeIssueSuggestionLineDto( + int SalesSlipLineId, + int ItemId, + string ItemSku, + string ItemName, + decimal Qty, + decimal SuggestedFreeQty, + decimal TriggerQty, + IReadOnlyList RewardOptions); + +public sealed record SalesFreeIssueSuggestionDto( + int SalesSlipId, + string SlipNo, + DateTime SlipDate, + IReadOnlyList Lines); \ No newline at end of file diff --git a/Backend/ERPCore/Dtos/Sales/SalesReportQueryDtos.cs b/Backend/ERPCore/Dtos/Sales/SalesReportQueryDtos.cs new file mode 100644 index 0000000..c900df0 --- /dev/null +++ b/Backend/ERPCore/Dtos/Sales/SalesReportQueryDtos.cs @@ -0,0 +1,21 @@ +namespace ERPCore.Dtos.Sales; + +public sealed record SalesReportDefinitionDto( + string Id, + string Name, + string Description, + IReadOnlyList SupportedFilters); + +public sealed record SalesReportQueryRequest( + string ReportType, + DateOnly From, + DateOnly To, + int? ItemId = null, + int? CustomerId = null, + int? WarehouseId = null); + +public sealed record SalesReportQueryResponse( + string ReportType, + DateOnly From, + DateOnly To, + IReadOnlyList Rows); diff --git a/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs b/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs index 72270ad..85ec177 100644 --- a/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs +++ b/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs @@ -23,6 +23,25 @@ public sealed record SalesSlipSummaryDto( string CustomerSnapshotName, int WarehouseId, SalesSlipStatus Status, SalesSlipTotalsDto Totals, DateTime CreatedAt); +public sealed record SalesSlipPostingIssueDto( + int SalesSlipLineId, int ItemId, string ItemSku, string ItemName, int WarehouseId, + decimal RequestedQty, decimal AvailableQty, decimal ShortQty, bool IsFreeIssue); + +public sealed record SalesSlipPostingCheckDto( + int SalesSlipId, string SlipNo, SalesSlipStatus Status, bool CanPost, + IReadOnlyList Issues); + +public sealed record FreeIssueSummaryDto( + int SalesSlipId, string SlipNo, SalesSlipStatus Status, DateTime CreatedAt, + int WarehouseId, string WarehouseName, int ItemId, string ItemSku, string ItemName, + int UomId, string UomName, decimal Qty, decimal FreeQty, string SchemeLabel); + +public sealed record FreeIssueDto( + int SalesSlipId, string SlipNo, DateTime SlipDate, SalesSlipStatus Status, + int CustomerId, string CustomerSnapshotName, int WarehouseId, string WarehouseName, + int CashierUserId, DateTime CreatedAt, DateTime? UpdatedAt, FreeIssueSummaryDto Summary, + IReadOnlyList Lines); + public sealed class CreateSalesSlipLineRequest { [Required] public int ItemId { get; set; } diff --git a/Backend/ERPCore/Services/Interfaces/ISalesInvoiceService.cs b/Backend/ERPCore/Services/Interfaces/ISalesInvoiceService.cs index a0a987b..c54c918 100644 --- a/Backend/ERPCore/Services/Interfaces/ISalesInvoiceService.cs +++ b/Backend/ERPCore/Services/Interfaces/ISalesInvoiceService.cs @@ -9,6 +9,7 @@ public interface ISalesInvoiceService { Task> ListAsync(PageQuery query, SalesInvoiceStatus? status, int? customerId, int? warehouseId, CancellationToken ct = default); Task?> GetAsync(int salesInvoiceId, CancellationToken ct = default); + Task CheckPostingAsync(int salesInvoiceId, CancellationToken ct = default); Task> CreateAsync(CreateSalesInvoiceRequest request, CancellationToken ct = default); Task> UpdateAsync(int salesInvoiceId, UpdateSalesInvoiceRequest request, uint expectedRowVersion, CancellationToken ct = default); Task PostAsync(int salesInvoiceId, CancellationToken ct = default); diff --git a/Backend/ERPCore/Services/Interfaces/ISalesPromotionSuggestionService.cs b/Backend/ERPCore/Services/Interfaces/ISalesPromotionSuggestionService.cs new file mode 100644 index 0000000..97158ec --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/ISalesPromotionSuggestionService.cs @@ -0,0 +1,8 @@ +using ERPCore.Dtos.Sales; + +namespace ERPCore.Services.Interfaces; + +public interface ISalesPromotionSuggestionService +{ + Task GetFreeIssueSuggestionsAsync(int salesSlipId, CancellationToken ct = default); +} \ No newline at end of file diff --git a/Backend/ERPCore/Services/Interfaces/ISalesReportService.cs b/Backend/ERPCore/Services/Interfaces/ISalesReportService.cs index 6e6e591..32fa43d 100644 --- a/Backend/ERPCore/Services/Interfaces/ISalesReportService.cs +++ b/Backend/ERPCore/Services/Interfaces/ISalesReportService.cs @@ -4,6 +4,9 @@ namespace ERPCore.Services.Interfaces; public interface ISalesReportService { + IReadOnlyList ListReports(); + SalesReportDefinitionDto? GetReport(string reportId); + Task> QueryAsync(string reportType, DateOnly from, DateOnly to, int? itemId, int? customerId, int? warehouseId, CancellationToken ct = default); Task> DailySummaryAsync(DateOnly from, DateOnly to, CancellationToken ct = default); Task> ItemSummaryAsync(DateOnly from, DateOnly to, int? itemId, int? warehouseId, CancellationToken ct = default); Task> CustomerSummaryAsync(DateOnly from, DateOnly to, int? customerId, CancellationToken ct = default); diff --git a/Backend/ERPCore/Services/Interfaces/ISalesSlipService.cs b/Backend/ERPCore/Services/Interfaces/ISalesSlipService.cs index bd94715..6411b0f 100644 --- a/Backend/ERPCore/Services/Interfaces/ISalesSlipService.cs +++ b/Backend/ERPCore/Services/Interfaces/ISalesSlipService.cs @@ -9,6 +9,9 @@ public interface ISalesSlipService { Task> ListAsync(PageQuery query, SalesSlipStatus? status, int? customerId, int? warehouseId, CancellationToken ct = default); Task?> GetAsync(int salesSlipId, CancellationToken ct = default); + Task> ListFreeIssuesAsync(PageQuery query, SalesSlipStatus? status, int? customerId, int? warehouseId, CancellationToken ct = default); + Task?> GetFreeIssueAsync(int salesSlipId, CancellationToken ct = default); + Task CheckPostingAsync(int salesSlipId, CancellationToken ct = default); Task> CreateAsync(CreateSalesSlipRequest request, CancellationToken ct = default); Task> UpdateAsync(int salesSlipId, UpdateSalesSlipRequest request, uint expectedRowVersion, CancellationToken ct = default); Task PostAsync(int salesSlipId, CancellationToken ct = default); diff --git a/Backend/ERPCore/Services/SalesInvoiceService.cs b/Backend/ERPCore/Services/SalesInvoiceService.cs index 5f06694..4052aae 100644 --- a/Backend/ERPCore/Services/SalesInvoiceService.cs +++ b/Backend/ERPCore/Services/SalesInvoiceService.cs @@ -69,6 +69,41 @@ public sealed class SalesInvoiceService : ISalesInvoiceService return invoice is null ? null : new ETagged(Map(invoice), invoice.RowVersion); } + public async Task CheckPostingAsync(int salesInvoiceId, CancellationToken ct = default) + { + var invoice = await _invoices.Query().AsNoTracking().Include(x => x.Lines) + .FirstOrDefaultAsync(x => x.SalesInvoiceId == salesInvoiceId, ct) + ?? throw new NotFoundException($"Sales invoice {salesInvoiceId} was not found."); + + if (invoice.Status != SalesInvoiceStatus.Draft) + return new SalesInvoicePostingCheckDto(invoice.SalesInvoiceId, invoice.InvoiceNo, invoice.Status, false, Array.Empty()); + + var issues = new List(); + foreach (var line in invoice.Lines) + { + var requestedQty = line.Qty + line.FreeQty; + var available = await _fifo.GetOnHandAsync(line.ItemId, line.WarehouseId, ct); + if (available >= requestedQty) continue; + + var item = await _items.Query().AsNoTracking() + .Where(x => x.ItemId == line.ItemId) + .Select(x => new { x.Sku, x.Name }) + .FirstAsync(ct); + issues.Add(new SalesInvoicePostingIssueDto( + line.SalesInvoiceLineId, + line.ItemId, + item.Sku, + item.Name, + line.WarehouseId, + requestedQty, + available, + requestedQty - available, + line.IsFreeIssue)); + } + + return new SalesInvoicePostingCheckDto(invoice.SalesInvoiceId, invoice.InvoiceNo, invoice.Status, issues.Count == 0, issues); + } + public async Task> CreateAsync(CreateSalesInvoiceRequest request, CancellationToken ct = default) { await ValidateReferencesAsync(request.CustomerId, request.WarehouseId, request.Lines, ct); @@ -228,7 +263,7 @@ public sealed class SalesInvoiceService : ISalesInvoiceService private static decimal CalculateDiscount(decimal gross, SalesDiscountMode mode, decimal discountPct, decimal discountValue, decimal legacyDiscountAmount) { - var computed = mode == SalesDiscountMode.FixedAmount + var computed = mode == SalesDiscountMode.Amount ? discountValue : gross * (discountPct / 100m); diff --git a/Backend/ERPCore/Services/SalesPromotionSuggestionService.cs b/Backend/ERPCore/Services/SalesPromotionSuggestionService.cs new file mode 100644 index 0000000..bdec4a0 --- /dev/null +++ b/Backend/ERPCore/Services/SalesPromotionSuggestionService.cs @@ -0,0 +1,74 @@ +using ERPCore.Domain.Entities; +using ERPCore.Domain.Enums; +using ERPCore.Dtos.Sales; +using ERPCore.Repositories.Interfaces; +using ERPCore.Services.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace ERPCore.Services; + +public sealed class SalesPromotionSuggestionService : ISalesPromotionSuggestionService +{ + private const decimal FreeIssueThreshold = 10m; + + private readonly IRepository _slips; + private readonly IRepository _items; + + public SalesPromotionSuggestionService(IRepository slips, IRepository items) + { + _slips = slips; + _items = items; + } + + public async Task GetFreeIssueSuggestionsAsync(int salesSlipId, CancellationToken ct = default) + { + var slip = await _slips.Query().AsNoTracking().Include(x => x.Lines) + .FirstOrDefaultAsync(x => x.SalesSlipId == salesSlipId, ct); + + if (slip is null) return null; + + var itemIds = slip.Lines.Select(x => x.ItemId).Distinct().ToList(); + var candidateItems = await _items.Query().AsNoTracking() + .Where(x => itemIds.Contains(x.ItemId) && x.Status == EntityStatus.Active) + .ToListAsync(ct); + + var byItemId = candidateItems.ToDictionary(x => x.ItemId); + var suggestions = new List(); + + foreach (var line in slip.Lines.Where(x => x.Qty >= FreeIssueThreshold)) + { + if (!byItemId.TryGetValue(line.ItemId, out var item)) continue; + + var freeQty = Math.Floor(line.Qty / FreeIssueThreshold); + if (freeQty <= 0m) continue; + + var rewardOptions = new List + { + new(item.ItemId, item.Sku, item.Name, item.SalePrice) + }; + + var alternates = await _items.Query().AsNoTracking() + .Where(x => x.Status == EntityStatus.Active && x.CategoryId == item.CategoryId && x.ItemId != item.ItemId) + .OrderBy(x => x.Name) + .Take(3) + .Select(x => new SalesFreeIssueRewardOptionDto(x.ItemId, x.Sku, x.Name, x.SalePrice)) + .ToListAsync(ct); + + rewardOptions.AddRange(alternates.Where(x => rewardOptions.All(r => r.ItemId != x.ItemId))); + + suggestions.Add(new SalesFreeIssueSuggestionLineDto( + line.SalesSlipLineId, + item.ItemId, + item.Sku, + item.Name, + line.Qty, + freeQty, + FreeIssueThreshold, + rewardOptions)); + } + + return suggestions.Count == 0 + ? new SalesFreeIssueSuggestionDto(slip.SalesSlipId, slip.SlipNo, slip.SlipDate, Array.Empty()) + : new SalesFreeIssueSuggestionDto(slip.SalesSlipId, slip.SlipNo, slip.SlipDate, suggestions); + } +} \ No newline at end of file diff --git a/Backend/ERPCore/Services/SalesReportService.cs b/Backend/ERPCore/Services/SalesReportService.cs index 77e5d08..e931fba 100644 --- a/Backend/ERPCore/Services/SalesReportService.cs +++ b/Backend/ERPCore/Services/SalesReportService.cs @@ -3,12 +3,23 @@ using ERPCore.Domain.Enums; using ERPCore.Dtos.Sales; using ERPCore.Repositories.Interfaces; using ERPCore.Services.Interfaces; +using ERPCore.System.Errors; using Microsoft.EntityFrameworkCore; namespace ERPCore.Services; public sealed class SalesReportService : ISalesReportService { + private static readonly SalesReportDefinitionDto[] ReportDefinitions = + [ + new("daily-summary", "Daily Summary", "Aggregated sales by day across posted invoices and slips.", ["from", "to"]), + new("item-summary", "Item Summary", "Aggregated sales by item across posted invoices and slips.", ["from", "to", "itemId", "warehouseId"]), + new("customer-summary", "Customer Summary", "Aggregated sales by customer across posted invoices and slips.", ["from", "to", "customerId"]), + new("warehouse-summary", "Warehouse Summary", "Aggregated sales by warehouse across posted invoices and slips.", ["from", "to", "warehouseId"]), + new("discount-summary", "Discount Summary", "Documents with discounts applied.", ["from", "to"]), + new("free-issue-summary", "Free Issue Summary", "Lines with free quantities issued.", ["from", "to"]) + ]; + private readonly IRepository _invoices; private readonly IRepository _slips; @@ -18,6 +29,54 @@ public sealed class SalesReportService : ISalesReportService _slips = slips; } + public IReadOnlyList ListReports() => ReportDefinitions; + + public SalesReportDefinitionDto? GetReport(string reportId) + { + var normalized = reportId.Trim().ToLowerInvariant(); + return ReportDefinitions.FirstOrDefault(r => r.Id == normalized); + } + + public async Task> QueryAsync(string reportType, DateOnly from, DateOnly to, int? itemId, int? customerId, int? warehouseId, CancellationToken ct = default) + { + var normalized = reportType.Trim().ToLowerInvariant(); + ValidateFilters(normalized, itemId, customerId, warehouseId); + + return normalized switch + { + "daily" or "daily-summary" => (await DailySummaryAsync(from, to, ct)).Cast().ToList(), + "item" or "item-summary" or "item-wise" => (await ItemSummaryAsync(from, to, itemId, warehouseId, ct)).Cast().ToList(), + "customer" or "customer-summary" or "customer-wise" => (await CustomerSummaryAsync(from, to, customerId, ct)).Cast().ToList(), + "warehouse" or "warehouse-summary" or "warehouse-wise" => (await WarehouseSummaryAsync(from, to, warehouseId, ct)).Cast().ToList(), + "discount" or "discount-summary" => (await DiscountSummaryAsync(from, to, ct)).Cast().ToList(), + "free-issue" or "free-issue-summary" => (await FreeIssueSummaryAsync(from, to, ct)).Cast().ToList(), + _ => throw new DomainException("INVALID_REPORT_TYPE", $"Unsupported sales report type '{reportType}'.", 400) + }; + } + + private static void ValidateFilters(string reportType, int? itemId, int? customerId, int? warehouseId) + { + var allowed = reportType switch + { + "daily" or "daily-summary" => new FilterSet(false, false, false), + "item" or "item-summary" or "item-wise" => new FilterSet(true, false, true), + "customer" or "customer-summary" or "customer-wise" => new FilterSet(false, true, false), + "warehouse" or "warehouse-summary" or "warehouse-wise" => new FilterSet(false, false, true), + "discount" or "discount-summary" => new FilterSet(false, false, false), + "free-issue" or "free-issue-summary" => new FilterSet(false, false, false), + _ => throw new DomainException("INVALID_REPORT_TYPE", $"Unsupported sales report type '{reportType}'.", 400) + }; + + if (!allowed.Item && itemId is not null) + throw new DomainException("INVALID_REPORT_FILTER", $"Filter 'itemId' is not valid for report type '{reportType}'.", 400); + if (!allowed.Customer && customerId is not null) + throw new DomainException("INVALID_REPORT_FILTER", $"Filter 'customerId' is not valid for report type '{reportType}'.", 400); + if (!allowed.Warehouse && warehouseId is not null) + throw new DomainException("INVALID_REPORT_FILTER", $"Filter 'warehouseId' is not valid for report type '{reportType}'.", 400); + } + + private readonly record struct FilterSet(bool Item, bool Customer, bool Warehouse); + public async Task> DailySummaryAsync(DateOnly from, DateOnly to, CancellationToken ct = default) { var invoiceRows = await _invoices.Query().AsNoTracking() @@ -72,42 +131,51 @@ public sealed class SalesReportService : ISalesReportService public async Task> ItemSummaryAsync(DateOnly from, DateOnly to, int? itemId, int? warehouseId, CancellationToken ct = default) { - var invoiceLines = _invoices.Query().AsNoTracking() - .Where(x => x.Status == SalesInvoiceStatus.Posted && x.InvoiceDate >= from.ToDateTime(TimeOnly.MinValue) && x.InvoiceDate < to.AddDays(1).ToDateTime(TimeOnly.MinValue)); - if (warehouseId is not null) invoiceLines = invoiceLines.Where(x => x.WarehouseId == warehouseId); - var invoiceQuery = invoiceLines.SelectMany(x => x.Lines.Select(l => new - { - l.ItemId, - l.Description, - l.Qty, - l.FreeQty, - Gross = l.Qty * l.UnitPrice, - l.DiscountAmount, - l.TaxAmount, - l.LineTotal, - l.WarehouseId - })); - if (itemId is not null) invoiceQuery = invoiceQuery.Where(x => x.ItemId == itemId); + var invoiceRows = await _invoices.Query().AsNoTracking() + .Where(x => x.Status == SalesInvoiceStatus.Posted && x.InvoiceDate >= from.ToDateTime(TimeOnly.MinValue) && x.InvoiceDate < to.AddDays(1).ToDateTime(TimeOnly.MinValue)) + .SelectMany(x => x.Lines.Select(l => new + { + l.ItemId, + l.Description, + l.Qty, + l.FreeQty, + Gross = l.Qty * l.UnitPrice, + l.DiscountAmount, + l.TaxAmount, + l.LineTotal, + x.WarehouseId + })) + .ToListAsync(ct); - var slipLines = _slips.Query().AsNoTracking() - .Where(x => x.Status == SalesSlipStatus.Posted && x.SlipDate >= from.ToDateTime(TimeOnly.MinValue) && x.SlipDate < to.AddDays(1).ToDateTime(TimeOnly.MinValue)); - if (warehouseId is not null) slipLines = slipLines.Where(x => x.WarehouseId == warehouseId); - var slipQuery = slipLines.SelectMany(x => x.Lines.Select(l => new - { - l.ItemId, - l.Description, - l.Qty, - l.FreeQty, - Gross = l.Qty * l.UnitPrice, - l.DiscountAmount, - l.TaxAmount, - l.LineTotal, - l.WarehouseId - })); - if (itemId is not null) slipQuery = slipQuery.Where(x => x.ItemId == itemId); + if (warehouseId is not null) + invoiceRows = invoiceRows.Where(x => x.WarehouseId == warehouseId).ToList(); + if (itemId is not null) + invoiceRows = invoiceRows.Where(x => x.ItemId == itemId).ToList(); - var rows = await invoiceQuery.Concat(slipQuery) + var slipRows = await _slips.Query().AsNoTracking() + .Where(x => x.Status == SalesSlipStatus.Posted && x.SlipDate >= from.ToDateTime(TimeOnly.MinValue) && x.SlipDate < to.AddDays(1).ToDateTime(TimeOnly.MinValue)) + .SelectMany(x => x.Lines.Select(l => new + { + l.ItemId, + l.Description, + l.Qty, + l.FreeQty, + Gross = l.Qty * l.UnitPrice, + l.DiscountAmount, + l.TaxAmount, + l.LineTotal, + x.WarehouseId + })) + .ToListAsync(ct); + + if (warehouseId is not null) + slipRows = slipRows.Where(x => x.WarehouseId == warehouseId).ToList(); + if (itemId is not null) + slipRows = slipRows.Where(x => x.ItemId == itemId).ToList(); + + return invoiceRows.Concat(slipRows) .GroupBy(x => new { x.ItemId, x.Description }) + .OrderByDescending(g => g.Sum(x => x.LineTotal) + g.Sum(x => x.TaxAmount)) .Select(g => new SalesItemSummaryRowDto( g.Key.ItemId, g.Key.Description, @@ -116,11 +184,8 @@ public sealed class SalesReportService : ISalesReportService g.Sum(x => x.Gross), g.Sum(x => x.DiscountAmount), g.Sum(x => x.TaxAmount), - g.Sum(x => x.LineTotal + x.TaxAmount))) - .OrderByDescending(x => x.NetAmount) - .ToListAsync(ct); - - return rows; + g.Sum(x => x.LineTotal) + g.Sum(x => x.TaxAmount))) + .ToList(); } public async Task> CustomerSummaryAsync(DateOnly from, DateOnly to, int? customerId, CancellationToken ct = default) diff --git a/Backend/ERPCore/Services/SalesSlipService.cs b/Backend/ERPCore/Services/SalesSlipService.cs index 1fa58a2..3d79bdb 100644 --- a/Backend/ERPCore/Services/SalesSlipService.cs +++ b/Backend/ERPCore/Services/SalesSlipService.cs @@ -70,6 +70,66 @@ public sealed class SalesSlipService : ISalesSlipService return slip is null ? null : new ETagged(Map(slip), slip.RowVersion); } + public async Task> ListFreeIssuesAsync(PageQuery query, SalesSlipStatus? status, int? customerId, int? warehouseId, CancellationToken ct = default) + { + IQueryable q = _slips.Query().AsNoTracking().Include(x => x.Lines); + q = q.Where(x => x.Lines.Any(l => l.IsFreeIssue || l.FreeQty > 0m)); + if (!string.IsNullOrWhiteSpace(query.Q)) + { + var term = query.Q.Trim(); + q = q.Where(x => EF.Functions.ILike(x.SlipNo, $"%{term}%") || EF.Functions.ILike(x.CustomerSnapshotName, $"%{term}%")); + } + if (status is not null) q = q.Where(x => x.Status == status); + if (customerId is not null) q = q.Where(x => x.CustomerId == customerId); + if (warehouseId is not null) q = q.Where(x => x.WarehouseId == warehouseId); + + var total = await q.CountAsync(ct); + var rows = await q.OrderByDescending(x => x.SalesSlipId).Skip(query.Skip).Take(query.PageSize).ToListAsync(ct); + return PagedResponse.Create(rows.Select(MapFreeIssueSummary).ToList(), query.Page, query.PageSize, total); + } + + public async Task?> GetFreeIssueAsync(int salesSlipId, CancellationToken ct = default) + { + var slip = await _slips.Query().AsNoTracking().Include(x => x.Lines) + .FirstOrDefaultAsync(x => x.SalesSlipId == salesSlipId, ct); + return slip is null ? null : new ETagged(MapFreeIssue(slip), slip.RowVersion); + } + + public async Task CheckPostingAsync(int salesSlipId, CancellationToken ct = default) + { + var slip = await _slips.Query().AsNoTracking().Include(x => x.Lines) + .FirstOrDefaultAsync(x => x.SalesSlipId == salesSlipId, ct) + ?? throw new NotFoundException($"Sales slip {salesSlipId} was not found."); + + if (slip.Status != SalesSlipStatus.Draft) + return new SalesSlipPostingCheckDto(slip.SalesSlipId, slip.SlipNo, slip.Status, false, Array.Empty()); + + var issues = new List(); + foreach (var line in slip.Lines) + { + var requestedQty = line.Qty + line.FreeQty; + var available = await _fifo.GetOnHandAsync(line.ItemId, line.WarehouseId, ct); + if (available >= requestedQty) continue; + + var item = await _items.Query().AsNoTracking() + .Where(x => x.ItemId == line.ItemId) + .Select(x => new { x.Sku, x.Name }) + .FirstAsync(ct); + issues.Add(new SalesSlipPostingIssueDto( + line.SalesSlipLineId, + line.ItemId, + item.Sku, + item.Name, + line.WarehouseId, + requestedQty, + available, + requestedQty - available, + line.IsFreeIssue)); + } + + return new SalesSlipPostingCheckDto(slip.SalesSlipId, slip.SlipNo, slip.Status, issues.Count == 0, issues); + } + public async Task> CreateAsync(CreateSalesSlipRequest request, CancellationToken ct = default) { await ValidateReferencesAsync(request.CustomerId, request.WarehouseId, request.CashierUserId, request.Lines, ct); @@ -227,7 +287,7 @@ public sealed class SalesSlipService : ISalesSlipService private static decimal CalculateDiscount(decimal gross, SalesDiscountMode mode, decimal discountPct, decimal discountValue, decimal legacyDiscountAmount) { - var computed = mode == SalesDiscountMode.FixedAmount + var computed = mode == SalesDiscountMode.Amount ? discountValue : gross * (discountPct / 100m); @@ -241,6 +301,57 @@ public sealed class SalesSlipService : ISalesSlipService x.SalesSlipId, x.SlipNo, x.SlipDate, x.CustomerId, x.CustomerSnapshotName, x.WarehouseId, x.Status, new SalesSlipTotalsDto(x.Subtotal, x.DiscountTotal, x.Lines.Sum(l => l.FreeQty), x.TaxTotal, x.GrandTotal, x.PaidAmount, x.BalanceAmount), x.CreatedAt); + private FreeIssueSummaryDto MapFreeIssueSummary(SalesSlip x) + { + var line = x.Lines.FirstOrDefault(); + var item = line is null ? null : _items.Query().AsNoTracking() + .Where(i => i.ItemId == line.ItemId) + .Select(i => new { i.ItemId, i.Sku, i.Name, i.BaseUomId }) + .FirstOrDefault(); + var uom = line is null ? null : _uoms.Query().AsNoTracking() + .Where(u => u.UomId == line.UomId) + .Select(u => new { u.UomId, u.Name }) + .FirstOrDefault(); + var warehouse = _warehouses.Query().AsNoTracking() + .Where(w => w.WarehouseId == x.WarehouseId) + .Select(w => new { w.WarehouseId, w.Name }) + .FirstOrDefault(); + return new FreeIssueSummaryDto( + x.SalesSlipId, + x.SlipNo, + x.Status, + x.CreatedAt, + x.WarehouseId, + warehouse?.Name ?? $"Warehouse {x.WarehouseId}", + line?.ItemId ?? 0, + item?.Sku ?? $"SKU-{line?.ItemId ?? 0}", + item?.Name ?? line?.Description ?? "—", + line?.UomId ?? 0, + uom?.Name ?? $"UOM {line?.UomId ?? 0}", + line?.Qty ?? 0m, + line?.FreeQty ?? 0m, + line is null ? "No line" : $"Buy {line.Qty} Get {line.FreeQty}"); + } + + private FreeIssueDto MapFreeIssue(SalesSlip x) + { + var summary = MapFreeIssueSummary(x); + return new FreeIssueDto( + x.SalesSlipId, + x.SlipNo, + x.SlipDate, + x.Status, + x.CustomerId, + x.CustomerSnapshotName, + x.WarehouseId, + summary.WarehouseName, + x.CashierUserId, + x.CreatedAt, + x.UpdatedAt, + summary, + x.Lines.Select(l => new SalesSlipLineDto(l.SalesSlipLineId, l.ItemId, l.Description, l.Qty, l.FreeQty, l.UomId, l.WarehouseId, l.UnitPrice, l.BaseCost, l.PriceSource, l.DiscountPct, l.DiscountAmount, l.DiscountMode, l.NetUnitPrice, l.LineTotal, l.TaxPct, l.TaxAmount, l.IsFreeIssue, l.ParentLineId)).ToList()); + } + private static SalesSlipDto Map(SalesSlip x) => new( x.SalesSlipId, x.SlipNo, x.SlipDate, x.CustomerId, x.CustomerSnapshotName, x.WarehouseId, x.CashierUserId, x.Status, x.CreatedAt, x.UpdatedAt, new SalesSlipTotalsDto(x.Subtotal, x.DiscountTotal, x.Lines.Sum(l => l.FreeQty), x.TaxTotal, x.GrandTotal, x.PaidAmount, x.BalanceAmount), From 266a2a2c14ee37ff690fef123de248a655d9768e Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Sun, 2 Aug 2026 01:25:17 +0530 Subject: [PATCH 5/5] implement fe with backend --- .../ERPCore/Infra/Persistence/DataSeeder.cs | 542 ++++++++++++++++ .../ERPCore/Infra/Persistence/ErpDbContext.cs | 35 +- Backend/ERPCore/Program.cs | 15 +- .../Interfaces/ICompanyProfileService.cs | 10 + Backend/PROGRESS.md | 5 + Frontend/PROGRESS.md | 7 + .../dashboard/sales/free-issues/new/page.tsx | 493 ++++++++++++++ .../app/dashboard/sales/free-issues/page.tsx | 3 + .../dashboard/sales/invoices/[id]/page.tsx | 605 ++++++++++++++++++ .../sales/invoices/[id]/print/page.tsx | 218 +++++++ .../app/dashboard/sales/invoices/new/page.tsx | 490 ++++++++++++++ .../app/dashboard/sales/invoices/page.tsx | 233 +++++++ .../erp-system/app/dashboard/sales/page.tsx | 73 +++ .../sales/reports/[reportId]/page.tsx | 242 +++++++ .../app/dashboard/sales/reports/page.tsx | 81 +++ .../app/dashboard/sales/slips/[id]/page.tsx | 456 +++++++++++++ .../app/dashboard/sales/slips/new/page.tsx | 440 +++++++++++++ .../app/dashboard/sales/slips/page.tsx | 229 +++++++ .../settings/company-profile/page.tsx | 135 ++++ .../app/dashboard/settings/page.tsx | 8 +- Frontend/erp-system/app/globals.css | 31 + Frontend/erp-system/app/layout.tsx | 12 +- Frontend/erp-system/app/login/page.tsx | 7 + .../components/Layouts/AppSidebar.tsx | 19 +- .../erp-system/components/Layouts/Header.tsx | 1 + .../dashboard/recent-orders-table.tsx | 4 +- Frontend/erp-system/components/providers.tsx | 13 + .../sales/FreeIssuePromotionSuggestions.tsx | 65 ++ .../components/sales/FreeIssueSuggestions.tsx | 42 ++ Frontend/erp-system/lib/api/customers.ts | 15 + Frontend/erp-system/lib/api/sales.ts | 139 ++++ Frontend/erp-system/lib/sales-line-utils.ts | 6 + Frontend/erp-system/types/customers.ts | 24 + Frontend/erp-system/types/sales.ts | 314 +++++++++ docs/00-CORE.md | 4 + docs/01-DOC-GUIDE.md | 3 +- docs/14-BACKEND-SALES-API.md | 224 +++++++ docs/SALES_MODULE_PLAN.md | 18 +- 38 files changed, 5229 insertions(+), 32 deletions(-) create mode 100644 Backend/ERPCore/Services/Interfaces/ICompanyProfileService.cs create mode 100644 Frontend/erp-system/app/dashboard/sales/free-issues/new/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/free-issues/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/invoices/[id]/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/invoices/[id]/print/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/invoices/new/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/invoices/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/reports/[reportId]/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/reports/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/slips/[id]/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/slips/new/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/sales/slips/page.tsx create mode 100644 Frontend/erp-system/app/dashboard/settings/company-profile/page.tsx create mode 100644 Frontend/erp-system/components/providers.tsx create mode 100644 Frontend/erp-system/components/sales/FreeIssuePromotionSuggestions.tsx create mode 100644 Frontend/erp-system/components/sales/FreeIssueSuggestions.tsx create mode 100644 Frontend/erp-system/lib/api/customers.ts create mode 100644 Frontend/erp-system/lib/api/sales.ts create mode 100644 Frontend/erp-system/lib/sales-line-utils.ts create mode 100644 Frontend/erp-system/types/customers.ts create mode 100644 Frontend/erp-system/types/sales.ts create mode 100644 docs/14-BACKEND-SALES-API.md diff --git a/Backend/ERPCore/Infra/Persistence/DataSeeder.cs b/Backend/ERPCore/Infra/Persistence/DataSeeder.cs index 8972da9..af1ecb5 100644 --- a/Backend/ERPCore/Infra/Persistence/DataSeeder.cs +++ b/Backend/ERPCore/Infra/Persistence/DataSeeder.cs @@ -1,4 +1,5 @@ using ERPCore.Domain.Entities; +using ERPCore.Domain; using ERPCore.Domain.Enums; using Microsoft.EntityFrameworkCore; @@ -42,7 +43,11 @@ public static class DataSeeder { var dirty = await SeedReasonCodesAsync(db, ct); dirty |= await SeedItemTypesAsync(db, ct); + dirty |= await SeedCompanyProfileAsync(db, ct); dirty |= await SeedProductConfigAsync(db, ct); + dirty |= await SeedSalesMastersAsync(db, ct); + dirty |= await SeedSalesStockAsync(db, ct); + dirty |= await SeedSalesAsync(db, ct); if (dirty) await db.SaveChangesAsync(ct); } @@ -99,4 +104,541 @@ public static class DataSeeder }); return true; } + + /// + /// Seeds a printable company profile with reasonable defaults for invoice headers. + /// These values are intentionally editable later through the API. + /// + private static async Task SeedCompanyProfileAsync(ErpDbContext db, CancellationToken ct) + { + if (await db.CompanyProfiles.AnyAsync(c => c.CompanyProfileId == CompanyProfile.SingletonId, ct)) return false; + + db.CompanyProfiles.Add(new CompanyProfile + { + CompanyProfileId = CompanyProfile.SingletonId, + LegalName = "ERP Core Trading (Pvt) Ltd", + TradeName = "ERP Core Trading", + TaxRegistrationNo = "TAX-DEFAULT-001", + VatRegistrationNo = "VAT-DEFAULT-001", + AddressLine1 = "1 Demo Street", + City = "Colombo", + Country = "Sri Lanka", + Phone = "+94 11 000 0000", + Email = "accounts@example.com", + BankName = "Demo Bank", + BankBranch = "Colombo Main", + AccountName = "ERP Core Trading (Pvt) Ltd", + AccountNumber = "000123456789", + SwiftCode = "DEMO1234", + FooterNote = "Thank you for your business." + }); + return true; + } + + /// + /// Seeds the minimum catalog data required for the sales demo rows to exist. + /// These are safe additive rows and do not alter any existing data. + /// + private static async Task SeedSalesMastersAsync(ErpDbContext db, CancellationToken ct) + { + var dirty = false; + + dirty |= await SeedWarehousesAsync(db, ct); + dirty |= await SeedUomsAsync(db, ct); + dirty |= await SeedCategoriesAsync(db, ct); + dirty |= await SeedItemsAsync(db, ct); + + return dirty; + } + + /// + /// Seeds a simple on-hand FIFO layer for the demo sales item so the sample + /// invoices can be posted without immediately failing stock validation. + /// This keeps the stock-check and posting flows testable on a fresh database. + /// + private static async Task SeedSalesStockAsync(ErpDbContext db, CancellationToken ct) + { + var warehouse = await db.Warehouses.AsNoTracking() + .OrderBy(w => w.WarehouseId) + .FirstOrDefaultAsync(ct); + var item = await db.Items.AsNoTracking() + .OrderBy(i => i.ItemId) + .FirstOrDefaultAsync(ct); + var secondItem = await db.Items.AsNoTracking() + .OrderByDescending(i => i.ItemId) + .FirstOrDefaultAsync(ct); + + if (warehouse is null || item is null || secondItem is null) + return false; + + var existing = await db.StockLayers.AnyAsync( + l => (l.ItemId == item.ItemId || l.ItemId == secondItem.ItemId) && l.WarehouseId == warehouse.WarehouseId && l.QtyRemaining > 0m, + ct); + if (existing) return false; + + db.StockLayers.Add(new StockLayer + { + ItemId = item.ItemId, + WarehouseId = warehouse.WarehouseId, + QtyReceived = 100m, + QtyRemaining = 100m, + UnitCost = item.SalePrice.GetValueOrDefault() > 0m ? item.SalePrice.GetValueOrDefault() / 2m : 25m, + ReceiptDate = DateTime.UtcNow.AddDays(-7) + }); + db.StockLayers.Add(new StockLayer + { + ItemId = secondItem.ItemId, + WarehouseId = warehouse.WarehouseId, + QtyReceived = 5m, + QtyRemaining = 5m, + UnitCost = secondItem.SalePrice.GetValueOrDefault() > 0m ? secondItem.SalePrice.GetValueOrDefault() / 2m : 15m, + ReceiptDate = DateTime.UtcNow.AddDays(-6) + }); + return true; + } + + private static async Task SeedWarehousesAsync(ErpDbContext db, CancellationToken ct) + { + var existingCodes = await db.Warehouses.Select(w => w.Code).ToListAsync(ct); + var have = existingCodes.ToHashSet(StringComparer.OrdinalIgnoreCase); + + var seeds = new[] + { + new Warehouse { Code = "MAIN", Name = "Main Warehouse" }, + new Warehouse { Code = "SHOP", Name = "Sales Counter" } + }; + + var toAdd = seeds.Where(w => !have.Contains(w.Code)).ToList(); + if (toAdd.Count == 0) return false; + + db.Warehouses.AddRange(toAdd); + return true; + } + + private static async Task SeedUomsAsync(ErpDbContext db, CancellationToken ct) + { + var existingNames = await db.Uoms.Select(u => u.Name).ToListAsync(ct); + var have = existingNames.ToHashSet(StringComparer.OrdinalIgnoreCase); + + var seeds = new[] + { + new Uom { Name = "PCS" }, + new Uom { Name = "BOX" } + }; + + var toAdd = seeds.Where(u => !have.Contains(u.Name)).ToList(); + if (toAdd.Count == 0) return false; + + db.Uoms.AddRange(toAdd); + return true; + } + + private static async Task SeedCategoriesAsync(ErpDbContext db, CancellationToken ct) + { + var existingNames = await db.Categories.Select(c => c.Name).ToListAsync(ct); + var have = existingNames.ToHashSet(StringComparer.OrdinalIgnoreCase); + + var seeds = new[] + { + new Category { Name = "General Goods", Status = EntityStatus.Active, CreatedAt = DateTime.UtcNow }, + new Category { Name = "Accessories", Status = EntityStatus.Active, CreatedAt = DateTime.UtcNow } + }; + + var toAdd = seeds.Where(c => !have.Contains(c.Name)).ToList(); + if (toAdd.Count == 0) return false; + + db.Categories.AddRange(toAdd); + return true; + } + + private static async Task SeedItemsAsync(ErpDbContext db, CancellationToken ct) + { + var existingSkus = await db.Items.Select(i => i.Sku).ToListAsync(ct); + var have = existingSkus.ToHashSet(StringComparer.OrdinalIgnoreCase); + + var category = await db.Categories.AsNoTracking() + .OrderBy(c => c.CategoryId) + .FirstOrDefaultAsync(ct); + var uom = await db.Uoms.AsNoTracking() + .OrderBy(u => u.UomId) + .FirstOrDefaultAsync(ct); + + if (category is null || uom is null) + return false; + + var now = DateTime.UtcNow; + var seeds = new[] + { + new Item + { + Sku = "SKU-DEMO-001", + Name = "Demo Item 1", + Description = "Seeded sample item for sales documents", + CategoryId = category.CategoryId, + BaseUomId = uom.UomId, + StockNature = StockNature.Stocked, + TrackingMode = TrackingMode.None, + SalePrice = 100m, + Status = EntityStatus.Active, + CreatedAt = now + }, + new Item + { + Sku = "SKU-DEMO-002", + Name = "Demo Item 2", + Description = "Secondary seeded sample item for sales documents", + CategoryId = category.CategoryId, + BaseUomId = uom.UomId, + StockNature = StockNature.Stocked, + TrackingMode = TrackingMode.None, + SalePrice = 50m, + Status = EntityStatus.Active, + CreatedAt = now + } + }; + + var toAdd = seeds.Where(i => !have.Contains(i.Sku)).ToList(); + if (toAdd.Count == 0) return false; + + db.Items.AddRange(toAdd); + return true; + } + + /// + /// Seeds the minimum sales bootstrap data needed for UI/backend development: + /// a couple of customer rows, current-year document counters, and a few draft + /// invoice/slip samples when the required master data already exists. + /// This intentionally never clears or rewrites any existing rows. + /// + private static async Task SeedSalesAsync(ErpDbContext db, CancellationToken ct) + { + var dirty = false; + + dirty |= await SeedSalesCustomersAsync(db, ct); + dirty |= await SeedSalesSequencesAsync(db, ct); + dirty |= await SeedSampleSalesDocsAsync(db, ct); + + return dirty; + } + + private static async Task SeedSalesCustomersAsync(ErpDbContext db, CancellationToken ct) + { + var existingCodes = await db.Customers.Select(c => c.CustomerCode).ToListAsync(ct); + var have = existingCodes.ToHashSet(StringComparer.OrdinalIgnoreCase); + + var seeds = new[] + { + new Customer + { + CustomerCode = "CUST-WALKIN", + CustomerType = CustomerType.B2C, + Name = "Walk-in Customer", + DisplayName = "Walk-in Customer", + CreditLimit = 0m, + CreditDays = 0, + Status = EntityStatus.Active, + CreatedAt = DateTime.UtcNow + }, + new Customer + { + CustomerCode = "CUST-DEMO", + CustomerType = CustomerType.B2B, + Name = "Demo Retail Ltd", + DisplayName = "Demo Retail Ltd", + Phone = "+94 11 000 0000", + Email = "sales@example.com", + AddressLine1 = "1 Demo Street", + City = "Colombo", + Country = "Sri Lanka", + TaxRegistrationNo = "VAT-DEMO-001", + CreditLimit = 250000m, + CreditDays = 30, + Status = EntityStatus.Active, + CreatedAt = DateTime.UtcNow + } + }; + + var toAdd = seeds.Where(c => !have.Contains(c.CustomerCode)).ToList(); + if (toAdd.Count == 0) return false; + + db.Customers.AddRange(toAdd); + return true; + } + + private static async Task SeedSalesSequencesAsync(ErpDbContext db, CancellationToken ct) + { + var year = DateTime.UtcNow.Year; + var existing = await db.NumberSequences + .Where(s => s.Year == year && (s.DocType == DocumentTypes.SalesInvoice || s.DocType == DocumentTypes.SalesSlip)) + .Select(s => s.DocType) + .ToListAsync(ct); + var have = existing.ToHashSet(StringComparer.OrdinalIgnoreCase); + + var seeds = new[] + { + new NumberSequence { DocType = DocumentTypes.SalesInvoice, Year = year, LastNumber = 0 }, + new NumberSequence { DocType = DocumentTypes.SalesSlip, Year = year, LastNumber = 0 } + }; + + var toAdd = seeds.Where(s => !have.Contains(s.DocType)).ToList(); + if (toAdd.Count == 0) return false; + + db.NumberSequences.AddRange(toAdd); + return true; + } + + private static async Task SeedSampleSalesDocsAsync(ErpDbContext db, CancellationToken ct) + { + if (await db.SalesInvoices.AnyAsync(ct) || await db.SalesSlips.AnyAsync(ct)) + return false; + + var customer = await db.Customers.AsNoTracking() + .OrderBy(c => c.CustomerId) + .FirstOrDefaultAsync(ct); + var warehouse = await db.Warehouses.AsNoTracking() + .OrderBy(w => w.WarehouseId) + .FirstOrDefaultAsync(ct); + var secondaryWarehouse = await db.Warehouses.AsNoTracking() + .OrderByDescending(w => w.WarehouseId) + .FirstOrDefaultAsync(ct); + var items = await db.Items.AsNoTracking() + .OrderBy(i => i.ItemId) + .Take(2) + .ToListAsync(ct); + var uom = await db.Uoms.AsNoTracking() + .OrderBy(u => u.UomId) + .FirstOrDefaultAsync(ct); + var user = await db.Users.AsNoTracking() + .OrderBy(u => u.UserId) + .FirstOrDefaultAsync(ct); + + if (customer is null || warehouse is null || secondaryWarehouse is null || items.Count < 2 || uom is null || user is null) + return false; + + var postableItem = items[0]; + var shortageItem = items[1]; + var today = DateTime.UtcNow.Date; + var createdAt = DateTime.UtcNow.AddDays(-1); + + db.SalesInvoices.AddRange( + new SalesInvoice + { + InvoiceNo = $"SI-{today:yyyy}-00001", + InvoiceDate = today.AddDays(-2), + CustomerId = customer.CustomerId, + CustomerSnapshotName = customer.Name, + CustomerSnapshotTaxNo = customer.TaxRegistrationNo, + WarehouseId = warehouse.WarehouseId, + InvoiceType = SalesInvoiceType.B2C, + Status = SalesInvoiceStatus.Draft, + Subtotal = 200m, + DiscountTotal = 0m, + TaxTotal = 0m, + GrandTotal = 200m, + RoundOff = 0m, + NetPayable = 200m, + PaidAmount = 0m, + BalanceAmount = 200m, + CreatedBy = user.UserId, + CreatedAt = createdAt, + Lines = + [ + new SalesInvoiceLine + { + ItemId = postableItem.ItemId, + Description = postableItem.Name, + Qty = 2m, + FreeQty = 0m, + UomId = uom.UomId, + WarehouseId = warehouse.WarehouseId, + UnitPrice = 100m, + BaseCost = 0m, + PriceSource = "seed", + DiscountMode = SalesDiscountMode.Percentage, + DiscountPct = 0m, + DiscountAmount = 0m, + NetUnitPrice = 100m, + LineTotal = 200m, + TaxPct = 0m, + TaxAmount = 0m, + IsFreeIssue = false + } + ] + }, + new SalesInvoice + { + InvoiceNo = $"SI-{today:yyyy}-00002", + InvoiceDate = today.AddDays(-1), + CustomerId = customer.CustomerId, + CustomerSnapshotName = customer.Name, + CustomerSnapshotTaxNo = customer.TaxRegistrationNo, + WarehouseId = secondaryWarehouse.WarehouseId, + InvoiceType = SalesInvoiceType.B2B, + Status = SalesInvoiceStatus.Draft, + Subtotal = 300m, + DiscountTotal = 0m, + TaxTotal = 0m, + GrandTotal = 300m, + RoundOff = 0m, + NetPayable = 300m, + PaidAmount = 0m, + BalanceAmount = 300m, + CreatedBy = user.UserId, + CreatedAt = createdAt, + Lines = + [ + new SalesInvoiceLine + { + ItemId = shortageItem.ItemId, + Description = shortageItem.Name, + Qty = 6m, + FreeQty = 0m, + UomId = uom.UomId, + WarehouseId = secondaryWarehouse.WarehouseId, + UnitPrice = 50m, + BaseCost = 0m, + PriceSource = "seed", + DiscountMode = SalesDiscountMode.Percentage, + DiscountPct = 0m, + DiscountAmount = 0m, + NetUnitPrice = 50m, + LineTotal = 300m, + TaxPct = 0m, + TaxAmount = 0m, + IsFreeIssue = false + } + ] + }, + new SalesInvoice + { + InvoiceNo = $"SI-{today:yyyy}-00003", + InvoiceDate = today, + CustomerId = customer.CustomerId, + CustomerSnapshotName = customer.Name, + CustomerSnapshotTaxNo = customer.TaxRegistrationNo, + WarehouseId = warehouse.WarehouseId, + InvoiceType = SalesInvoiceType.B2C, + Status = SalesInvoiceStatus.Posted, + Subtotal = 100m, + DiscountTotal = 0m, + TaxTotal = 0m, + GrandTotal = 100m, + RoundOff = 0m, + NetPayable = 100m, + PaidAmount = 100m, + BalanceAmount = 0m, + CreatedBy = user.UserId, + CreatedAt = createdAt, + UpdatedAt = DateTime.UtcNow, + Lines = + [ + new SalesInvoiceLine + { + ItemId = postableItem.ItemId, + Description = postableItem.Name, + Qty = 1m, + FreeQty = 0m, + UomId = uom.UomId, + WarehouseId = warehouse.WarehouseId, + UnitPrice = 100m, + BaseCost = 0m, + PriceSource = "seed", + DiscountMode = SalesDiscountMode.Percentage, + DiscountPct = 0m, + DiscountAmount = 0m, + NetUnitPrice = 100m, + LineTotal = 100m, + TaxPct = 0m, + TaxAmount = 0m, + IsFreeIssue = false + } + ] + } + ); + + db.SalesSlips.AddRange( + new SalesSlip + { + SlipNo = $"SSL-{today:yyyy}-00001", + SlipDate = today.AddDays(-2), + CustomerId = customer.CustomerId, + CustomerSnapshotName = customer.Name, + WarehouseId = warehouse.WarehouseId, + CashierUserId = user.UserId, + Status = SalesSlipStatus.Draft, + Subtotal = 50m, + DiscountTotal = 0m, + TaxTotal = 0m, + GrandTotal = 50m, + PaidAmount = 0m, + BalanceAmount = 50m, + CreatedAt = createdAt, + Lines = + [ + new SalesSlipLine + { + ItemId = postableItem.ItemId, + Description = postableItem.Name, + Qty = 1m, + FreeQty = 0m, + UomId = uom.UomId, + WarehouseId = warehouse.WarehouseId, + UnitPrice = 50m, + BaseCost = 0m, + PriceSource = "seed", + DiscountMode = SalesDiscountMode.Percentage, + DiscountPct = 0m, + DiscountAmount = 0m, + NetUnitPrice = 50m, + LineTotal = 50m, + TaxPct = 0m, + TaxAmount = 0m, + IsFreeIssue = false + } + ] + }, + new SalesSlip + { + SlipNo = $"SSL-{today:yyyy}-00002", + SlipDate = today.AddDays(-1), + CustomerId = customer.CustomerId, + CustomerSnapshotName = customer.Name, + WarehouseId = secondaryWarehouse.WarehouseId, + CashierUserId = user.UserId, + Status = SalesSlipStatus.Draft, + Subtotal = 150m, + DiscountTotal = 15m, + TaxTotal = 0m, + GrandTotal = 135m, + PaidAmount = 0m, + BalanceAmount = 135m, + CreatedAt = createdAt, + Lines = + [ + new SalesSlipLine + { + ItemId = shortageItem.ItemId, + Description = shortageItem.Name, + Qty = 3m, + FreeQty = 0m, + UomId = uom.UomId, + WarehouseId = secondaryWarehouse.WarehouseId, + UnitPrice = 50m, + BaseCost = 0m, + PriceSource = "seed", + DiscountMode = SalesDiscountMode.Percentage, + DiscountPct = 10m, + DiscountAmount = 15m, + NetUnitPrice = 45m, + LineTotal = 135m, + TaxPct = 0m, + TaxAmount = 0m, + IsFreeIssue = false + } + ] + } + ); + + return true; + } } diff --git a/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs b/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs index ec71934..83c0d87 100644 --- a/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs +++ b/Backend/ERPCore/Infra/Persistence/ErpDbContext.cs @@ -15,6 +15,7 @@ namespace ERPCore.Infra.Persistence; public class ErpDbContext : DbContext { private readonly ICurrentUser _currentUser; + private bool _writingAuditLogs; public ErpDbContext(DbContextOptions options, ICurrentUser currentUser) : base(options) { @@ -35,6 +36,8 @@ public class ErpDbContext : DbContext public DbSet Vendors => Set(); public DbSet Warehouses => Set(); public DbSet Bins => Set(); + /// Singleton business profile used by printable documents. + public DbSet CompanyProfiles => Set(); /// Singleton row (FR-MD-11). public DbSet ProductConfig => Set(); @@ -185,24 +188,44 @@ public class ErpDbContext : DbContext // persists the logs without re-auditing them. public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken ct = default) { - var pending = AuditScribe.Capture(ChangeTracker); + IReadOnlyList pending = _writingAuditLogs + ? Array.Empty() + : AuditScribe.Capture(ChangeTracker); var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct); if (pending.Count > 0) { - WriteAuditLogs(pending); - await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct); + try + { + _writingAuditLogs = true; + WriteAuditLogs(pending); + await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct); + } + finally + { + _writingAuditLogs = false; + } } return result; } public override int SaveChanges(bool acceptAllChangesOnSuccess) { - var pending = AuditScribe.Capture(ChangeTracker); + IReadOnlyList pending = _writingAuditLogs + ? Array.Empty() + : AuditScribe.Capture(ChangeTracker); var result = base.SaveChanges(acceptAllChangesOnSuccess); if (pending.Count > 0) { - WriteAuditLogs(pending); - base.SaveChanges(acceptAllChangesOnSuccess); + try + { + _writingAuditLogs = true; + WriteAuditLogs(pending); + base.SaveChanges(acceptAllChangesOnSuccess); + } + finally + { + _writingAuditLogs = false; + } } return result; } diff --git a/Backend/ERPCore/Program.cs b/Backend/ERPCore/Program.cs index 4df0ad6..8b2f945 100644 --- a/Backend/ERPCore/Program.cs +++ b/Backend/ERPCore/Program.cs @@ -11,7 +11,6 @@ using ERPCore.Services; using ERPCore.Services.Interfaces; using ERPCore.Services.Auth; using ERPCore.Services.Hrm; -using ERPCore.Services.Interfaces; using ERPCore.Services.Production; using ERPCore.Services.Stock; using ERPCore.System.Errors; @@ -79,6 +78,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -102,6 +102,9 @@ builder.Services.AddScoped(); // Sales (Phase 1) builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Stock transactions + reference data (docs/11 §5–6) builder.Services.AddScoped(); @@ -177,7 +180,15 @@ var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); - await DataSeeder.SeedAsync(db); + await db.Database.MigrateAsync(); + try + { + await DataSeeder.SeedAsync(db); + } + catch (Exception ex) + { + throw new InvalidOperationException("Database migration succeeded, but startup seeding failed.", ex); + } } app.UseSerilogRequestLogging(); diff --git a/Backend/ERPCore/Services/Interfaces/ICompanyProfileService.cs b/Backend/ERPCore/Services/Interfaces/ICompanyProfileService.cs new file mode 100644 index 0000000..c63ddec --- /dev/null +++ b/Backend/ERPCore/Services/Interfaces/ICompanyProfileService.cs @@ -0,0 +1,10 @@ +using ERPCore.Common.Http; +using ERPCore.Dtos.Config; + +namespace ERPCore.Services.Interfaces; + +public interface ICompanyProfileService +{ + Task> GetAsync(CancellationToken ct = default); + Task> UpdateAsync(UpdateCompanyProfileRequest request, uint expectedRowVersion, CancellationToken ct = default); +} diff --git a/Backend/PROGRESS.md b/Backend/PROGRESS.md index f1e9752..f65e8f9 100644 --- a/Backend/PROGRESS.md +++ b/Backend/PROGRESS.md @@ -4,6 +4,11 @@ Legend: `[ ]` not started · `[~]` in progress · `[x]` done Spec: `docs/10-BACKEND-PHASE1.md` (model + rules) · `docs/11-BACKEND-PHASE1.md` (API) Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** as the code. When ticking `[x]`, append a short note + any deviation. +## 8. Sales +- [x] Sales bootstrap data seeded locally for development: warehouses, UOMs, categories, items, customers, current-year `SI`/`SSL` sequences, plus sample invoice/slip headers and lines. Existing data is preserved. +- [x] Sales report API consolidated into `GET /api/v1/reports/sales` (catalog), `GET /api/v1/reports/sales/{reportId}` (report metadata), and `POST /api/v1/reports/sales/query` (filtered data). Legacy per-report GET routes removed; invalid report/filter combinations now fail validation. +- [x] Free-issue CRUD exposed as `api/v1/free-issues` as a thin alias over sales slips. Free issue remains a line-level `IsFreeIssue` / `FreeQty` behavior, not a separate table. + ## 0. Bootstrap - [x] Solution + Web API project (`net10.0`), packages restored (00-CORE §5.4) - [x] Folder structure per 00-CORE §5.3 diff --git a/Frontend/PROGRESS.md b/Frontend/PROGRESS.md index b6cb645..0d154fb 100644 --- a/Frontend/PROGRESS.md +++ b/Frontend/PROGRESS.md @@ -42,6 +42,13 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** - [~] Purchase Order (`.../purchase-orders` list, `/new` create — **Save as draft** or **Create & submit** (auto-approve), prefillable from a Requisition or an RFQ+vendor quotation via query params — `/[id]` detail: **Draft** is editable (ETag/If-Match) with **Submit** + **Delete**; a submitted/open PO is read-only with **Cancel** (with reason)) — FR-PROC-03..07. **2026-07-20:** rewired to the draft lifecycle — `isPoEditable` is now `Draft`-only, `submit`/`remove` added to `lib/api/purchase-orders.ts`, `saveAsDraft` on the create request. See the 2026-07-20 entry. - [~] Purchase Return (`.../purchase-returns` list, `/new` create against a Confirmed/Closed GRN's lines) — FR-PROC-08; also reachable from a `Rejected` GRN line via a "Create Return" button on the GRN detail page +## 3.5 Sales screens +- [~] Sales hub (`app/dashboard/sales`) — new module entry point linking to invoices, slips, free issues, and reports +- [~] Sales invoices (`app/dashboard/sales/invoices`, `/new`, `/[id]`) — list/detail/create/edit routing wired to the real sales invoice API, including save/post/cancel on the detail page +- [~] Sales slips (`app/dashboard/sales/slips`, `/new`, `/[id]`) — list/detail/create/edit routing wired to the real sales slip API, including save/post/cancel on the detail page +- [~] Free issues (`app/dashboard/sales/free-issues`, `/new`, `/[id]`) — alias-only surface over sales slips for free-issue handling; edit/save/post/cancel stays on the slip screen +- [~] Sales reports (`app/dashboard/sales/reports`, `/[reportId]`) — report catalog + report metadata view wired to `/reports/sales` + ## 4. Receiving screens - [~] GRN list (`app/dashboard/receiving/grn/page.tsx`) — loading/empty/error states, links to detail - [~] GRN create (`app/dashboard/receiving/grn/new/page.tsx`) — "Against PO" (picks an Approved/PartiallyReceived PO, prefills open lines) and "Direct receipt" (vendor + manual lines) modes; per-line bin, qty, unit cost, **discount % / VAT %** (with live after-discount/after-VAT line total + a per-row PO-price variance hint and a document total), hold status, and batch (batchNo+expiryDate) or serial-number-list capture driven by the item's `trackingMode`. **2026-07-20:** discount/VAT/variance added — see the 2026-07-20 entry. **2026-07-22:** "Add line" now works in **PO mode** (off-PO items) + **"New item"** (opens `/dashboard/products/new` in a new tab) + **refresh** icon — see the 2026-07-22 entry. diff --git a/Frontend/erp-system/app/dashboard/sales/free-issues/new/page.tsx b/Frontend/erp-system/app/dashboard/sales/free-issues/new/page.tsx new file mode 100644 index 0000000..8e1bf9c --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/free-issues/new/page.tsx @@ -0,0 +1,493 @@ +"use client" + +import { useEffect, useState } from "react" +import Link from "next/link" +import { ArrowLeft, Pencil, Plus, Save, Trash2, X } from "lucide-react" + +import { Button, buttonVariants } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { cn } from "@/lib/utils" +import { errorMessage } from "@/lib/error-map" +import { salesApi } from "@/lib/api/sales" +import { customersApi } from "@/lib/api/customers" +import { itemsApi } from "@/lib/api/items" +import { uomsApi } from "@/lib/api/uoms" +import { warehousesApi } from "@/lib/api/warehouses" +import { usersApi } from "@/lib/api/users" +import { toast } from "@/components/ui/toast" +import { Customer } from "@/types/customers" +import { ItemListItem, Uom, Warehouse } from "@/types/master-data" +import { ManagedUser } from "@/types/users" +import { CreateSalesSlipLineRequest, CreateSalesSlipRequest } from "@/types/sales" + +type Line = CreateSalesSlipLineRequest & { key: string } + +type FreeIssueRow = { + salesSlipId: number + slipNo: string + status: string + etag: string + warehouseName: string + itemName: string + itemSku: string + uomName: string + qty: number + freeQty: number +} + +const blankLine = (key: string): Line => ({ + key, + itemId: 0, + uomId: 0, + warehouseId: 0, + qty: 1, + freeQty: 0, + allowManualPriceOverride: true, + discountMode: "Percentage", + discountPct: 0, + discountAmount: 0, + discountValue: 0, + taxPct: 0, + isFreeIssue: false, + parentLineId: null, +}) + +export default function NewFreeIssuePage() { + const [customers, setCustomers] = useState([]) + const [items, setItems] = useState([]) + const [uoms, setUoms] = useState([]) + const [warehouses, setWarehouses] = useState([]) + const [users, setUsers] = useState([]) + const [customerId, setCustomerId] = useState(null) + const [warehouseId, setWarehouseId] = useState(null) + const [cashierUserId, setCashierUserId] = useState(null) + const [lines, setLines] = useState([blankLine("line-1")]) + const [rows, setRows] = useState([]) + const [editingRowId, setEditingRowId] = useState(null) + const [editingLines, setEditingLines] = useState([]) + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + async function refreshRows() { + const list = await salesApi.listFreeIssues({ pageSize: 50 }) + const details = await Promise.all( + list.items.map(async (summary) => { + const detail = await salesApi.getFreeIssue(summary.salesSlipId) + const firstLine = detail.data.lines[0] + const item = items.find((x) => x.itemId === firstLine?.itemId) + const uom = uoms.find((x) => x.uomId === firstLine?.uomId) + const warehouse = warehouses.find((x) => x.warehouseId === detail.data.warehouseId) + return { + salesSlipId: detail.data.salesSlipId, + slipNo: detail.data.slipNo, + status: detail.data.status, + etag: detail.etag ?? "", + warehouseName: warehouse?.name ?? `Warehouse ${detail.data.warehouseId}`, + itemName: item?.name ?? firstLine?.description ?? "—", + itemSku: item?.sku ?? `SKU-${firstLine?.itemId ?? 0}`, + uomName: uom?.name ?? `UOM ${firstLine?.uomId ?? 0}`, + qty: firstLine?.qty ?? 0, + freeQty: firstLine?.freeQty ?? 0, + } satisfies FreeIssueRow + }), + ) + setRows(details.filter((row) => row.status !== "Cancelled")) + } + + useEffect(() => { + Promise.all([ + customersApi.list({ pageSize: 200 }), + itemsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200 }), + warehousesApi.list({ pageSize: 200 }), + usersApi.list({ pageSize: 200 }), + ]) + .then(async ([cust, itemRes, uomRes, whRes, userRes]) => { + setCustomers(cust.items) + setItems(itemRes.items) + setUoms(uomRes.items) + setWarehouses(whRes.items) + setUsers(userRes.items) + setCustomerId(cust.items[0]?.customerId ?? null) + setWarehouseId(whRes.items[0]?.warehouseId ?? null) + setCashierUserId(userRes.items[0]?.userId ?? null) + setLines([ + { + ...blankLine("line-1"), + itemId: itemRes.items[0]?.itemId ?? 0, + uomId: itemRes.items[0]?.baseUomId ?? uomRes.items[0]?.uomId ?? 0, + warehouseId: whRes.items[0]?.warehouseId ?? 0, + }, + ]) + await refreshRows() + }) + .catch((err) => setError(errorMessage(err))) + .finally(() => setLoading(false)) + }, []) + + function updateLine(key: string, patch: Partial) { + setLines((prev) => prev.map((line) => (line.key === key ? { ...line, ...patch } : line))) + } + + function updateEditingLine(key: string, patch: Partial) { + setEditingLines((prev) => prev.map((line) => (line.key === key ? { ...line, ...patch } : line))) + } + + function addLine() { + setLines((prev) => [...prev, blankLine(`line-${Date.now()}`)]) + } + + function removeLine(key: string) { + setLines((prev) => (prev.length === 1 ? prev : prev.filter((line) => line.key !== key))) + } + + function selectItem(key: string, itemId: number) { + const item = items.find((candidate) => candidate.itemId === itemId) + updateLine(key, { itemId, uomId: item?.baseUomId ?? 0 }) + } + + function selectEditingItem(key: string, itemId: number) { + const item = items.find((candidate) => candidate.itemId === itemId) + updateEditingLine(key, { itemId, uomId: item?.baseUomId ?? 0 }) + } + + async function submit() { + const activeLines = editingRowId ? editingLines : lines + if (!customerId || !warehouseId || !cashierUserId) return setError("Select customer, warehouse, and cashier.") + if (activeLines.some((line) => !line.itemId)) return setError("Select an item for every line.") + if (activeLines.some((line) => !line.uomId)) return setError("Select a valid UOM for every line.") + if (activeLines.some((line) => !line.warehouseId)) return setError("Select a warehouse for every line.") + + setSaving(true) + setError(null) + try { + const payload: CreateSalesSlipRequest = { + customerId, + warehouseId, + cashierUserId, + lines: activeLines.map((line) => ({ + itemId: Number(line.itemId), + uomId: Number(line.uomId), + warehouseId: Number(line.warehouseId), + qty: Number(line.qty), + freeQty: Number(line.freeQty), + unitPrice: line.unitPrice === null ? null : Number(line.unitPrice), + allowManualPriceOverride: line.allowManualPriceOverride, + discountMode: line.discountMode, + discountPct: Number(line.discountPct), + discountAmount: Number(line.discountAmount), + discountValue: Number(line.discountValue), + taxPct: Number(line.taxPct), + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId || null, + })), + } + + if (editingRowId) { + const latest = await salesApi.getFreeIssue(editingRowId) + await salesApi.updateFreeIssue(editingRowId, payload, latest.etag) + toast.success("Free issue updated") + setEditingRowId(null) + setEditingLines([]) + } else { + const created = await salesApi.createFreeIssue(payload) + toast.success("Free issue created", created.data.slipNo) + } + + setLines([blankLine("line-1")]) + await refreshRows() + } catch (err) { + setError(errorMessage(err)) + } finally { + setSaving(false) + } + } + + async function startEdit(row: FreeIssueRow) { + try { + const detail = await salesApi.getFreeIssue(row.salesSlipId) + const detailLine = detail.data.lines[0] + setEditingRowId(row.salesSlipId) + setCustomerId(detail.data.customerId) + setWarehouseId(detail.data.warehouseId) + setCashierUserId(detail.data.cashierUserId) + setEditingLines([ + { + key: "edit-line-1", + itemId: detailLine?.itemId ?? 0, + uomId: detailLine?.uomId ?? 0, + warehouseId: detailLine?.warehouseId ?? detail.data.warehouseId, + qty: detailLine?.qty ?? 1, + freeQty: detailLine?.freeQty ?? 0, + unitPrice: detailLine?.unitPrice ?? null, + allowManualPriceOverride: true, + discountMode: detailLine?.discountMode ?? "Percentage", + discountPct: detailLine?.discountPct ?? 0, + discountAmount: detailLine?.discountAmount ?? 0, + discountValue: 0, + taxPct: detailLine?.taxPct ?? 0, + isFreeIssue: detailLine?.isFreeIssue ?? false, + parentLineId: detailLine?.parentLineId ?? null, + }, + ]) + } catch (err) { + setError(errorMessage(err)) + } + } + + function cancelEdit() { + setEditingRowId(null) + setEditingLines([]) + } + + async function deleteRow(row: FreeIssueRow) { + if (row.status !== "Draft") return + try { + await salesApi.cancelFreeIssue(row.salesSlipId) + toast.success("Free issue cancelled", row.slipNo) + if (editingRowId === row.salesSlipId) cancelEdit() + await refreshRows() + } catch (err) { + setError(errorMessage(err)) + } + } + + if (loading) { + return
Loading masters...
+ } + + const activeLines = editingRowId ? editingLines : lines + + return ( +
+
+
+ + + +
+

Free Issues

+

Create and manage free-issue sales slips from the backend.

+
+
+ +
+ + {error ?
{error}
: null} + + {editingRowId ? ( +
+
+

Inline edit free issue

+ +
+
+ + + + ID + Item + UOM + Qty + Free + + + + + {activeLines.map((line, idx) => ( + + {idx + 1} + + + + + + + + updateEditingLine(line.key, { qty: Number(e.target.value) })} className="h-9 w-24 text-right font-mono text-sm tabular-nums" /> + + + updateEditingLine(line.key, { freeQty: Number(e.target.value) })} className="h-9 w-24 text-right font-mono text-sm tabular-nums" /> + + editing + + ))} + +
+
+
+ ) : ( +
+
+

Free issue lines

+ +
+
+ + + + ID + Item + UOM + Qty + Free + + + + + {lines.map((line, idx) => ( + + {idx + 1} + + + + + + + + updateLine(line.key, { qty: Number(e.target.value) })} className="h-9 w-24 text-right font-mono text-sm tabular-nums" /> + + + updateLine(line.key, { freeQty: Number(e.target.value) })} className="h-9 w-24 text-right font-mono text-sm tabular-nums" /> + + + + + + ))} + +
+
+
+ )} + +
+
+
+

Created free issues

+

Persisted backend records with draft-only cancellation.

+
+ +
+
+ + + + Promotion + Warehouse + Product + Qty + Free + Actions + + + + {rows.length > 0 ? ( + rows.map((row) => ( + + +
Buy {row.qty} Get {row.freeQty || 0}
+
{row.slipNo}
+
+ {row.warehouseName} + +
{row.itemName}
+
{row.itemSku} · {row.uomName}
+
+ {row.qty} + {row.freeQty} + +
+ + {row.status === "Draft" ? ( + + ) : null} +
+
+
+ )) + ) : ( + + + No free issues created yet. + + + )} +
+
+
+
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/free-issues/page.tsx b/Frontend/erp-system/app/dashboard/sales/free-issues/page.tsx new file mode 100644 index 0000000..474a1ae --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/free-issues/page.tsx @@ -0,0 +1,3 @@ +"use client" + +export { default } from "./new/page" diff --git a/Frontend/erp-system/app/dashboard/sales/invoices/[id]/page.tsx b/Frontend/erp-system/app/dashboard/sales/invoices/[id]/page.tsx new file mode 100644 index 0000000..113ec6d --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/invoices/[id]/page.tsx @@ -0,0 +1,605 @@ +"use client" + +import { use, useEffect, useMemo, useState } from "react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { ArrowLeft, Minus, Plus, Printer, Save, Send, X } from "lucide-react" + +import { companyApi } from "@/lib/api/company" +import { salesApi } from "@/lib/api/sales" +import { customersApi } from "@/lib/api/customers" +import { itemsApi } from "@/lib/api/items" +import { uomsApi } from "@/lib/api/uoms" +import { warehousesApi } from "@/lib/api/warehouses" +import { errorMessage } from "@/lib/error-map" +import { cn } from "@/lib/utils" +import { getSuggestedUnitPrice } from "@/lib/sales-line-utils" +import { CompanyProfile } from "@/types/company" +import { Customer } from "@/types/customers" +import { ItemListItem, Uom, Warehouse } from "@/types/master-data" +import { CreateSalesInvoiceLineRequest, SalesInvoice, SalesInvoicePostingCheck, SalesInvoiceStatus, SalesInvoiceType } from "@/types/sales" +import { toast } from "@/components/ui/toast" +import { Label } from "@/components/ui/label" + +type Line = CreateSalesInvoiceLineRequest & { key: string } + +const money = new Intl.NumberFormat("en-LK", { + style: "currency", + currency: "LKR", + minimumFractionDigits: 2, +}) + +const blankLine = (key: string): Line => ({ + key, + itemId: 0, + uomId: 0, + warehouseId: 0, + qty: 1, + freeQty: 0, + unitPrice: null, + allowManualPriceOverride: true, + discountMode: "Percentage", + discountPct: 0, + discountAmount: 0, + discountValue: 0, + taxPct: 0, + isFreeIssue: false, + parentLineId: null, +}) + +function statusClass(status: SalesInvoiceStatus) { + switch (status) { + case "Draft": + return "border-amber-200 bg-amber-50 text-amber-800" + case "Posted": + return "border-emerald-200 bg-emerald-50 text-emerald-800" + case "Cancelled": + return "border-rose-200 bg-rose-50 text-rose-800" + } +} + +export default function SalesInvoiceDetailPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter() + const resolvedParams = use(params) + const invoiceId = Number(resolvedParams.id) + + const [invoice, setInvoice] = useState(null) + const [company, setCompany] = useState(null) + const [customers, setCustomers] = useState([]) + const [items, setItems] = useState([]) + const [uoms, setUoms] = useState([]) + const [warehouses, setWarehouses] = useState([]) + const [lines, setLines] = useState([blankLine("line-1")]) + const [customerId, setCustomerId] = useState(null) + const [warehouseId, setWarehouseId] = useState(null) + const [invoiceType, setInvoiceType] = useState("B2C") + const [etag, setEtag] = useState(null) + const [postingCheck, setPostingCheck] = useState(null) + const [error, setError] = useState(null) + const [saving, setSaving] = useState(false) + const [busy, setBusy] = useState<"post" | "cancel" | null>(null) + + useEffect(() => { + if (!Number.isFinite(invoiceId)) { + setError(`Invalid invoice id '${resolvedParams.id}'.`) + return + } + + Promise.all([ + companyApi.getProfile(), + customersApi.list({ pageSize: 200 }), + itemsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200 }), + warehousesApi.list({ pageSize: 200 }), + salesApi.getInvoice(invoiceId), + ]) + .then(([companyRes, customerRes, itemRes, uomRes, warehouseRes, doc]) => { + setCompany(companyRes.data) + setCustomers(customerRes.items) + setItems(itemRes.items) + setUoms(uomRes.items) + setWarehouses(warehouseRes.items) + setInvoice(doc.data) + setEtag(doc.etag) + setCustomerId(doc.data.customerId) + setWarehouseId(doc.data.warehouseId) + setInvoiceType(doc.data.invoiceType) + setLines( + doc.data.lines.map((line) => ({ + key: String(line.salesInvoiceLineId), + itemId: line.itemId, + uomId: line.uomId, + warehouseId: line.warehouseId, + qty: line.qty, + freeQty: line.freeQty, + unitPrice: line.unitPrice, + allowManualPriceOverride: true, + discountMode: line.discountMode, + discountPct: line.discountPct, + discountAmount: line.discountAmount, + discountValue: 0, + taxPct: line.taxPct, + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId, + })) + ) + }) + .catch((err) => setError(errorMessage(err))) + }, [invoiceId, resolvedParams.id]) + + useEffect(() => { + if (!invoice || invoice.status !== "Draft") { + setPostingCheck(null) + return + } + salesApi + .checkInvoicePosting(invoiceId) + .then((result) => setPostingCheck(result)) + .catch(() => setPostingCheck(null)) + }, [invoice, invoiceId]) + + const isDraft = invoice?.status === "Draft" + const customer = useMemo(() => customers.find((c) => c.customerId === invoice?.customerId), [customers, invoice?.customerId]) + const warehouse = useMemo(() => warehouses.find((w) => w.warehouseId === invoice?.warehouseId), [warehouses, invoice?.warehouseId]) + const freeQtyTotal = invoice?.totals.freeQtyTotal ?? 0 + const canPost = invoice?.status === "Draft" && (postingCheck?.canPost ?? true) && !busy && !saving + + function updateLine(key: string, patch: Partial) { + setLines((prev) => prev.map((line) => (line.key === key ? { ...line, ...patch } : line))) + } + + function updateHeaderWarehouse(nextWarehouseId: number | null) { + setWarehouseId(nextWarehouseId) + setLines((prev) => prev.map((line) => ({ ...line, warehouseId: nextWarehouseId ?? line.warehouseId }))) + } + + function selectItem(key: string, itemId: number) { + const item = items.find((candidate) => candidate.itemId === itemId) + updateLine(key, { + itemId, + uomId: item?.baseUomId ?? 0, + unitPrice: getSuggestedUnitPrice(items, itemId), + }) + } + + function addLine() { + setLines((prev) => [...prev, { ...blankLine(`line-${Date.now()}`), warehouseId: warehouseId ?? 0 }]) + } + + function removeLine(key: string) { + setLines((prev) => (prev.length === 1 ? prev : prev.filter((line) => line.key !== key))) + } + + async function save() { + if (!customerId || !warehouseId || !etag) return + if (lines.some((line) => !line.itemId || !line.uomId || !line.warehouseId)) { + setError("Select item, UOM and warehouse for every line.") + return + } + + setSaving(true) + setError(null) + try { + const updated = await salesApi.updateInvoice( + invoiceId, + { + customerId, + warehouseId, + invoiceType, + lines: lines.map((line) => ({ + itemId: Number(line.itemId), + uomId: Number(line.uomId), + warehouseId: Number(line.warehouseId), + qty: Number(line.qty), + freeQty: Number(line.freeQty), + unitPrice: line.unitPrice === null ? null : Number(line.unitPrice), + allowManualPriceOverride: line.allowManualPriceOverride, + discountMode: line.discountMode, + discountPct: Number(line.discountPct), + discountAmount: Number(line.discountAmount), + discountValue: Number(line.discountValue), + taxPct: Number(line.taxPct), + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId || null, + })), + }, + etag, + ) + setInvoice(updated.data) + setEtag(updated.etag) + toast.success("Invoice saved", updated.data.invoiceNo) + } catch (err) { + setError(errorMessage(err)) + } finally { + setSaving(false) + } + } + + async function post() { + if (!postingCheck?.canPost) { + setError("Resolve stock shortages before posting this invoice.") + return + } + setBusy("post") + setError(null) + try { + const posted = await salesApi.postInvoice(invoiceId) + setInvoice(posted) + toast.success("Invoice posted", posted.invoiceNo) + router.refresh() + } catch (err) { + setError(errorMessage(err)) + } finally { + setBusy(null) + } + } + + async function cancel() { + setBusy("cancel") + setError(null) + try { + const cancelled = await salesApi.cancelInvoice(invoiceId) + setInvoice(cancelled) + toast.success("Invoice cancelled", cancelled.invoiceNo) + router.refresh() + } catch (err) { + setError(errorMessage(err)) + } finally { + setBusy(null) + } + } + + if (error && !invoice) { + return
{error}
+ } + + if (!invoice) { + return
{error ?? "Invoice data is loading or unavailable."}
+ } + + return ( +
+
+
+ + + +
+

Invoice Details

+

{invoice.invoiceNo}

+
+
+ + + Print + +
+ + {error ?
{error}
: null} + +
+
+
+
+
Sales Invoice
+

{invoice.invoiceNo}

+
+ Status: + {invoice.status} + Type: {invoice.invoiceType} + Date: {new Date(invoice.invoiceDate).toLocaleDateString()} +
+
+
+
{company?.tradeName ?? company?.legalName ?? "ERP Core Trading"}
+
+ {company?.addressLine1 ?? ""} + {company?.city ? `, ${company.city}` : ""} +
+ {company?.taxRegistrationNo ?
Tax No: {company.taxRegistrationNo}
: null} +
+
+
+ +
+
+
Bill To
+
{invoice.customerSnapshotName}
+
Customer ID: {invoice.customerId}
+ {customer?.displayName ?
Registered name: {customer.displayName}
: null} + {invoice.customerSnapshotTaxNo ?
Tax No: {invoice.customerSnapshotTaxNo}
: null} +
+
+
Warehouse
+
{warehouse?.name ?? `#${invoice.warehouseId}`}
+
Code: {warehouse?.code ?? invoice.warehouseId}
+
Location: {warehouse?.location ?? "—"}
+
+
+
Totals
+
+
Subtotal
+
{money.format(invoice.totals.subtotal)}
+
Discount
+
{money.format(invoice.totals.discountTotal)}
+
Free qty
+
{freeQtyTotal.toFixed(0)}
+
Tax
+
{money.format(invoice.totals.taxTotal)}
+
Net payable
+
{money.format(invoice.totals.netPayable)}
+
+
+
+ +
+ + + + + + + + + + + + + + + {invoice.lines.map((line) => ( + + + + + + + + + + + ))} + +
ItemUOMQtyFreeUnit priceDiscountTaxLine total
+
{line.description}
+
SKU {items.find((i) => i.itemId === line.itemId)?.sku ?? `#${line.itemId}`}
+
{uoms.find((u) => u.uomId === line.uomId)?.name ?? `#${line.uomId}`}{line.qty.toFixed(0)}{line.freeQty > 0 ? line.freeQty.toFixed(0) : "—"}{money.format(line.unitPrice)}{money.format(line.discountAmount)}{money.format(line.taxAmount)}{money.format(line.lineTotal)}
+
+ + {invoice.lines.some((line) => line.freeQty > 0) ? ( +
+
Free issue summary
+
+ {invoice.lines.filter((line) => line.freeQty > 0).map((line) => ( +
+
{line.description}
+
Free qty: {line.freeQty.toFixed(0)}
+
+ ))} +
+
+ ) : null} + + {invoice.status === "Draft" && postingCheck && !postingCheck.canPost ? ( +
+
+
Stock shortage detected before posting
+
The invoice cannot be posted until every line has enough available stock in the selected warehouse.
+
+ + + + + + + + + + + + {postingCheck.issues.map((issue) => ( + + + + + + + + ))} + +
ItemWarehouseRequestedAvailableShort
+
{issue.itemSku}
+
{issue.itemName}{issue.isFreeIssue ? " · free issue" : ""}
+
{issue.warehouseId}{issue.requestedQty.toFixed(0)}{issue.availableQty.toFixed(0)}{issue.shortQty.toFixed(0)}
+
+
+
+ ) : null} + +
+
Notes
+

{company?.footerNote ?? "Standard invoice template view."}

+
+ +
+
+

Actions

+ {isDraft ? "Draft invoice can be edited." : "Only draft invoices are editable."} +
+ + {isDraft ? ( + <> +
+ + + + +
+ +
+ + + + + + + + + + + + + {lines.map((line) => ( + + + + + + + + + ))} + +
ItemUOMQtyFreeUnit priceRemove
+ + + + + updateLine(line.key, { qty: Number(e.target.value) || 0 })} + className="h-10 w-full rounded-lg border border-input bg-background px-3 text-right font-mono tabular-nums text-sm" + /> + + updateLine(line.key, { freeQty: Number(e.target.value) || 0 })} + className="h-10 w-full rounded-lg border border-input bg-background px-3 text-right font-mono tabular-nums text-sm" + /> + + updateLine(line.key, { unitPrice: e.target.value === "" ? null : Number(e.target.value) })} + className="h-10 w-full rounded-lg border border-input bg-background px-3 text-right font-mono tabular-nums text-sm" + /> + + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + ) : ( +
+ This invoice is {invoice.status.toLowerCase()} and cannot be edited. +
+ )} +
+
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/invoices/[id]/print/page.tsx b/Frontend/erp-system/app/dashboard/sales/invoices/[id]/print/page.tsx new file mode 100644 index 0000000..6fca4fb --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/invoices/[id]/print/page.tsx @@ -0,0 +1,218 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import Link from "next/link" +import { ArrowLeft, Printer } from "lucide-react" + +import { salesApi } from "@/lib/api/sales" +import { companyApi } from "@/lib/api/company" +import { customersApi } from "@/lib/api/customers" +import { itemsApi } from "@/lib/api/items" +import { uomsApi } from "@/lib/api/uoms" +import { warehousesApi } from "@/lib/api/warehouses" +import { errorMessage } from "@/lib/error-map" +import { Button, buttonVariants } from "@/components/ui/button" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { cn } from "@/lib/utils" +import { Customer } from "@/types/customers" +import { CompanyProfile } from "@/types/company" +import { ItemListItem, Uom, Warehouse } from "@/types/master-data" +import { SalesInvoice } from "@/types/sales" + +export default function SalesInvoicePrintPage({ params }: { params: { id: string } }) { + const invoiceId = Number(params.id) + const [invoice, setInvoice] = useState(null) + const [company, setCompany] = useState(null) + const [customers, setCustomers] = useState([]) + const [items, setItems] = useState([]) + const [uoms, setUoms] = useState([]) + const [warehouses, setWarehouses] = useState([]) + const [error, setError] = useState(null) + + useEffect(() => { + if (!Number.isFinite(invoiceId)) { + setError(`Invalid invoice id '${params.id}'.`) + return + } + Promise.all([ + companyApi.getProfile(), + customersApi.list({ pageSize: 200 }), + itemsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200 }), + warehousesApi.list({ pageSize: 200 }), + salesApi.getInvoice(invoiceId), + ]) + .then(([companyRes, cust, itemRes, uomRes, whRes, doc]) => { + setCompany(companyRes.data) + setCustomers(cust.items) + setItems(itemRes.items) + setUoms(uomRes.items) + setWarehouses(whRes.items) + setInvoice(doc.data) + }) + .catch((err) => setError(errorMessage(err))) + }, [params.id, invoiceId]) + + const subtotal = useMemo(() => invoice?.totals.subtotal ?? 0, [invoice]) + const freeQtyTotal = invoice?.totals.freeQtyTotal ?? 0 + + if (error && !invoice) { + return
{error}
+ } + + if (!invoice) { + return
{error ?? "Invoice print data is loading or unavailable."}
+ } + + const customer = customers.find((c) => c.customerId === invoice.customerId) + const warehouse = warehouses.find((w) => w.warehouseId === invoice.warehouseId) + + return ( +
+
+
+ + + +
+

Invoice Print

+

{invoice.invoiceNo}

+
+
+ +
+ +
+
+
+
Sales Invoice
+

{invoice.invoiceNo}

+
Status: {invoice.status} · Type: {invoice.invoiceType}
+
+
+
{company?.tradeName ?? company?.legalName ?? "ERP Core Trading"}
+ {company?.logoUrl ? {company.tradeName : null} +
{company?.addressLine1}{company?.city ? `, ${company.city}` : ""}
+ {company?.taxRegistrationNo ?
Tax No: {company.taxRegistrationNo}
: null} +
Invoice date: {new Date(invoice.invoiceDate).toLocaleDateString()}
+
Printed: {new Date().toLocaleString()}
+
Free qty total: {freeQtyTotal.toFixed(2)}
+
+
+ +
+
+
Customer
+
{invoice.customerSnapshotName}
+
Customer ID: {invoice.customerId}
+ {invoice.customerSnapshotTaxNo ?
Tax No: {invoice.customerSnapshotTaxNo}
: null} + {customer?.displayName ?
Customer: {customer.displayName}
: null} +
+
+
Warehouse
+
{warehouse?.name ?? `#${invoice.warehouseId}`}
+
Code: {warehouse?.code ?? invoice.warehouseId}
+
+
+
Totals
+
+
Subtotal
{invoice.totals.subtotal.toFixed(2)}
+
Discount
{invoice.totals.discountTotal.toFixed(2)}
+
Free qty
{freeQtyTotal.toFixed(2)}
+
Tax
{invoice.totals.taxTotal.toFixed(2)}
+
Net payable
{invoice.totals.netPayable.toFixed(2)}
+
+
+
+ +
+ + + + Item + UOM + Qty + Free + Unit price + Discount + Tax + Line total + + + + {invoice.lines.map((line) => ( + + +
{line.description}
+
SKU {items.find((i) => i.itemId === line.itemId)?.sku ?? `#${line.itemId}`}
+
+ {uoms.find((u) => u.uomId === line.uomId)?.name ?? `#${line.uomId}`} + {line.qty.toFixed(2)} + {line.freeQty > 0 ? line.freeQty.toFixed(2) : "—"} + {line.unitPrice.toFixed(2)} + {line.discountAmount.toFixed(2)} + {line.taxAmount.toFixed(2)} + {line.lineTotal.toFixed(2)} +
+ ))} +
+
+
+ + {invoice.lines.some((line) => line.freeQty > 0) ? ( +
+
Free issue summary
+
+ {invoice.lines.filter((line) => line.freeQty > 0).map((line) => ( +
+
{line.description}
+
Invoice qty: {line.qty.toFixed(2)}
+
Free qty: {line.freeQty.toFixed(2)}
+
+ ))} +
+
+ ) : null} + +
+
+
Notes
+

+ {company?.footerNote ?? "Standard invoice print view."} +

+
+
+
Totals
+
+
Subtotal{invoice.totals.subtotal.toFixed(2)}
+
Discount total{invoice.totals.discountTotal.toFixed(2)}
+
Free qty total{invoice.totals.freeQtyTotal.toFixed(2)}
+
Tax total{invoice.totals.taxTotal.toFixed(2)}
+
Net payable{invoice.totals.netPayable.toFixed(2)}
+
Paid{invoice.totals.paidAmount.toFixed(2)}
+
Balance{invoice.totals.balanceAmount.toFixed(2)}
+
+
+
+ + {company ? ( +
+
Bank Details
+
+
Bank {company.bankName ?? "—"}
+
Branch {company.bankBranch ?? "—"}
+
Account Name {company.accountName ?? "—"}
+
Account No {company.accountNumber ?? "—"}
+
SWIFT {company.swiftCode ?? "—"}
+
VAT {company.vatRegistrationNo ?? "—"}
+
+
+ ) : null} +
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/invoices/new/page.tsx b/Frontend/erp-system/app/dashboard/sales/invoices/new/page.tsx new file mode 100644 index 0000000..6da9053 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/invoices/new/page.tsx @@ -0,0 +1,490 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import { useRouter } from "next/navigation" +import Link from "next/link" +import { ArrowLeft, Minus, Plus, Save, Trash2 } from "lucide-react" + +import { Button, buttonVariants } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Badge } from "@/components/ui/badge" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { cn } from "@/lib/utils" +import { getSuggestedUnitPrice } from "@/lib/sales-line-utils" +import { errorMessage } from "@/lib/error-map" +import { salesApi } from "@/lib/api/sales" +import { customersApi } from "@/lib/api/customers" +import { itemsApi } from "@/lib/api/items" +import { uomsApi } from "@/lib/api/uoms" +import { warehousesApi } from "@/lib/api/warehouses" +import { Customer } from "@/types/customers" +import { ItemListItem, Uom, Warehouse } from "@/types/master-data" +import { CreateSalesInvoiceLineRequest, CreateSalesInvoiceRequest, SalesInvoiceType } from "@/types/sales" +import { toast } from "@/components/ui/toast" + +type Line = CreateSalesInvoiceLineRequest & { key: string } +type ActiveFocScheme = { + id: number + slipNo: string + schemeLabel: string + warehouseName: string + productLabel: string +} + +const blankLine = (key: string): Line => ({ + key, + itemId: 0, + uomId: 0, + warehouseId: 0, + qty: 1, + freeQty: 0, + unitPrice: null, + allowManualPriceOverride: true, + discountMode: "Percentage", + discountPct: 0, + discountAmount: 0, + discountValue: 0, + taxPct: 0, + isFreeIssue: false, + parentLineId: null, +}) + +const lkr = new Intl.NumberFormat("en-LK", { + style: "currency", + currency: "LKR", + minimumFractionDigits: 2, +}) + +export default function NewSalesInvoicePage() { + const router = useRouter() + const [customers, setCustomers] = useState([]) + const [items, setItems] = useState([]) + const [uoms, setUoms] = useState([]) + const [warehouses, setWarehouses] = useState([]) + const [customerId, setCustomerId] = useState(null) + const [warehouseId, setWarehouseId] = useState(null) + const [invoiceType, setInvoiceType] = useState("B2C") + const [lines, setLines] = useState([blankLine("line-1")]) + const [activeFocSchemes, setActiveFocSchemes] = useState([]) + const [loading, setLoading] = useState(true) + const [submitError, setSubmitError] = useState(null) + const [saving, setSaving] = useState(false) + + useEffect(() => { + Promise.all([ + customersApi.list({ pageSize: 200 }), + itemsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200 }), + warehousesApi.list({ pageSize: 200 }), + salesApi.listFreeIssues({ pageSize: 20 }), + ]) + .then(([cust, itemRes, uomRes, whRes, freeIssueRes]) => { + setCustomers(cust.items) + setItems(itemRes.items) + setUoms(uomRes.items) + setWarehouses(whRes.items) + const defaultWarehouseId = whRes.items[0]?.warehouseId ?? null + setCustomerId(cust.items[0]?.customerId ?? null) + setWarehouseId(defaultWarehouseId) + setLines([ + { + ...blankLine("line-1"), + itemId: itemRes.items[0]?.itemId ?? 0, + uomId: itemRes.items[0]?.baseUomId ?? uomRes.items[0]?.uomId ?? 0, + warehouseId: defaultWarehouseId ?? 0, + unitPrice: getSuggestedUnitPrice(itemRes.items, itemRes.items[0]?.itemId), + }, + ]) + setActiveFocSchemes( + freeIssueRes.items.flatMap((issue) => { + const firstLine = issue.lines?.[0] + if (!firstLine) return [] + const item = itemRes.items.find((candidate) => candidate.itemId === firstLine.itemId) + const warehouse = whRes.items.find((candidate) => candidate.warehouseId === issue.warehouseId) + return [ + { + id: issue.salesSlipId, + slipNo: issue.slipNo, + schemeLabel: `Buy ${firstLine.qty} Get ${firstLine.freeQty || 0}`, + warehouseName: warehouse?.name ?? `Warehouse ${issue.warehouseId}`, + productLabel: `${item?.sku ?? `SKU-${firstLine.itemId}`} - ${item?.name ?? firstLine.itemName ?? `Item ${firstLine.itemId}`}`, + }, + ] + }), + ) + }) + .catch((err) => setSubmitError(errorMessage(err))) + .finally(() => setLoading(false)) + }, []) + + function updateLine(key: string, patch: Partial) { + setLines((prev) => prev.map((line) => (line.key === key ? { ...line, ...patch } : line))) + } + + function updateHeaderWarehouse(nextWarehouseId: number | null) { + setWarehouseId(nextWarehouseId) + setLines((prev) => prev.map((line) => ({ ...line, warehouseId: nextWarehouseId ?? line.warehouseId }))) + } + + function selectItem(key: string, itemId: number) { + const item = items.find((candidate) => candidate.itemId === itemId) + updateLine(key, { + itemId, + uomId: item?.baseUomId ?? 0, + unitPrice: getSuggestedUnitPrice(items, itemId), + }) + } + + function addLine() { + setLines((prev) => [ + ...prev, + { + ...blankLine(`line-${Date.now()}`), + warehouseId: warehouseId ?? 0, + }, + ]) + } + + function removeLine(key: string) { + setLines((prev) => (prev.length === 1 ? prev : prev.filter((line) => line.key !== key))) + } + + const grossTotal = useMemo( + () => lines.reduce((sum, line) => sum + Number(line.unitPrice ?? 0) * Number(line.qty || 0), 0), + [lines], + ) + const discountTotal = useMemo( + () => + lines.reduce((sum, line) => { + const gross = Number(line.unitPrice ?? 0) * Number(line.qty || 0) + const mode = String(line.discountMode) + return sum + (mode === "Amount" ? Number(line.discountAmount || 0) : gross * (Number(line.discountPct || 0) / 100)) + }, 0), + [lines], + ) + const taxTotal = useMemo( + () => + lines.reduce((sum, line) => { + const gross = Number(line.unitPrice ?? 0) * Number(line.qty || 0) + const mode = String(line.discountMode) + const discount = mode === "Amount" ? Number(line.discountAmount || 0) : gross * (Number(line.discountPct || 0) / 100) + const taxable = Math.max(0, gross - discount) + return sum + taxable * (Number(line.taxPct || 0) / 100) + }, 0), + [lines], + ) + const netTotal = Math.max(0, grossTotal - discountTotal) + const payableTotal = netTotal + taxTotal + + async function submit() { + if (!customerId || !warehouseId) return setSubmitError("Select a customer and warehouse.") + if (lines.some((line) => !line.itemId)) return setSubmitError("Select an item for every line.") + if (lines.some((line) => !line.warehouseId)) return setSubmitError("Select a warehouse for every line.") + if (lines.some((line) => !line.uomId)) return setSubmitError("Select a valid UOM for every line.") + + const payload: CreateSalesInvoiceRequest = { + customerId, + warehouseId, + invoiceType, + lines: lines.map((line) => ({ + itemId: Number(line.itemId), + uomId: Number(line.uomId), + warehouseId: Number(line.warehouseId), + qty: Number(line.qty), + freeQty: Number(line.freeQty), + unitPrice: line.unitPrice === null ? null : Number(line.unitPrice), + allowManualPriceOverride: line.allowManualPriceOverride, + discountMode: line.discountMode, + discountPct: Number(line.discountPct), + discountAmount: Number(line.discountAmount), + discountValue: Number(line.discountValue), + taxPct: Number(line.taxPct), + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId || null, + })), + } + + setSaving(true) + setSubmitError(null) + try { + const created = await salesApi.createInvoice(payload) + toast.success("Invoice created", created.data.invoiceNo) + router.push(`/dashboard/sales/invoices/${created.data.salesInvoiceId}`) + } catch (err) { + if (err instanceof Error && "status" in err && (err as { status?: number }).status === 401) { + router.push(`/login?next=/dashboard/sales/invoices/new`) + return + } + setSubmitError(errorMessage(err)) + } finally { + setSaving(false) + } + } + + if (loading) { + return
Loading masters...
+ } + + return ( +
+
+
+ + + +
+

New invoice

+

Select items, set quantity, unit price and line discount.

+
+
+
+ + +
+
+ + {submitError ?
{submitError}
: null} + +
+

Invoice header

+
+
+ + +
+
+ + +
+
+ + value={invoiceType} onValueChange={(v) => v && setInvoiceType(v)}> + + + + + B2B + B2C + Cash + Credit + + +
+
+ +
+ + Draft + + {lkr.format(payableTotal)} +
+
+
+
+ +
+
+

Invoice lines

+ +
+
+ + + + # + Item + UOM + Qty + Free + Unit price + Discount % + Line total + + + + + {lines.map((line, idx) => { + const lineGross = Number(line.unitPrice ?? 0) * Number(line.qty || 0) + const lineDiscount = + line.discountMode === "Amount" + ? Number(line.discountAmount || 0) + : lineGross * (Number(line.discountPct || 0) / 100) + const lineNet = Math.max(0, lineGross - lineDiscount) + + return ( + + {idx + 1} + + + + + + + + updateLine(line.key, { qty: Number(e.target.value) })} + className="h-9 w-24 text-right font-mono text-sm tabular-nums" + /> + + + updateLine(line.key, { freeQty: Number(e.target.value) })} + className="h-9 w-24 text-right font-mono text-sm tabular-nums" + /> + + + updateLine(line.key, { unitPrice: e.target.value === "" ? null : Number(e.target.value) })} + className="h-9 w-28 text-right font-mono text-sm tabular-nums" + /> + + + updateLine(line.key, { discountPct: Number(e.target.value) || 0 })} + className="h-9 w-24 text-right font-mono text-sm tabular-nums" + /> + + {lkr.format(lineNet)} + + + + + ) + })} + +
+
+
+ +
+ {/*
+

Active FOC schemes

+
    + {activeFocSchemes.length > 0 ? ( + activeFocSchemes.map((scheme) => ( +
  • +
    + {scheme.schemeLabel} + {scheme.slipNo} +
    +
    + {scheme.productLabel} + {scheme.warehouseName} +
    +
  • + )) + ) : ( +
  • + No active free-issue schemes found. +
  • + )} +
+
*/} + +
+ {[ + ["Gross", lkr.format(grossTotal)], + ["Discount", `-${lkr.format(discountTotal)}`], + ["Net", lkr.format(netTotal)], + ["Tax", lkr.format(taxTotal)], + ].map(([k, v]) => ( +
+
{k}
+
{v}
+
+ ))} +
+
Payable
+
{lkr.format(payableTotal)}
+
+
+
+ +
+ + Cancel + + +
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx b/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx new file mode 100644 index 0000000..5e306e3 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/invoices/page.tsx @@ -0,0 +1,233 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import Link from "next/link" +import { ChevronLeft, ChevronRight, Eye, Filter, FileText, Plus, Printer } from "lucide-react" + +import { salesApi } from "@/lib/api/sales" +import { errorMessage } from "@/lib/error-map" +import { Button, buttonVariants } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Input } from "@/components/ui/input" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { PaginationMeta } from "@/types/common" +import { SalesInvoiceStatus, SalesInvoiceSummary } from "@/types/sales" +import { cn } from "@/lib/utils" + +type StatusFilter = SalesInvoiceStatus | "All" + +const PAGE_SIZE = 10 +const tabs = ["All", "Draft", "Posted", "Cancelled"] as const + +function statusClass(status: SalesInvoiceStatus) { + switch (status) { + case "Draft": + return "border-amber-200 bg-amber-50 text-amber-800" + case "Posted": + return "border-emerald-200 bg-emerald-50 text-emerald-800" + case "Cancelled": + return "border-rose-200 bg-rose-50 text-rose-800" + } +} + +export default function SalesInvoicesPage() { + const [rows, setRows] = useState(null) + const [pagination, setPagination] = useState(null) + const [error, setError] = useState(null) + const [status, setStatus] = useState("All") + const [searchInput, setSearchInput] = useState("") + const [query, setQuery] = useState("") + const [page, setPage] = useState(1) + + useEffect(() => { + const timeout = setTimeout(() => setQuery(searchInput.trim()), 300) + return () => clearTimeout(timeout) + }, [searchInput]) + + useEffect(() => setPage(1), [query, status]) + + useEffect(() => { + setError(null) + salesApi + .listInvoices({ page, pageSize: PAGE_SIZE, status: status === "All" ? undefined : status }) + .then((res) => { + setRows(res.items) + setPagination(res.pagination) + }) + .catch((err) => setError(errorMessage(err))) + }, [page, status]) + + const visibleRows = useMemo( + () => + rows?.filter((row) => + `${row.invoiceNo} ${row.customerSnapshotName}`.toLowerCase().includes(query.toLowerCase()) + ) ?? [], + [rows, query] + ) + const hasFilters = status !== "All" || query.length > 0 + 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) + + return ( +
+
+
+

Sales Invoices

+

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

+
+
+ + + + New Invoice + +
+
+ +
+
+
+ {tabs.map((t) => ( + + ))} +
+ +
+ setSearchInput(e.target.value)} + placeholder="Filter by customer or invoice number" + className="h-12 w-full lg:max-w-sm" + /> + +
+
+ + {error &&
{error}
} + + {!error && rows === null && ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+ )} + + {!error && rows !== null && visibleRows.length === 0 && ( +
+ +

{hasFilters ? "No invoices match your filters." : "No invoices yet."}

+
+ )} + + {!error && rows !== null && visibleRows.length > 0 && ( + <> +
+ + + + Invoice + Customer + Date + Due + Lines + Gross + Discount + Net + Status + View + + + + {visibleRows.map((row) => ( + + + + {row.invoiceNo} + + + {row.customerSnapshotName} + {new Date(row.createdAt).toLocaleDateString()} + {new Date(row.invoiceDate).toLocaleDateString()} + {row.totals.freeQtyTotal.toFixed(2)} + {row.totals.subtotal.toFixed(2)} + -{row.totals.discountTotal.toFixed(2)} + {row.totals.grandTotal.toFixed(2)} + + + {row.status} + + + + + + + + + ))} + +
+
+ +
+
+
+
Gross total
+
{grossTotal.toFixed(2)}
+
+
+
Discount total
+
-{discountTotal.toFixed(2)}
+
+
+
Net total
+
{netTotal.toFixed(2)}
+
+
+
+ + {pagination && pagination.totalPages > 1 && ( +
+

+ Showing {(pagination.page - 1) * pagination.pageSize + 1}-{Math.min(pagination.page * pagination.pageSize, pagination.totalItems)} of {pagination.totalItems} +

+
+ + Page {pagination.page} of {pagination.totalPages} + +
+
+ )} + + )} +
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/page.tsx b/Frontend/erp-system/app/dashboard/sales/page.tsx new file mode 100644 index 0000000..5d9fcbd --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/page.tsx @@ -0,0 +1,73 @@ +import Link from "next/link" +import { FileBarChart, FileText, PackageX, ReceiptText, ShoppingCart } from "lucide-react" + +import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +const sections = [ + { + title: "Invoices", + description: "Create and manage sales invoices.", + href: "/dashboard/sales/invoices", + icon: FileText, + }, + { + title: "Slips", + description: "Counter-style sales documents.", + href: "/dashboard/sales/slips", + icon: ShoppingCart, + }, + { + title: "Free Issues", + description: "Promotional free-issue slips.", + href: "/dashboard/sales/free-issues", + icon: PackageX, + }, + { + title: "Reports", + description: "Sales report catalog and query entry point.", + href: "/dashboard/sales/reports", + icon: FileBarChart, + }, +] + +export default function SalesHubPage() { + return ( +
+
+
+
+ + Sales +
+

Sales

+

+ Invoices, slips, free issues, and reporting in one place. +

+
+
+ +
+ {sections.map((section) => { + const Icon = section.icon + return ( + +
+ +
+

{section.title}

+

{section.description}

+
+ Open +
+ + ) + })} +
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/reports/[reportId]/page.tsx b/Frontend/erp-system/app/dashboard/sales/reports/[reportId]/page.tsx new file mode 100644 index 0000000..3b0677c --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/reports/[reportId]/page.tsx @@ -0,0 +1,242 @@ +"use client" + +import { use, useEffect, useRef, useState } from "react" +import Link from "next/link" +import { ArrowLeft, FileBarChart } from "lucide-react" + +import { salesApi } from "@/lib/api/sales" +import { errorMessage } from "@/lib/error-map" +import { Button, buttonVariants } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Skeleton } from "@/components/ui/skeleton" +import { SalesReportDefinition } from "@/types/sales" + +function formatHeader(key: string) { + return key + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") + .replace(/_/g, " ") + .trim() +} + +function formatCell(value: unknown) { + if (value === null || value === undefined) return "" + if (typeof value === "number") return value.toLocaleString("en-LK", { maximumFractionDigits: 2 }) + if (typeof value === "string") { + const isoDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value) || /^\d{4}-\d{2}-\d{2}$/.test(value) + if (isoDate) { + const parsed = new Date(value) + if (!Number.isNaN(parsed.getTime())) { + return new Intl.DateTimeFormat("en-LK", { + year: "numeric", + month: "short", + day: "2-digit", + }).format(parsed) + } + } + return value + } + if (typeof value === "boolean") return value ? "Yes" : "No" + if (Array.isArray(value)) return value.map((item) => formatCell(item)).join(", ") + if (typeof value === "object") return JSON.stringify(value) + return String(value) +} + +const reportColumns: Record = { + "daily-summary": ["date", "invoiceCount", "slipCount", "invoiceSubtotal", "slipSubtotal", "discountTotal", "freeQtyTotal", "taxTotal", "grandTotal"], + "item-summary": ["itemId", "itemName", "soldQty", "freeQty", "grossAmount", "discountTotal", "taxTotal", "netAmount"], + "customer-summary": ["customerId", "customerName", "invoiceCount", "slipCount", "soldQty", "freeQty", "grossAmount", "discountTotal", "taxTotal", "netAmount"], + "warehouse-summary": ["warehouseId", "warehouseName", "invoiceCount", "slipCount", "soldQty", "freeQty", "grossAmount", "discountTotal", "taxTotal", "netAmount"], + "discount-summary": ["documentType", "documentNo", "documentDate", "customerName", "subtotal", "discountTotal", "taxTotal", "netAmount"], + "free-issue-summary": ["documentType", "documentNo", "documentDate", "customerName", "itemId", "itemName", "freeQty", "freeValue", "warehouseId", "warehouseName"], +} + +export default function SalesReportDetailPage({ params }: { params: Promise<{ reportId: string }> }) { + const resolvedParams = use(params) + const [report, setReport] = useState(null) + const [error, setError] = useState(null) + const [loading, setLoading] = useState(true) + const [from, setFrom] = useState("") + const [to, setTo] = useState("") + const [queryLoading, setQueryLoading] = useState(false) + const [rows, setRows] = useState(null) + const [queryError, setQueryError] = useState(null) + const lastAutoRunKey = useRef("") + + useEffect(() => { + setError(null) + setLoading(true) + salesApi.getReport(resolvedParams.reportId) + .then(setReport) + .catch((err) => setError(errorMessage(err))) + .finally(() => setLoading(false)) + }, [resolvedParams.reportId]) + + useEffect(() => { + const now = new Date() + const firstDay = new Date(now.getFullYear(), now.getMonth(), 1) + setFrom(firstDay.toISOString().slice(0, 10)) + setTo(now.toISOString().slice(0, 10)) + }, [resolvedParams.reportId]) + + useEffect(() => { + if (!report || !from || !to) return + + const runKey = `${report.id}:${from}:${to}` + if (lastAutoRunKey.current === runKey) return + + lastAutoRunKey.current = runKey + setQueryError(null) + setQueryLoading(true) + salesApi + .queryReport({ + reportType: report.id, + from, + to, + }) + .then((response) => setRows(response.rows)) + .catch((err) => { + setRows(null) + setQueryError(errorMessage(err)) + }) + .finally(() => setQueryLoading(false)) + }, [report, from, to]) + + const runReport = () => { + if (!report) return + if (!from || !to) { + setQueryError("Select both from and to dates.") + setRows(null) + return + } + + lastAutoRunKey.current = `${report.id}:${from}:${to}` + setQueryError(null) + setQueryLoading(true) + salesApi + .queryReport({ + reportType: report.id, + from, + to, + }) + .then((response) => setRows(response.rows)) + .catch((err) => { + setRows(null) + setQueryError(errorMessage(err)) + }) + .finally(() => setQueryLoading(false)) + } + + const columns = report ? (reportColumns[report.id] ?? (rows && rows.length > 0 ? Object.keys(rows[0] as Record) : [])) : [] + + return ( +
+
+ + + +
+

Report Details

+

Metadata for the selected sales report.

+
+
+ + {error &&
{error}
} + + {!error && loading && } + + {!error && !loading && report === null && ( +
+ Report metadata could not be loaded. The report id may be invalid, or your session may have expired. +
+ )} + + {!error && report && ( +
+ + +
+
+ +
+
+ {report.name} +

{report.id}

+
+
+
+ +

{report.description}

+
+
+ + {resolvedParams.reportId !== "daily-summary" && ( + + +
+ + setFrom(e.target.value)} /> +
+
+ + setTo(e.target.value)} /> +
+
+ +
+
+
+ )} + + {queryError &&
{queryError}
} + + {queryLoading && } + + {!queryLoading && rows && rows.length === 0 && ( +
+ No rows returned for the selected date range. +
+ )} + + {!queryLoading && rows && rows.length > 0 && ( + + + Report Results + + + + + + {columns.map((key) => ( + + ))} + + + + {rows.map((row, index) => { + const record = row as Record + return ( + + {columns.map((key) => ( + + ))} + + ) + })} + +
+ {formatHeader(key)} +
+ {formatCell(record[key])} +
+
+
+ )} +
+ )} +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/reports/page.tsx b/Frontend/erp-system/app/dashboard/sales/reports/page.tsx new file mode 100644 index 0000000..3a6797f --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/reports/page.tsx @@ -0,0 +1,81 @@ +"use client" + +import { useEffect, useState } from "react" +import Link from "next/link" +import { BarChart3, CalendarRange, FileBarChart, ShoppingCart, type LucideIcon } from "lucide-react" + +import { salesApi } from "@/lib/api/sales" +import { errorMessage } from "@/lib/error-map" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { SalesReportDefinition } from "@/types/sales" + +const reportIcons: Record = { + sales: BarChart3, + invoice: FileBarChart, + product: ShoppingCart, + period: CalendarRange, +} + +export default function SalesReportsPage() { + const [reports, setReports] = useState(null) + const [error, setError] = useState(null) + + useEffect(() => { + setError(null) + salesApi.listReports().then(setReports).catch((err) => setError(errorMessage(err))) + }, []) + + return ( +
+
+

Sales Reports

+

+ Card-based report hub with the same layout and handling style used across stock management. +

+
+ + {error &&
{error}
} + + {!error && reports === null && ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ )} + + {!error && reports && reports.length === 0 && ( +
+ +

No sales reports are available.

+
+ )} + + {!error && reports && reports.length > 0 && ( +
+ {reports.map((report) => ( + + + +
+
+ {(() => { + const Icon = reportIcons[report.id.toLowerCase()] ?? FileBarChart + return + })()} +
+ {report.name} +
+
+ +

{report.description}

+
+
+ + ))} +
+ )} +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/slips/[id]/page.tsx b/Frontend/erp-system/app/dashboard/sales/slips/[id]/page.tsx new file mode 100644 index 0000000..3d7f552 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/slips/[id]/page.tsx @@ -0,0 +1,456 @@ +"use client" + +import { use, useEffect, useMemo, useState } from "react" +import Link from "next/link" +import { ArrowLeft, Minus, Plus, Save, Send, X } from "lucide-react" + +import { salesApi } from "@/lib/api/sales" +import { customersApi } from "@/lib/api/customers" +import { itemsApi } from "@/lib/api/items" +import { uomsApi } from "@/lib/api/uoms" +import { warehousesApi } from "@/lib/api/warehouses" +import { usersApi } from "@/lib/api/users" +import { errorMessage } from "@/lib/error-map" +import { Button, buttonVariants } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +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 { cn } from "@/lib/utils" +import { getSuggestedUnitPrice } from "@/lib/sales-line-utils" +import { FreeIssuePromotionSuggestions } from "@/components/sales/FreeIssuePromotionSuggestions" +import { Customer } from "@/types/customers" +import { ItemListItem, Uom, Warehouse } from "@/types/master-data" +import { ManagedUser } from "@/types/users" +import { CreateSalesSlipLineRequest, SalesFreeIssueSuggestion, SalesSlip, SalesSlipPostingCheck } from "@/types/sales" +import { toast } from "@/components/ui/toast" + +type Line = CreateSalesSlipLineRequest & { key: string } + +const money = new Intl.NumberFormat("en-LK", { + style: "currency", + currency: "LKR", + minimumFractionDigits: 2, +}) + +const blankLine = (key: string): Line => ({ + key, + itemId: 0, + uomId: 0, + warehouseId: 0, + qty: 1, + freeQty: 0, + unitPrice: null, + allowManualPriceOverride: true, + discountMode: "Percentage", + discountPct: 0, + discountAmount: 0, + discountValue: 0, + taxPct: 0, + isFreeIssue: false, + parentLineId: null, +}) + +export default function SalesSlipDetailPage({ params }: { params: Promise<{ id: string }> }) { + const resolvedParams = use(params) + const slipId = Number(resolvedParams.id) + const [slip, setSlip] = useState(null) + const [etag, setEtag] = useState(null) + const [customers, setCustomers] = useState([]) + const [items, setItems] = useState([]) + const [uoms, setUoms] = useState([]) + const [warehouses, setWarehouses] = useState([]) + const [users, setUsers] = useState([]) + const [promotionSuggestion, setPromotionSuggestion] = useState(null) + const [postingCheck, setPostingCheck] = useState(null) + const [customerId, setCustomerId] = useState(null) + const [warehouseId, setWarehouseId] = useState(null) + const [cashierUserId, setCashierUserId] = useState(null) + const [lines, setLines] = useState([blankLine("line-1")]) + const [error, setError] = useState(null) + const [saving, setSaving] = useState(false) + const [actionBusy, setActionBusy] = useState<"post" | "cancel" | null>(null) + + useEffect(() => { + if (!Number.isFinite(slipId)) { + setError(`Invalid slip id '${resolvedParams.id}'.`) + return + } + Promise.all([ + customersApi.list({ pageSize: 200 }), + itemsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200 }), + warehousesApi.list({ pageSize: 200 }), + usersApi.list({ pageSize: 200 }), + salesApi.getSlip(slipId), + ]) + .then(([cust, itemRes, uomRes, whRes, userRes, doc]) => { + setCustomers(cust.items) + setItems(itemRes.items) + setUoms(uomRes.items) + setWarehouses(whRes.items) + setUsers(userRes.items) + setSlip(doc.data) + setEtag(doc.etag) + setCustomerId(doc.data.customerId) + setWarehouseId(doc.data.warehouseId) + setCashierUserId(doc.data.cashierUserId) + setPromotionSuggestion(null) + setLines( + doc.data.lines.map((line) => ({ + key: String(line.salesSlipLineId), + itemId: line.itemId, + uomId: line.uomId, + warehouseId: line.warehouseId, + qty: line.qty, + freeQty: line.freeQty, + unitPrice: line.unitPrice, + allowManualPriceOverride: true, + discountMode: line.discountMode, + discountPct: line.discountPct, + discountAmount: line.discountAmount, + discountValue: 0, + taxPct: line.taxPct, + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId, + })) + ) + }) + .then(async () => { + try { + const suggestions = await salesApi.getFreeIssueSuggestions(slipId) + setPromotionSuggestion(suggestions) + } catch { + setPromotionSuggestion(null) + } + }) + .catch((err) => setError(errorMessage(err))) + }, [resolvedParams.id, slipId]) + + useEffect(() => { + if (!slip || slip.status !== "Draft") { + setPostingCheck(null) + return + } + salesApi + .checkSlipPosting(slipId) + .then((result) => setPostingCheck(result)) + .catch(() => setPostingCheck(null)) + }, [slip, slipId]) + + const subtotal = useMemo( + () => lines.reduce((sum, line) => sum + Number(line.qty || 0) * Number(line.unitPrice ?? 0), 0), + [lines] + ) + + function updateLine(key: string, patch: Partial) { + setLines((prev) => prev.map((line) => (line.key === key ? { ...line, ...patch } : line))) + } + + function selectItem(key: string, itemId: number) { + updateLine(key, { + itemId, + unitPrice: getSuggestedUnitPrice(items, itemId), + }) + } + + function addLine() { + setLines((prev) => [...prev, blankLine(`line-${Date.now()}`)]) + } + + function removeLine(key: string) { + setLines((prev) => (prev.length === 1 ? prev : prev.filter((line) => line.key !== key))) + } + + async function save() { + if (!customerId || !warehouseId || !cashierUserId || !etag) return + setSaving(true) + setError(null) + try { + const payload = { + customerId, + warehouseId, + cashierUserId, + lines: lines.map((line) => ({ + itemId: Number(line.itemId), + uomId: Number(line.uomId), + warehouseId: Number(line.warehouseId), + qty: Number(line.qty), + freeQty: Number(line.freeQty), + unitPrice: line.unitPrice === null ? null : Number(line.unitPrice), + allowManualPriceOverride: line.allowManualPriceOverride, + discountMode: line.discountMode, + discountPct: Number(line.discountPct), + discountAmount: Number(line.discountAmount), + discountValue: Number(line.discountValue), + taxPct: Number(line.taxPct), + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId || null, + })), + } + const updated = await salesApi.updateSlip(slipId, payload, etag) + setSlip(updated.data) + setEtag(updated.etag) + toast.success("Slip saved", updated.data.slipNo) + } catch (err) { + setError(errorMessage(err)) + } finally { + setSaving(false) + } + } + + async function post() { + if (!postingCheck?.canPost) { + setError("Resolve stock shortages before posting this slip.") + return + } + setActionBusy("post") + setError(null) + try { + const posted = await salesApi.postSlip(slipId) + setSlip(posted) + toast.success("Slip posted", posted.slipNo) + } catch (err) { + setError(errorMessage(err)) + } finally { + setActionBusy(null) + } + } + + async function cancel() { + setActionBusy("cancel") + setError(null) + try { + const cancelled = await salesApi.cancelSlip(slipId) + setSlip(cancelled) + toast.success("Slip cancelled", cancelled.slipNo) + } catch (err) { + setError(errorMessage(err)) + } finally { + setActionBusy(null) + } + } + + if (error && !slip) return
{error}
+ if (!slip) return
{error ?? "Slip data is loading or unavailable."}
+ + const locked = slip.status !== "Draft" + const canPost = slip.status === "Draft" && (postingCheck?.canPost ?? true) && actionBusy === null + + return ( +
+
+
+ + + +
+

Sales Slip

+

{slip.slipNo} · {slip.status}

+
+
+
+ + + +
+
+ + {error &&
{error}
} + +
+
+
+
+
Sales Slip
+

{slip.slipNo}

+
+ Status: + + {slip.status} + + Date: {new Date(slip.slipDate).toLocaleDateString()} + Cashier: {users.find((u) => u.userId === slip.cashierUserId)?.displayName ?? `#${slip.cashierUserId}`} +
+
+
+
{customers.find((c) => c.customerId === slip.customerId)?.displayName ?? customers.find((c) => c.customerId === slip.customerId)?.name ?? slip.customerSnapshotName}
+
Warehouse: {warehouses.find((w) => w.warehouseId === slip.warehouseId)?.name ?? `#${slip.warehouseId}`}
+
Code: {warehouses.find((w) => w.warehouseId === slip.warehouseId)?.code ?? slip.warehouseId}
+
+
+
+ +
+
+
Customer
+
{slip.customerSnapshotName}
+
Customer ID: {slip.customerId}
+
+
+
Warehouse
+
{warehouses.find((w) => w.warehouseId === slip.warehouseId)?.name ?? `#${slip.warehouseId}`}
+
Code: {warehouses.find((w) => w.warehouseId === slip.warehouseId)?.code ?? slip.warehouseId}
+
+
+
Totals
+
+
Subtotal
+
{money.format(slip.totals.subtotal)}
+
Discount
+
{money.format(slip.totals.discountTotal)}
+
Free qty
+
{slip.totals.freeQtyTotal.toFixed(0)}
+
Grand total
+
{money.format(slip.totals.grandTotal)}
+
+
+
+ +
+ + + + Item + UOM + Qty + Free + Unit price + Line total + + + + {slip.lines.map((line) => ( + + +
{line.description}
+
SKU {items.find((i) => i.itemId === line.itemId)?.sku ?? `#${line.itemId}`}
+
+ {uoms.find((u) => u.uomId === line.uomId)?.name ?? `#${line.uomId}`} + {line.qty.toFixed(0)} + {line.freeQty > 0 ? line.freeQty.toFixed(0) : "—"} + {money.format(line.unitPrice)} + {money.format(line.lineTotal)} +
+ ))} +
+
+
+
+ + {slip.status === "Draft" && postingCheck && !postingCheck.canPost ? ( +
+
Stock shortage detected before posting
+
This slip cannot be posted until every line has enough available stock in the selected warehouse.
+
+ + + + + + + + + + + + {postingCheck.issues.map((issue) => ( + + + + + + + + ))} + +
ItemWarehouseRequestedAvailableShort
+
{issue.itemSku}
+
{issue.itemName}{issue.isFreeIssue ? " · free issue" : ""}
+
{issue.warehouseId}{issue.requestedQty.toFixed(0)}{issue.availableQty.toFixed(0)}{issue.shortQty.toFixed(0)}
+
+
+ ) : null} + + {slip.status === "Draft" ? ( + <> +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+

Lines

+ +
+ + + + Item + UOM + Qty + Free + Unit price + + + + + {lines.map((line) => ( + + + + + + + + updateLine(line.key, { qty: Number(e.target.value) })} disabled={locked} /> + updateLine(line.key, { freeQty: Number(e.target.value) })} disabled={locked} /> + updateLine(line.key, { unitPrice: e.target.value === "" ? null : Number(e.target.value) })} disabled={locked} /> + + + ))} + +
+
Subtotal: {subtotal.toFixed(2)}
+
+ + + +
+ Back + + +
+ + ) : null} +
+ ) +} + diff --git a/Frontend/erp-system/app/dashboard/sales/slips/new/page.tsx b/Frontend/erp-system/app/dashboard/sales/slips/new/page.tsx new file mode 100644 index 0000000..b3b3088 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/slips/new/page.tsx @@ -0,0 +1,440 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import { useRouter } from "next/navigation" +import Link from "next/link" +import { ArrowLeft, Minus, Plus, Save, Trash2 } from "lucide-react" + +import { Button, buttonVariants } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Badge } from "@/components/ui/badge" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { cn } from "@/lib/utils" +import { getSuggestedUnitPrice } from "@/lib/sales-line-utils" +import { errorMessage } from "@/lib/error-map" +import { salesApi } from "@/lib/api/sales" +import { customersApi } from "@/lib/api/customers" +import { itemsApi } from "@/lib/api/items" +import { uomsApi } from "@/lib/api/uoms" +import { warehousesApi } from "@/lib/api/warehouses" +import { usersApi } from "@/lib/api/users" +import { Customer } from "@/types/customers" +import { ItemListItem, Uom, Warehouse } from "@/types/master-data" +import { ManagedUser } from "@/types/users" +import { CreateSalesSlipLineRequest, CreateSalesSlipRequest } from "@/types/sales" +import { toast } from "@/components/ui/toast" + +type Line = CreateSalesSlipLineRequest & { key: string } + +const blankLine = (key: string): Line => ({ + key, + itemId: 0, + uomId: 0, + warehouseId: 0, + qty: 1, + freeQty: 0, + unitPrice: null, + allowManualPriceOverride: true, + discountMode: "Percentage", + discountPct: 0, + discountAmount: 0, + discountValue: 0, + taxPct: 0, + isFreeIssue: false, + parentLineId: null, +}) + +const lkr = new Intl.NumberFormat("en-LK", { + style: "currency", + currency: "LKR", + minimumFractionDigits: 2, +}) + +export default function NewSalesSlipPage() { + const router = useRouter() + const [customers, setCustomers] = useState([]) + const [items, setItems] = useState([]) + const [uoms, setUoms] = useState([]) + const [warehouses, setWarehouses] = useState([]) + const [users, setUsers] = useState([]) + const [customerId, setCustomerId] = useState(null) + const [warehouseId, setWarehouseId] = useState(null) + const [cashierUserId, setCashierUserId] = useState(null) + const [lines, setLines] = useState([blankLine("line-1")]) + const [loading, setLoading] = useState(true) + const [submitError, setSubmitError] = useState(null) + const [saving, setSaving] = useState(false) + + useEffect(() => { + Promise.all([ + customersApi.list({ pageSize: 200 }), + itemsApi.list({ pageSize: 200 }), + uomsApi.list({ pageSize: 200 }), + warehousesApi.list({ pageSize: 200 }), + usersApi.list({ pageSize: 200 }), + ]) + .then(([cust, itemRes, uomRes, whRes, userRes]) => { + setCustomers(cust.items) + setItems(itemRes.items) + setUoms(uomRes.items) + setWarehouses(whRes.items) + setUsers(userRes.items) + setCustomerId(cust.items[0]?.customerId ?? null) + setWarehouseId(whRes.items[0]?.warehouseId ?? null) + setCashierUserId(userRes.items[0]?.userId ?? null) + setLines([ + { + ...blankLine("line-1"), + itemId: itemRes.items[0]?.itemId ?? 0, + uomId: itemRes.items[0]?.baseUomId ?? uomRes.items[0]?.uomId ?? 0, + warehouseId: whRes.items[0]?.warehouseId ?? 0, + unitPrice: getSuggestedUnitPrice(itemRes.items, itemRes.items[0]?.itemId), + }, + ]) + }) + .catch((err) => setSubmitError(errorMessage(err))) + .finally(() => setLoading(false)) + }, []) + + function updateLine(key: string, patch: Partial) { + setLines((prev) => prev.map((line) => (line.key === key ? { ...line, ...patch } : line))) + } + + function selectItem(key: string, itemId: number) { + const item = items.find((candidate) => candidate.itemId === itemId) + updateLine(key, { + itemId, + uomId: item?.baseUomId ?? 0, + unitPrice: getSuggestedUnitPrice(items, itemId), + }) + } + + function addLine() { + setLines((prev) => [...prev, blankLine(`line-${Date.now()}`)]) + } + + function removeLine(key: string) { + setLines((prev) => (prev.length === 1 ? prev : prev.filter((line) => line.key !== key))) + } + + const grossTotal = useMemo( + () => lines.reduce((sum, line) => sum + Number(line.unitPrice ?? 0) * Number(line.qty || 0), 0), + [lines], + ) + const discountTotal = useMemo( + () => + lines.reduce((sum, line) => { + const gross = Number(line.unitPrice ?? 0) * Number(line.qty || 0) + const mode = String(line.discountMode) + return sum + (mode === "Amount" ? Number(line.discountAmount || 0) : gross * (Number(line.discountPct || 0) / 100)) + }, 0), + [lines], + ) + const taxTotal = useMemo( + () => + lines.reduce((sum, line) => { + const gross = Number(line.unitPrice ?? 0) * Number(line.qty || 0) + const mode = String(line.discountMode) + const discount = mode === "Amount" ? Number(line.discountAmount || 0) : gross * (Number(line.discountPct || 0) / 100) + const taxable = Math.max(0, gross - discount) + return sum + taxable * (Number(line.taxPct || 0) / 100) + }, 0), + [lines], + ) + const netTotal = Math.max(0, grossTotal - discountTotal) + const payableTotal = netTotal + taxTotal + + async function submit() { + if (!customerId || !warehouseId || !cashierUserId) return setSubmitError("Select customer, warehouse, and cashier.") + if (lines.some((line) => Number(line.itemId) === 0)) return setSubmitError("Select an item for every line.") + if (lines.some((line) => Number(line.warehouseId) === 0)) return setSubmitError("Select a warehouse for every line.") + if (lines.some((line) => Number(line.uomId) === 0)) return setSubmitError("Select a valid UOM for every line.") + + const payload: CreateSalesSlipRequest = { + customerId, + warehouseId, + cashierUserId, + lines: lines.map((line) => ({ + itemId: Number(line.itemId), + uomId: Number(line.uomId), + warehouseId: Number(line.warehouseId), + qty: Number(line.qty), + freeQty: Number(line.freeQty), + unitPrice: line.unitPrice === null ? null : Number(line.unitPrice), + allowManualPriceOverride: line.allowManualPriceOverride, + discountMode: line.discountMode, + discountPct: Number(line.discountPct), + discountAmount: Number(line.discountAmount), + discountValue: Number(line.discountValue), + taxPct: Number(line.taxPct), + isFreeIssue: line.isFreeIssue, + parentLineId: line.parentLineId || null, + })), + } + + setSaving(true) + setSubmitError(null) + try { + const created = await salesApi.createSlip(payload) + toast.success("Slip created", created.data.slipNo) + router.push(`/dashboard/sales/slips/${created.data.salesSlipId}`) + } catch (err) { + setSubmitError(errorMessage(err)) + } finally { + setSaving(false) + } + } + + if (loading) { + return
Loading masters...
+ } + + return ( +
+
+
+ + + +
+

New sales slip

+

Create counter sales slips from the live backend.

+
+
+
+ + +
+
+ + {submitError ?
{submitError}
: null} + +
+

Slip header

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + Draft + + {lkr.format(payableTotal)} +
+
+
+
+ +
+
+

Slip lines

+ +
+
+ + + + # + Item + UOM + Qty + Free + Unit price + Discount % + Line total + + + + + {lines.map((line, idx) => { + const lineGross = Number(line.unitPrice ?? 0) * Number(line.qty || 0) + const lineDiscount = + String(line.discountMode) === "Amount" + ? Number(line.discountAmount || 0) + : lineGross * (Number(line.discountPct || 0) / 100) + const lineNet = Math.max(0, lineGross - lineDiscount) + + return ( + + {idx + 1} + + + + + + + + updateLine(line.key, { qty: Number(e.target.value) })} + className="h-9 w-24 text-right font-mono text-sm tabular-nums" + /> + + + updateLine(line.key, { freeQty: Number(e.target.value) })} + className="h-9 w-24 text-right font-mono text-sm tabular-nums" + /> + + + updateLine(line.key, { unitPrice: e.target.value === "" ? null : Number(e.target.value) })} + className="h-9 w-28 text-right font-mono text-sm tabular-nums" + /> + + + updateLine(line.key, { discountPct: Number(e.target.value) || 0 })} + className="h-9 w-24 text-right font-mono text-sm tabular-nums" + /> + + {lkr.format(lineNet)} + + + + + ) + })} + +
+
+
+ +
+
+

Slip notes

+
    +
  • Cashier posting follows the standard sales-slip workflow.
  • +
  • Free issue lines are captured from the slip itself, not from a separate register here.
  • +
  • Use the slip detail page after save to post or cancel.
  • +
+
+ +
+ {[ + ["Gross", lkr.format(grossTotal)], + ["Discount", `-${lkr.format(discountTotal)}`], + ["Net", lkr.format(netTotal)], + ["Tax", lkr.format(taxTotal)], + ].map(([k, v]) => ( +
+
{k}
+
{v}
+
+ ))} +
+
Payable
+
{lkr.format(payableTotal)}
+
+
+
+ +
+ + Cancel + + +
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/sales/slips/page.tsx b/Frontend/erp-system/app/dashboard/sales/slips/page.tsx new file mode 100644 index 0000000..98806e3 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/sales/slips/page.tsx @@ -0,0 +1,229 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import Link from "next/link" +import { ChevronLeft, ChevronRight, Eye, Filter, Package2, Plus, Printer } from "lucide-react" + +import { salesApi } from "@/lib/api/sales" +import { errorMessage } from "@/lib/error-map" +import { Button, buttonVariants } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Input } from "@/components/ui/input" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { cn } from "@/lib/utils" +import { PaginationMeta } from "@/types/common" +import { SalesSlipStatus, SalesSlipSummary } from "@/types/sales" + +type StatusFilter = SalesSlipStatus | "All" + +const PAGE_SIZE = 10 +const tabs = ["All", "Draft", "Posted", "Cancelled"] as const + +function statusClass(status: SalesSlipStatus) { + switch (status) { + case "Draft": + return "border-amber-200 bg-amber-50 text-amber-800" + case "Posted": + return "border-emerald-200 bg-emerald-50 text-emerald-800" + case "Cancelled": + return "border-rose-200 bg-rose-50 text-rose-800" + } +} + +export default function SalesSlipsPage() { + const [rows, setRows] = useState(null) + const [pagination, setPagination] = useState(null) + const [error, setError] = useState(null) + const [status, setStatus] = useState("All") + const [searchInput, setSearchInput] = useState("") + const [query, setQuery] = useState("") + const [page, setPage] = useState(1) + + useEffect(() => { + const timeout = setTimeout(() => setQuery(searchInput.trim()), 300) + return () => clearTimeout(timeout) + }, [searchInput]) + + useEffect(() => setPage(1), [query, status]) + + useEffect(() => { + setError(null) + salesApi + .listSlips({ page, pageSize: PAGE_SIZE, status: status === "All" ? undefined : status }) + .then((res) => { + setRows(res.items) + setPagination(res.pagination) + }) + .catch((err) => setError(errorMessage(err))) + }, [page, status]) + + const visibleRows = useMemo( + () => + rows?.filter((row) => + `${row.slipNo} ${row.customerSnapshotName}`.toLowerCase().includes(query.toLowerCase()) + ) ?? [], + [rows, query] + ) + const hasFilters = status !== "All" || query.length > 0 + 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) + + return ( +
+
+
+

Sales Slips

+

Counter sales register with posting and cancellation flow.

+
+
+ + + + New Slip + +
+
+ +
+
+
+ {tabs.map((t) => ( + + ))} +
+ +
+ setSearchInput(e.target.value)} + placeholder="Filter by customer or slip number" + className="h-12 w-full lg:max-w-sm" + /> + +
+
+ + {error &&
{error}
} + + {!error && rows === null && ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+ )} + + {!error && rows !== null && visibleRows.length === 0 && ( +
+ +

{hasFilters ? "No slips match your filters." : "No slips yet."}

+
+ )} + + {!error && rows !== null && visibleRows.length > 0 && ( + <> +
+ + + + Slip + Customer + Date + Status + Lines + Gross + Discount + Net + Actions + + + + {visibleRows.map((row) => ( + + + + {row.slipNo} + + + {row.customerSnapshotName} + {new Date(row.createdAt).toLocaleDateString()} + + + {row.status} + + + {row.totals.freeQtyTotal.toFixed(2)} + {row.totals.subtotal.toFixed(2)} + -{row.totals.discountTotal.toFixed(2)} + {row.totals.grandTotal.toFixed(2)} + +
+ + + +
+
+
+ ))} +
+
+
+ +
+
+
+
Gross total
+
{grossTotal.toFixed(2)}
+
+
+
Discount total
+
-{discountTotal.toFixed(2)}
+
+
+
Net total
+
{netTotal.toFixed(2)}
+
+
+
+ + {pagination && pagination.totalPages > 1 && ( +
+

+ Showing {(pagination.page - 1) * pagination.pageSize + 1}-{Math.min(pagination.page * pagination.pageSize, pagination.totalItems)} of {pagination.totalItems} +

+
+ + Page {pagination.page} of {pagination.totalPages} + +
+
+ )} + + )} +
+
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/settings/company-profile/page.tsx b/Frontend/erp-system/app/dashboard/settings/company-profile/page.tsx new file mode 100644 index 0000000..a149a86 --- /dev/null +++ b/Frontend/erp-system/app/dashboard/settings/company-profile/page.tsx @@ -0,0 +1,135 @@ +"use client" + +import { useEffect, useState } from "react" +import Link from "next/link" +import { ArrowLeft, Building2, Save } from "lucide-react" + +import { companyApi } from "@/lib/api/company" +import { errorMessage } from "@/lib/error-map" +import { cn } from "@/lib/utils" +import { CompanyProfile } from "@/types/company" + +import { buttonVariants, Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Skeleton } from "@/components/ui/skeleton" +import { toast } from "@/components/ui/toast" + +export default function CompanyProfilePage() { + const [profile, setProfile] = useState(null) + const [etag, setEtag] = useState(null) + const [error, setError] = useState(null) + const [saving, setSaving] = useState(false) + + useEffect(() => { + companyApi + .getProfile() + .then((res) => { + setProfile(res.data) + setEtag(res.etag) + }) + .catch((err) => setError(errorMessage(err))) + }, []) + + function patch(key: K, value: CompanyProfile[K]) { + setProfile((prev) => (prev ? { ...prev, [key]: value } : prev)) + } + + async function save() { + if (!profile || !etag) return + setSaving(true) + setError(null) + try { + const updated = await companyApi.updateProfile(profile, etag) + setProfile(updated.data) + setEtag(updated.etag) + toast.success("Company profile saved", updated.data.legalName) + } catch (err) { + setError(errorMessage(err)) + } finally { + setSaving(false) + } + } + + return ( +
+
+ + + +
+

Company Profile

+

Invoice header, tax details, logo, and bank information.

+
+
+ + {error &&
{error}
} + {!error && !profile && } + + {!error && profile && ( +
+
+ +

Invoice Header

+
+ +
+ patch("legalName", v)} /> + patch("tradeName", v)} /> + patch("logoUrl", v)} /> + patch("taxRegistrationNo", v)} /> + patch("vatRegistrationNo", v)} /> + patch("phone", v)} /> + patch("email", v)} /> + patch("city", v)} /> + patch("country", v)} /> + patch("addressLine1", v)} /> + patch("addressLine2", v)} /> +
+ +
+ +
+ +

Bank Details

+
+ +
+ patch("bankName", v)} /> + patch("bankBranch", v)} /> + patch("accountName", v)} /> + patch("accountNumber", v)} /> + patch("swiftCode", v)} /> + patch("footerNote", v)} /> +
+ +
+ + Cancel + + +
+
+ )} +
+ ) +} + +function Field({ + label, + value, + onChange, +}: { + label: string + value: string + onChange: (value: string) => void +}) { + return ( +
+ + onChange(e.target.value)} /> +
+ ) +} diff --git a/Frontend/erp-system/app/dashboard/settings/page.tsx b/Frontend/erp-system/app/dashboard/settings/page.tsx index dbe71d9..70fe245 100644 --- a/Frontend/erp-system/app/dashboard/settings/page.tsx +++ b/Frontend/erp-system/app/dashboard/settings/page.tsx @@ -1,5 +1,5 @@ import Link from "next/link" -import { ShieldCheck, SlidersHorizontal, Users } from "lucide-react" +import { Building2, ShieldCheck, SlidersHorizontal, Users } from "lucide-react" const cards = [ { @@ -20,6 +20,12 @@ const cards = [ icon: SlidersHorizontal, description: "Configure product and master-data options", }, + { + title: "Company Profile", + href: "/dashboard/settings/company-profile", + icon: Building2, + description: "Invoice header, tax, and bank details", + }, ] export default function SettingsPage() { diff --git a/Frontend/erp-system/app/globals.css b/Frontend/erp-system/app/globals.css index a0ee55b..b251b5b 100644 --- a/Frontend/erp-system/app/globals.css +++ b/Frontend/erp-system/app/globals.css @@ -219,3 +219,34 @@ } } +@media print { + body { + background: white !important; + color: black !important; + } + + .print\:hidden { + display: none !important; + } + + .invoice-sheet { + border: 0 !important; + box-shadow: none !important; + padding: 0 !important; + } + + .invoice-header { + break-inside: avoid; + } + + .invoice-sheet table { + width: 100% !important; + } + + .invoice-sheet tr, + .invoice-sheet td, + .invoice-sheet th { + break-inside: avoid; + } +} + diff --git a/Frontend/erp-system/app/layout.tsx b/Frontend/erp-system/app/layout.tsx index b1c1f57..73ba6c1 100644 --- a/Frontend/erp-system/app/layout.tsx +++ b/Frontend/erp-system/app/layout.tsx @@ -1,7 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; -import { ThemeProvider } from "next-themes"; -import { TooltipProvider } from "@/components/ui/tooltip"; +import { Providers } from "@/components/providers"; import "./globals.css"; const geistSans = Geist({ @@ -31,14 +30,7 @@ export default function RootLayout({ suppressHydrationWarning > - - {children} - + {children} ); diff --git a/Frontend/erp-system/app/login/page.tsx b/Frontend/erp-system/app/login/page.tsx index 612976d..345bfa1 100644 --- a/Frontend/erp-system/app/login/page.tsx +++ b/Frontend/erp-system/app/login/page.tsx @@ -31,6 +31,8 @@ function LoginForm() { const [showPassword, setShowPassword] = useState(false) const [remember, setRemember] = useState(false) const [submitError, setSubmitError] = useState(null) + const returnTo = searchParams.get("next") + const sessionNotice = returnTo ? "Your session is missing or expired. Sign in again to continue." : null const form = useForm({ resolver: zodResolver(loginSchema), @@ -85,6 +87,11 @@ function LoginForm() {

Sign in to your account

Access your ERP dashboard and manage your business

+ {sessionNotice ? ( +
+ {sessionNotice} +
+ ) : null}
diff --git a/Frontend/erp-system/components/Layouts/AppSidebar.tsx b/Frontend/erp-system/components/Layouts/AppSidebar.tsx index 8b52e99..a286926 100644 --- a/Frontend/erp-system/components/Layouts/AppSidebar.tsx +++ b/Frontend/erp-system/components/Layouts/AppSidebar.tsx @@ -33,6 +33,7 @@ import { PlayCircle, PieChart, Receipt, + ReceiptText, Ruler, Scale, Settings, @@ -99,6 +100,20 @@ const navItems: { { title: "RFQs", code: "procurement.rfqs", href: "/dashboard/procurement/rfqs", icon: FileText }, ], }, + { + title: "Sales", + code: "sales", + href: "/dashboard/sales", + landingHref: "/dashboard/sales/invoices", + icon: ReceiptText, + chevron: true, + children: [ + { title: "Invoices", code: "sales.invoices", href: "/dashboard/sales/invoices", icon: FileText }, + { title: "Slips", code: "sales.slips", href: "/dashboard/sales/slips", icon: ShoppingCart }, + { title: "Free Issues", code: "sales.free-issues", href: "/dashboard/sales/free-issues", icon: PackageX }, + { title: "Reports", code: "sales.reports", href: "/dashboard/sales/reports", icon: FileBarChart }, + ], + }, { title: "Receiving", code: "receiving", href: "/dashboard/receiving/grn", icon: PackageCheck, chevron: true }, { title: "Stock", code: "stock", href: "/dashboard/stock", icon: Warehouse, chevron: true }, { title: "Warehouses", code: "warehouses", href: "/dashboard/warehouse", icon: Building2, chevron: true }, @@ -386,14 +401,14 @@ export function AppSidebar() { // 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" and "production" are exempted from that check (frontend-only): no + // "procurement", "hrm", "sales" and "production" are exempted from that check (frontend-only): no // role is currently seeded with NAV:procurement/NAV:hrm/NAV:production or their children // server-side, 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", "hrm", "production"]) + const bypassCodes = new Set(["procurement", "sales", "hrm", "production"]) const visibleItems = loading ? [] : navItems diff --git a/Frontend/erp-system/components/Layouts/Header.tsx b/Frontend/erp-system/components/Layouts/Header.tsx index 7e0942d..c8b4046 100644 --- a/Frontend/erp-system/components/Layouts/Header.tsx +++ b/Frontend/erp-system/components/Layouts/Header.tsx @@ -111,6 +111,7 @@ function titleFromPath(pathname: string) { if (pathname === "/dashboard/products/brands") return "Brands" if (pathname === "/dashboard/products/item-types") return "Item Types" if (pathname === "/dashboard/products/settings") return "Product Configuration" + if (pathname === "/dashboard/settings/company-profile") return "Company Profile" if (/^\/dashboard\/products\/[^/]+$/.test(pathname)) return "Item" const segment = pathname.split("/").filter(Boolean).pop() ?? "dashboard" diff --git a/Frontend/erp-system/components/dashboard/recent-orders-table.tsx b/Frontend/erp-system/components/dashboard/recent-orders-table.tsx index 3bf1829..45c5fe6 100644 --- a/Frontend/erp-system/components/dashboard/recent-orders-table.tsx +++ b/Frontend/erp-system/components/dashboard/recent-orders-table.tsx @@ -29,9 +29,9 @@ const statusStyles: Record = { } // Use a fixed locale to avoid hydration mismatches between server and client -const currency = new Intl.NumberFormat("en-US", { +const currency = new Intl.NumberFormat("en-LK", { style: "currency", - currency: "USD", + currency: "LKR", }) const columns: DataTableColumn[] = [ diff --git a/Frontend/erp-system/components/providers.tsx b/Frontend/erp-system/components/providers.tsx new file mode 100644 index 0000000..e3dd194 --- /dev/null +++ b/Frontend/erp-system/components/providers.tsx @@ -0,0 +1,13 @@ +"use client" + +import { ThemeProvider } from "next-themes" + +import { TooltipProvider } from "@/components/ui/tooltip" + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/Frontend/erp-system/components/sales/FreeIssuePromotionSuggestions.tsx b/Frontend/erp-system/components/sales/FreeIssuePromotionSuggestions.tsx new file mode 100644 index 0000000..03d0387 --- /dev/null +++ b/Frontend/erp-system/components/sales/FreeIssuePromotionSuggestions.tsx @@ -0,0 +1,65 @@ +"use client" + +import Link from "next/link" +import { Lightbulb, PackageCheck } from "lucide-react" + +import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" +import { SalesFreeIssueSuggestion } from "@/types/sales" + +export function FreeIssuePromotionSuggestions({ suggestion }: { suggestion: SalesFreeIssueSuggestion | null }) { + if (!suggestion || suggestion.lines.length === 0) { + return ( +
+ No free-issue promotion suggestions were generated for this slip yet. +
+ ) + } + + return ( +
+
+
+
+ + Backend suggestion +
+

Free-issue promotions

+

The server suggests reward quantities and alternate products for this slip.

+
+ + Create free issue + +
+ +
+ {suggestion.lines.map((line) => ( +
+
+
+
{line.itemName}
+
{line.itemSku} • Qty {line.qty}
+
+
+ Buy {line.triggerQty} get {line.suggestedFreeQty} free +
+
+ +
+ {line.rewardOptions.map((option, index) => ( + + {index === 0 ? : null} + {option.name} + + ))} +
+ +
+ Suggested free qty: {line.suggestedFreeQty.toFixed(2)} +
+
+ ))} +
+
+ ) +} \ No newline at end of file diff --git a/Frontend/erp-system/components/sales/FreeIssueSuggestions.tsx b/Frontend/erp-system/components/sales/FreeIssueSuggestions.tsx new file mode 100644 index 0000000..2813912 --- /dev/null +++ b/Frontend/erp-system/components/sales/FreeIssueSuggestions.tsx @@ -0,0 +1,42 @@ +"use client" + +import Link from "next/link" + +import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" +import { SalesSlipSummary } from "@/types/sales" + +export function FreeIssueSuggestions({ rows }: { rows: SalesSlipSummary[] }) { + if (rows.length === 0) return null + + return ( +
+
+
+

Free issue suggestions

+

Recent free-issue slips created from the free-issues page.

+
+ + Open free issues + +
+ +
+ {rows.map((row) => ( + +
+
{row.slipNo}
+
{row.status}
+
+
{row.customerSnapshotName}
+
Free qty total: {row.totals.freeQtyTotal.toFixed(2)}
+ + ))} +
+
+ ) +} \ No newline at end of file diff --git a/Frontend/erp-system/lib/api/customers.ts b/Frontend/erp-system/lib/api/customers.ts new file mode 100644 index 0000000..eaeabb9 --- /dev/null +++ b/Frontend/erp-system/lib/api/customers.ts @@ -0,0 +1,15 @@ +import { apiRequest, buildQuery } from "@/lib/api-client" +import { PagedResponse } from "@/types/common" +import { Customer } from "@/types/customers" + +export interface ListCustomersParams { + page?: number + pageSize?: number + q?: string +} + +export const customersApi = { + list(params: ListCustomersParams = {}): Promise> { + return apiRequest>(`/customers${buildQuery(params)}`) + }, +} diff --git a/Frontend/erp-system/lib/api/sales.ts b/Frontend/erp-system/lib/api/sales.ts new file mode 100644 index 0000000..fa8a9c3 --- /dev/null +++ b/Frontend/erp-system/lib/api/sales.ts @@ -0,0 +1,139 @@ +import { apiRequest, apiRequestWithETag, buildQuery } from "@/lib/api-client" +import { ApiResult, PagedResponse } from "@/types/common" +import { + CreateSalesInvoiceRequest, + CreateSalesSlipRequest, + SalesInvoice, + SalesInvoiceStatus, + SalesInvoicePostingCheck, + SalesInvoiceSummary, + SalesListFilterParams, + SalesReportDefinition, + SalesReportQueryRequest, + SalesReportQueryResponse, + SalesFreeIssueSuggestion, + FreeIssueDetail, + FreeIssueSummary, + SalesSlip, + SalesSlipStatus, + SalesSlipPostingCheck, + SalesSlipSummary, + UpdateSalesInvoiceRequest, + UpdateSalesSlipRequest, +} from "@/types/sales" + +type SalesReportId = string + +export interface ListSalesInvoicesParams extends SalesListFilterParams { + status?: SalesInvoiceStatus +} + +export interface ListSalesSlipsParams extends SalesListFilterParams { + status?: SalesSlipStatus +} + +export interface ListFreeIssuesParams extends SalesListFilterParams { + status?: SalesSlipStatus +} + +export const salesApi = { + listInvoices(params: ListSalesInvoicesParams = {}): Promise> { + return apiRequest>(`/sales-invoices${buildQuery(params)}`) + }, + + getInvoice(salesInvoiceId: number): Promise> { + return apiRequestWithETag(`/sales-invoices/${salesInvoiceId}`) + }, + + createInvoice(request: CreateSalesInvoiceRequest): Promise> { + return apiRequestWithETag("/sales-invoices", { method: "POST", body: request }) + }, + + updateInvoice(salesInvoiceId: number, request: UpdateSalesInvoiceRequest, ifMatch: string): Promise> { + return apiRequestWithETag(`/sales-invoices/${salesInvoiceId}`, { method: "PUT", body: request, ifMatch }) + }, + + postInvoice(salesInvoiceId: number): Promise { + return apiRequest(`/sales-invoices/${salesInvoiceId}/post`, { method: "POST" }) + }, + + checkInvoicePosting(salesInvoiceId: number): Promise { + return apiRequest(`/sales-invoices/${salesInvoiceId}/posting-check`) + }, + + cancelInvoice(salesInvoiceId: number): Promise { + return apiRequest(`/sales-invoices/${salesInvoiceId}/cancel`, { method: "POST" }) + }, + + listSlips(params: ListSalesSlipsParams = {}): Promise> { + return apiRequest>(`/sales-slips${buildQuery(params)}`) + }, + + getSlip(salesSlipId: number): Promise> { + return apiRequestWithETag(`/sales-slips/${salesSlipId}`) + }, + + createSlip(request: CreateSalesSlipRequest): Promise> { + return apiRequestWithETag("/sales-slips", { method: "POST", body: request }) + }, + + updateSlip(salesSlipId: number, request: UpdateSalesSlipRequest, ifMatch: string): Promise> { + return apiRequestWithETag(`/sales-slips/${salesSlipId}`, { method: "PUT", body: request, ifMatch }) + }, + + postSlip(salesSlipId: number): Promise { + return apiRequest(`/sales-slips/${salesSlipId}/post`, { method: "POST" }) + }, + + checkSlipPosting(salesSlipId: number): Promise { + return apiRequest(`/sales-slips/${salesSlipId}/posting-check`) + }, + + cancelSlip(salesSlipId: number): Promise { + return apiRequest(`/sales-slips/${salesSlipId}/cancel`, { method: "POST" }) + }, + + listFreeIssues(params: ListFreeIssuesParams = {}): Promise> { + return apiRequest>(`/free-issues${buildQuery(params)}`) + }, + + getFreeIssue(freeIssueId: number): Promise> { + return apiRequestWithETag(`/free-issues/${freeIssueId}`) + }, + + createFreeIssue(request: CreateSalesSlipRequest): Promise> { + return apiRequestWithETag("/free-issues", { method: "POST", body: request }) + }, + + updateFreeIssue(freeIssueId: number, request: UpdateSalesSlipRequest, ifMatch: string): Promise> { + return apiRequestWithETag(`/free-issues/${freeIssueId}`, { method: "PUT", body: request, ifMatch }) + }, + + postFreeIssue(freeIssueId: number): Promise { + return apiRequest(`/free-issues/${freeIssueId}/post`, { method: "POST" }) + }, + + checkFreeIssuePosting(freeIssueId: number): Promise { + return apiRequest(`/free-issues/${freeIssueId}/posting-check`) + }, + + cancelFreeIssue(freeIssueId: number): Promise { + return apiRequest(`/free-issues/${freeIssueId}/cancel`, { method: "POST" }) + }, + + getFreeIssueSuggestions(salesSlipId: number): Promise { + return apiRequest(`/sales-slips/${salesSlipId}/free-issue-suggestions`) + }, + + listReports(): Promise { + return apiRequest("/reports/sales") + }, + + getReport(reportId: SalesReportId): Promise { + return apiRequest(`/reports/sales/${reportId}`) + }, + + queryReport(request: SalesReportQueryRequest): Promise { + return apiRequest("/reports/sales/query", { method: "POST", body: request }) + }, +} diff --git a/Frontend/erp-system/lib/sales-line-utils.ts b/Frontend/erp-system/lib/sales-line-utils.ts new file mode 100644 index 0000000..5037f43 --- /dev/null +++ b/Frontend/erp-system/lib/sales-line-utils.ts @@ -0,0 +1,6 @@ +import { ItemListItem } from "@/types/master-data" + +export function getSuggestedUnitPrice(items: ItemListItem[], itemId: number | null | undefined): number | null { + if (!itemId) return null + return items.find((item) => item.itemId === itemId)?.salePrice ?? null +} \ No newline at end of file diff --git a/Frontend/erp-system/types/customers.ts b/Frontend/erp-system/types/customers.ts new file mode 100644 index 0000000..a11afdc --- /dev/null +++ b/Frontend/erp-system/types/customers.ts @@ -0,0 +1,24 @@ +import { EntityStatus } from "@/types/common" + +export type CustomerType = "B2B" | "B2C" + +export interface Customer { + customerId: number + customerCode: string + customerType: CustomerType + name: string + displayName: string | null + phone: string | null + email: string | null + addressLine1: string | null + addressLine2: string | null + city: string | null + country: string | null + taxRegistrationNo: string | null + creditLimit: number + creditDays: number + defaultWarehouseId: number | null + status: EntityStatus + createdAt: string + updatedAt: string | null +} diff --git a/Frontend/erp-system/types/sales.ts b/Frontend/erp-system/types/sales.ts new file mode 100644 index 0000000..b973e39 --- /dev/null +++ b/Frontend/erp-system/types/sales.ts @@ -0,0 +1,314 @@ +import { EntityStatus } from "@/types/common" + +export type SalesInvoiceType = "B2B" | "B2C" | "Cash" | "Credit" +export type SalesInvoiceStatus = "Draft" | "Posted" | "Cancelled" +export type SalesSlipStatus = "Draft" | "Posted" | "Cancelled" +export type SalesDiscountMode = "Percentage" | "Amount" + +export interface SalesInvoiceLine { + salesInvoiceLineId: number + itemId: number + description: string + qty: number + freeQty: number + uomId: number + warehouseId: number + unitPrice: number + baseCost: number + priceSource: string + discountPct: number + discountAmount: number + discountMode: SalesDiscountMode + netUnitPrice: number + lineTotal: number + taxPct: number + taxAmount: number + isFreeIssue: boolean + parentLineId: number | null +} + +export interface SalesInvoiceTotals { + subtotal: number + discountTotal: number + freeQtyTotal: number + taxTotal: number + grandTotal: number + roundOff: number + netPayable: number + paidAmount: number + balanceAmount: number +} + +export interface SalesInvoiceSummary { + salesInvoiceId: number + invoiceNo: string + invoiceDate: string + customerId: number + customerSnapshotName: string + warehouseId: number + invoiceType: SalesInvoiceType + status: SalesInvoiceStatus + totals: SalesInvoiceTotals + createdAt: string +} + +export interface SalesInvoicePostingIssue { + salesInvoiceLineId: number + itemId: number + itemSku: string + itemName: string + warehouseId: number + requestedQty: number + availableQty: number + shortQty: number + isFreeIssue: boolean +} + +export interface SalesInvoicePostingCheck { + salesInvoiceId: number + invoiceNo: string + status: SalesInvoiceStatus + canPost: boolean + issues: SalesInvoicePostingIssue[] +} + +export interface SalesSlipPostingIssue { + salesSlipLineId: number + itemId: number + itemSku: string + itemName: string + warehouseId: number + requestedQty: number + availableQty: number + shortQty: number + isFreeIssue: boolean +} + +export interface SalesSlipPostingCheck { + salesSlipId: number + slipNo: string + status: SalesSlipStatus + canPost: boolean + issues: SalesSlipPostingIssue[] +} + +export interface SalesInvoice extends SalesInvoiceSummary { + customerSnapshotTaxNo: string | null + createdBy: number + updatedAt: string | null + lines: SalesInvoiceLine[] +} + +export interface CreateSalesInvoiceLineRequest { + itemId: number + uomId: number + warehouseId: number + qty: number + freeQty: number + unitPrice?: number | null + allowManualPriceOverride: boolean + discountMode: SalesDiscountMode + discountPct: number + discountAmount: number + discountValue: number + taxPct: number + isFreeIssue: boolean + parentLineId?: number | null +} + +export interface CreateSalesInvoiceRequest { + customerId: number + warehouseId: number + invoiceType: SalesInvoiceType + lines: CreateSalesInvoiceLineRequest[] +} + +export type UpdateSalesInvoiceRequest = CreateSalesInvoiceRequest + +export interface SalesSlipLine { + salesSlipLineId: number + itemId: number + description: string + qty: number + freeQty: number + uomId: number + warehouseId: number + unitPrice: number + baseCost: number + priceSource: string + discountPct: number + discountAmount: number + discountMode: SalesDiscountMode + netUnitPrice: number + lineTotal: number + taxPct: number + taxAmount: number + isFreeIssue: boolean + parentLineId: number | null +} + +export interface SalesSlipTotals { + subtotal: number + discountTotal: number + freeQtyTotal: number + taxTotal: number + grandTotal: number + paidAmount: number + balanceAmount: number +} + +export interface SalesSlipSummary { + salesSlipId: number + slipNo: string + slipDate: string + customerId: number + customerSnapshotName: string + warehouseId: number + status: SalesSlipStatus + totals: SalesSlipTotals + createdAt: string +} + +export interface SalesSlip { + salesSlipId: number + slipNo: string + slipDate: string + customerId: number + customerSnapshotName: string + warehouseId: number + cashierUserId: number + status: SalesSlipStatus + createdAt: string + updatedAt: string | null + totals: SalesSlipTotals + lines: SalesSlipLine[] +} + +export interface CreateSalesSlipLineRequest { + itemId: number + uomId: number + warehouseId: number + qty: number + freeQty: number + unitPrice?: number | null + allowManualPriceOverride: boolean + discountMode: SalesDiscountMode + discountPct: number + discountAmount: number + discountValue: number + taxPct: number + isFreeIssue: boolean + parentLineId?: number | null +} + +export interface CreateSalesSlipRequest { + customerId: number + warehouseId: number + cashierUserId: number + lines: CreateSalesSlipLineRequest[] +} + +export type UpdateSalesSlipRequest = CreateSalesSlipRequest + +export interface SalesReportDefinition { + id: string + name: string + description: string + supportedFilters: string[] +} + +export interface SalesReportQueryRequest { + reportType: string + from: string + to: string + itemId?: number | null + customerId?: number | null + warehouseId?: number | null +} + +export interface SalesReportQueryResponse { + reportType: string + from: string + to: string + rows: unknown[] +} + +export interface SalesListFilterParams { + page?: number + pageSize?: number + status?: SalesInvoiceStatus | SalesSlipStatus + customerId?: number + warehouseId?: number +} + +export interface FreeIssueSummaryItem extends SalesSlipSummary { + // Alias for the free issue route. Kept as a slip-shaped payload because the backend + // stores free issues as sales slips with free-issue line flags. + isFreeIssue?: boolean +} + +export interface FreeIssueSummary { + salesSlipId: number + slipNo: string + status: SalesSlipStatus + createdAt: string + warehouseId: number + warehouseName: string + itemId: number + itemSku: string + itemName: string + uomId: number + uomName: string + qty: number + freeQty: number + schemeLabel: string +} + +export interface FreeIssueDetail { + salesSlipId: number + slipNo: string + slipDate: string + status: SalesSlipStatus + customerId: number + customerSnapshotName: string + warehouseId: number + warehouseName: string + cashierUserId: number + createdAt: string + updatedAt: string | null + summary: FreeIssueSummary + lines: SalesSlipLine[] +} + +export interface SalesFreeIssueRewardOption { + itemId: number + sku: string + name: string + salePrice: number | null +} + +export interface SalesFreeIssueSuggestionLine { + salesSlipLineId: number + itemId: number + itemSku: string + itemName: string + qty: number + suggestedFreeQty: number + triggerQty: number + rewardOptions: SalesFreeIssueRewardOption[] +} + +export interface SalesFreeIssueSuggestion { + salesSlipId: number + slipNo: string + slipDate: string + lines: SalesFreeIssueSuggestionLine[] +} + +export interface SalesCustomer { + customerId: number + code: string + name: string + taxNo: string | null + status: EntityStatus +} diff --git a/docs/00-CORE.md b/docs/00-CORE.md index 428709a..0218b00 100644 --- a/docs/00-CORE.md +++ b/docs/00-CORE.md @@ -21,6 +21,7 @@ A modular ERP built in phases. **Phase 1** delivers the **Inventory & Supply Cha Full requirements live in the backend spec (see §7). This file does **not** duplicate them. **Phase 2 (HRM)** is now underway alongside Phase 1 — see §7 routing to `12-BACKEND-HRM.md` / `13-BACKEND-HRM-API.md` / `21-FRONTEND-HRM.md`. +Sales has its own API reference at `14-BACKEND-SALES-API.md`. --- @@ -45,6 +46,7 @@ erp-monorepo/ ├── 02-SECURITY.md # accepted-risks register + per-feature security checklist ├── 10-BACKEND-PHASE1.md # backend spec: SRS + ER/entities + tech + architecture ├── 11-BACKEND-PHASE1.md # backend API reference (complete req/res) + ├── 14-BACKEND-SALES-API.md # sales API reference (invoices, slips, free issues, reports) ├── 12-GENERAL-LEDGER-INTEGRATION.md # ERPCore ↔ external General Ledger service (transport only) ├── 20-FRONTEND.md # frontend user-flows + architecture rules + validation posture └── 21-GENERAL-LEDGER-FRONTEND.md # Ledgers section: reports UI + cash/bank accounts @@ -344,6 +346,7 @@ All frontend work is governed by `20-FRONTEND.md`. | HRM API endpoints, request/response shapes, error catalog (Phase 2) | **`13-BACKEND-HRM-API.md`** | | Connecting to the external General Ledger service (proxy, config, API key) | **`12-GENERAL-LEDGER-INTEGRATION.md`** | | The Ledgers frontend section (reports, cash/bank accounts) | **`21-GENERAL-LEDGER-FRONTEND.md`** | +| Sales API endpoints, request/response shapes, error catalog, enums | **`14-BACKEND-SALES-API.md`** | | Frontend user-flows, screen flow, architecture rules, validation posture (Phase 1) | **`20-FRONTEND.md`** | | HRM frontend user-flows (Phase 2) | **`21-FRONTEND-HRM.md`** | | Manufacturing requirements, business rules, entities, stock/costing integration, API (Phase 2) | **`30-BACKEND-PHASE2.md`** | @@ -356,6 +359,7 @@ All frontend work is governed by `20-FRONTEND.md`. Quick resolver: - *"Where is the model / an entity defined?"* → `10-BACKEND-PHASE1.md` (Phase 1) / `12-BACKEND-HRM.md` (HRM) — schema is authoritative there. - *"What does this endpoint accept/return?"* → `11-BACKEND-PHASE1.md` (Phase 1) / `13-BACKEND-HRM-API.md` (HRM). +- *"What does the sales API accept/return?"* → `14-BACKEND-SALES-API.md`. - *"How should the UI flow / what do I validate where?"* → `20-FRONTEND.md` (Phase 1) / `21-FRONTEND-HRM.md` (HRM). - *"What security risks / checks apply to this feature?"* → `02-SECURITY.md`. diff --git a/docs/01-DOC-GUIDE.md b/docs/01-DOC-GUIDE.md index ac7e22f..3545ee6 100644 --- a/docs/01-DOC-GUIDE.md +++ b/docs/01-DOC-GUIDE.md @@ -39,6 +39,7 @@ | `12-BACKEND-HRM.md` | High | HRM backend spec: SRS, ER model, HRM-specific architecture notes. Schema is **authoritative** here. | Any HRM model / business-rule / requirement work (Phase 2). | Claude + humans | | `13-BACKEND-HRM-API.md` | High | HRM **API reference**: every endpoint, error catalog additions, enums. | Any HRM API contract / controller work. | Claude + humans | | `12-GENERAL-LEDGER-INTEGRATION.md` | High | ERPCore ↔ external **General Ledger service**: connection/proxy contract, config, progress. GL's own endpoint contract lives in the GL service's own repo, not here. | Any work touching the GL proxy or a future internal GL caller. | Claude + humans | +| `14-BACKEND-SALES-API.md` | High | Sales **API reference**: invoices, slips, free issues, reports, enums. | Any sales API contract / controller work. | Claude + humans | | `20-FRONTEND.md` | High | Frontend **user-flows**, architecture rules to follow, **validation posture**. | Any frontend work (Phase 1). | Claude + humans | | `21-FRONTEND-HRM.md` | High | HRM frontend **user-flows**, screens, validation specifics. | Any HRM frontend work. | Claude + humans | | `30-BACKEND-PHASE2.md` | High | Manufacturing (Production Lines) backend: SRS, ER model, status machines, stock/costing integration, **and** the full API contract — model and API in one doc, unlike Phase 1. Schema is **authoritative** here. | Any manufacturing model / rule / API work. | Claude + humans | @@ -197,7 +198,7 @@ Spec: docs/20-FRONTEND.md (flows + rules) · docs/11-BACKEND-PHASE1.md (API cont ## 7. Adding future docs When later phases arrive (Sales & CRM, Manufacturing, QC/QA, Accounting), follow the same scheme HRM (Phase 2) established: -- Backend spec/API for a phase → new `1x-` files continuing the backend decade (e.g. HRM used `12-BACKEND-HRM.md` + `13-BACKEND-HRM-API.md`, mirroring the `10`/`11` SRS+ER / API-reference split), linked from the hub. +- Backend spec/API for a phase → new `1x-` files continuing the backend decade (e.g. HRM used `12-BACKEND-HRM.md` + `13-BACKEND-HRM-API.md`; sales now uses `14-BACKEND-SALES-API.md` alongside the existing phase docs), linked from the hub. - Frontend additions → extend `20-FRONTEND.md` or add `2x-` files (e.g. HRM used `21-FRONTEND-HRM.md`). - Always register the new doc in `00-CORE.md` routing (§7) and in this index (§2). diff --git a/docs/14-BACKEND-SALES-API.md b/docs/14-BACKEND-SALES-API.md new file mode 100644 index 0000000..a683434 --- /dev/null +++ b/docs/14-BACKEND-SALES-API.md @@ -0,0 +1,224 @@ +# 14 · BACKEND — Sales API Reference + +> **Authoritative for:** sales API contracts for invoices, slips, free issues, and sales reports. +> **Navigation:** start from `00-CORE.md`. Sales business rules live in `docs/SALES_MODULE_PLAN.md` and the sales-related backend progress is tracked in `Backend/PROGRESS.md`. +> **Scope:** this document covers the sales endpoints currently implemented in ERPCore. Free issue is modeled as a sales-slip alias, not a separate table. + +--- + +## 1. Conventions + +- Base URL: `https://{host}/api/v1` +- Authentication: same as the rest of the v1 API +- Concurrency: `ETag` / `If-Match` on mutable resources +- Validation: `ProblemDetails` / `ValidationProblemDetails` + +--- + +## 2. Sales Invoices + +### `GET /api/v1/sales-invoices` +Query: +- `page` +- `pageSize` +- `q` +- `status` +- `customerId` +- `warehouseId` + +Returns a paged list of `SalesInvoiceSummaryDto`. +The current frontend register uses this endpoint directly for the invoice hub view. + +### `GET /api/v1/sales-invoices/{salesInvoiceId}` +Returns `SalesInvoiceDto`. +The detail page shows the document template and only exposes edit actions when the invoice is `Draft`. + +### `POST /api/v1/sales-invoices` +Creates a draft sales invoice. + +Request: +```json +{ + "customerId": 2, + "warehouseId": 1, + "invoiceType": "B2C", + "lines": [ + { + "itemId": 1, + "uomId": 1, + "warehouseId": 1, + "qty": 1, + "freeQty": 0, + "unitPrice": 100, + "allowManualPriceOverride": false, + "discountMode": "Percentage", + "discountPct": 0, + "discountAmount": 0, + "discountValue": 0, + "taxPct": 0, + "isFreeIssue": false, + "parentLineId": null + } + ] +} +``` + +### `PUT /api/v1/sales-invoices/{salesInvoiceId}` +Updates a draft invoice. Requires `If-Match`. + +### `POST /api/v1/sales-invoices/{salesInvoiceId}/post` +Posts stock out and marks the invoice as `Posted`. +Before posting, the UI calls `GET /api/v1/sales-invoices/{salesInvoiceId}/posting-check` to show shortages. + +### `POST /api/v1/sales-invoices/{salesInvoiceId}/cancel` +Cancels a draft invoice. + +--- + +## 3. Sales Slips + +### `GET /api/v1/sales-slips` +Query: +- `page` +- `pageSize` +- `q` +- `status` +- `customerId` +- `warehouseId` + +Returns a paged list of `SalesSlipSummaryDto`. + +### `GET /api/v1/sales-slips/{salesSlipId}` +Returns `SalesSlipDto`. +The detail page follows the same document-style layout and draft-only edit behavior as invoices. + +### `POST /api/v1/sales-slips` +Creates a draft sales slip. + +Request: +```json +{ + "customerId": 2, + "warehouseId": 1, + "cashierUserId": 1, + "lines": [ + { + "itemId": 1, + "uomId": 1, + "warehouseId": 1, + "qty": 1, + "freeQty": 0, + "unitPrice": 50, + "allowManualPriceOverride": false, + "discountMode": "Percentage", + "discountPct": 0, + "discountAmount": 0, + "discountValue": 0, + "taxPct": 0, + "isFreeIssue": false, + "parentLineId": null + } + ] +} +``` + +### `PUT /api/v1/sales-slips/{salesSlipId}` +Updates a draft slip. Requires `If-Match`. + +### `POST /api/v1/sales-slips/{salesSlipId}/post` +Posts stock out and marks the slip as `Posted`. +Before posting, the UI calls `GET /api/v1/sales-slips/{salesSlipId}/posting-check`. + +### `POST /api/v1/sales-slips/{salesSlipId}/cancel` +Cancels a draft slip. + +--- + +## 4. Free Issues + +Free issue is a business alias over sales slips. +There is no separate free-issue table in the current schema. + +### `GET /api/v1/free-issues` +Same contract as `GET /api/v1/sales-slips`. + +### `GET /api/v1/free-issues/{freeIssueId}` +Same contract as `GET /api/v1/sales-slips/{salesSlipId}`. + +### `POST /api/v1/free-issues` +Same contract as `POST /api/v1/sales-slips`. + +### `PUT /api/v1/free-issues/{freeIssueId}` +Same contract as `PUT /api/v1/sales-slips/{salesSlipId}`. + +### `POST /api/v1/free-issues/{freeIssueId}/post` +Same contract as `POST /api/v1/sales-slips/{salesSlipId}/post`. + +### `POST /api/v1/free-issues/{freeIssueId}/cancel` +Same contract as `POST /api/v1/sales-slips/{salesSlipId}/cancel`. + +Business rule: +- free issue is represented by `IsFreeIssue=true` and/or `FreeQty>0` +- it still posts stock out through the normal slip posting flow +- the frontend free-issue screen is the main working page for create/list/edit/cancel +- cancelled free-issue records are hidden from the working list view + +--- + +## 5. Sales Reports + +### `GET /api/v1/reports/sales` +Returns the report catalog. + +Response: +```json +[ + { + "id": "daily-summary", + "name": "Daily Summary", + "description": "Aggregated sales by day across posted invoices and slips.", + "supportedFilters": ["from", "to"] + } +] +``` + +### `GET /api/v1/reports/sales/{reportId}` +Returns a single report definition by id. + +### `POST /api/v1/reports/sales/query` +Returns the actual report data. + +Request: +```json +{ + "reportType": "item-summary", + "from": "2026-07-01", + "to": "2026-07-28", + "itemId": 1, + "customerId": null, + "warehouseId": 1 +} +``` + +Supported report types: +- `daily-summary` +- `item-summary` +- `customer-summary` +- `warehouse-summary` +- `discount-summary` +- `free-issue-summary` + +Validation rule: +- invalid filters for a given report type are rejected +- unsupported report types are rejected +- the frontend report page uses the same endpoint for all report types +- `daily-summary` auto-loads with a default date range in the UI +- the report results table is rendered from the backend response rows, not hardcoded data + +--- + +## 6. Notes + +- The sales report service reads both invoices and slips where relevant. +- Free issue reporting is derived from the same sales document lines. +- There is no separate free-issue table in the current schema. diff --git a/docs/SALES_MODULE_PLAN.md b/docs/SALES_MODULE_PLAN.md index efb4bea..fc2038d 100644 --- a/docs/SALES_MODULE_PLAN.md +++ b/docs/SALES_MODULE_PLAN.md @@ -229,13 +229,15 @@ When an invoice or slip is posted: - `POST /api/v1/sales-slips` - `POST /api/v1/sales-slips/{id}/post` - `POST /api/v1/sales-slips/{id}/cancel` -- `GET /api/v1/sales-reports/daily-summary` -- `GET /api/v1/sales-reports/item-wise` -- `GET /api/v1/sales-reports/customer-wise` -- `GET /api/v1/sales-reports/warehouse-wise` -- `GET /api/v1/sales-reports/discount-summary` -- `GET /api/v1/sales-reports/free-issue-summary` -- `GET /api/v1/sales-reports/margin-summary` +- `GET /api/v1/free-issues` +- `GET /api/v1/free-issues/{id}` +- `POST /api/v1/free-issues` +- `PUT /api/v1/free-issues/{id}` +- `POST /api/v1/free-issues/{id}/post` +- `POST /api/v1/free-issues/{id}/cancel` +- `GET /api/v1/reports/sales` +- `GET /api/v1/reports/sales/{reportId}` +- `POST /api/v1/reports/sales/query` ### Phase 1 Folder / Module Plan - `Domain/Entities` @@ -264,7 +266,7 @@ When an invoice or slip is posted: 4. Pricing resolver 5. Discount computation 6. Free issue handling -7. Stock posting and ledger integration +7. Stock posting 8. Basic sales reports 9. Controllers and swagger wiring