using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; internal sealed class SaasFeatureConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("saas_features"); builder.ConfigureTimestamps(); builder.Property(value => value.Code).HasMaxLength(120); builder.Property(value => value.Name).HasMaxLength(200); builder.Property(value => value.Category).HasMaxLength(100); builder.Property(value => value.Description).HasMaxLength(1000); builder.Property(value => value.Currency).HasMaxLength(10); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.HasIndex(value => value.Code).IsUnique(); builder.HasIndex(value => new { value.Status, value.Category, value.SortOrder }); builder.ToTable(table => table.HasCheckConstraint("ck_saas_features_reference_price", "reference_price_cents >= 0")); } } internal sealed class PermissionModuleConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("permission_modules"); builder.ConfigureTimestamps(); builder.Property(value => value.Code).HasMaxLength(120); builder.Property(value => value.Name).HasMaxLength(200); builder.Property(value => value.Area).HasSnakeCaseEnum(); builder.Property(value => value.RequiredFeatureCode).HasMaxLength(120); builder.Property(value => value.Description).HasMaxLength(1000); builder.HasIndex(value => value.Code).IsUnique(); builder.HasIndex(value => new { value.Area, value.SortOrder }); builder.HasOne().WithMany() .HasPrincipalKey(value => value.Code) .HasForeignKey(value => value.RequiredFeatureCode) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class SaasOfferingConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("saas_offerings"); builder.ConfigureTimestamps(); builder.Property(value => value.Code).HasMaxLength(120); builder.Property(value => value.Name).HasMaxLength(200); builder.Property(value => value.Type).HasSnakeCaseEnum(); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.Description).HasMaxLength(1000); builder.HasIndex(value => value.Code).IsUnique(); builder.HasIndex(value => new { value.Type, value.Status, value.SortOrder }); } } internal sealed class SaasOfferingVersionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("saas_offering_versions"); builder.ConfigureTimestamps(); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.BillingCycle).HasSnakeCaseEnum(); builder.Property(value => value.Currency).HasMaxLength(10); builder.Property(value => value.Metadata).IsJson("{}"); builder.HasIndex(value => new { value.OfferingId, value.Version }).IsUnique(); builder.HasIndex(value => new { value.OfferingId, value.Status, value.EffectiveAt }); builder.HasOne().WithMany().HasForeignKey(value => value.OfferingId).OnDelete(DeleteBehavior.Cascade); builder.ToTable(table => { table.HasCheckConstraint("ck_saas_offering_versions_version", "version > 0"); table.HasCheckConstraint("ck_saas_offering_versions_amount", "original_amount_cents >= 0 and amount_cents >= 0 and amount_cents <= original_amount_cents"); }); } } internal sealed class SaasOfferingVersionFeatureConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("saas_offering_version_features"); builder.Property(value => value.FeatureCode).HasMaxLength(120); builder.HasIndex(value => new { value.OfferingVersionId, value.FeatureCode }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Restrict); } } internal sealed class SaasFeatureLimitDefinitionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("saas_feature_limit_definitions"); builder.ConfigureTimestamps(); builder.Property(value => value.MetricCode).HasMaxLength(120); builder.Property(value => value.FeatureCode).HasMaxLength(120); builder.Property(value => value.Name).HasMaxLength(200); builder.Property(value => value.Unit).HasMaxLength(50); builder.Property(value => value.Kind).HasSnakeCaseEnum(); builder.HasIndex(value => value.MetricCode).IsUnique(); builder.HasIndex(value => new { value.FeatureCode, value.Kind }); builder.HasOne().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Cascade); builder.ToTable(table => table.HasCheckConstraint("ck_saas_feature_limit_warning", "warning_percent between 1 and 100")); } } internal sealed class SaasOfferingVersionLimitConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("saas_offering_version_limits"); builder.Property(value => value.MetricCode).HasMaxLength(120); builder.HasIndex(value => new { value.OfferingVersionId, value.MetricCode }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasPrincipalKey(value => value.MetricCode).HasForeignKey(value => value.MetricCode).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_saas_offering_version_limits_value", "limit_value >= 0")); } } internal sealed class TenantSaasSubscriptionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_saas_subscriptions"); builder.ConfigureTimestamps(); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.LifecycleVersion).IsConcurrencyToken(); builder.Property(value => value.Metadata).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.Status, value.CurrentPeriodEnd }); builder.HasIndex(value => value.TenantId).IsUnique().HasFilter("status in ('trial', 'active', 'past_due', 'suspended')"); builder.HasOne().WithMany().HasForeignKey(value => value.BaseOfferingVersionId).OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(value => value.ScheduledBaseOfferingVersionId).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_tenant_saas_subscriptions_period", "current_period_end > current_period_start and current_period_start >= starts_at")); } } internal sealed class TenantSaasSubscriptionItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_saas_subscription_items"); builder.ConfigureTimestamps(); builder.Property(value => value.ItemType).HasSnakeCaseEnum(); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.HasIndex(value => new { value.TenantId, value.SubscriptionId, value.OfferingVersionId, value.Status }); builder.HasIndex(value => new { value.TenantId, value.SubscriptionId, value.ItemType }) .HasDatabaseName("ux_tenant_saas_subscription_items_current_base") .IsUnique() .HasFilter("item_type = 'base_plan' and status = 'active'"); builder.HasIndex(value => new { value.TenantId, value.SubscriptionId, value.Status }) .HasDatabaseName("ux_tenant_saas_subscription_items_scheduled_base") .IsUnique() .HasFilter("item_type = 'base_plan' and status = 'scheduled'"); builder.HasOne().WithMany() .HasForeignKey(value => new { value.TenantId, value.SubscriptionId }) .HasPrincipalKey(value => new { value.TenantId, value.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(value => new { value.TenantId, value.SourceOrderItemId }) .HasPrincipalKey(value => new { value.TenantId, value.Id }) .OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_tenant_saas_subscription_items_period", "ends_at > starts_at")); } } internal sealed class TenantFeatureOverrideConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_feature_overrides"); builder.ConfigureTimestamps(); builder.Property(value => value.FeatureCode).HasMaxLength(120); builder.Property(value => value.Mode).HasSnakeCaseEnum(); builder.Property(value => value.Reason).HasMaxLength(1000); builder.HasIndex(value => new { value.TenantId, value.FeatureCode }).IsUnique(); builder.HasOne().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Restrict); } } internal sealed class TenantFeatureUsageConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_feature_usage"); builder.ConfigureTimestamps(); builder.Property(value => value.MetricCode).HasMaxLength(120); builder.Property(value => value.Version).IsConcurrencyToken(); builder.HasIndex(value => new { value.TenantId, value.MetricCode, value.PeriodStart, value.PeriodEnd }).IsUnique(); builder.HasOne().WithMany().HasPrincipalKey(value => value.MetricCode).HasForeignKey(value => value.MetricCode).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => { table.HasCheckConstraint("ck_tenant_feature_usage_values", "used_value >= 0 and limit_value_snapshot >= 0"); table.HasCheckConstraint("ck_tenant_feature_usage_period", "period_end > period_start"); }); } } internal sealed class PlatformBillingQuoteConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("platform_billing_quotes"); builder.ConfigureTimestamps(); builder.Property(value => value.QuoteNo).HasMaxLength(100); builder.Property(value => value.IdempotencyKey).HasMaxLength(200); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.Purpose).HasSnakeCaseEnum(); builder.Property(value => value.Currency).HasMaxLength(10); builder.Property(value => value.FeatureSnapshot).IsJson("[]"); builder.Property(value => value.LimitSnapshot).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.QuoteNo }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.IdempotencyKey }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.Status, value.ExpiresAt }); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_quotes_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents")); } } internal sealed class PlatformBillingQuoteItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("platform_billing_quote_items"); builder.HasAlternateKey(value => new { value.TenantId, value.Id }); builder.Property(value => value.ItemType).HasSnakeCaseEnum(); builder.Property(value => value.Snapshot).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.QuoteId, value.OfferingVersionId }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.QuoteId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_quote_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0")); } } internal sealed class PlatformBillingOrderConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("platform_billing_orders"); builder.ConfigureTimestamps(); builder.Property(value => value.OrderNo).HasMaxLength(100); builder.Property(value => value.IdempotencyKey).HasMaxLength(200); builder.Property(value => value.Purpose).HasSnakeCaseEnum(); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.Currency).HasMaxLength(10); builder.Property(value => value.Snapshot).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.OrderNo }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.IdempotencyKey }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.Status, value.CreatedAt }); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.QuoteId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_orders_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents")); } } internal sealed class PlatformBillingOrderItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("platform_billing_order_items"); builder.HasAlternateKey(value => new { value.TenantId, value.Id }); builder.Property(value => value.ItemType).HasSnakeCaseEnum(); builder.Property(value => value.Snapshot).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.OrderId, value.OfferingVersionId }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_order_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0")); } } internal sealed class PlatformBillingPaymentConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("platform_billing_payments"); builder.ConfigureTimestamps(); builder.Property(value => value.PaymentNo).HasMaxLength(100); builder.Property(value => value.IdempotencyKey).HasMaxLength(200); builder.Property(value => value.Provider).HasMaxLength(50); builder.Property(value => value.Method).HasMaxLength(50); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.ProviderTradeNo).HasMaxLength(200); builder.Property(value => value.ClientPayload).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.PaymentNo }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.IdempotencyKey }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.OrderId, value.Status }); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_payments_amount", "amount_cents >= 0")); } } internal sealed class PlatformBillingPaymentEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("platform_billing_payment_events"); builder.HasAlternateKey(value => new { value.TenantId, value.Id }); builder.Property(value => value.Provider).HasMaxLength(50); builder.Property(value => value.ProviderEventId).HasMaxLength(200); builder.Property(value => value.EventType).HasMaxLength(100); builder.Property(value => value.Payload).IsJson("{}"); builder.Property(value => value.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(value => new { value.TenantId, value.Provider, value.ProviderEventId }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.PaymentId, value.CreatedAt }); builder.HasOne().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.PaymentId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); } } internal sealed class PlatformBillingRefundConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("platform_billing_refunds"); builder.ConfigureTimestamps(); builder.Property(value => value.RefundNo).HasMaxLength(100); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.Reason).HasMaxLength(1000); builder.Property(value => value.ProviderRefundNo).HasMaxLength(200); builder.HasIndex(value => new { value.TenantId, value.RefundNo }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.OrderId, value.Status }); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.PaymentId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_refunds_amount", "amount_cents > 0")); } } internal sealed class PlatformBillingInvoiceConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("platform_billing_invoices"); builder.ConfigureTimestamps(); builder.Property(value => value.InvoiceNo).HasMaxLength(100); builder.Property(value => value.Status).HasSnakeCaseEnum(); builder.Property(value => value.Currency).HasMaxLength(10); builder.Property(value => value.BillingProfileSnapshot).IsJson("{}"); builder.HasIndex(value => new { value.TenantId, value.InvoiceNo }).IsUnique(); builder.HasIndex(value => new { value.TenantId, value.Status, value.DueDate }); builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_invoices_amount", "total_amount_cents >= 0")); } }