using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Tiku.Domain.Catalog; using Tiku.Domain.Commerce; using Tiku.Domain.Identity; using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; internal sealed class ProductConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("products"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Title).HasMaxLength(300); builder.Property(entity => entity.LegacyPriceText).HasMaxLength(100); builder.Property(entity => entity.Link).HasMaxLength(2048); builder.Property(entity => entity.Type).HasSnakeCaseEnum(); builder.Property(entity => entity.Tags).IsJson("[]"); builder.Property(entity => entity.Cover).HasMaxLength(2048); builder.Property(entity => entity.PreviewIframe).HasMaxLength(2048); builder.Property(entity => entity.DetailImages).IsJson("[]"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.RegionId, entity.Type, entity.SortOrder }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class SvipPlanConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("svip_plans"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Name).HasMaxLength(200); builder.Property(entity => entity.PerDayLabel).HasMaxLength(100); builder.Property(entity => entity.Badge).HasMaxLength(100); builder.Property(entity => entity.VpProductId).HasMaxLength(100); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.RegionId, entity.IsActive, entity.SortOrder }); builder.ToTable(table => { table.HasCheckConstraint("ck_svip_plans_price", "price_cents >= 0 and (original_price_cents is null or original_price_cents >= 0)"); table.HasCheckConstraint("ck_svip_plans_days", "days >= 0"); }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class OrderConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("orders"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.LegacyUserId).HasMaxLength(64); builder.Property(entity => entity.LegacyPlanId).HasMaxLength(64); builder.Property(entity => entity.LegacyRegionId).HasMaxLength(64); builder.Property(entity => entity.OrderNo).HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.ProductType).HasMaxLength(100); builder.Property(entity => entity.ProductName).HasMaxLength(300); builder.Property(entity => entity.PayMethod).HasMaxLength(50); builder.Property(entity => entity.PayProvider).HasMaxLength(50); builder.Property(entity => entity.TradeNo).HasMaxLength(200); builder.Property(entity => entity.RawPayload).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.OrderNo }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CreatedAt }); builder.ToTable(table => table.HasCheckConstraint("ck_orders_amount", "amount_cents >= 0")); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PlanId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class OrderItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("order_items"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.ItemType).HasMaxLength(100); builder.Property(entity => entity.Name).HasMaxLength(300); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.OrderId }); builder.ToTable(table => { table.HasCheckConstraint("ck_order_items_quantity", "quantity > 0"); table.HasCheckConstraint("ck_order_items_amounts", "unit_amount_cents >= 0 and total_amount_cents >= 0"); }); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.OrderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class PaymentConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("payments"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.LegacyOrderId).HasMaxLength(64); 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.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.OrderId, entity.UpdatedAt }); builder.HasIndex(entity => new { entity.Provider, entity.ProviderTradeNo }) .IsUnique() .HasFilter("provider_trade_no is not null"); builder.ToTable(table => table.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0")); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.OrderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); } } internal sealed class PaymentEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("payment_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.Provider).HasMaxLength(50); builder.Property(entity => entity.EventType).HasMaxLength(100); builder.Property(entity => entity.EventId).HasMaxLength(200); builder.Property(entity => entity.Payload).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.Provider, entity.EventId }).IsUnique(); builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PaymentId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class EntitlementConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("entitlements"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.EntitlementType).HasMaxLength(50); builder.Property(entity => entity.ScopeType).HasSnakeCaseEnum(); builder.Property(entity => entity.SourceType).HasMaxLength(100); builder.Property(entity => entity.LegacySourceId).HasMaxLength(64); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.Property(entity => entity.StartsAt).HasDefaultValueSql("now()"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.Status, entity.ExpiresAt }); builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade); } } internal sealed class CodeBatchConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("code_batches"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Name).HasMaxLength(200); builder.Property(entity => entity.SaleType).HasMaxLength(50); builder.Property(entity => entity.Channel).HasMaxLength(100); builder.Property(entity => entity.CampaignName).HasMaxLength(200); builder.Property(entity => entity.LegacyRegionId).HasMaxLength(64); builder.Property(entity => entity.CommissionRate).HasPrecision(6, 4); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.ToTable(table => { table.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0"); table.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0"); }); } } internal sealed class ActivationCodeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("activation_codes"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Code).HasColumnType("citext").HasMaxLength(100); builder.Property(entity => entity.SaleType).HasMaxLength(50); builder.Property(entity => entity.SoldTo).HasMaxLength(200); builder.Property(entity => entity.CouponCode).HasMaxLength(100); builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.BatchId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.UsedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.AgentUserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.UsedRegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.CouponRedemptionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class CouponConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("coupons"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.Code).HasColumnType("citext").HasMaxLength(100); builder.Property(entity => entity.DiscountType).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.DiscountValue).HasPrecision(10, 2); builder.Property(entity => entity.Source).HasMaxLength(50); builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PlanId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class CouponRedemptionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("coupon_redemptions"); builder.ConfigureTimestamps(); builder.Property(entity => entity.LegacyId).HasMaxLength(64); builder.Property(entity => entity.CouponCode).HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Source).HasMaxLength(50); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CouponId }) .IsUnique() .HasFilter("coupon_id is not null"); builder.HasIndex(entity => new { entity.TenantId, entity.OrderId, entity.UserId }) .HasFilter("order_id is not null"); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.CouponId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PlanId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.OrderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RegionId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); } } internal sealed class TenantPaymentAccountConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("tenant_payment_accounts"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Provider).HasMaxLength(50); builder.Property(entity => entity.Mode).HasSnakeCaseEnum(); builder.Property(entity => entity.DisplayName).HasMaxLength(200); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.ConfigPublic).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.Provider }).IsUnique(); } } internal sealed class TenantSubscriptionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder 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 { public void Configure(EntityTypeBuilder 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().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); } }