using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Tiku.Domain.Commerce; using Tiku.Domain.Identity; using Tiku.Domain.Learning; using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; internal sealed class BusinessLineConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("business_lines"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Code).HasMaxLength(50); builder.Property(entity => entity.Name).HasMaxLength(100); builder.Property(entity => entity.RegionAccessStrategy).HasSnakeCaseEnum().HasMaxLength(64); builder.Property(entity => entity.TargetRegionCooldownDays).HasDefaultValue(30); builder.ToTable(table => table.HasCheckConstraint( "ck_business_lines_target_region_cooldown_days", "target_region_cooldown_days between 0 and 365")); builder.HasIndex(entity => entity.Code).IsUnique(); } } internal sealed class MarketRegionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("market_regions"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Code).HasMaxLength(50); builder.Property(entity => entity.Name).HasMaxLength(100); builder.Property(entity => entity.ParentCode).HasMaxLength(50); builder.HasIndex(entity => entity.Code).IsUnique(); } } internal sealed class TenantLearningLicenseConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_learning_licenses"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Version).IsConcurrencyToken().HasDefaultValue(1L); builder.HasIndex(entity => new { entity.TenantId, entity.BusinessLineId, entity.Status }); builder.HasIndex(entity => entity.TenantId).IsUnique().HasFilter("is_primary and status = 'active'"); builder.HasOne().WithMany().HasForeignKey(entity => entity.BusinessLineId) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class TenantLearningLicenseRegionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("tenant_learning_license_regions"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.HasIndex(entity => new { entity.TenantId, entity.LicenseId, entity.MarketRegionId }).IsUnique(); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.LicenseId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.MarketRegionId) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class LearningProductConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("learning_products"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Code).HasMaxLength(100); builder.Property(entity => entity.Name).HasMaxLength(200); builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(entity => entity.BusinessLineId) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class LearningProductScopeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("learning_product_scopes"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.HasIndex(entity => new { entity.TenantId, entity.ProductId, entity.ContentSliceId }).IsUnique(); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ProductId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { TenantId = entity.ContentSliceOwnerTenantId, Id = entity.ContentSliceId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class ContentSliceConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("content_slices"); builder.ConfigureTimestamps(); builder.Property(entity => entity.RegionScope).HasSnakeCaseEnum(); builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.ContentVersion).IsConcurrencyToken().HasDefaultValue(1L); builder.HasIndex(entity => new { entity.TenantId, entity.ResourceType, entity.ResourceId, entity.Status }); builder.HasIndex(entity => new { entity.BusinessLineId, entity.RegionScope, entity.MarketRegionId }); builder.HasOne().WithMany().HasForeignKey(entity => entity.BusinessLineId) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(entity => entity.MarketRegionId) .OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint( "ck_content_slices_region_scope", "(region_scope = 'national' and market_region_id is null) or (region_scope = 'region' and market_region_id is not null)")); } } internal sealed class StudentTargetRegionHistoryConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("student_target_region_history"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Reason).HasMaxLength(1000); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.BusinessLineId }).IsUnique() .HasDatabaseName("ux_student_target_region_current") .HasFilter("is_current"); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.BusinessLineId, entity.EffectiveAt }).HasDatabaseName("ix_student_target_region_history"); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.BusinessLineId) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(entity => entity.ChangedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.MarketRegionId) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class ClassContentAssignmentConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("class_content_assignments"); builder.ConfigureTimestamps(); builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.RevokedReason).HasMaxLength(1000); builder.HasIndex(entity => new { entity.TenantId, entity.ClassId, entity.Status, entity.StartsAt }); builder.HasIndex(entity => new { entity.TenantId, entity.ResourceType, entity.ResourceId, entity.Status }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ClassId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { TenantId = entity.ContentSliceOwnerTenantId, Id = entity.ContentSliceId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.RevokedBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class LearningAccessVersionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("learning_access_versions"); builder.Property(entity => entity.GrantVersion).IsConcurrencyToken().HasDefaultValue(1L); builder.Property(entity => entity.ContentVersion).HasDefaultValue(1L); builder.Property(entity => entity.StrongRevocationVersion).HasDefaultValue(1L); builder.HasIndex(entity => new { entity.TenantId, entity.UserId }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade); } }