feat(saas): implement marketplace and tenant onboarding

This commit is contained in:
2026-07-29 13:58:59 +08:00
parent 76606029e2
commit 6db200a2fc
145 changed files with 16862 additions and 94529 deletions

View File

@@ -89,8 +89,16 @@ public sealed class PersistenceModelTests
Assert.Contains("activation_codes", tableNames);
Assert.Contains("coupons", tableNames);
Assert.Contains("coupon_redemptions", tableNames);
Assert.Contains("tenant_subscriptions", tableNames);
Assert.Contains("tenant_usage_records", tableNames);
Assert.Contains("saas_features", tableNames);
Assert.Contains("permission_modules", tableNames);
Assert.Contains("saas_offerings", tableNames);
Assert.Contains("saas_offering_versions", tableNames);
Assert.Contains("tenant_saas_subscriptions", tableNames);
Assert.Contains("tenant_saas_subscription_items", tableNames);
Assert.Contains("tenant_feature_usage", tableNames);
Assert.Contains("platform_billing_orders", tableNames);
Assert.Contains("platform_billing_payments", tableNames);
Assert.Contains("platform_billing_invoices", tableNames);
Assert.Contains("commerce_refund_requests", tableNames);
Assert.Contains("commerce_refund_events", tableNames);
Assert.Contains("commerce_reconciliation_batches", tableNames);
@@ -128,16 +136,13 @@ public sealed class PersistenceModelTests
Assert.Contains("tenant_content_notifications", tableNames);
Assert.Contains("tenant_theme_templates", tableNames);
Assert.Contains("tenant_theme_configs", tableNames);
Assert.Contains("platform_saas_plans", tableNames);
Assert.Contains("tenant_billing_profiles", tableNames);
Assert.Contains("tenant_invoices", tableNames);
Assert.Contains("tenant_invoice_items", tableNames);
Assert.Contains("tenant_invoice_payments", tableNames);
Assert.Contains("tenant_invoice_reminders", tableNames);
Assert.Contains("platform_billing_invoices", tableNames);
Assert.Contains("platform_billing_invoice_reminders", tableNames);
Assert.Contains("platform_audit_alert_rules", tableNames);
Assert.Contains("platform_audit_alerts", tableNames);
Assert.Contains("platform_dunning_notification_channels", tableNames);
Assert.Contains("platform_dunning_notification_events", tableNames);
Assert.Contains("platform_billing_dunning_notification_channels", tableNames);
Assert.Contains("platform_billing_dunning_notification_events", tableNames);
Assert.Contains("pb_import_runs", tableNames);
Assert.Contains("pb_raw_records", tableNames);
Assert.Contains("pb_import_issues", tableNames);
@@ -572,8 +577,7 @@ public sealed class PersistenceModelTests
[InlineData(typeof(Payment), nameof(Payment.RawPayload), "'{}'::jsonb")]
[InlineData(typeof(PaymentEvent), nameof(PaymentEvent.Payload), "'{}'::jsonb")]
[InlineData(typeof(Entitlement), nameof(Entitlement.Metadata), "'{}'::jsonb")]
[InlineData(typeof(TenantSubscription), nameof(TenantSubscription.Metadata), "'{}'::jsonb")]
[InlineData(typeof(TenantUsageRecord), nameof(TenantUsageRecord.Metadata), "'{}'::jsonb")]
[InlineData(typeof(TenantSaasSubscription), nameof(TenantSaasSubscription.Metadata), "'{}'::jsonb")]
[InlineData(typeof(CommerceRefundRequest), nameof(CommerceRefundRequest.Metadata), "'{}'::jsonb")]
[InlineData(typeof(CommerceRefundEvent), nameof(CommerceRefundEvent.Details), "'{}'::jsonb")]
[InlineData(typeof(CommerceReconciliationBatch), nameof(CommerceReconciliationBatch.Metadata), "'{}'::jsonb")]
@@ -802,20 +806,18 @@ public sealed class PersistenceModelTests
}
[Theory]
[InlineData(typeof(PlatformSaasPlan), nameof(PlatformSaasPlan.IncludedQuotas), "'{}'::jsonb")]
[InlineData(typeof(PlatformSaasPlan), nameof(PlatformSaasPlan.OveragePrices), "'{}'::jsonb")]
[InlineData(typeof(PlatformSaasPlan), nameof(PlatformSaasPlan.FeatureFlags), "'{}'::jsonb")]
[InlineData(typeof(SaasOfferingVersion), nameof(SaasOfferingVersion.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformBillingQuote), nameof(PlatformBillingQuote.FeatureSnapshot), "'[]'::jsonb")]
[InlineData(typeof(PlatformBillingQuote), nameof(PlatformBillingQuote.LimitSnapshot), "'{}'::jsonb")]
[InlineData(typeof(TenantBillingProfile), nameof(TenantBillingProfile.Metadata), "'{}'::jsonb")]
[InlineData(typeof(TenantInvoice), nameof(TenantInvoice.Metadata), "'{}'::jsonb")]
[InlineData(typeof(TenantInvoiceItem), nameof(TenantInvoiceItem.Metadata), "'{}'::jsonb")]
[InlineData(typeof(TenantInvoicePayment), nameof(TenantInvoicePayment.RawPayload), "'{}'::jsonb")]
[InlineData(typeof(TenantInvoiceReminder), nameof(TenantInvoiceReminder.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformBillingInvoice), nameof(PlatformBillingInvoice.BillingProfileSnapshot), "'{}'::jsonb")]
[InlineData(typeof(PlatformBillingInvoiceReminder), nameof(PlatformBillingInvoiceReminder.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformAuditAlertRule), nameof(PlatformAuditAlertRule.Conditions), "'{}'::jsonb")]
[InlineData(typeof(PlatformAuditAlertRule), nameof(PlatformAuditAlertRule.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformAuditAlert), nameof(PlatformAuditAlert.Details), "'{}'::jsonb")]
[InlineData(typeof(PlatformDunningNotificationChannel), nameof(PlatformDunningNotificationChannel.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformDunningNotificationEvent), nameof(PlatformDunningNotificationEvent.RequestPayload), "'{}'::jsonb")]
[InlineData(typeof(PlatformDunningNotificationEvent), nameof(PlatformDunningNotificationEvent.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformBillingDunningNotificationChannel), nameof(PlatformBillingDunningNotificationChannel.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PlatformBillingDunningNotificationEvent), nameof(PlatformBillingDunningNotificationEvent.RequestPayload), "'{}'::jsonb")]
[InlineData(typeof(PlatformBillingDunningNotificationEvent), nameof(PlatformBillingDunningNotificationEvent.Metadata), "'{}'::jsonb")]
public void Platform_operations_json_properties_are_mapped_to_jsonb(
Type entityType,
string propertyName,
@@ -836,27 +838,23 @@ public sealed class PersistenceModelTests
{
using var context = new TikuDbContext(Options);
AssertHasUniqueIndex<PlatformSaasPlan>(nameof(PlatformSaasPlan.Code));
AssertHasUniqueIndex<TenantInvoice>(
nameof(TenantInvoice.TenantId),
nameof(TenantInvoice.InvoiceNo));
AssertHasUniqueIndex<TenantInvoice>(
nameof(TenantInvoice.TenantId),
nameof(TenantInvoice.BillingPeriodStart),
nameof(TenantInvoice.BillingPeriodEnd));
AssertHasUniqueIndex<TenantInvoiceReminder>(
nameof(TenantInvoiceReminder.TenantId),
nameof(TenantInvoiceReminder.InvoiceId),
nameof(TenantInvoiceReminder.ReminderType),
nameof(TenantInvoiceReminder.Channel),
nameof(TenantInvoiceReminder.ReminderDate));
AssertHasUniqueIndex<SaasFeature>(nameof(SaasFeature.Code));
AssertHasUniqueIndex<PlatformBillingInvoice>(
nameof(PlatformBillingInvoice.TenantId),
nameof(PlatformBillingInvoice.InvoiceNo));
AssertHasUniqueIndex<PlatformBillingInvoiceReminder>(
nameof(PlatformBillingInvoiceReminder.TenantId),
nameof(PlatformBillingInvoiceReminder.InvoiceId),
nameof(PlatformBillingInvoiceReminder.ReminderType),
nameof(PlatformBillingInvoiceReminder.Channel),
nameof(PlatformBillingInvoiceReminder.ReminderDate));
AssertHasUniqueIndex<PlatformAuditAlertRule>(nameof(PlatformAuditAlertRule.Code));
AssertHasUniqueIndex<PlatformAuditAlert>(nameof(PlatformAuditAlert.RuleId), nameof(PlatformAuditAlert.AuditLogId));
AssertHasUniqueIndex<PlatformDunningNotificationChannel>(nameof(PlatformDunningNotificationChannel.ChannelCode));
AssertHasUniqueIndex<PlatformDunningNotificationEvent>(
nameof(PlatformDunningNotificationEvent.TenantId),
nameof(PlatformDunningNotificationEvent.ChannelId),
nameof(PlatformDunningNotificationEvent.ReminderId));
AssertHasUniqueIndex<PlatformBillingDunningNotificationChannel>(nameof(PlatformBillingDunningNotificationChannel.ChannelCode));
AssertHasUniqueIndex<PlatformBillingDunningNotificationEvent>(
nameof(PlatformBillingDunningNotificationEvent.TenantId),
nameof(PlatformBillingDunningNotificationEvent.ChannelId),
nameof(PlatformBillingDunningNotificationEvent.ReminderId));
Assert.Equal(
"text[]",
@@ -865,12 +863,12 @@ public sealed class PersistenceModelTests
.GetColumnType());
Assert.Equal(
"uuid[]",
context.Model.FindEntityType(typeof(PlatformDunningNotificationChannel))!
.FindProperty(nameof(PlatformDunningNotificationChannel.TenantIds))!
context.Model.FindEntityType(typeof(PlatformBillingDunningNotificationChannel))!
.FindProperty(nameof(PlatformBillingDunningNotificationChannel.TenantIds))!
.GetColumnType());
var total = context.Model.FindEntityType(typeof(TenantInvoice))!
.FindProperty(nameof(TenantInvoice.TotalCents))!;
var total = context.Model.FindEntityType(typeof(PlatformBillingInvoice))!
.FindProperty(nameof(PlatformBillingInvoice.TotalAmountCents))!;
Assert.Equal(typeof(int), total.ClrType);
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)