using System.Text.Json; using AMREZ.EOP.Domain.Entities.Authentications; using AMREZ.EOP.Domain.Entities.Common; using AMREZ.EOP.Domain.Entities.Customers; using AMREZ.EOP.Domain.Entities.HumanResources; using AMREZ.EOP.Domain.Entities.MasterData; using AMREZ.EOP.Domain.Entities.Tenancy; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Route = AMREZ.EOP.Domain.Entities.MasterData.Route; namespace AMREZ.EOP.Infrastructures.Data; public class AppDbContext : DbContext { public AppDbContext(DbContextOptions options) : base(options) { } // ===== Auth ===== public DbSet Users => Set(); public DbSet UserIdentities => Set(); public DbSet UserMfaFactors => Set(); public DbSet UserSessions => Set(); public DbSet UserPasswordHistories => Set(); public DbSet UserExternalAccounts => Set(); public DbSet Roles => Set(); public DbSet Permissions => Set(); public DbSet UserRoles => Set(); public DbSet RolePermissions => Set(); // ===== HR ===== public DbSet UserProfiles => Set(); public DbSet Departments => Set(); public DbSet Positions => Set(); public DbSet Employments => Set(); public DbSet EmployeeAddresses => Set(); public DbSet EmergencyContacts => Set(); public DbSet EmployeeBankAccounts => Set(); // ===== Tenancy (meta) ===== public DbSet Tenants => Set(); public DbSet TenantDomains => Set(); // ===== Customers ===== public DbSet CustomerProfiles => Set(); public DbSet PersonProfiles => Set(); public DbSet OrganizationProfiles => Set(); public DbSet
Addresses => Set
(); public DbSet CustomerAddresses => Set(); public DbSet CustomerContacts => Set(); public DbSet CustomerTags => Set(); // ===== Master Data ===== public DbSet Brands => Set(); public DbSet BrandBlocks => Set(); public DbSet Categories => Set(); public DbSet CategoryBlocks => Set(); public DbSet CategoryExts => Set(); public DbSet Uoms => Set(); public DbSet UomBlocks => Set(); public DbSet Currencies => Set(); public DbSet CurrencyBlocks => Set(); public DbSet Countries => Set(); public DbSet CountryBlocks => Set(); public DbSet Manufacturers => Set(); public DbSet ManufacturerBlocks => Set(); public DbSet Markets => Set(); public DbSet MarketBlocks => Set(); public DbSet Languages => Set(); public DbSet LanguageBlocks => Set(); public DbSet ComplianceStatuses => Set(); public DbSet ComplianceStatusBlocks => Set(); public DbSet QaStages => Set(); public DbSet QaStageBlocks => Set(); public DbSet Routes => Set(); public DbSet RouteBlocks => Set(); public DbSet RxSchedules => Set(); public DbSet RxScheduleBlocks => Set(); public DbSet StabilityStatuses => Set(); public DbSet StabilityStatusBlocks => Set(); public DbSet RiskClasses => Set(); public DbSet RiskClassBlocks => Set(); public DbSet SterilizationMethods => Set(); public DbSet SterilizationMethodBlocks => Set(); public DbSet QcStatuses => Set(); public DbSet QcStatusBlocks => Set(); public DbSet DocControlStatuses => Set(); public DbSet DocControlStatusBlocks => Set(); public DbSet RecallClasses => Set(); public DbSet RecallClassBlocks => Set(); public DbSet PackingGroups => Set(); public DbSet PackingGroupBlocks => Set(); public DbSet Vvms => Set(); public DbSet VvmBlocks => Set(); public DbSet FuncTests => Set(); public DbSet FuncTestBlocks => Set(); public DbSet HazardClasses => Set(); public DbSet HazardClassBlocks => Set(); public DbSet Species => Set(); public DbSet SpeciesBlocks => Set(); public DbSet Allergens => Set(); public DbSet AllergenBlocks => Set(); public DbSet Provinces => Set(); public DbSet ProvinceBlocks => Set(); public DbSet Districts => Set(); public DbSet DistrictBlocks => Set(); public DbSet Subdistricts => Set(); public DbSet SubdistrictBlocks => Set(); protected override void OnModelCreating(ModelBuilder model) { // JSON converters/comparers var jsonOpts = new JsonSerializerOptions(JsonSerializerDefaults.Web); var dictStrConverter = new ValueConverter?, string?>( v => v == null ? null : JsonSerializer.Serialize(v, jsonOpts), v => string.IsNullOrWhiteSpace(v) ? null : JsonSerializer.Deserialize>(v!, jsonOpts)!); var dictStrComparer = new ValueComparer?>( (l, r) => JsonSerializer.Serialize(l, jsonOpts) == JsonSerializer.Serialize(r, jsonOpts), v => v == null ? 0 : JsonSerializer.Serialize(v, jsonOpts).GetHashCode(), v => v == null ? null : JsonSerializer.Deserialize>(JsonSerializer.Serialize(v, jsonOpts), jsonOpts)); var dictObjConverter = new ValueConverter?, string?>( v => v == null ? null : JsonSerializer.Serialize(v, jsonOpts), v => string.IsNullOrWhiteSpace(v) ? null : JsonSerializer.Deserialize>(v!, jsonOpts)!); var dictObjComparer = new ValueComparer?>( (l, r) => JsonSerializer.Serialize(l, jsonOpts) == JsonSerializer.Serialize(r, jsonOpts), v => v == null ? 0 : JsonSerializer.Serialize(v, jsonOpts).GetHashCode(), v => v == null ? null : JsonSerializer.Deserialize>(JsonSerializer.Serialize(v, jsonOpts), jsonOpts)); void ConfigureMasterBase(string table, string schema = "master") where TEntity : MasterBase { var b = model.Entity(); b.ToTable(table, schema); b.HasKey(x => x.Id); b.HasAlternateKey(x => new { x.TenantId, x.Id }); b.Property(x => x.Scope).IsRequired().HasDefaultValue("global"); b.Property(x => x.Code).IsRequired(); b.Property(x => x.Name).IsRequired(); b.Property(x => x.IsActive).HasDefaultValue(true); b.Property(x => x.IsSystem).HasDefaultValue(false); var nameI18nProp = b.Property(x => x.NameI18n).HasConversion(dictStrConverter); nameI18nProp.Metadata.SetValueComparer(dictStrComparer); nameI18nProp.HasColumnType("jsonb"); var metaProp = b.Property(x => x.Meta).HasConversion(dictObjConverter); metaProp.Metadata.SetValueComparer(dictObjComparer); metaProp.HasColumnType("jsonb"); b.HasIndex(x => new { x.Scope, x.TenantId, x.Code }).IsUnique(); } void ConfigureBlockBase(string table, string schema = "master") where TBlock : MasterBlockBase where TMaster : MasterBase { var b = model.Entity(); b.ToTable(table, schema); b.HasKey(x => x.Id); b.HasIndex(x => new { x.TenantId, x.GlobalId }).IsUnique(); b.HasIndex(x => x.GlobalId); b.HasOne() .WithMany() .HasForeignKey(x => x.GlobalId) .OnDelete(DeleteBehavior.Cascade); } // ====== Tenancy (meta) ====== model.Entity(b => { b.ToTable("tenants", schema: "meta"); b.HasKey(x => x.TenantKey); b.HasAlternateKey(x => x.TenantId); b.HasIndex(x => x.TenantId).IsUnique(); b.Property(x => x.TenantKey).HasMaxLength(128).IsRequired(); b.Property(x => x.Schema).HasMaxLength(128); b.Property(x => x.ConnectionString); b.Property(x => x.Mode).IsRequired(); b.Property(x => x.IsActive).HasDefaultValue(true); b.Property(x => x.UpdatedAtUtc).HasColumnName("updated_at_utc") .HasDefaultValueSql("now() at time zone 'utc'"); b.HasIndex(x => x.IsActive); }); model.Entity(b => { b.ToTable("tenant_domains", schema: "meta"); b.HasKey(x => x.Domain); b.Property(x => x.Domain).HasMaxLength(253).IsRequired(); b.Property(x => x.TenantKey).HasMaxLength(128); b.Property(x => x.IsPlatformBaseDomain).HasDefaultValue(false); b.Property(x => x.IsActive).HasDefaultValue(true); b.Property(x => x.UpdatedAtUtc).HasColumnName("updated_at_utc") .HasDefaultValueSql("now() at time zone 'utc'"); b.HasIndex(x => x.TenantKey); b.HasIndex(x => x.IsPlatformBaseDomain); b.HasIndex(x => x.IsActive); b.HasOne() .WithMany() .HasForeignKey(x => x.TenantKey) .OnDelete(DeleteBehavior.Cascade); }); // ====== Auth ====== model.Entity(b => { b.ToTable("users"); b.HasKey(x => x.Id); b.HasAlternateKey(u => new { u.TenantId, u.Id }); b.Property(x => x.PasswordHash).IsRequired(); b.Property(x => x.IsActive).HasDefaultValue(true); b.Property(x => x.AccessFailedCount).HasDefaultValue(0); b.Property(x => x.MfaEnabled).HasDefaultValue(false); }); model.Entity(b => { b.ToTable("user_identities"); b.HasKey(x => x.Id); b.Property(x => x.Type).IsRequired(); b.Property(x => x.Identifier).IsRequired().HasMaxLength(256); b.Property(x => x.IsPrimary).HasDefaultValue(false); b.HasIndex(x => new { x.TenantId, x.Type, x.Identifier }).IsUnique(); b.HasIndex(x => new { x.TenantId, x.UserId, x.Type, x.IsPrimary }) .HasDatabaseName("ix_user_identity_primary_per_type"); b.HasOne(i => i.User) .WithMany(u => u.Identities) .HasForeignKey(i => new { i.TenantId, i.UserId }) .HasPrincipalKey(nameof(User.TenantId), nameof(User.Id)) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("user_mfa_factors"); b.HasKey(x => x.Id); b.Property(x => x.Type).IsRequired(); b.Property(x => x.Enabled).HasDefaultValue(true); b.HasIndex(x => new { x.TenantId, x.UserId }); b.HasOne(x => x.User) .WithMany(u => u.MfaFactors) .HasForeignKey(x => new { x.TenantId, x.UserId }) .HasPrincipalKey(nameof(User.TenantId), nameof(User.Id)) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("user_sessions"); b.HasKey(x => x.Id); b.Property(x => x.RefreshTokenHash).IsRequired(); b.HasIndex(x => new { x.TenantId, x.UserId }); b.HasIndex(x => new { x.TenantId, x.DeviceId }); b.HasOne(x => x.User) .WithMany(u => u.Sessions) .HasForeignKey(x => new { x.TenantId, x.UserId }) .HasPrincipalKey(nameof(User.TenantId), nameof(User.Id)) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("user_password_histories"); b.HasKey(x => x.Id); b.Property(x => x.PasswordHash).IsRequired(); b.HasIndex(x => new { x.TenantId, x.UserId, x.ChangedAt }); b.HasOne(x => x.User) .WithMany(u => u.PasswordHistories) .HasForeignKey(x => new { x.TenantId, x.UserId }) .HasPrincipalKey(nameof(User.TenantId), nameof(User.Id)) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("user_external_accounts"); b.HasKey(x => x.Id); b.Property(x => x.Provider).IsRequired(); b.Property(x => x.Subject).IsRequired(); b.HasIndex(x => new { x.TenantId, x.Provider, x.Subject }).IsUnique(); b.HasOne(x => x.User) .WithMany(u => u.ExternalAccounts) .HasForeignKey(x => new { x.TenantId, x.UserId }) .HasPrincipalKey(nameof(User.TenantId), nameof(User.Id)) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("roles"); b.HasKey(x => x.Id); b.HasAlternateKey(r => new { r.TenantId, r.Id }); b.Property(x => x.Code).IsRequired().HasMaxLength(128); b.Property(x => x.Name).IsRequired().HasMaxLength(256); b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); }); model.Entity(b => { b.ToTable("permissions"); b.HasKey(x => x.Id); b.HasAlternateKey(p => new { p.TenantId, p.Id }); b.Property(x => x.Code).IsRequired().HasMaxLength(256); b.Property(x => x.Name).IsRequired().HasMaxLength(256); b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); }); model.Entity(b => { b.ToTable("user_roles"); b.HasKey(x => x.Id); b.HasIndex(x => new { x.TenantId, x.UserId, x.RoleId }).IsUnique(); b.HasOne() .WithMany() .HasForeignKey(x => new { x.TenantId, x.UserId }) .HasPrincipalKey(nameof(User.TenantId), nameof(User.Id)) .OnDelete(DeleteBehavior.Cascade); b.HasOne() .WithMany() .HasForeignKey(x => new { x.TenantId, x.RoleId }) .HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id)) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("role_permissions"); b.HasKey(x => x.Id); b.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique(); b.HasOne() .WithMany() .HasForeignKey(x => new { x.TenantId, x.RoleId }) .HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id)) .OnDelete(DeleteBehavior.Cascade); b.HasOne() .WithMany() .HasForeignKey(x => new { x.TenantId, x.PermissionId }) .HasPrincipalKey(nameof(Permission.TenantId), nameof(Permission.Id)) .OnDelete(DeleteBehavior.Cascade); }); // ====== HR ====== model.Entity(b => { b.ToTable("user_profiles"); b.HasKey(x => x.Id); b.HasAlternateKey(p => new { p.TenantId, p.Id }); b.Property(x => x.FirstName).IsRequired().HasMaxLength(128); b.Property(x => x.LastName).IsRequired().HasMaxLength(128); b.HasIndex(x => new { x.TenantId, x.UserId }).IsUnique(); b.HasOne(x => x.User) .WithOne() .HasForeignKey(x => new { x.TenantId, x.UserId }) .HasPrincipalKey(u => new { u.TenantId, u.Id }) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("departments"); b.HasKey(x => x.Id); b.HasAlternateKey(d => new { d.TenantId, d.Id }); b.Property(x => x.Code).IsRequired().HasMaxLength(64); b.Property(x => x.Name).IsRequired().HasMaxLength(256); b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); b.HasOne(x => x.Parent) .WithMany(x => x.Children) .HasForeignKey(x => new { x.TenantId, x.ParentDepartmentId }) .HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id)) .OnDelete(DeleteBehavior.Restrict); }); model.Entity(b => { b.ToTable("positions"); b.HasKey(x => x.Id); b.HasAlternateKey(p => new { p.TenantId, p.Id }); b.Property(x => x.Code).IsRequired().HasMaxLength(64); b.Property(x => x.Title).IsRequired().HasMaxLength(256); b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); }); model.Entity(b => { b.ToTable("employments"); b.HasKey(x => x.Id); b.Property(x => x.EmploymentType).IsRequired(); b.Property(x => x.StartDate).IsRequired(); b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.StartDate }); b.HasOne(x => x.UserProfile) .WithMany(p => p.Employments) .HasForeignKey(x => new { x.TenantId, x.UserProfileId }) .HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id)) .OnDelete(DeleteBehavior.Cascade); b.HasOne(x => x.Department) .WithMany() .HasForeignKey(x => new { x.TenantId, x.DepartmentId }) .HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id)) .OnDelete(DeleteBehavior.Restrict); b.HasOne(x => x.Position) .WithMany() .HasForeignKey(x => new { x.TenantId, x.PositionId }) .HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id)) .OnDelete(DeleteBehavior.Restrict); }); model.Entity(b => { b.ToTable("employee_addresses"); b.HasKey(x => x.Id); b.Property(x => x.Line1).IsRequired().HasMaxLength(256); b.Property(x => x.City).IsRequired().HasMaxLength(128); b.Property(x => x.PostalCode).IsRequired().HasMaxLength(32); b.Property(x => x.Country).IsRequired().HasMaxLength(64); b.HasOne(x => x.UserProfile) .WithMany(p => p.Addresses) .HasForeignKey(x => new { x.TenantId, x.UserProfileId }) .HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id)) .OnDelete(DeleteBehavior.Cascade); b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary }); }); model.Entity(b => { b.ToTable("emergency_contacts"); b.HasKey(x => x.Id); b.Property(x => x.Name).IsRequired().HasMaxLength(128); b.Property(x => x.Relationship).IsRequired().HasMaxLength(64); b.HasOne(x => x.UserProfile) .WithMany(p => p.EmergencyContacts) .HasForeignKey(x => new { x.TenantId, x.UserProfileId }) .HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id)) .OnDelete(DeleteBehavior.Cascade); b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary }); }); model.Entity(b => { b.ToTable("employee_bank_accounts"); b.HasKey(x => x.Id); b.Property(x => x.BankName).IsRequired().HasMaxLength(128); b.Property(x => x.AccountNumber).IsRequired().HasMaxLength(64); b.Property(x => x.AccountHolder).IsRequired().HasMaxLength(128); b.HasOne(x => x.UserProfile) .WithMany(p => p.BankAccounts) .HasForeignKey(x => new { x.TenantId, x.UserProfileId }) .HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id)) .OnDelete(DeleteBehavior.Cascade); b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary }); }); // ====== Customers ====== model.Entity(b => { b.ToTable("customer_profiles"); b.HasKey(x => x.Id); b.HasAlternateKey(x => new { x.TenantId, x.Id }); b.Property(x => x.DisplayName).HasMaxLength(256).IsRequired(); b.Property(x => x.Type).IsRequired(); b.Property(x => x.Status).IsRequired(); b.HasIndex(x => new { x.TenantId, x.DisplayName }); b.HasMany(x => x.Contacts) .WithOne(x => x.Customer) .HasForeignKey(x => new { x.TenantId, x.CustomerProfileId }) .HasPrincipalKey(x => new { x.TenantId, x.Id }) .OnDelete(DeleteBehavior.Cascade); b.HasMany(x => x.Addresses) .WithOne(x => x.Customer) .HasForeignKey(x => new { x.TenantId, x.CustomerProfileId }) .HasPrincipalKey(x => new { x.TenantId, x.Id }) .OnDelete(DeleteBehavior.Cascade); b.HasMany(x => x.Tags) .WithOne(x => x.Customer) .HasForeignKey(x => new { x.TenantId, x.CustomerProfileId }) .HasPrincipalKey(x => new { x.TenantId, x.Id }) .OnDelete(DeleteBehavior.Cascade); b.HasOne(x => x.Person) .WithOne(x => x.Customer) .HasForeignKey(x => new { x.TenantId, x.CustomerProfileId }) .HasPrincipalKey(x => new { x.TenantId, x.Id }) .OnDelete(DeleteBehavior.Cascade); b.HasOne(x => x.Organization) .WithOne(x => x.Customer) .HasForeignKey(x => new { x.TenantId, x.CustomerProfileId }) .HasPrincipalKey(x => new { x.TenantId, x.Id }) .OnDelete(DeleteBehavior.Cascade); }); model.Entity(b => { b.ToTable("person_profiles"); b.HasKey(x => x.Id); b.Property(x => x.Prefix).HasMaxLength(32); b.Property(x => x.FirstNameTh).HasMaxLength(128); b.Property(x => x.LastNameTh).HasMaxLength(128); b.Property(x => x.FirstNameEn).HasMaxLength(128); b.Property(x => x.LastNameEn).HasMaxLength(128); b.Property(x => x.NationalId).HasMaxLength(13); b.Property(x => x.PassportNo).HasMaxLength(32); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId }).IsUnique(); }); model.Entity(b => { b.ToTable("organization_profiles"); b.HasKey(x => x.Id); b.Property(x => x.LegalNameTh).HasMaxLength(256).IsRequired(); b.Property(x => x.LegalNameEn).HasMaxLength(256); b.Property(x => x.RegistrationNo).HasMaxLength(64); b.Property(x => x.TaxId13).HasMaxLength(13); b.Property(x => x.BranchCode).HasMaxLength(5); b.Property(x => x.CompanyType).HasMaxLength(64); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId }).IsUnique(); b.HasIndex(x => new { x.TenantId, x.TaxId13, x.BranchCode }).IsUnique() .HasFilter("\"TaxId13\" IS NOT NULL"); }); model.Entity(b => { b.ToTable("customer_contacts"); b.HasKey(x => x.Id); b.Property(x => x.Type).IsRequired(); b.Property(x => x.Value).HasMaxLength(256).IsRequired(); b.Property(x => x.IsPrimary).HasDefaultValue(false); b.Property(x => x.IsVerified).HasDefaultValue(false); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Type }); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Type, x.IsPrimary }) .HasDatabaseName("ux_customer_contact_primary_per_type") .IsUnique() .HasFilter("\"IsPrimary\" = TRUE"); }); model.Entity
(b => { b.ToTable("addresses"); b.HasKey(x => x.Id); b.HasAlternateKey(x => new { x.TenantId, x.Id }); b.Property(x => x.Line1).HasMaxLength(256); b.Property(x => x.Line2).HasMaxLength(256); b.Property(x => x.Village).HasMaxLength(128); b.Property(x => x.Soi).HasMaxLength(128); b.Property(x => x.Road).HasMaxLength(128); b.Property(x => x.Subdistrict).HasMaxLength(128); b.Property(x => x.District).HasMaxLength(128); b.Property(x => x.Province).HasMaxLength(128); b.Property(x => x.PostalCode).HasMaxLength(10); b.Property(x => x.CountryCode).HasMaxLength(2).IsRequired(); }); model.Entity(b => { b.ToTable("customer_addresses"); b.HasKey(x => x.Id); b.Property(x => x.Label).IsRequired(); b.Property(x => x.IsDefault).HasDefaultValue(false); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Label }); b.HasOne(x => x.Customer) .WithMany(c => c.Addresses) .HasForeignKey(x => new { x.TenantId, x.CustomerProfileId }) .HasPrincipalKey(c => new { c.TenantId, c.Id }) .OnDelete(DeleteBehavior.Cascade); b.HasOne(x => x.Address) .WithMany(a => a.CustomerLinks) .HasForeignKey(x => new { x.TenantId, x.AddressId }) .HasPrincipalKey(a => new { a.TenantId, a.Id }) .OnDelete(DeleteBehavior.Cascade); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Label, x.IsDefault }) .HasDatabaseName("ux_default_customer_address_per_label") .IsUnique() .HasFilter("\"IsDefault\" = TRUE"); }); model.Entity(b => { b.ToTable("customer_tags"); b.HasKey(x => x.Id); b.Property(x => x.Tag).HasMaxLength(64).IsRequired(); b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Tag }).IsUnique(); }); // ===== Master: mapping ===== ConfigureMasterBase("brands"); ConfigureBlockBase("brand_blocks"); model.Entity(b => { ConfigureMasterBase("categories"); b.HasOne(x => x.Parent) .WithMany(x => x.Children) .HasForeignKey(x => new { x.TenantId, x.ParentId }) .HasPrincipalKey(x => new { x.TenantId, x.Id }) .OnDelete(DeleteBehavior.Restrict); b.HasIndex(x => x.ParentId); b.HasIndex(x => x.Path); b.HasIndex(x => new { x.Scope, x.TenantId, x.SortOrder }); }); ConfigureBlockBase("category_blocks"); model.Entity(b => { b.ToTable("category_exts", "master"); b.HasKey(x => x.CategoryId); b.HasOne(x => x.Category).WithOne().HasForeignKey(x => x.CategoryId) .OnDelete(DeleteBehavior.Cascade); var chVisProp = b.Property(x => x.ChannelVisibility).HasConversion(dictObjConverter); chVisProp.Metadata.SetValueComparer(dictObjComparer); chVisProp.HasColumnType("jsonb"); var facetSchemaProp = b.Property(x => x.FacetSchema).HasConversion(dictObjConverter); facetSchemaProp.Metadata.SetValueComparer(dictObjComparer); facetSchemaProp.HasColumnType("jsonb"); }); ConfigureMasterBase("uoms"); ConfigureBlockBase("uom_blocks"); ConfigureMasterBase("currencies"); ConfigureBlockBase("currency_blocks"); ConfigureMasterBase("countries"); ConfigureBlockBase("country_blocks"); ConfigureMasterBase("manufacturers"); ConfigureBlockBase("manufacturer_blocks"); ConfigureMasterBase("markets"); ConfigureBlockBase("market_blocks"); ConfigureMasterBase("languages"); ConfigureBlockBase("language_blocks"); ConfigureMasterBase("compliance_statuses"); ConfigureBlockBase("compliance_status_blocks"); ConfigureMasterBase("qa_stages"); ConfigureBlockBase("qa_stage_blocks"); ConfigureMasterBase("routes"); ConfigureBlockBase("route_blocks"); ConfigureMasterBase("rx_schedules"); ConfigureBlockBase("rx_schedule_blocks"); ConfigureMasterBase("stability_statuses"); ConfigureBlockBase("stability_status_blocks"); ConfigureMasterBase("risk_classes"); ConfigureBlockBase("risk_class_blocks"); ConfigureMasterBase("sterilization_methods"); ConfigureBlockBase("sterilization_method_blocks"); ConfigureMasterBase("qc_statuses"); ConfigureBlockBase("qc_status_blocks"); ConfigureMasterBase("doc_control_statuses"); ConfigureBlockBase("doc_control_status_blocks"); ConfigureMasterBase("recall_classes"); ConfigureBlockBase("recall_class_blocks"); ConfigureMasterBase("packing_groups"); ConfigureBlockBase("packing_group_blocks"); ConfigureMasterBase("vvms"); ConfigureBlockBase("vvm_blocks"); ConfigureMasterBase("func_tests"); ConfigureBlockBase("func_test_blocks"); ConfigureMasterBase("hazard_classes"); ConfigureBlockBase("hazard_class_blocks"); ConfigureMasterBase("species"); ConfigureBlockBase("species_blocks"); ConfigureMasterBase("allergens"); ConfigureBlockBase("allergen_blocks"); ConfigureMasterBase("provinces"); ConfigureBlockBase("province_blocks"); model.Entity(b => { b.Property(x => x.Code) .IsRequired() .HasMaxLength(2); b.HasIndex(x => x.Code); }); ConfigureMasterBase("districts"); ConfigureBlockBase("district_blocks"); model.Entity(b => { b.Property(x => x.Code) .IsRequired() .HasMaxLength(4); b.HasIndex(x => x.Code); b.HasOne(x => x.Province) .WithMany(p => p.Districts) .HasForeignKey(x => new { x.TenantId, x.ProvinceId }) .HasPrincipalKey(p => new { p.TenantId, p.Id }) .OnDelete(DeleteBehavior.Restrict); b.HasIndex(x => new { x.TenantId, x.ProvinceId }); }); ConfigureMasterBase("subdistricts"); ConfigureBlockBase("subdistrict_blocks"); model.Entity(b => { b.Property(x => x.Code) .IsRequired() .HasMaxLength(6); b.Property(x => x.Postcode) .HasMaxLength(10); b.HasIndex(x => x.Code); b.HasIndex(x => x.Postcode); b.HasOne(x => x.District) .WithMany(d => d.Subdistricts) .HasForeignKey(x => new { x.TenantId, x.DistrictId }) .HasPrincipalKey(d => new { d.TenantId, d.Id }) .OnDelete(DeleteBehavior.Restrict); b.HasIndex(x => new { x.TenantId, x.DistrictId }); }); // ====== Enum conversions ====== model.Entity().Property(x => x.Type).HasConversion(); model.Entity().Property(x => x.Type).HasConversion(); model.Entity().Property(x => x.Provider).HasConversion(); model.Entity().Property(x => x.EmploymentType).HasConversion(); model.Entity().Property(x => x.Gender).HasConversion(); model.Entity().Property(x => x.Type).HasConversion(); model.Entity().Property(x => x.Status).HasConversion(); model.Entity().Property(x => x.Type).HasConversion(); model.Entity().Property(x => x.Label).HasConversion(); // ====== BaseEntity common mapping ====== foreach (var et in model.Model.GetEntityTypes().Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType))) { var b = model.Entity(et.ClrType); b.Property(nameof(BaseEntity.TenantId)) .HasColumnName("tenant_id") .HasColumnType("uuid") .IsRequired() .ValueGeneratedNever(); b.HasIndex(nameof(BaseEntity.TenantId)); var tn = et.GetTableName(); if (!string.IsNullOrEmpty(tn)) { b.HasCheckConstraint($"ck_{tn}_tenant_not_null", "tenant_id is not null"); b.HasCheckConstraint($"ck_{tn}_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'"); } b.Property(nameof(BaseEntity.CreatedAt)).HasColumnName("created_at") .HasDefaultValueSql("now() at time zone 'utc'"); b.Property(nameof(BaseEntity.UpdatedAt)).HasColumnName("updated_at"); b.Property(nameof(BaseEntity.CreatedBy)).HasColumnName("created_by"); b.Property(nameof(BaseEntity.UpdatedBy)).HasColumnName("updated_by"); b.Property(nameof(BaseEntity.IsDeleted)).HasColumnName("is_deleted").HasDefaultValue(false); } } }