using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Tiku.Domain.Growth; using Tiku.Domain.Identity; using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; internal sealed class ReferralTrackConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("referral_tracks"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.EventType).HasMaxLength(100); builder.Property(entity => entity.RefCode).HasColumnType("citext").HasMaxLength(100); builder.Property(entity => entity.Source).HasMaxLength(100); builder.Property(entity => entity.IpAddress).HasMaxLength(100); builder.Property(entity => entity.UserAgent).HasMaxLength(1024); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.LeadId, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.RefCode, entity.EventType, entity.CreatedAt }); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReferrerUserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.TargetUserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.LeadId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class ReferralCodeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("referral_codes"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Code).HasColumnType("citext").HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Channel).HasMaxLength(100); builder.Property(entity => entity.LandingPath).HasMaxLength(2048); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.UserId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.Code, entity.Status }); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade); } } internal sealed class ReferralLeadConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("referral_leads"); builder.ConfigureTimestamps(); builder.Property(entity => entity.RefCode).HasColumnType("citext").HasMaxLength(100); builder.Property(entity => entity.Source).HasMaxLength(100); builder.Property(entity => entity.BindType).HasSnakeCaseEnum(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.AssignmentMode).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.Property(entity => entity.BoundAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.StudentUserId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.ReferrerUserId, entity.BoundAt }); builder.HasIndex(entity => new { entity.TenantId, entity.AssignedToUserId, entity.Status, entity.BoundAt }); builder.HasOne().WithMany().HasForeignKey(entity => entity.StudentUserId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReferrerUserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.AssignedToUserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.AssignedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.FirstTrackId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class ReferralTeamEdgeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("referral_team_edges"); builder.ConfigureTimestamps(); builder.Property(entity => entity.RelationType).HasSnakeCaseEnum(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.MemberUserId, entity.RelationType }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.LeaderUserId, entity.Status }); builder.HasOne().WithMany().HasForeignKey(entity => entity.MemberUserId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.LeaderUserId).OnDelete(DeleteBehavior.SetNull); } } internal sealed class ReferralQrcodeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("referral_qrcodes"); builder.ConfigureTimestamps(); builder.Property(entity => entity.RefCode).HasColumnType("citext").HasMaxLength(100); builder.Property(entity => entity.Scene).HasMaxLength(300); builder.Property(entity => entity.Page).HasMaxLength(300); builder.Property(entity => entity.Provider).HasMaxLength(100); builder.Property(entity => entity.QrcodeUrl).HasMaxLength(2048); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.Provider, entity.Scene, entity.Page }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.RefCode, entity.Status }); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CrmConfigConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("crm_config"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Url).HasMaxLength(2048); builder.Property(entity => entity.SecretRef).HasMaxLength(300); builder.Property(entity => entity.FormName).HasMaxLength(200); builder.Property(entity => entity.ExamType).HasMaxLength(100); builder.Property(entity => entity.AssignmentMode).HasSnakeCaseEnum(); builder.Property(entity => entity.AssignmentPool).IsJson("[]"); builder.Property(entity => entity.AssignmentConfig).IsJson("{}"); builder.HasIndex(entity => entity.TenantId).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.AssignmentMode }); } } internal sealed class CrmWebhookQueueItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("crm_webhook_queue"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.RecordId).HasMaxLength(200); builder.Property(entity => entity.LeadId).HasMaxLength(200); builder.Property(entity => entity.Source).HasMaxLength(200); builder.Property(entity => entity.Provider).HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.IdempotencyKey).HasMaxLength(200); builder.Property(entity => entity.TargetUrl).HasMaxLength(2048); builder.Property(entity => entity.Payload).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.RecordId }) .IsUnique() .HasFilter("record_id is not null"); builder.HasIndex(entity => new { entity.TenantId, entity.IdempotencyKey }) .IsUnique() .HasFilter("idempotency_key is not null"); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.ScheduledAt, entity.NextAttemptAt }); builder.HasIndex(entity => new { entity.TenantId, entity.RecordId, entity.CreatedAt }) .HasFilter("record_id is not null"); builder.HasIndex(entity => new { entity.TenantId, entity.Source, entity.CreatedAt }) .HasFilter("source = 'tenant.student.crm_push'"); builder.ToTable(table => table.HasCheckConstraint("ck_crm_webhook_queue_attempts", "attempts >= 0")); } } internal sealed class CrmWebhookLogConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("crm_webhook_log"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.RecordId).HasMaxLength(200); builder.Property(entity => entity.LeadId).HasMaxLength(200); builder.Property(entity => entity.Outcome).HasMaxLength(100); builder.Property(entity => entity.RequestPayload).IsJson("{}"); builder.Property(entity => entity.ResponseSummary).HasMaxLength(2000); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.RecordId, entity.CreatedAt }); } } internal sealed class TenantCommissionSettingConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_commission_settings"); builder.ConfigureTimestamps(); builder.Property(entity => entity.DefaultRate).HasPrecision(6, 4); builder.Property(entity => entity.SettlementCycle).HasSnakeCaseEnum(); builder.Property(entity => entity.Config).IsJson("{}"); builder.HasIndex(entity => entity.TenantId).IsUnique(); builder.ToTable(table => { table.HasCheckConstraint("ck_tenant_commission_settings_default_rate", "default_rate >= 0 and default_rate <= 1"); table.HasCheckConstraint("ck_tenant_commission_settings_min_settlement", "min_settlement_cents >= 0"); }); builder.HasOne().WithMany().HasForeignKey(entity => entity.UpdatedBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommissionSettlementConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("commission_settlements"); builder.ConfigureTimestamps(); builder.Property(entity => entity.SettlementNo).HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.DefaultRate).HasPrecision(6, 4); builder.Property(entity => entity.EffectiveRate).HasPrecision(6, 4); builder.Property(entity => entity.PaymentMethod).HasMaxLength(100); builder.Property(entity => entity.PaymentAccount).HasMaxLength(200); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.SettlementNo }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.ReferrerUserId, entity.PeriodStart, entity.PeriodEnd }) .IsUnique() .HasDatabaseName("ux_commission_settlements_referrer_period"); builder.HasIndex(entity => new { entity.TenantId, entity.ReferrerUserId, entity.PeriodStart }) .HasDatabaseName("ix_commission_settlements_referrer_start"); builder.ToTable(table => { table.HasCheckConstraint("ck_commission_settlements_counts", "source_count >= 0 and paid_user_count >= 0"); table.HasCheckConstraint("ck_commission_settlements_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); table.HasCheckConstraint("ck_commission_settlements_rates", "default_rate >= 0 and default_rate <= 1 and (effective_rate is null or (effective_rate >= 0 and effective_rate <= 1))"); table.HasCheckConstraint("ck_commission_settlements_period", "period_end >= period_start"); }); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReferrerUserId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.GeneratedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReviewedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.PaidBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommissionSettlementItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("commission_settlement_items"); builder.ConfigureTimestamps(); builder.Property(entity => entity.SourceType).HasSnakeCaseEnum(); builder.Property(entity => entity.SourceNo).HasMaxLength(100); builder.Property(entity => entity.CommissionRate).HasPrecision(6, 4); builder.Property(entity => entity.RateSource).HasSnakeCaseEnum(); builder.Property(entity => entity.AttributionType).HasMaxLength(100); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.SourceType, entity.SourceId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.ReferrerUserId, entity.SourcePaidAt }); builder.HasIndex(entity => new { entity.TenantId, entity.SettlementId }); builder.ToTable(table => { table.HasCheckConstraint("ck_commission_settlement_items_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); table.HasCheckConstraint("ck_commission_settlement_items_rate", "commission_rate >= 0 and commission_rate <= 1"); }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.SettlementId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReferrerUserId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.StudentUserId).OnDelete(DeleteBehavior.SetNull); } }