diff --git a/.gitignore b/.gitignore
index 2b7364c..cb8adfc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,12 @@ yarn-error.log*
Thumbs.db
.idea/
+# ── Playwright E2E (Testing/e2e) ──────────────────────────────────────
+Testing/e2e/playwright-report/
+Testing/e2e/test-results/
+Testing/e2e/.auth/
+Testing/e2e/blob-report/
+
# ── Migrations ─────────────────────────────────────────────────────────
# Reverted 2026-07-31: excluding new EF Core migrations while
# ErpDbContextModelSnapshot.cs stayed tracked meant every `dotnet ef
diff --git a/Backend/ERPCore/Migrations/20260804111315_a.Designer.cs b/Backend/ERPCore/Migrations/20260804111315_a.Designer.cs
new file mode 100644
index 0000000..5147aad
--- /dev/null
+++ b/Backend/ERPCore/Migrations/20260804111315_a.Designer.cs
@@ -0,0 +1,7093 @@
+//
+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("20260804111315_a")]
+ partial class a
+ {
+ ///
+ 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.BundleSale", b =>
+ {
+ b.Property("BundleSaleId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BundleSaleId"));
+
+ b.Property("BundleCode")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("BundleDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("BundleName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("BundleNo")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("BundlePrice")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("BundleSaleTemplateId")
+ .HasColumnType("integer");
+
+ b.Property("CashierUserId")
+ .HasColumnType("integer");
+
+ b.Property("ComponentSubtotal")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0);
+
+ 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("MarginAmount")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("Status")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasDefaultValue("Draft");
+
+ 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("BundleSaleId");
+
+ b.HasIndex("BundleNo")
+ .IsUnique();
+
+ b.HasIndex("BundleSaleTemplateId");
+
+ b.HasIndex("CashierUserId");
+
+ b.HasIndex("CustomerId");
+
+ b.HasIndex("WarehouseId");
+
+ b.ToTable("bundle_sales", (string)null);
+ });
+
+ modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleLine", b =>
+ {
+ b.Property("BundleSaleLineId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BundleSaleLineId"));
+
+ b.Property("BundleSaleId")
+ .HasColumnType("integer");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("IncludeInBundle")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(true);
+
+ b.Property("IsComponent")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(true);
+
+ b.Property("ItemId")
+ .HasColumnType("integer");
+
+ b.Property("LineTotal")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("ParentLineId")
+ .HasColumnType("integer");
+
+ b.Property("Qty")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("UnitPrice")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("UomId")
+ .HasColumnType("integer");
+
+ b.Property("WarehouseId")
+ .HasColumnType("integer");
+
+ b.HasKey("BundleSaleLineId");
+
+ b.HasIndex("BundleSaleId");
+
+ b.HasIndex("ItemId");
+
+ b.HasIndex("UomId");
+
+ b.HasIndex("WarehouseId");
+
+ b.ToTable("bundle_sale_lines", (string)null);
+ });
+
+ modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplate", b =>
+ {
+ b.Property("BundleSaleTemplateId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BundleSaleTemplateId"));
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0);
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)");
+
+ b.Property("Status")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasDefaultValue("Active");
+
+ b.Property("TemplateCode")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("TemplateName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("BundleSaleTemplateId");
+
+ b.HasIndex("TemplateCode")
+ .IsUnique();
+
+ b.ToTable("bundle_sale_templates", (string)null);
+ });
+
+ modelBuilder.Entity("ERPCore.Domain.Entities.BundleSaleTemplateLine", b =>
+ {
+ b.Property("BundleSaleTemplateLineId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("BundleSaleTemplateLineId"));
+
+ b.Property("BundleSaleTemplateId")
+ .HasColumnType("integer");
+
+ b.Property("IncludeInBundle")
+ .HasColumnType("boolean");
+
+ b.Property("ItemId")
+ .HasColumnType("integer");
+
+ b.Property("Qty")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("SortOrder")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0);
+
+ b.Property("UnitPrice")
+ .HasPrecision(18, 4)
+ .HasColumnType("numeric(18,4)");
+
+ b.Property("UomId")
+ .HasColumnType("integer");
+
+ b.Property("WarehouseId")
+ .HasColumnType("integer");
+
+ b.HasKey("BundleSaleTemplateLineId");
+
+ b.HasIndex("BundleSaleTemplateId");
+
+ b.HasIndex("ItemId");
+
+ b.HasIndex("UomId");
+
+ b.HasIndex("WarehouseId");
+
+ b.ToTable("bundle_sale_template_lines", (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