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.Property(entity => entity.IsActive).HasDefaultValue(true); builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.RegionId, entity.Type, entity.IsActive, 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"); table.HasCheckConstraint("ck_orders_refunded_amount", "refunded_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.TenantId, entity.Provider, entity.ProviderTradeNo }) .IsUnique() .HasFilter("provider_trade_no is not null"); builder.ToTable(table => { table.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0"); table.HasCheckConstraint("ck_payments_refunded_amount", "refunded_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.TenantId, 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.RevokedReason).HasMaxLength(1000); 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); builder.HasOne().WithMany().HasForeignKey(entity => entity.RevokedBy).OnDelete(DeleteBehavior.SetNull); } } 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 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); } } internal sealed class CommerceRefundRequestConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("commerce_refund_requests"); builder.ConfigureTimestamps(); builder.Property(entity => entity.RefundNo).HasMaxLength(100); builder.Property(entity => entity.Provider).HasMaxLength(50); builder.Property(entity => entity.ProviderRefundNo).HasMaxLength(200); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.EntitlementAction).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.Property(entity => entity.RequestedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.RefundNo }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.OrderId, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt }); builder.ToTable(table => table.HasCheckConstraint("ck_commerce_refund_requests_amount", "amount_cents > 0")); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.OrderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PaymentId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(entity => entity.RequestedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReviewedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.ProcessedBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommerceRefundEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("commerce_refund_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.FromStatus).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.ToStatus).HasSnakeCaseEnum(); builder.Property(entity => entity.EventType).HasMaxLength(100); builder.Property(entity => entity.Details).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.RefundRequestId, entity.CreatedAt }); builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RefundRequestId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.ActorUserId).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommerceReconciliationBatchConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("commerce_reconciliation_batches"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Provider).HasMaxLength(50); builder.Property(entity => entity.BillType).HasSnakeCaseEnum(); builder.Property(entity => entity.Source).HasSnakeCaseEnum(); builder.Property(entity => entity.SourceName).HasMaxLength(300); builder.Property(entity => entity.SourceHash).HasMaxLength(200); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.Provider, entity.BillDate, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt }); builder.ToTable(table => { table.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0"); table.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); }); builder.HasOne().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommerceReconciliationItemConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("commerce_reconciliation_items"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.Provider).HasMaxLength(50); builder.Property(entity => entity.TransactionType).HasSnakeCaseEnum(); builder.Property(entity => entity.ProviderTradeNo).HasMaxLength(200); builder.Property(entity => entity.ProviderRefundNo).HasMaxLength(200); builder.Property(entity => entity.OrderNo).HasMaxLength(100); builder.Property(entity => entity.RefundNo).HasMaxLength(100); builder.Property(entity => entity.ProviderStatus).HasMaxLength(100); builder.Property(entity => entity.LocalStatus).HasMaxLength(100); builder.Property(entity => entity.MatchStatus).HasSnakeCaseEnum(); builder.Property(entity => entity.Severity).HasSnakeCaseEnum(); builder.Property(entity => entity.IssueCode).HasMaxLength(100); builder.Property(entity => entity.Details).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.BatchId, entity.RowNo }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.BatchId, entity.MatchStatus, entity.RowNo }); builder.HasIndex(entity => new { entity.TenantId, entity.OrderNo, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.RefundNo, entity.ProviderRefundNo, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.Provider, entity.ProviderTradeNo, entity.CreatedAt }); builder.ToTable(table => { table.HasCheckConstraint("ck_commerce_reconciliation_items_row", "row_no > 0"); table.HasCheckConstraint("ck_commerce_reconciliation_items_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); }); builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.BatchId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.OrderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PaymentId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RefundRequestId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); } } internal sealed class CommerceReconciliationIssueConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("commerce_reconciliation_issues"); builder.ConfigureTimestamps(); builder.Property(entity => entity.IssueNo).HasMaxLength(100); builder.Property(entity => entity.Provider).HasMaxLength(50); builder.Property(entity => entity.TransactionType).HasSnakeCaseEnum(); builder.Property(entity => entity.IssueCode).HasMaxLength(100); builder.Property(entity => entity.MatchStatus).HasSnakeCaseEnum(); builder.Property(entity => entity.Severity).HasSnakeCaseEnum(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.ResolutionType).HasSnakeCaseEnum(); builder.Property(entity => entity.OrderNo).HasMaxLength(100); builder.Property(entity => entity.RefundNo).HasMaxLength(100); builder.Property(entity => entity.ProviderTradeNo).HasMaxLength(200); builder.Property(entity => entity.ProviderRefundNo).HasMaxLength(200); builder.Property(entity => entity.Summary).HasMaxLength(1000); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.IssueNo }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.ItemId }) .IsUnique() .HasFilter("item_id is not null and status in ('open', 'investigating', 'escalated')"); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.Severity, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.AssignedTo, entity.Status, entity.DueAt }); builder.HasIndex(entity => new { entity.TenantId, entity.BatchId, entity.Status, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.OrderNo, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.PaymentId, entity.CreatedAt }); builder.ToTable(table => table.HasCheckConstraint("ck_commerce_reconciliation_issues_amounts", "amount_cents >= 0 and refund_amount_cents >= 0")); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.BatchId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ItemId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.OrderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.PaymentId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RefundRequestId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Restrict); builder.HasOne().WithMany().HasForeignKey(entity => entity.AssignedTo).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.ResolvedBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommerceReconciliationIssueEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("commerce_reconciliation_issue_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.FromStatus).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.ToStatus).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.EventType).HasMaxLength(100); builder.Property(entity => entity.Details).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.IssueId, entity.CreatedAt }); builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.IssueId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.ActorUserId).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommerceAdjustmentVoucherConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureTenantEntity("commerce_adjustment_vouchers"); builder.ConfigureTimestamps(); builder.Property(entity => entity.VoucherNo).HasMaxLength(100); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); builder.Property(entity => entity.Direction).HasSnakeCaseEnum(); builder.Property(entity => entity.Currency).HasMaxLength(10); builder.Property(entity => entity.Reason).HasMaxLength(1000); builder.Property(entity => entity.ProofAssetKey).HasMaxLength(500); builder.Property(entity => entity.Metadata).IsJson("{}"); builder.HasIndex(entity => new { entity.TenantId, entity.VoucherNo }).IsUnique(); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.IssueId, entity.CreatedAt }); builder.ToTable(table => table.HasCheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0")); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.IssueId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); 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 => new { entity.TenantId, entity.ItemId }) .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.PaymentId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.RefundRequestId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull); builder.HasOne().WithMany().HasForeignKey(entity => entity.ReviewedBy).OnDelete(DeleteBehavior.SetNull); } } internal sealed class CommerceAdjustmentVoucherEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ConfigureEntity("commerce_adjustment_voucher_events"); builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); builder.Property(entity => entity.FromStatus).HasNullableSnakeCaseEnum(); builder.Property(entity => entity.ToStatus).HasSnakeCaseEnum(); builder.Property(entity => entity.Note).HasMaxLength(1000); builder.Property(entity => entity.Details).IsJson("{}"); builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); builder.HasIndex(entity => new { entity.TenantId, entity.VoucherId, entity.CreatedAt }); builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.VoucherId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); builder.HasOne().WithMany().HasForeignKey(entity => entity.ActorUserId).OnDelete(DeleteBehavior.SetNull); } }