forked from xiongyuxing/tiku-backend.net
feat(saas): implement marketplace and tenant onboarding
This commit is contained in:
@@ -299,35 +299,6 @@ internal sealed class CouponRedemptionConfiguration : IEntityTypeConfiguration<C
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantSubscriptionConfiguration : IEntityTypeConfiguration<TenantSubscription>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantSubscription> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_subscriptions");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.PlanCode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.BillingCycle).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.ExpiresAt });
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantUsageRecordConfiguration : IEntityTypeConfiguration<TenantUsageRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantUsageRecord> builder)
|
||||
{
|
||||
builder.ConfigureEntity("tenant_usage_records");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.Property(entity => entity.MetricKey).HasMaxLength(100);
|
||||
builder.Property(entity => entity.MetricValue).HasPrecision(18, 4);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.MetricKey, entity.PeriodStart, entity.PeriodEnd });
|
||||
builder.HasOne<Tenant>().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CommerceRefundRequestConfiguration : IEntityTypeConfiguration<CommerceRefundRequest>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<CommerceRefundRequest> builder)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Content;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.Operations;
|
||||
using Tiku.Domain.Platform;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
@@ -89,10 +90,14 @@ internal sealed class BackendPermissionConfiguration : IEntityTypeConfiguration<
|
||||
builder.Property(entity => entity.Code).HasMaxLength(160);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.Area).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Module).HasMaxLength(100);
|
||||
builder.Property(entity => entity.PermissionModuleCode).HasMaxLength(120);
|
||||
builder.Property(entity => entity.Description).HasMaxLength(1000);
|
||||
builder.HasIndex(entity => entity.Code).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.Area, entity.Module, entity.SortOrder });
|
||||
builder.HasIndex(entity => new { entity.Area, entity.PermissionModuleCode, entity.SortOrder });
|
||||
builder.HasOne<PermissionModule>().WithMany()
|
||||
.HasPrincipalKey(entity => entity.Code)
|
||||
.HasForeignKey(entity => entity.PermissionModuleCode)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,76 +7,6 @@ using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class PlatformSaasPlanConfiguration : IEntityTypeConfiguration<PlatformSaasPlan>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlatformSaasPlan> builder)
|
||||
{
|
||||
builder.ConfigureEntity("platform_saas_plans");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.Code).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.BillingCycle).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Currency).HasMaxLength(10);
|
||||
builder.Property(entity => entity.IncludedQuotas).IsJson("{}");
|
||||
builder.Property(entity => entity.OveragePrices).IsJson("{}");
|
||||
builder.Property(entity => entity.FeatureFlags).IsJson("{}");
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.HasIndex(entity => entity.Code).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.Status, entity.SortOrder });
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ProductModuleConfiguration : IEntityTypeConfiguration<ProductModule>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProductModule> builder)
|
||||
{
|
||||
builder.ConfigureEntity("product_modules");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.Code).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.Description).HasMaxLength(1000);
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.HasIndex(entity => entity.Code).IsUnique();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PlanModuleEntitlementConfiguration : IEntityTypeConfiguration<PlanModuleEntitlement>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlanModuleEntitlement> builder)
|
||||
{
|
||||
builder.ConfigureEntity("plan_module_entitlements");
|
||||
builder.Property(entity => entity.PlanCode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.ModuleCode).HasMaxLength(100);
|
||||
builder.HasIndex(entity => new { entity.PlanCode, entity.ModuleCode }).IsUnique();
|
||||
builder.HasOne<PlatformSaasPlan>().WithMany()
|
||||
.HasPrincipalKey(entity => entity.Code)
|
||||
.HasForeignKey(entity => entity.PlanCode)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<ProductModule>().WithMany()
|
||||
.HasPrincipalKey(entity => entity.Code)
|
||||
.HasForeignKey(entity => entity.ModuleCode)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantModuleOverrideConfiguration : IEntityTypeConfiguration<TenantModuleOverride>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantModuleOverride> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_module_overrides");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.ModuleCode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Mode).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Reason).HasMaxLength(1000);
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.ModuleCode }).IsUnique();
|
||||
builder.HasOne<ProductModule>().WithMany()
|
||||
.HasPrincipalKey(entity => entity.Code)
|
||||
.HasForeignKey(entity => entity.ModuleCode)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantBillingProfileConfiguration : IEntityTypeConfiguration<TenantBillingProfile>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantBillingProfile> builder)
|
||||
@@ -98,84 +28,11 @@ internal sealed class TenantBillingProfileConfiguration : IEntityTypeConfigurati
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantInvoiceConfiguration : IEntityTypeConfiguration<TenantInvoice>
|
||||
internal sealed class PlatformBillingInvoiceReminderConfiguration : IEntityTypeConfiguration<PlatformBillingInvoiceReminder>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantInvoice> builder)
|
||||
public void Configure(EntityTypeBuilder<PlatformBillingInvoiceReminder> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_invoices");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.InvoiceNo).HasMaxLength(100);
|
||||
builder.Property(entity => entity.InvoiceType).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Currency).HasMaxLength(10);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.InvoiceNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.DueDate });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.BillingPeriodStart, entity.BillingPeriodEnd })
|
||||
.IsUnique()
|
||||
.HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'");
|
||||
builder.ToTable(table =>
|
||||
{
|
||||
table.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0");
|
||||
table.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start");
|
||||
});
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantInvoiceItemConfiguration : IEntityTypeConfiguration<TenantInvoiceItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantInvoiceItem> builder)
|
||||
{
|
||||
builder.ConfigureEntity("tenant_invoice_items");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.Property(entity => entity.ItemType).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Description).HasMaxLength(500);
|
||||
builder.Property(entity => entity.Quantity).HasPrecision(12, 2);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => entity.InvoiceId);
|
||||
builder.ToTable(table =>
|
||||
{
|
||||
table.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0");
|
||||
table.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0");
|
||||
});
|
||||
builder.HasOne<Tenant>().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<TenantInvoice>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantInvoicePaymentConfiguration : IEntityTypeConfiguration<TenantInvoicePayment>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantInvoicePayment> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_invoice_payments");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.PaymentNo).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Provider).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Method).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.ProviderTradeNo).HasMaxLength(200);
|
||||
builder.Property(entity => entity.RawPayload).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.PaymentNo }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.InvoiceId, entity.Status });
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"));
|
||||
builder.HasOne<TenantInvoice>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.ReceivedBy).OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantInvoiceReminderConfiguration : IEntityTypeConfiguration<TenantInvoiceReminder>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantInvoiceReminder> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_invoice_reminders");
|
||||
builder.ConfigureTenantEntity("platform_billing_invoice_reminders");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.ReminderType).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Channel).HasSnakeCaseEnum();
|
||||
@@ -186,10 +43,10 @@ internal sealed class TenantInvoiceReminderConfiguration : IEntityTypeConfigurat
|
||||
builder.HasIndex(entity => new { entity.InvoiceId, entity.ReminderDate });
|
||||
builder.ToTable(table =>
|
||||
{
|
||||
table.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20");
|
||||
table.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0");
|
||||
table.HasCheckConstraint("ck_platform_billing_invoice_reminders_level", "reminder_level between 1 and 20");
|
||||
table.HasCheckConstraint("ck_platform_billing_invoice_reminders_balance", "balance_cents_snapshot >= 0");
|
||||
});
|
||||
builder.HasOne<TenantInvoice>().WithMany()
|
||||
builder.HasOne<PlatformBillingInvoice>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
@@ -243,11 +100,11 @@ internal sealed class PlatformAuditAlertConfiguration : IEntityTypeConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PlatformDunningNotificationChannelConfiguration : IEntityTypeConfiguration<PlatformDunningNotificationChannel>
|
||||
internal sealed class PlatformBillingDunningNotificationChannelConfiguration : IEntityTypeConfiguration<PlatformBillingDunningNotificationChannel>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlatformDunningNotificationChannel> builder)
|
||||
public void Configure(EntityTypeBuilder<PlatformBillingDunningNotificationChannel> builder)
|
||||
{
|
||||
builder.ConfigureEntity("platform_dunning_notification_channels");
|
||||
builder.ConfigureEntity("platform_billing_dunning_notification_channels");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.ChannelCode).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
@@ -262,17 +119,17 @@ internal sealed class PlatformDunningNotificationChannelConfiguration : IEntityT
|
||||
builder.HasIndex(entity => new { entity.Enabled, entity.MinReminderLevel, entity.ChannelCode });
|
||||
builder.ToTable(table =>
|
||||
{
|
||||
table.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20");
|
||||
table.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60");
|
||||
table.HasCheckConstraint("ck_platform_billing_dunning_channels_level", "min_reminder_level between 1 and 20");
|
||||
table.HasCheckConstraint("ck_platform_billing_dunning_channels_timeout", "timeout_seconds between 1 and 60");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PlatformDunningNotificationEventConfiguration : IEntityTypeConfiguration<PlatformDunningNotificationEvent>
|
||||
internal sealed class PlatformBillingDunningNotificationEventConfiguration : IEntityTypeConfiguration<PlatformBillingDunningNotificationEvent>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlatformDunningNotificationEvent> builder)
|
||||
public void Configure(EntityTypeBuilder<PlatformBillingDunningNotificationEvent> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("platform_dunning_notification_events");
|
||||
builder.ConfigureTenantEntity("platform_billing_dunning_notification_events");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.Provider).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
@@ -284,15 +141,20 @@ internal sealed class PlatformDunningNotificationEventConfiguration : IEntityTyp
|
||||
builder.HasIndex(entity => new { entity.ReminderId, entity.Status, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.InvoiceId, entity.Status, entity.CreatedAt });
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt });
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"));
|
||||
builder.HasOne<PlatformDunningNotificationChannel>().WithMany().HasForeignKey(entity => entity.ChannelId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<TenantInvoiceReminder>().WithMany()
|
||||
builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_dunning_events_attempts", "attempts >= 0"));
|
||||
builder.HasOne<PlatformBillingDunningNotificationChannel>().WithMany()
|
||||
.HasForeignKey(entity => entity.ChannelId)
|
||||
.HasConstraintName("fk_platform_billing_dunning_events_channel")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<PlatformBillingInvoiceReminder>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.ReminderId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.HasConstraintName("fk_platform_billing_dunning_events_reminder")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<TenantInvoice>().WithMany()
|
||||
builder.HasOne<PlatformBillingInvoice>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.HasConstraintName("fk_platform_billing_dunning_events_invoice")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,9 @@ internal sealed class TenantAuthPolicyConfiguration : IEntityTypeConfiguration<T
|
||||
builder.HasKey(entity => entity.TenantId);
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.AllowExternalStudentSelfRegistration).HasDefaultValue(false);
|
||||
builder.Property(entity => entity.AllowedStudentLoginMethods)
|
||||
.HasColumnType("text[]")
|
||||
.HasDefaultValueSql("ARRAY['password']::text[]");
|
||||
builder.HasOne<Tenant>().WithOne()
|
||||
.HasForeignKey<TenantAuthPolicy>(entity => entity.TenantId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
Reference in New Issue
Block a user