Files

350 lines
22 KiB
C#

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<SaasFeature>
{
public void Configure(EntityTypeBuilder<SaasFeature> 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<PermissionModule>
{
public void Configure(EntityTypeBuilder<PermissionModule> 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<SaasFeature>().WithMany()
.HasPrincipalKey(value => value.Code)
.HasForeignKey(value => value.RequiredFeatureCode)
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class SaasOfferingConfiguration : IEntityTypeConfiguration<SaasOffering>
{
public void Configure(EntityTypeBuilder<SaasOffering> 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<SaasOfferingVersion>
{
public void Configure(EntityTypeBuilder<SaasOfferingVersion> 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<SaasOffering>().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<SaasOfferingVersionFeature>
{
public void Configure(EntityTypeBuilder<SaasOfferingVersionFeature> 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<SaasOfferingVersion>().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<SaasFeature>().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class SaasFeatureLimitDefinitionConfiguration : IEntityTypeConfiguration<SaasFeatureLimitDefinition>
{
public void Configure(EntityTypeBuilder<SaasFeatureLimitDefinition> 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<SaasFeature>().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<SaasOfferingVersionLimit>
{
public void Configure(EntityTypeBuilder<SaasOfferingVersionLimit> 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<SaasOfferingVersion>().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<SaasFeatureLimitDefinition>().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<TenantSaasSubscription>
{
public void Configure(EntityTypeBuilder<TenantSaasSubscription> 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<SaasOfferingVersion>().WithMany().HasForeignKey(value => value.BaseOfferingVersionId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<SaasOfferingVersion>().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<TenantSaasSubscriptionItem>
{
public void Configure(EntityTypeBuilder<TenantSaasSubscriptionItem> 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<TenantSaasSubscription>().WithMany()
.HasForeignKey(value => new { value.TenantId, value.SubscriptionId })
.HasPrincipalKey(value => new { value.TenantId, value.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<SaasOfferingVersion>().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<PlatformBillingOrderItem>().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<TenantFeatureOverride>
{
public void Configure(EntityTypeBuilder<TenantFeatureOverride> 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<SaasFeature>().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class TenantFeatureUsageConfiguration : IEntityTypeConfiguration<TenantFeatureUsage>
{
public void Configure(EntityTypeBuilder<TenantFeatureUsage> 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<SaasFeatureLimitDefinition>().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<PlatformBillingQuote>
{
public void Configure(EntityTypeBuilder<PlatformBillingQuote> 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<PlatformBillingQuoteItem>
{
public void Configure(EntityTypeBuilder<PlatformBillingQuoteItem> 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<Tenant>().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PlatformBillingQuote>().WithMany().HasForeignKey(value => new { value.TenantId, value.QuoteId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<SaasOfferingVersion>().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<PlatformBillingOrder>
{
public void Configure(EntityTypeBuilder<PlatformBillingOrder> 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<PlatformBillingQuote>().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<PlatformBillingOrderItem>
{
public void Configure(EntityTypeBuilder<PlatformBillingOrderItem> 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<Tenant>().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PlatformBillingOrder>().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<SaasOfferingVersion>().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<PlatformBillingPayment>
{
public void Configure(EntityTypeBuilder<PlatformBillingPayment> 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<PlatformBillingOrder>().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<PlatformBillingPaymentEvent>
{
public void Configure(EntityTypeBuilder<PlatformBillingPaymentEvent> 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<Tenant>().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne<PlatformBillingPayment>().WithMany().HasForeignKey(value => new { value.TenantId, value.PaymentId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class PlatformBillingRefundConfiguration : IEntityTypeConfiguration<PlatformBillingRefund>
{
public void Configure(EntityTypeBuilder<PlatformBillingRefund> 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<PlatformBillingOrder>().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<PlatformBillingPayment>().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<PlatformBillingInvoice>
{
public void Configure(EntityTypeBuilder<PlatformBillingInvoice> 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<PlatformBillingOrder>().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"));
}
}