Add Master Data
This commit is contained in:
@@ -1,15 +1,22 @@
|
||||
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.Metadata;
|
||||
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<AppDbContext> options) : base(options) { }
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
// ===== Auth =====
|
||||
public DbSet<User> Users => Set<User>();
|
||||
@@ -36,14 +43,170 @@ public class AppDbContext : DbContext
|
||||
public DbSet<TenantConfig> Tenants => Set<TenantConfig>();
|
||||
public DbSet<TenantDomain> TenantDomains => Set<TenantDomain>();
|
||||
|
||||
// ===== Customers =====
|
||||
public DbSet<CustomerProfile> CustomerProfiles => Set<CustomerProfile>();
|
||||
public DbSet<PersonProfile> PersonProfiles => Set<PersonProfile>();
|
||||
public DbSet<OrganizationProfile> OrganizationProfiles => Set<OrganizationProfile>();
|
||||
public DbSet<Address> Addresses => Set<Address>();
|
||||
public DbSet<CustomerAddress> CustomerAddresses => Set<CustomerAddress>();
|
||||
public DbSet<CustomerContact> CustomerContacts => Set<CustomerContact>();
|
||||
public DbSet<CustomerTag> CustomerTags => Set<CustomerTag>();
|
||||
|
||||
// ===== Master Data =====
|
||||
public DbSet<Brand> Brands => Set<Brand>();
|
||||
public DbSet<BrandBlock> BrandBlocks => Set<BrandBlock>();
|
||||
|
||||
public DbSet<Category> Categories => Set<Category>();
|
||||
public DbSet<CategoryBlock> CategoryBlocks => Set<CategoryBlock>();
|
||||
public DbSet<CategoryExt> CategoryExts => Set<CategoryExt>();
|
||||
|
||||
public DbSet<Uom> Uoms => Set<Uom>();
|
||||
public DbSet<UomBlock> UomBlocks => Set<UomBlock>();
|
||||
|
||||
public DbSet<Currency> Currencies => Set<Currency>();
|
||||
public DbSet<CurrencyBlock> CurrencyBlocks => Set<CurrencyBlock>();
|
||||
|
||||
public DbSet<Country> Countries => Set<Country>();
|
||||
public DbSet<CountryBlock> CountryBlocks => Set<CountryBlock>();
|
||||
|
||||
public DbSet<Manufacturer> Manufacturers => Set<Manufacturer>();
|
||||
public DbSet<ManufacturerBlock> ManufacturerBlocks => Set<ManufacturerBlock>();
|
||||
|
||||
public DbSet<Market> Markets => Set<Market>();
|
||||
public DbSet<MarketBlock> MarketBlocks => Set<MarketBlock>();
|
||||
|
||||
public DbSet<Language> Languages => Set<Language>();
|
||||
public DbSet<LanguageBlock> LanguageBlocks => Set<LanguageBlock>();
|
||||
|
||||
public DbSet<ComplianceStatus> ComplianceStatuses => Set<ComplianceStatus>();
|
||||
public DbSet<ComplianceStatusBlock> ComplianceStatusBlocks => Set<ComplianceStatusBlock>();
|
||||
|
||||
public DbSet<QaStage> QaStages => Set<QaStage>();
|
||||
public DbSet<QaStageBlock> QaStageBlocks => Set<QaStageBlock>();
|
||||
|
||||
public DbSet<Route> Routes => Set<Route>();
|
||||
public DbSet<RouteBlock> RouteBlocks => Set<RouteBlock>();
|
||||
|
||||
public DbSet<RxSchedule> RxSchedules => Set<RxSchedule>();
|
||||
public DbSet<RxScheduleBlock> RxScheduleBlocks => Set<RxScheduleBlock>();
|
||||
|
||||
public DbSet<StabilityStatus> StabilityStatuses => Set<StabilityStatus>();
|
||||
public DbSet<StabilityStatusBlock> StabilityStatusBlocks => Set<StabilityStatusBlock>();
|
||||
|
||||
public DbSet<RiskClass> RiskClasses => Set<RiskClass>();
|
||||
public DbSet<RiskClassBlock> RiskClassBlocks => Set<RiskClassBlock>();
|
||||
|
||||
public DbSet<SterilizationMethod> SterilizationMethods => Set<SterilizationMethod>();
|
||||
public DbSet<SterilizationMethodBlock> SterilizationMethodBlocks => Set<SterilizationMethodBlock>();
|
||||
|
||||
public DbSet<QcStatus> QcStatuses => Set<QcStatus>();
|
||||
public DbSet<QcStatusBlock> QcStatusBlocks => Set<QcStatusBlock>();
|
||||
|
||||
public DbSet<DocControlStatus> DocControlStatuses => Set<DocControlStatus>();
|
||||
public DbSet<DocControlStatusBlock> DocControlStatusBlocks => Set<DocControlStatusBlock>();
|
||||
|
||||
public DbSet<RecallClass> RecallClasses => Set<RecallClass>();
|
||||
public DbSet<RecallClassBlock> RecallClassBlocks => Set<RecallClassBlock>();
|
||||
|
||||
public DbSet<PackingGroup> PackingGroups => Set<PackingGroup>();
|
||||
public DbSet<PackingGroupBlock> PackingGroupBlocks => Set<PackingGroupBlock>();
|
||||
|
||||
public DbSet<Vvm> Vvms => Set<Vvm>();
|
||||
public DbSet<VvmBlock> VvmBlocks => Set<VvmBlock>();
|
||||
|
||||
public DbSet<FuncTest> FuncTests => Set<FuncTest>();
|
||||
public DbSet<FuncTestBlock> FuncTestBlocks => Set<FuncTestBlock>();
|
||||
|
||||
public DbSet<HazardClass> HazardClasses => Set<HazardClass>();
|
||||
public DbSet<HazardClassBlock> HazardClassBlocks => Set<HazardClassBlock>();
|
||||
|
||||
public DbSet<Species> Species => Set<Species>();
|
||||
public DbSet<SpeciesBlock> SpeciesBlocks => Set<SpeciesBlock>();
|
||||
|
||||
public DbSet<Allergen> Allergens => Set<Allergen>();
|
||||
public DbSet<AllergenBlock> AllergenBlocks => Set<AllergenBlock>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder model)
|
||||
{
|
||||
// JSON converters/comparers
|
||||
var jsonOpts = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||
|
||||
var dictStrConverter = new ValueConverter<Dictionary<string, string>?, string?>(
|
||||
v => v == null ? null : JsonSerializer.Serialize(v, jsonOpts),
|
||||
v => string.IsNullOrWhiteSpace(v)
|
||||
? null
|
||||
: JsonSerializer.Deserialize<Dictionary<string, string>>(v!, jsonOpts)!);
|
||||
|
||||
var dictStrComparer = new ValueComparer<Dictionary<string, string>?>(
|
||||
(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<Dictionary<string, string>>(JsonSerializer.Serialize(v, jsonOpts),
|
||||
jsonOpts));
|
||||
|
||||
var dictObjConverter = new ValueConverter<Dictionary<string, object>?, string?>(
|
||||
v => v == null ? null : JsonSerializer.Serialize(v, jsonOpts),
|
||||
v => string.IsNullOrWhiteSpace(v)
|
||||
? null
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(v!, jsonOpts)!);
|
||||
|
||||
var dictObjComparer = new ValueComparer<Dictionary<string, object>?>(
|
||||
(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<Dictionary<string, object>>(JsonSerializer.Serialize(v, jsonOpts),
|
||||
jsonOpts));
|
||||
|
||||
void ConfigureMasterBase<TEntity>(string table, string schema = "master")
|
||||
where TEntity : MasterBase
|
||||
{
|
||||
var b = model.Entity<TEntity>();
|
||||
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();
|
||||
// b.HasIndex(x => new { x.TenantId, x.OverridesGlobalId }).IsUnique().HasFilter("\"OverridesGlobalId\" IS NOT NULL");
|
||||
}
|
||||
|
||||
void ConfigureBlockBase<TBlock, TMaster>(string table, string schema = "master")
|
||||
where TBlock : MasterBlockBase
|
||||
where TMaster : MasterBase
|
||||
{
|
||||
var b = model.Entity<TBlock>();
|
||||
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<TMaster>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.GlobalId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
// ====== Tenancy (meta) ======
|
||||
model.Entity<TenantConfig>(b =>
|
||||
{
|
||||
b.ToTable("tenants", schema: "meta");
|
||||
b.HasKey(x => x.TenantKey); // PK = key (slug)
|
||||
b.HasAlternateKey(x => x.TenantId); // AK = GUID
|
||||
b.HasKey(x => x.TenantKey);
|
||||
b.HasAlternateKey(x => x.TenantId);
|
||||
b.HasIndex(x => x.TenantId).IsUnique();
|
||||
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128).IsRequired();
|
||||
@@ -51,8 +214,7 @@ public class AppDbContext : DbContext
|
||||
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")
|
||||
b.Property(x => x.UpdatedAtUtc).HasColumnName("updated_at_utc")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
b.HasIndex(x => x.IsActive);
|
||||
});
|
||||
@@ -62,11 +224,10 @@ public class AppDbContext : DbContext
|
||||
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); // optional
|
||||
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")
|
||||
b.Property(x => x.UpdatedAtUtc).HasColumnName("updated_at_utc")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
|
||||
b.HasIndex(x => x.TenantKey);
|
||||
@@ -84,8 +245,6 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("users");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
// principal key สำหรับ composite FK จากลูก ๆ
|
||||
b.HasAlternateKey(u => new { u.TenantId, u.Id });
|
||||
|
||||
b.Property(x => x.PasswordHash).IsRequired();
|
||||
@@ -105,14 +264,13 @@ public class AppDbContext : DbContext
|
||||
|
||||
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");
|
||||
.HasDatabaseName("ix_user_identity_primary_per_type");
|
||||
|
||||
// (TenantId, UserId) -> User.(TenantId, Id)
|
||||
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);
|
||||
.WithMany(u => u.Identities)
|
||||
.HasForeignKey(i => new { i.TenantId, i.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserMfaFactor>(b =>
|
||||
@@ -125,10 +283,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.WithMany(u => u.MfaFactors)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserSession>(b =>
|
||||
@@ -141,10 +299,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.WithMany(u => u.Sessions)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserPasswordHistory>(b =>
|
||||
@@ -155,10 +313,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.WithMany(u => u.PasswordHistories)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserExternalAccount>(b =>
|
||||
@@ -171,10 +329,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.WithMany(u => u.ExternalAccounts)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<Role>(b =>
|
||||
@@ -209,16 +367,16 @@ public class AppDbContext : DbContext
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.RoleId }).IsUnique();
|
||||
|
||||
b.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne<Role>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<RolePermission>(b =>
|
||||
@@ -229,16 +387,16 @@ public class AppDbContext : DbContext
|
||||
b.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique();
|
||||
|
||||
b.HasOne<Role>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne<Permission>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PermissionId })
|
||||
.HasPrincipalKey(nameof(Permission.TenantId), nameof(Permission.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PermissionId })
|
||||
.HasPrincipalKey(nameof(Permission.TenantId), nameof(Permission.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// ====== HR ======
|
||||
@@ -256,7 +414,7 @@ public class AppDbContext : DbContext
|
||||
b.HasOne(x => x.User)
|
||||
.WithOne()
|
||||
.HasForeignKey<UserProfile>(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey<User>(u => new { u.TenantId, u.Id }) // <-- เปลี่ยนตรงนี้
|
||||
.HasPrincipalKey<User>(u => new { u.TenantId, u.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
@@ -272,10 +430,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => new { x.TenantId, x.ParentDepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
model.Entity<Position>(b =>
|
||||
@@ -301,22 +459,22 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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);
|
||||
.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);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PositionId })
|
||||
.HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
model.Entity<EmployeeAddress>(b =>
|
||||
@@ -330,10 +488,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 });
|
||||
});
|
||||
@@ -347,10 +505,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 });
|
||||
});
|
||||
@@ -365,28 +523,280 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 });
|
||||
});
|
||||
|
||||
// ====== Enums as ints ======
|
||||
// ====== Customers ======
|
||||
model.Entity<CustomerProfile>(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<PersonProfile>(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey<CustomerProfile>(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Organization)
|
||||
.WithOne(x => x.Customer)
|
||||
.HasForeignKey<OrganizationProfile>(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey<CustomerProfile>(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<PersonProfile>(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<OrganizationProfile>(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<CustomerContact>(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<Address>(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<CustomerAddress>(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<CustomerTag>(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<Brand>("brands");
|
||||
ConfigureBlockBase<BrandBlock, Brand>("brand_blocks");
|
||||
|
||||
model.Entity<Category>(b =>
|
||||
{
|
||||
ConfigureMasterBase<Category>("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<CategoryBlock, Category>("category_blocks");
|
||||
|
||||
model.Entity<CategoryExt>(b =>
|
||||
{
|
||||
b.ToTable("category_exts", "master");
|
||||
b.HasKey(x => x.CategoryId);
|
||||
b.HasOne(x => x.Category).WithOne().HasForeignKey<CategoryExt>(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<Uom>("uoms");
|
||||
ConfigureBlockBase<UomBlock, Uom>("uom_blocks");
|
||||
|
||||
ConfigureMasterBase<Currency>("currencies");
|
||||
ConfigureBlockBase<CurrencyBlock, Currency>("currency_blocks");
|
||||
|
||||
ConfigureMasterBase<Country>("countries");
|
||||
ConfigureBlockBase<CountryBlock, Country>("country_blocks");
|
||||
|
||||
ConfigureMasterBase<Manufacturer>("manufacturers");
|
||||
ConfigureBlockBase<ManufacturerBlock, Manufacturer>("manufacturer_blocks");
|
||||
|
||||
ConfigureMasterBase<Market>("markets");
|
||||
ConfigureBlockBase<MarketBlock, Market>("market_blocks");
|
||||
|
||||
ConfigureMasterBase<Language>("languages");
|
||||
ConfigureBlockBase<LanguageBlock, Language>("language_blocks");
|
||||
|
||||
ConfigureMasterBase<ComplianceStatus>("compliance_statuses");
|
||||
ConfigureBlockBase<ComplianceStatusBlock, ComplianceStatus>("compliance_status_blocks");
|
||||
|
||||
ConfigureMasterBase<QaStage>("qa_stages");
|
||||
ConfigureBlockBase<QaStageBlock, QaStage>("qa_stage_blocks");
|
||||
|
||||
ConfigureMasterBase<Route>("routes");
|
||||
ConfigureBlockBase<RouteBlock, Route>("route_blocks");
|
||||
|
||||
ConfigureMasterBase<RxSchedule>("rx_schedules");
|
||||
ConfigureBlockBase<RxScheduleBlock, RxSchedule>("rx_schedule_blocks");
|
||||
|
||||
ConfigureMasterBase<StabilityStatus>("stability_statuses");
|
||||
ConfigureBlockBase<StabilityStatusBlock, StabilityStatus>("stability_status_blocks");
|
||||
|
||||
ConfigureMasterBase<RiskClass>("risk_classes");
|
||||
ConfigureBlockBase<RiskClassBlock, RiskClass>("risk_class_blocks");
|
||||
|
||||
ConfigureMasterBase<SterilizationMethod>("sterilization_methods");
|
||||
ConfigureBlockBase<SterilizationMethodBlock, SterilizationMethod>("sterilization_method_blocks");
|
||||
|
||||
ConfigureMasterBase<QcStatus>("qc_statuses");
|
||||
ConfigureBlockBase<QcStatusBlock, QcStatus>("qc_status_blocks");
|
||||
|
||||
ConfigureMasterBase<DocControlStatus>("doc_control_statuses");
|
||||
ConfigureBlockBase<DocControlStatusBlock, DocControlStatus>("doc_control_status_blocks");
|
||||
|
||||
ConfigureMasterBase<RecallClass>("recall_classes");
|
||||
ConfigureBlockBase<RecallClassBlock, RecallClass>("recall_class_blocks");
|
||||
|
||||
ConfigureMasterBase<PackingGroup>("packing_groups");
|
||||
ConfigureBlockBase<PackingGroupBlock, PackingGroup>("packing_group_blocks");
|
||||
|
||||
ConfigureMasterBase<Vvm>("vvms");
|
||||
ConfigureBlockBase<VvmBlock, Vvm>("vvm_blocks");
|
||||
|
||||
ConfigureMasterBase<FuncTest>("func_tests");
|
||||
ConfigureBlockBase<FuncTestBlock, FuncTest>("func_test_blocks");
|
||||
|
||||
ConfigureMasterBase<HazardClass>("hazard_classes");
|
||||
ConfigureBlockBase<HazardClassBlock, HazardClass>("hazard_class_blocks");
|
||||
|
||||
ConfigureMasterBase<Species>("species");
|
||||
ConfigureBlockBase<SpeciesBlock, Species>("species_blocks");
|
||||
|
||||
ConfigureMasterBase<Allergen>("allergens");
|
||||
ConfigureBlockBase<AllergenBlock, Allergen>("allergen_blocks");
|
||||
|
||||
// ====== Enum conversions ======
|
||||
model.Entity<UserIdentity>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<UserMfaFactor>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<UserExternalAccount>().Property(x => x.Provider).HasConversion<int>();
|
||||
model.Entity<Employment>().Property(x => x.EmploymentType).HasConversion<int>();
|
||||
model.Entity<UserProfile>().Property(x => x.Gender).HasConversion<int?>();
|
||||
|
||||
model.Entity<CustomerProfile>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<CustomerProfile>().Property(x => x.Status).HasConversion<int>();
|
||||
model.Entity<CustomerContact>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<CustomerAddress>().Property(x => x.Label).HasConversion<int>();
|
||||
|
||||
// ====== BaseEntity common mapping ======
|
||||
foreach (var et in model.Model.GetEntityTypes()
|
||||
.Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType)))
|
||||
foreach (var et in model.Model.GetEntityTypes().Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType)))
|
||||
{
|
||||
var b = model.Entity(et.ClrType);
|
||||
|
||||
// Tenant
|
||||
b.Property<Guid>(nameof(BaseEntity.TenantId))
|
||||
.HasColumnName("tenant_id")
|
||||
.HasColumnType("uuid")
|
||||
@@ -395,23 +805,19 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasIndex(nameof(BaseEntity.TenantId));
|
||||
|
||||
// ชื่อ constraint สร้างแบบ concat แทน string interpolation กัน ambiguous handler
|
||||
var tn = et.GetTableName();
|
||||
if (!string.IsNullOrEmpty(tn))
|
||||
{
|
||||
b.HasCheckConstraint(string.Concat("ck_", tn, "_tenant_not_null"), "tenant_id is not null");
|
||||
b.HasCheckConstraint(string.Concat("ck_", tn, "_tenant_not_zero"),
|
||||
"tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
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'");
|
||||
}
|
||||
|
||||
// Audit
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnName("created_at")
|
||||
b.Property<DateTimeOffset>(nameof(BaseEntity.CreatedAt)).HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
b.Property<DateTimeOffset?>("UpdatedAt").HasColumnName("updated_at");
|
||||
b.Property<string?>("CreatedBy").HasColumnName("created_by");
|
||||
b.Property<string?>("UpdatedBy").HasColumnName("updated_by");
|
||||
b.Property<bool>("IsDeleted").HasColumnName("is_deleted").HasDefaultValue(false);
|
||||
b.Property<DateTimeOffset?>(nameof(BaseEntity.UpdatedAt)).HasColumnName("updated_at");
|
||||
b.Property<string?>(nameof(BaseEntity.CreatedBy)).HasColumnName("created_by");
|
||||
b.Property<string?>(nameof(BaseEntity.UpdatedBy)).HasColumnName("updated_by");
|
||||
b.Property<bool>(nameof(BaseEntity.IsDeleted)).HasColumnName("is_deleted").HasDefaultValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user