using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using TakeoutSaaS.Domain.Analytics.Entities; using TakeoutSaaS.Domain.Coupons.Entities; using TakeoutSaaS.Domain.CustomerService.Entities; using TakeoutSaaS.Domain.Deliveries.Entities; using TakeoutSaaS.Domain.Distribution.Entities; using TakeoutSaaS.Domain.Engagement.Entities; using TakeoutSaaS.Domain.GroupBuying.Entities; using TakeoutSaaS.Domain.Inventory.Entities; using TakeoutSaaS.Domain.Membership.Entities; using TakeoutSaaS.Domain.Merchants.Entities; using TakeoutSaaS.Domain.Navigation.Entities; using TakeoutSaaS.Domain.Ordering.Entities; using TakeoutSaaS.Domain.Orders.Entities; using TakeoutSaaS.Domain.Payments.Entities; using TakeoutSaaS.Domain.Products.Entities; using TakeoutSaaS.Domain.Queues.Entities; using TakeoutSaaS.Domain.Reservations.Entities; using TakeoutSaaS.Domain.Stores.Entities; using TakeoutSaaS.Domain.Stores.Enums; using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Infrastructure.Common.Persistence; using TakeoutSaaS.Shared.Abstractions.Ids; using TakeoutSaaS.Shared.Abstractions.Security; using TakeoutSaaS.Shared.Abstractions.Tenancy; using TakeoutSaaS.Infrastructure.App.Persistence.Configurations; using Microsoft.AspNetCore.Http; namespace TakeoutSaaS.Infrastructure.App.Persistence; /// /// 业务主库 DbContext。 /// public sealed class TakeoutAppDbContext( DbContextOptions options, ITenantProvider tenantProvider, ICurrentUserAccessor? currentUserAccessor = null, IIdGenerator? idGenerator = null, IHttpContextAccessor? httpContextAccessor = null) : TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator, httpContextAccessor) { /// /// 租户聚合根。 /// public DbSet Tenants => Set(); /// /// 租户套餐。 /// public DbSet TenantPackages => Set(); /// /// 租户订阅。 /// public DbSet TenantSubscriptions => Set(); /// /// 租户订阅历史。 /// public DbSet TenantSubscriptionHistories => Set(); /// /// 租户配额使用记录。 /// public DbSet TenantQuotaUsages => Set(); /// /// 租户配额使用历史记录。 /// public DbSet TenantQuotaUsageHistories => Set(); /// /// 租户账单。 /// public DbSet TenantBillingStatements => Set(); /// /// 租户支付记录。 /// public DbSet TenantPayments => Set(); /// /// 租户通知。 /// public DbSet TenantNotifications => Set(); /// /// 租户公告。 /// public DbSet TenantAnnouncements => Set(); /// /// 租户公告已读记录。 /// public DbSet TenantAnnouncementReads => Set(); /// /// 租户认证资料。 /// public DbSet TenantVerificationProfiles => Set(); /// /// 租户审核领取记录。 /// public DbSet TenantReviewClaims => Set(); /// /// 配额包定义。 /// public DbSet QuotaPackages => Set(); /// /// 租户配额包购买记录。 /// public DbSet TenantQuotaPackagePurchases => Set(); /// /// 商户实体。 /// public DbSet Merchants => Set(); /// /// 商户资质文件。 /// public DbSet MerchantDocuments => Set(); /// /// 商户合同。 /// public DbSet MerchantContracts => Set(); /// /// 商户员工。 /// public DbSet MerchantStaff => Set(); /// /// 商户分类。 /// public DbSet MerchantCategories => Set(); /// /// 门店实体。 /// public DbSet Stores => Set(); /// /// 门店费用配置。 /// public DbSet StoreFees => Set(); /// /// 门店资质证照。 /// public DbSet StoreQualifications => Set(); /// /// 门店审核记录。 /// public DbSet StoreAuditRecords => Set(); /// /// 门店营业时间。 /// public DbSet StoreBusinessHours => Set(); /// /// 门店节假日。 /// public DbSet StoreHolidays => Set(); /// /// 门店配送区域。 /// public DbSet StoreDeliveryZones => Set(); /// /// 门店桌台区域。 /// public DbSet StoreTableAreas => Set(); /// /// 门店桌台。 /// public DbSet StoreTables => Set(); /// /// 门店员工班次。 /// public DbSet StoreEmployeeShifts => Set(); /// /// 自提配置。 /// public DbSet StorePickupSettings => Set(); /// /// 自提时间段。 /// public DbSet StorePickupSlots => Set(); /// /// 商品分类。 /// public DbSet ProductCategories => Set(); /// /// 商品。 /// public DbSet Products => Set(); /// /// 商品属性组。 /// public DbSet ProductAttributeGroups => Set(); /// /// 商品属性项。 /// public DbSet ProductAttributeOptions => Set(); /// /// SKU 实体。 /// public DbSet ProductSkus => Set(); /// /// 加料分组。 /// public DbSet ProductAddonGroups => Set(); /// /// 加料选项。 /// public DbSet ProductAddonOptions => Set(); /// /// 定价规则。 /// public DbSet ProductPricingRules => Set(); /// /// 商品媒体资源。 /// public DbSet ProductMediaAssets => Set(); /// /// 库存项目。 /// public DbSet InventoryItems => Set(); /// /// 库存调整记录。 /// public DbSet InventoryAdjustments => Set(); /// /// 库存批次。 /// public DbSet InventoryBatches => Set(); /// /// 库存锁定记录。 /// public DbSet InventoryLockRecords => Set(); /// /// 购物车。 /// public DbSet ShoppingCarts => Set(); /// /// 购物车明细。 /// public DbSet CartItems => Set(); /// /// 购物车加料。 /// public DbSet CartItemAddons => Set(); /// /// 结账会话。 /// public DbSet CheckoutSessions => Set(); /// /// 订单聚合。 /// public DbSet Orders => Set(); /// /// 订单明细。 /// public DbSet OrderItems => Set(); /// /// 订单状态流转。 /// public DbSet OrderStatusHistories => Set(); /// /// 退款申请。 /// public DbSet RefundRequests => Set(); /// /// 支付记录。 /// public DbSet PaymentRecords => Set(); /// /// 支付退款记录。 /// public DbSet PaymentRefundRecords => Set(); /// /// 预订记录。 /// public DbSet Reservations => Set(); /// /// 排号记录。 /// public DbSet QueueTickets => Set(); /// /// 配送订单。 /// public DbSet DeliveryOrders => Set(); /// /// 配送事件。 /// public DbSet DeliveryEvents => Set(); /// /// 团购订单。 /// public DbSet GroupOrders => Set(); /// /// 团购参与者。 /// public DbSet GroupParticipants => Set(); /// /// 优惠券模板。 /// public DbSet CouponTemplates => Set(); /// /// 优惠券实例。 /// public DbSet Coupons => Set(); /// /// 营销活动。 /// public DbSet PromotionCampaigns => Set(); /// /// 会员档案。 /// public DbSet MemberProfiles => Set(); /// /// 会员等级。 /// public DbSet MemberTiers => Set(); /// /// 积分流水。 /// public DbSet MemberPointLedgers => Set(); /// /// 会话记录。 /// public DbSet ChatSessions => Set(); /// /// 会话消息。 /// public DbSet ChatMessages => Set(); /// /// 工单记录。 /// public DbSet SupportTickets => Set(); /// /// 工单评论。 /// public DbSet TicketComments => Set(); /// /// 分销合作伙伴。 /// public DbSet AffiliatePartners => Set(); /// /// 分销订单。 /// public DbSet AffiliateOrders => Set(); /// /// 分销结算。 /// public DbSet AffiliatePayouts => Set(); /// /// 打卡活动。 /// public DbSet CheckInCampaigns => Set(); /// /// 打卡记录。 /// public DbSet CheckInRecords => Set(); /// /// 社区帖子。 /// public DbSet CommunityPosts => Set(); /// /// 社区评论。 /// public DbSet CommunityComments => Set(); /// /// 社区互动。 /// public DbSet CommunityReactions => Set(); /// /// 地图位置。 /// public DbSet MapLocations => Set(); /// /// 导航请求。 /// public DbSet NavigationRequests => Set(); /// /// 指标定义。 /// public DbSet MetricDefinitions => Set(); /// /// 指标快照。 /// public DbSet MetricSnapshots => Set(); /// /// 告警规则。 /// public DbSet MetricAlertRules => Set(); /// /// 配置实体映射关系。 /// /// 模型构建器。 protected override void OnModelCreating(ModelBuilder modelBuilder) { // 1. 调用基类配置 base.OnModelCreating(modelBuilder); // 2. 配置全部实体映射 ConfigureTenant(modelBuilder.Entity()); ConfigureMerchant(modelBuilder.Entity()); ConfigureStore(modelBuilder.Entity()); ConfigureTenantPackage(modelBuilder.Entity()); ConfigureTenantSubscription(modelBuilder.Entity()); ConfigureTenantSubscriptionHistory(modelBuilder.Entity()); ConfigureTenantQuotaUsage(modelBuilder.Entity()); ConfigureTenantQuotaUsageHistory(modelBuilder.Entity()); ConfigureTenantBilling(modelBuilder.Entity()); ConfigureTenantPayment(modelBuilder.Entity()); ConfigureTenantNotification(modelBuilder.Entity()); ConfigureTenantAnnouncement(modelBuilder.Entity()); ConfigureTenantAnnouncementRead(modelBuilder.Entity()); ConfigureTenantVerificationProfile(modelBuilder.Entity()); ConfigureTenantReviewClaim(modelBuilder.Entity()); ConfigureQuotaPackage(modelBuilder.Entity()); ConfigureTenantQuotaPackagePurchase(modelBuilder.Entity()); ConfigureMerchantDocument(modelBuilder.Entity()); ConfigureMerchantContract(modelBuilder.Entity()); ConfigureMerchantStaff(modelBuilder.Entity()); ConfigureMerchantCategory(modelBuilder.Entity()); ConfigureStoreFee(modelBuilder.Entity()); ConfigureStoreQualification(modelBuilder.Entity()); ConfigureStoreAuditRecord(modelBuilder.Entity()); ConfigureStoreBusinessHour(modelBuilder.Entity()); ConfigureStoreHoliday(modelBuilder.Entity()); ConfigureStoreDeliveryZone(modelBuilder.Entity()); ConfigureStoreTableArea(modelBuilder.Entity()); ConfigureStoreTable(modelBuilder.Entity()); ConfigureStoreEmployeeShift(modelBuilder.Entity()); ConfigureStorePickupSetting(modelBuilder.Entity()); ConfigureStorePickupSlot(modelBuilder.Entity()); ConfigureProductCategory(modelBuilder.Entity()); ConfigureProduct(modelBuilder.Entity()); ConfigureProductAttributeGroup(modelBuilder.Entity()); ConfigureProductAttributeOption(modelBuilder.Entity()); ConfigureProductSku(modelBuilder.Entity()); ConfigureProductAddonGroup(modelBuilder.Entity()); ConfigureProductAddonOption(modelBuilder.Entity()); ConfigureProductPricingRule(modelBuilder.Entity()); ConfigureProductMediaAsset(modelBuilder.Entity()); ConfigureInventoryItem(modelBuilder.Entity()); ConfigureInventoryAdjustment(modelBuilder.Entity()); ConfigureInventoryBatch(modelBuilder.Entity()); ConfigureInventoryLockRecord(modelBuilder.Entity()); ConfigureShoppingCart(modelBuilder.Entity()); ConfigureCartItem(modelBuilder.Entity()); ConfigureCartItemAddon(modelBuilder.Entity()); ConfigureCheckoutSession(modelBuilder.Entity()); ConfigureOrder(modelBuilder.Entity()); ConfigureOrderItem(modelBuilder.Entity()); ConfigureOrderStatusHistory(modelBuilder.Entity()); ConfigureRefundRequest(modelBuilder.Entity()); ConfigurePaymentRecord(modelBuilder.Entity()); ConfigurePaymentRefundRecord(modelBuilder.Entity()); ConfigureReservation(modelBuilder.Entity()); ConfigureQueueTicket(modelBuilder.Entity()); ConfigureDelivery(modelBuilder.Entity()); ConfigureDeliveryEvent(modelBuilder.Entity()); ConfigureGroupOrder(modelBuilder.Entity()); ConfigureGroupParticipant(modelBuilder.Entity()); ConfigureCouponTemplate(modelBuilder.Entity()); ConfigureCoupon(modelBuilder.Entity()); ConfigurePromotionCampaign(modelBuilder.Entity()); ConfigureMemberProfile(modelBuilder.Entity()); ConfigureMemberTier(modelBuilder.Entity()); ConfigureMemberPointLedger(modelBuilder.Entity()); ConfigureChatSession(modelBuilder.Entity()); ConfigureChatMessage(modelBuilder.Entity()); ConfigureSupportTicket(modelBuilder.Entity()); ConfigureTicketComment(modelBuilder.Entity()); ConfigureAffiliatePartner(modelBuilder.Entity()); ConfigureAffiliateOrder(modelBuilder.Entity()); ConfigureAffiliatePayout(modelBuilder.Entity()); ConfigureCheckInCampaign(modelBuilder.Entity()); ConfigureCheckInRecord(modelBuilder.Entity()); ConfigureCommunityPost(modelBuilder.Entity()); ConfigureCommunityComment(modelBuilder.Entity()); ConfigureCommunityReaction(modelBuilder.Entity()); ConfigureMapLocation(modelBuilder.Entity()); ConfigureNavigationRequest(modelBuilder.Entity()); ConfigureMetricDefinition(modelBuilder.Entity()); ConfigureMetricSnapshot(modelBuilder.Entity()); ConfigureMetricAlertRule(modelBuilder.Entity()); ApplyTenantQueryFilters(modelBuilder); } private static void ConfigureTenant(EntityTypeBuilder builder) { builder.ToTable("tenants"); builder.HasKey(x => x.Id); builder.Property(x => x.Code).HasMaxLength(64).IsRequired(); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.ShortName).HasMaxLength(64); builder.Property(x => x.ContactName).HasMaxLength(64); builder.Property(x => x.ContactPhone).HasMaxLength(32); builder.Property(x => x.ContactEmail).HasMaxLength(128); builder.Property(x => x.Industry).HasMaxLength(64); builder.Property(x => x.LogoUrl).HasColumnType("text"); builder.Property(x => x.Remarks).HasMaxLength(512); builder.Property(x => x.OperatingMode).HasConversion(); builder.HasIndex(x => x.Code).IsUnique(); builder.HasIndex(x => x.ContactPhone).IsUnique(); } private static void ConfigureTenantVerificationProfile(EntityTypeBuilder builder) { builder.ToTable("tenant_verification_profiles"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.BusinessLicenseNumber).HasMaxLength(64); builder.Property(x => x.BusinessLicenseUrl).HasMaxLength(512); builder.Property(x => x.LegalPersonName).HasMaxLength(64); builder.Property(x => x.LegalPersonIdNumber).HasMaxLength(32); builder.Property(x => x.LegalPersonIdFrontUrl).HasMaxLength(512); builder.Property(x => x.LegalPersonIdBackUrl).HasMaxLength(512); builder.Property(x => x.BankAccountName).HasMaxLength(128); builder.Property(x => x.BankAccountNumber).HasMaxLength(64); builder.Property(x => x.BankName).HasMaxLength(128); builder.Property(x => x.ReviewRemarks).HasMaxLength(512); builder.Property(x => x.ReviewedByName).HasMaxLength(64); builder.HasIndex(x => x.TenantId).IsUnique(); } private static void ConfigureTenantReviewClaim(EntityTypeBuilder builder) { builder.ToTable("tenant_review_claims"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.ClaimedBy).IsRequired(); builder.Property(x => x.ClaimedByName).HasMaxLength(64).IsRequired(); builder.Property(x => x.ClaimedAt).IsRequired(); builder.Property(x => x.ReleasedAt); builder.HasIndex(x => x.TenantId); builder.HasIndex(x => x.ClaimedBy); builder.HasIndex(x => x.TenantId).IsUnique().HasFilter("\"ReleasedAt\" IS NULL AND \"DeletedAt\" IS NULL"); } private static void ConfigureTenantSubscriptionHistory(EntityTypeBuilder builder) { builder.ToTable("tenant_subscription_histories"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.TenantSubscriptionId).IsRequired(); builder.Property(x => x.Notes).HasMaxLength(512); builder.Property(x => x.Currency).HasMaxLength(8); builder.HasIndex(x => new { x.TenantId, x.TenantSubscriptionId }); } private static void ConfigureMerchant(EntityTypeBuilder builder) { builder.ToTable("merchants"); builder.HasKey(x => x.Id); builder.Property(x => x.BrandName).HasMaxLength(128).IsRequired(); builder.Property(x => x.BrandAlias).HasMaxLength(64); builder.Property(x => x.LegalPerson).HasMaxLength(64); builder.Property(x => x.BusinessLicenseNumber).HasMaxLength(64); builder.Property(x => x.ContactPhone).HasMaxLength(32).IsRequired(); builder.Property(x => x.ContactEmail).HasMaxLength(128); builder.Property(x => x.Province).HasMaxLength(64); builder.Property(x => x.City).HasMaxLength(64); builder.Property(x => x.District).HasMaxLength(64); builder.Property(x => x.Address).HasMaxLength(256); builder.Property(x => x.ReviewRemarks).HasMaxLength(512); builder.Property(x => x.OperatingMode).HasConversion(); builder.Property(x => x.IsFrozen).HasDefaultValue(false); builder.Property(x => x.FrozenReason).HasMaxLength(500); builder.Property(x => x.ClaimedByName).HasMaxLength(100); builder.Property(x => x.RowVersion) .IsRowVersion() .IsConcurrencyToken() .HasColumnType("bytea"); builder.HasIndex(x => x.TenantId); builder.HasIndex(x => new { x.TenantId, x.Status }); builder.HasIndex(x => x.ClaimedBy); } private static void ConfigureStore(EntityTypeBuilder builder) { builder.ToTable("stores"); builder.HasKey(x => x.Id); builder.Property(x => x.Code).HasMaxLength(32).IsRequired(); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Phone).HasMaxLength(32); builder.Property(x => x.ManagerName).HasMaxLength(64); builder.Property(x => x.BusinessLicenseNumber).HasMaxLength(50); builder.Property(x => x.LegalRepresentative).HasMaxLength(100); builder.Property(x => x.RegisteredAddress).HasMaxLength(500); builder.Property(x => x.BusinessLicenseImageUrl).HasMaxLength(500); builder.Property(x => x.SignboardImageUrl).HasMaxLength(500); builder.Property(x => x.OwnershipType).HasConversion(); builder.Property(x => x.AuditStatus).HasConversion(); builder.Property(x => x.BusinessStatus).HasConversion(); builder.Property(x => x.ClosureReason).HasConversion(); builder.Property(x => x.ClosureReasonText).HasMaxLength(500); builder.Property(x => x.RejectionReason).HasMaxLength(500); builder.Property(x => x.ForceCloseReason).HasMaxLength(500); builder.Property(x => x.Province).HasMaxLength(64); builder.Property(x => x.City).HasMaxLength(64); builder.Property(x => x.District).HasMaxLength(64); builder.Property(x => x.Address).HasMaxLength(256); builder.Property(x => x.BusinessHours).HasMaxLength(256); builder.Property(x => x.Announcement).HasMaxLength(512); builder.Property(x => x.DeliveryRadiusKm).HasPrecision(6, 2); builder.HasIndex(x => new { x.TenantId, x.MerchantId }); builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.AuditStatus }); builder.HasIndex(x => new { x.TenantId, x.BusinessStatus }); builder.HasIndex(x => new { x.TenantId, x.OwnershipType }); builder.HasIndex(x => new { x.Longitude, x.Latitude }) .HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL"); builder.HasIndex(x => new { x.MerchantId, x.BusinessLicenseNumber }) .IsUnique() .HasFilter("\"BusinessLicenseNumber\" IS NOT NULL AND \"Status\" <> 3"); } private static void ConfigureStoreFee(EntityTypeBuilder builder) { builder.ToTable("store_fees"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.MinimumOrderAmount).HasPrecision(10, 2); builder.Property(x => x.BaseDeliveryFee).HasPrecision(10, 2); builder.Property(x => x.PackagingFeeMode).HasConversion(); builder.Property(x => x.FixedPackagingFee).HasPrecision(10, 2); builder.Property(x => x.FreeDeliveryThreshold).HasPrecision(10, 2); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); builder.HasIndex(x => x.TenantId); } private static void ConfigureStoreQualification(EntityTypeBuilder builder) { builder.ToTable("store_qualifications"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.QualificationType).HasConversion(); builder.Property(x => x.FileUrl).HasMaxLength(500).IsRequired(); builder.Property(x => x.DocumentNumber).HasMaxLength(100); builder.Property(x => x.IssuedAt).HasColumnType("date"); builder.Property(x => x.ExpiresAt).HasColumnType("date"); builder.Property(x => x.SortOrder).HasDefaultValue(100); builder.HasIndex(x => new { x.TenantId, x.StoreId }); builder.HasIndex(x => x.ExpiresAt) .HasFilter("\"ExpiresAt\" IS NOT NULL"); builder.Ignore(x => x.IsExpired); builder.Ignore(x => x.IsExpiringSoon); } private static void ConfigureStoreAuditRecord(EntityTypeBuilder builder) { builder.ToTable("store_audit_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Action).HasConversion(); builder.Property(x => x.PreviousStatus).HasConversion(); builder.Property(x => x.NewStatus).HasConversion(); builder.Property(x => x.OperatorName).HasMaxLength(100).IsRequired(); builder.Property(x => x.RejectionReason).HasMaxLength(500); builder.Property(x => x.Remarks).HasMaxLength(1000); builder.HasIndex(x => new { x.TenantId, x.StoreId }); builder.HasIndex(x => x.CreatedAt); } private static void ConfigureProductCategory(EntityTypeBuilder builder) { builder.ToTable("product_categories"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Description).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.StoreId }); } private static void ConfigureProduct(EntityTypeBuilder builder) { builder.ToTable("products"); builder.HasKey(x => x.Id); builder.Property(x => x.SpuCode).HasMaxLength(32).IsRequired(); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Subtitle).HasMaxLength(256); builder.Property(x => x.Unit).HasMaxLength(16); builder.Property(x => x.Price).HasPrecision(18, 2); builder.Property(x => x.OriginalPrice).HasPrecision(18, 2); builder.Property(x => x.CoverImage).HasMaxLength(256); builder.Property(x => x.GalleryImages).HasMaxLength(1024); builder.Property(x => x.Description).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.StoreId }); builder.HasIndex(x => new { x.TenantId, x.SpuCode }).IsUnique(); } private static void ConfigureOrder(EntityTypeBuilder builder) { builder.ToTable("orders"); builder.HasKey(x => x.Id); builder.Property(x => x.OrderNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.CustomerName).HasMaxLength(64); builder.Property(x => x.CustomerPhone).HasMaxLength(32); builder.Property(x => x.TableNo).HasMaxLength(32); builder.Property(x => x.QueueNumber).HasMaxLength(32); builder.Property(x => x.CancelReason).HasMaxLength(256); builder.Property(x => x.Remark).HasMaxLength(512); builder.Property(x => x.ItemsAmount).HasPrecision(18, 2); builder.Property(x => x.DiscountAmount).HasPrecision(18, 2); builder.Property(x => x.PayableAmount).HasPrecision(18, 2); builder.Property(x => x.PaidAmount).HasPrecision(18, 2); builder.HasIndex(x => new { x.TenantId, x.OrderNo }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Status }); } private static void ConfigureOrderItem(EntityTypeBuilder builder) { builder.ToTable("order_items"); builder.HasKey(x => x.Id); builder.Property(x => x.ProductName).HasMaxLength(128).IsRequired(); builder.Property(x => x.SkuName).HasMaxLength(128); builder.Property(x => x.Unit).HasMaxLength(16); builder.Property(x => x.UnitPrice).HasPrecision(18, 2); builder.Property(x => x.DiscountAmount).HasPrecision(18, 2); builder.Property(x => x.SubTotal).HasPrecision(18, 2); builder.Property(x => x.AttributesJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.OrderId }); builder.HasOne() .WithMany() .HasForeignKey(x => x.OrderId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigurePaymentRecord(EntityTypeBuilder builder) { builder.ToTable("payment_records"); builder.HasKey(x => x.Id); builder.Property(x => x.Amount).HasPrecision(18, 2); builder.Property(x => x.TradeNo).HasMaxLength(64); builder.Property(x => x.ChannelTransactionId).HasMaxLength(64); builder.Property(x => x.Remark).HasMaxLength(256); builder.Property(x => x.Payload).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.OrderId }); } private static void ConfigureReservation(EntityTypeBuilder builder) { builder.ToTable("reservations"); builder.HasKey(x => x.Id); builder.Property(x => x.ReservationNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.CustomerName).HasMaxLength(64).IsRequired(); builder.Property(x => x.CustomerPhone).HasMaxLength(32).IsRequired(); builder.Property(x => x.TablePreference).HasMaxLength(64); builder.Property(x => x.Remark).HasMaxLength(512); builder.Property(x => x.CheckInCode).HasMaxLength(32); builder.HasIndex(x => new { x.TenantId, x.StoreId }); builder.HasIndex(x => new { x.TenantId, x.ReservationNo }).IsUnique(); } private static void ConfigureQueueTicket(EntityTypeBuilder builder) { builder.ToTable("queue_tickets"); builder.HasKey(x => x.Id); builder.Property(x => x.TicketNumber).HasMaxLength(32).IsRequired(); builder.Property(x => x.Remark).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.StoreId }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.TicketNumber }).IsUnique(); } private static void ConfigureDelivery(EntityTypeBuilder builder) { builder.ToTable("delivery_orders"); builder.HasKey(x => x.Id); builder.Property(x => x.ProviderOrderId).HasMaxLength(64); builder.Property(x => x.DeliveryFee).HasPrecision(18, 2); builder.Property(x => x.CourierName).HasMaxLength(64); builder.Property(x => x.CourierPhone).HasMaxLength(32); builder.Property(x => x.FailureReason).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.OrderId }).IsUnique(); } private static void ConfigureTenantPackage(EntityTypeBuilder builder) { builder.ToTable("tenant_packages"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Description).HasMaxLength(512); builder.Property(x => x.FeaturePoliciesJson).HasColumnType("text"); builder.Property(x => x.PublishStatus) .HasConversion() .HasDefaultValue(TenantPackagePublishStatus.Draft) .HasSentinel((TenantPackagePublishStatus)(-1)) .HasComment("发布状态:0=草稿,1=已发布。"); // 1. 解决 EF Core 默认值哨兵问题:当我们希望插入 false/0 时,若数据库配置了 default 且 EF 认为该值是“未设置”,会导致 insert 省略列,最终落库为默认值。 // 2. 发布状态使用 -1 作为哨兵,避免 Draft=0 被误判为“未设置”而触发数据库默认值(发布/草稿切换必须可控)。 // 3. 将布尔开关哨兵值设置为数据库默认值:true 作为哨兵,false 才会被显式写入,从而保证“可见性/可售开关”在新增时可正确落库。 builder.Property(x => x.IsPublicVisible) .HasDefaultValue(true) .HasSentinel(true) .HasComment("是否对外可见(展示页/套餐列表可见性)。"); builder.Property(x => x.IsAllowNewTenantPurchase) .HasDefaultValue(true) .HasSentinel(true) .HasComment("是否允许新租户购买/选择(仅影响新购)。"); // 4. 展示配置:推荐标识与标签(用于套餐展示页/对比页) builder.Property(x => x.IsRecommended) .HasDefaultValue(false) .HasComment("是否推荐展示(运营推荐标识)。"); builder.Property(x => x.Tags) .HasColumnType("text[]") .HasComment("套餐标签(用于展示与对比页)。"); builder.Property(x => x.SortOrder).HasDefaultValue(0).HasComment("展示排序,数值越小越靠前。"); builder.HasIndex(x => new { x.IsActive, x.SortOrder }); builder.HasIndex(x => new { x.PublishStatus, x.IsActive, x.IsPublicVisible, x.IsAllowNewTenantPurchase, x.SortOrder }); } private static void ConfigureTenantSubscription(EntityTypeBuilder builder) { builder.ToTable("tenant_subscriptions"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantPackageId).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.TenantPackageId }); } private static void ConfigureTenantQuotaUsage(EntityTypeBuilder builder) { builder.ToTable("tenant_quota_usages"); builder.HasKey(x => x.Id); builder.Property(x => x.QuotaType).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.QuotaType }).IsUnique(); } private static void ConfigureTenantQuotaUsageHistory(EntityTypeBuilder builder) { builder.ToTable("tenant_quota_usage_histories"); builder.HasKey(x => x.Id); builder.Property(x => x.QuotaType).HasConversion(); builder.Property(x => x.ChangeType).HasConversion(); builder.Property(x => x.ChangeReason).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.QuotaType, x.RecordedAt }); builder.HasIndex(x => new { x.TenantId, x.RecordedAt }); } private static void ConfigureTenantBilling(EntityTypeBuilder builder) { new TenantBillingStatementConfiguration().Configure(builder); } private static void ConfigureTenantPayment(EntityTypeBuilder builder) { new TenantPaymentConfiguration().Configure(builder); } private static void ConfigureTenantNotification(EntityTypeBuilder builder) { builder.ToTable("tenant_notifications"); builder.HasKey(x => x.Id); builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); builder.Property(x => x.Message).HasMaxLength(1024).IsRequired(); builder.Property(x => x.Channel).HasConversion(); builder.Property(x => x.Severity).HasConversion(); builder.Property(x => x.MetadataJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.Channel, x.SentAt }); } private static void ConfigureTenantAnnouncement(EntityTypeBuilder builder) { builder.ToTable("tenant_announcements"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); builder.Property(x => x.Content).HasColumnType("text").IsRequired(); builder.Property(x => x.AnnouncementType) .HasConversion(); builder.Property(x => x.PublisherScope) .HasConversion(); builder.Property(x => x.PublisherUserId); builder.Property(x => x.Status) .HasConversion(); builder.Property(x => x.PublishedAt); builder.Property(x => x.RevokedAt); builder.Property(x => x.ScheduledPublishAt); builder.Property(x => x.TargetType).HasMaxLength(64).IsRequired(); builder.Property(x => x.TargetParameters).HasColumnType("text"); builder.Property(x => x.Priority).IsRequired(); builder.Property("IsActive").IsRequired(); builder.Property(x => x.RowVersion) .IsRowVersion() .IsConcurrencyToken() .HasColumnType("bytea"); ConfigureAuditableEntity(builder); ConfigureSoftDeleteEntity(builder); builder.HasIndex("TenantId", "AnnouncementType", "IsActive"); builder.HasIndex(x => new { x.TenantId, x.EffectiveFrom, x.EffectiveTo }); builder.HasIndex(x => new { x.TenantId, x.Status, x.EffectiveFrom }); builder.HasIndex(x => new { x.Status, x.EffectiveFrom }) .HasFilter("\"TenantId\" = 0"); } private static void ConfigureTenantAnnouncementRead(EntityTypeBuilder builder) { builder.ToTable("tenant_announcement_reads"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.AnnouncementId).IsRequired(); builder.Property(x => x.UserId); builder.Property(x => x.ReadAt).IsRequired(); ConfigureAuditableEntity(builder); ConfigureSoftDeleteEntity(builder); builder.HasIndex(x => new { x.TenantId, x.AnnouncementId, x.UserId }).IsUnique(); } private static void ConfigureMerchantDocument(EntityTypeBuilder builder) { builder.ToTable("merchant_documents"); builder.HasKey(x => x.Id); builder.Property(x => x.MerchantId).IsRequired(); builder.Property(x => x.DocumentType).HasConversion(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.FileUrl).HasMaxLength(512).IsRequired(); builder.Property(x => x.DocumentNumber).HasMaxLength(64); builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.DocumentType }); } private static void ConfigureMerchantContract(EntityTypeBuilder builder) { builder.ToTable("merchant_contracts"); builder.HasKey(x => x.Id); builder.Property(x => x.MerchantId).IsRequired(); builder.Property(x => x.ContractNumber).HasMaxLength(64).IsRequired(); builder.Property(x => x.FileUrl).HasMaxLength(512).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.TerminationReason).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.ContractNumber }).IsUnique(); } private static void ConfigureMerchantStaff(EntityTypeBuilder builder) { builder.ToTable("merchant_staff"); builder.HasKey(x => x.Id); builder.Property(x => x.MerchantId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Phone).HasMaxLength(32).IsRequired(); builder.Property(x => x.Email).HasMaxLength(128); builder.Property(x => x.RoleType).HasConversion(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.PermissionsJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.Phone }); } private static void ConfigureMerchantCategory(EntityTypeBuilder builder) { builder.ToTable("merchant_categories"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.DisplayOrder).HasDefaultValue(0); builder.Property(x => x.IsActive).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.Name }).IsUnique(); } private static void ConfigureStoreBusinessHour(EntityTypeBuilder builder) { builder.ToTable("store_business_hours"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.HourType).HasConversion(); builder.Property(x => x.Notes).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.DayOfWeek }); } private static void ConfigureStoreHoliday(EntityTypeBuilder builder) { builder.ToTable("store_holidays"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Date).IsRequired(); builder.Property(x => x.EndDate); builder.Property(x => x.IsAllDay).HasDefaultValue(true); builder.Property(x => x.StartTime); builder.Property(x => x.EndTime); builder.Property(x => x.OverrideType).HasDefaultValue(OverrideType.Closed); builder.Property(x => x.IsClosed).HasDefaultValue(true); builder.Property(x => x.Reason).HasMaxLength(200); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Date }); } private static void ConfigureStoreDeliveryZone(EntityTypeBuilder builder) { builder.ToTable("store_delivery_zones"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.ZoneName).HasMaxLength(64).IsRequired(); builder.Property(x => x.PolygonGeoJson).HasColumnType("text").IsRequired(); builder.Property(x => x.MinimumOrderAmount).HasPrecision(18, 2); builder.Property(x => x.DeliveryFee).HasPrecision(18, 2); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ZoneName }); } private static void ConfigureStoreTableArea(EntityTypeBuilder builder) { builder.ToTable("store_table_areas"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Description).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }).IsUnique(); } private static void ConfigureStoreTable(EntityTypeBuilder builder) { builder.ToTable("store_tables"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.TableCode).HasMaxLength(32).IsRequired(); builder.Property(x => x.Tags).HasMaxLength(128); builder.Property(x => x.QrCodeUrl).HasMaxLength(512); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.TableCode }).IsUnique(); } private static void ConfigureStoreEmployeeShift(EntityTypeBuilder builder) { builder.ToTable("store_employee_shifts"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.StaffId).IsRequired(); builder.Property(x => x.RoleType).HasConversion(); builder.Property(x => x.Notes).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ShiftDate, x.StaffId }).IsUnique(); } private static void ConfigureStorePickupSetting(EntityTypeBuilder builder) { builder.ToTable("store_pickup_settings"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.DefaultCutoffMinutes).HasDefaultValue(30); builder.Property(x => x.RowVersion) .IsConcurrencyToken(); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } private static void ConfigureStorePickupSlot(EntityTypeBuilder builder) { builder.ToTable("store_pickup_slots"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Weekdays).HasMaxLength(32).IsRequired(); builder.Property(x => x.CutoffMinutes).HasDefaultValue(30); builder.Property(x => x.RowVersion) .IsConcurrencyToken(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }); } private static void ConfigureProductAttributeGroup(EntityTypeBuilder builder) { builder.ToTable("product_attribute_groups"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.SelectionType).HasConversion(); builder.Property(x => x.StoreId); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }); } private static void ConfigureProductAttributeOption(EntityTypeBuilder builder) { builder.ToTable("product_attribute_options"); builder.HasKey(x => x.Id); builder.Property(x => x.AttributeGroupId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.ExtraPrice).HasPrecision(18, 2); builder.HasIndex(x => new { x.TenantId, x.AttributeGroupId, x.Name }).IsUnique(); } private static void ConfigureProductSku(EntityTypeBuilder builder) { builder.ToTable("product_skus"); builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.SkuCode).HasMaxLength(32).IsRequired(); builder.Property(x => x.Barcode).HasMaxLength(64); builder.Property(x => x.Price).HasPrecision(18, 2); builder.Property(x => x.OriginalPrice).HasPrecision(18, 2); builder.Property(x => x.Weight).HasPrecision(10, 3); builder.Property(x => x.AttributesJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.SkuCode }).IsUnique(); } private static void ConfigureProductAddonGroup(EntityTypeBuilder builder) { builder.ToTable("product_addon_groups"); builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.SelectionType).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.ProductId, x.Name }); } private static void ConfigureProductAddonOption(EntityTypeBuilder builder) { builder.ToTable("product_addon_options"); builder.HasKey(x => x.Id); builder.Property(x => x.AddonGroupId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.ExtraPrice).HasPrecision(18, 2); } private static void ConfigureProductPricingRule(EntityTypeBuilder builder) { builder.ToTable("product_pricing_rules"); builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.RuleType).HasConversion(); builder.Property(x => x.ConditionsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.Price).HasPrecision(18, 2); builder.Property(x => x.WeekdaysJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.ProductId, x.RuleType }); } private static void ConfigureProductMediaAsset(EntityTypeBuilder builder) { builder.ToTable("product_media_assets"); builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.MediaType).HasConversion(); builder.Property(x => x.Url).HasMaxLength(512).IsRequired(); builder.Property(x => x.Caption).HasMaxLength(256); } private static void ConfigureInventoryItem(EntityTypeBuilder builder) { builder.ToTable("inventory_items"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.ProductSkuId).IsRequired(); builder.Property(x => x.BatchNumber).HasMaxLength(64); builder.Property(x => x.Location).HasMaxLength(64); builder.Property(x => x.RowVersion) .IsConcurrencyToken(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.BatchNumber }); } private static void ConfigureInventoryAdjustment(EntityTypeBuilder builder) { builder.ToTable("inventory_adjustments"); builder.HasKey(x => x.Id); builder.Property(x => x.InventoryItemId).IsRequired(); builder.Property(x => x.AdjustmentType).HasConversion(); builder.Property(x => x.Reason).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.InventoryItemId, x.OccurredAt }); } private static void ConfigureInventoryBatch(EntityTypeBuilder builder) { builder.ToTable("inventory_batches"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.ProductSkuId).IsRequired(); builder.Property(x => x.BatchNumber).HasMaxLength(64).IsRequired(); builder.Property(x => x.RowVersion) .IsConcurrencyToken(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.BatchNumber }).IsUnique(); } private static void ConfigureInventoryLockRecord(EntityTypeBuilder builder) { builder.ToTable("inventory_lock_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.ProductSkuId).IsRequired(); builder.Property(x => x.Quantity).IsRequired(); builder.Property(x => x.IdempotencyKey).HasMaxLength(128).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.RowVersion) .IsConcurrencyToken(); builder.HasIndex(x => new { x.TenantId, x.IdempotencyKey }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.Status }); } private static void ConfigureShoppingCart(EntityTypeBuilder builder) { builder.ToTable("shopping_carts"); builder.HasKey(x => x.Id); builder.Property(x => x.UserId).IsRequired(); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.TableContext).HasMaxLength(64); builder.Property(x => x.DeliveryPreference).HasMaxLength(32); builder.HasIndex(x => new { x.TenantId, x.UserId, x.StoreId }).IsUnique(); } private static void ConfigureCartItem(EntityTypeBuilder builder) { builder.ToTable("cart_items"); builder.HasKey(x => x.Id); builder.Property(x => x.ShoppingCartId).IsRequired(); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.ProductName).HasMaxLength(128).IsRequired(); builder.Property(x => x.UnitPrice).HasPrecision(18, 2); builder.Property(x => x.Remark).HasMaxLength(256); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.AttributesJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.ShoppingCartId }); } private static void ConfigureCartItemAddon(EntityTypeBuilder builder) { builder.ToTable("cart_item_addons"); builder.HasKey(x => x.Id); builder.Property(x => x.CartItemId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.ExtraPrice).HasPrecision(18, 2); } private static void ConfigureCheckoutSession(EntityTypeBuilder builder) { builder.ToTable("checkout_sessions"); builder.HasKey(x => x.Id); builder.Property(x => x.UserId).IsRequired(); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.SessionToken).HasMaxLength(64).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.ValidationResultJson).HasColumnType("text").IsRequired(); builder.HasIndex(x => new { x.TenantId, x.SessionToken }).IsUnique(); } private static void ConfigureOrderStatusHistory(EntityTypeBuilder builder) { builder.ToTable("order_status_histories"); builder.HasKey(x => x.Id); builder.Property(x => x.OrderId).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.Notes).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.OrderId, x.OccurredAt }); } private static void ConfigureRefundRequest(EntityTypeBuilder builder) { builder.ToTable("refund_requests"); builder.HasKey(x => x.Id); builder.Property(x => x.OrderId).IsRequired(); builder.Property(x => x.RefundNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.Amount).HasPrecision(18, 2); builder.Property(x => x.Reason).HasMaxLength(256).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.ReviewNotes).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.RefundNo }).IsUnique(); } private static void ConfigurePaymentRefundRecord(EntityTypeBuilder builder) { builder.ToTable("payment_refund_records"); builder.HasKey(x => x.Id); builder.Property(x => x.PaymentRecordId).IsRequired(); builder.Property(x => x.OrderId).IsRequired(); builder.Property(x => x.Amount).HasPrecision(18, 2); builder.Property(x => x.ChannelRefundId).HasMaxLength(64); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.Payload).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.PaymentRecordId }); } private static void ConfigureDeliveryEvent(EntityTypeBuilder builder) { builder.ToTable("delivery_events"); builder.HasKey(x => x.Id); builder.Property(x => x.DeliveryOrderId).IsRequired(); builder.Property(x => x.EventType).HasConversion(); builder.Property(x => x.Message).HasMaxLength(256); builder.Property(x => x.Payload).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.DeliveryOrderId, x.EventType }); } private static void ConfigureGroupOrder(EntityTypeBuilder builder) { builder.ToTable("group_orders"); builder.HasKey(x => x.Id); builder.Property(x => x.GroupOrderNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.GroupPrice).HasPrecision(18, 2); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.GroupOrderNo }).IsUnique(); } private static void ConfigureGroupParticipant(EntityTypeBuilder builder) { builder.ToTable("group_participants"); builder.HasKey(x => x.Id); builder.Property(x => x.GroupOrderId).IsRequired(); builder.Property(x => x.OrderId).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.GroupOrderId, x.UserId }).IsUnique(); } private static void ConfigureCouponTemplate(EntityTypeBuilder builder) { builder.ToTable("coupon_templates"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.CouponType).HasConversion(); builder.Property(x => x.Description).HasMaxLength(512); builder.Property(x => x.TotalQuantity); builder.Property(x => x.StoreScopeJson).HasColumnType("text"); builder.Property(x => x.ProductScopeJson).HasColumnType("text"); builder.Property(x => x.ChannelsJson).HasColumnType("text"); builder.Property(x => x.Status).HasConversion(); } private static void ConfigureCoupon(EntityTypeBuilder builder) { builder.ToTable("coupons"); builder.HasKey(x => x.Id); builder.Property(x => x.CouponTemplateId).IsRequired(); builder.Property(x => x.Code).HasMaxLength(32).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); } private static void ConfigurePromotionCampaign(EntityTypeBuilder builder) { builder.ToTable("promotion_campaigns"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.PromotionType).HasConversion(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.RulesJson).HasColumnType("text").IsRequired(); builder.Property(x => x.AudienceDescription).HasMaxLength(512); builder.Property(x => x.BannerUrl).HasMaxLength(512); } private static void ConfigureMemberProfile(EntityTypeBuilder builder) { builder.ToTable("member_profiles"); builder.HasKey(x => x.Id); builder.Property(x => x.Mobile).HasMaxLength(32).IsRequired(); builder.Property(x => x.Nickname).HasMaxLength(64); builder.Property(x => x.AvatarUrl).HasMaxLength(256); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.Mobile }).IsUnique(); } private static void ConfigureMemberTier(EntityTypeBuilder builder) { builder.ToTable("member_tiers"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.BenefitsJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.Name }).IsUnique(); } private static void ConfigureMemberPointLedger(EntityTypeBuilder builder) { builder.ToTable("member_point_ledgers"); builder.HasKey(x => x.Id); builder.Property(x => x.MemberId).IsRequired(); builder.Property(x => x.Reason).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.MemberId, x.OccurredAt }); } private static void ConfigureChatSession(EntityTypeBuilder builder) { builder.ToTable("chat_sessions"); builder.HasKey(x => x.Id); builder.Property(x => x.SessionCode).HasMaxLength(64).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.SessionCode }).IsUnique(); } private static void ConfigureChatMessage(EntityTypeBuilder builder) { builder.ToTable("chat_messages"); builder.HasKey(x => x.Id); builder.Property(x => x.ChatSessionId).IsRequired(); builder.Property(x => x.SenderType).HasConversion(); builder.Property(x => x.ContentType).HasMaxLength(64).IsRequired(); builder.Property(x => x.Content).HasMaxLength(1024).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.ChatSessionId, x.CreatedAt }); } private static void ConfigureSupportTicket(EntityTypeBuilder builder) { builder.ToTable("support_tickets"); builder.HasKey(x => x.Id); builder.Property(x => x.TicketNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.Subject).HasMaxLength(128).IsRequired(); builder.Property(x => x.Description).HasColumnType("text").IsRequired(); builder.Property(x => x.Priority).HasConversion(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.TicketNo }).IsUnique(); } private static void ConfigureTicketComment(EntityTypeBuilder builder) { builder.ToTable("ticket_comments"); builder.HasKey(x => x.Id); builder.Property(x => x.SupportTicketId).IsRequired(); builder.Property(x => x.Content).HasMaxLength(1024).IsRequired(); builder.Property(x => x.AttachmentsJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.SupportTicketId }); } private static void ConfigureAffiliatePartner(EntityTypeBuilder builder) { builder.ToTable("affiliate_partners"); builder.HasKey(x => x.Id); builder.Property(x => x.DisplayName).HasMaxLength(64).IsRequired(); builder.Property(x => x.Phone).HasMaxLength(32); builder.Property(x => x.ChannelType).HasConversion(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.Remarks).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.DisplayName }); } private static void ConfigureAffiliateOrder(EntityTypeBuilder builder) { builder.ToTable("affiliate_orders"); builder.HasKey(x => x.Id); builder.Property(x => x.AffiliatePartnerId).IsRequired(); builder.Property(x => x.OrderId).IsRequired(); builder.Property(x => x.OrderAmount).HasPrecision(18, 2); builder.Property(x => x.EstimatedCommission).HasPrecision(18, 2); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.AffiliatePartnerId, x.OrderId }).IsUnique(); } private static void ConfigureAffiliatePayout(EntityTypeBuilder builder) { builder.ToTable("affiliate_payouts"); builder.HasKey(x => x.Id); builder.Property(x => x.AffiliatePartnerId).IsRequired(); builder.Property(x => x.Period).HasMaxLength(32).IsRequired(); builder.Property(x => x.Amount).HasPrecision(18, 2); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.Remarks).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.AffiliatePartnerId, x.Period }).IsUnique(); } private static void ConfigureCheckInCampaign(EntityTypeBuilder builder) { builder.ToTable("checkin_campaigns"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Description).HasMaxLength(512); builder.Property(x => x.RewardsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.Name }); } private static void ConfigureCheckInRecord(EntityTypeBuilder builder) { builder.ToTable("checkin_records"); builder.HasKey(x => x.Id); builder.Property(x => x.CheckInCampaignId).IsRequired(); builder.Property(x => x.UserId).IsRequired(); builder.Property(x => x.RewardJson).HasColumnType("text").IsRequired(); builder.HasIndex(x => new { x.TenantId, x.CheckInCampaignId, x.UserId, x.CheckInDate }).IsUnique(); } private static void ConfigureCommunityPost(EntityTypeBuilder builder) { builder.ToTable("community_posts"); builder.HasKey(x => x.Id); builder.Property(x => x.AuthorUserId).IsRequired(); builder.Property(x => x.Title).HasMaxLength(128); builder.Property(x => x.Content).HasColumnType("text").IsRequired(); builder.Property(x => x.MediaJson).HasColumnType("text"); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.AuthorUserId, x.CreatedAt }); } private static void ConfigureCommunityComment(EntityTypeBuilder builder) { builder.ToTable("community_comments"); builder.HasKey(x => x.Id); builder.Property(x => x.PostId).IsRequired(); builder.Property(x => x.Content).HasMaxLength(512).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.PostId, x.CreatedAt }); } private static void ConfigureCommunityReaction(EntityTypeBuilder builder) { builder.ToTable("community_reactions"); builder.HasKey(x => x.Id); builder.Property(x => x.PostId).IsRequired(); builder.Property(x => x.ReactionType).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.PostId, x.UserId }).IsUnique(); } private static void ConfigureMapLocation(EntityTypeBuilder builder) { builder.ToTable("map_locations"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Address).HasMaxLength(256).IsRequired(); builder.Property(x => x.Landmark).HasMaxLength(128); builder.HasIndex(x => new { x.TenantId, x.StoreId }); } private static void ConfigureNavigationRequest(EntityTypeBuilder builder) { builder.ToTable("navigation_requests"); builder.HasKey(x => x.Id); builder.Property(x => x.UserId).IsRequired(); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Channel).HasConversion(); builder.Property(x => x.TargetApp).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.UserId, x.StoreId, x.RequestedAt }); } private static void ConfigureMetricDefinition(EntityTypeBuilder builder) { builder.ToTable("metric_definitions"); builder.HasKey(x => x.Id); builder.Property(x => x.Code).HasMaxLength(64).IsRequired(); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.Description).HasMaxLength(512); builder.Property(x => x.DimensionsJson).HasColumnType("text"); builder.Property(x => x.DefaultAggregation).HasMaxLength(32).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique(); } private static void ConfigureMetricSnapshot(EntityTypeBuilder builder) { builder.ToTable("metric_snapshots"); builder.HasKey(x => x.Id); builder.Property(x => x.MetricDefinitionId).IsRequired(); builder.Property(x => x.DimensionKey).HasMaxLength(256).IsRequired(); builder.Property(x => x.Value).HasPrecision(18, 4); builder.HasIndex(x => new { x.TenantId, x.MetricDefinitionId, x.DimensionKey, x.WindowStart, x.WindowEnd }).IsUnique(); } private static void ConfigureMetricAlertRule(EntityTypeBuilder builder) { builder.ToTable("metric_alert_rules"); builder.HasKey(x => x.Id); builder.Property(x => x.MetricDefinitionId).IsRequired(); builder.Property(x => x.ConditionJson).HasColumnType("text").IsRequired(); builder.Property(x => x.Severity).HasConversion(); builder.Property(x => x.NotificationChannels).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.MetricDefinitionId, x.Severity }); } private static void ConfigureQuotaPackage(EntityTypeBuilder builder) { builder.ToTable("quota_packages"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(128).IsRequired(); builder.Property(x => x.QuotaType).HasConversion().IsRequired(); builder.Property(x => x.QuotaValue).HasPrecision(18, 2).IsRequired(); builder.Property(x => x.Price).HasPrecision(18, 2).IsRequired(); builder.Property(x => x.IsActive).IsRequired(); builder.Property(x => x.SortOrder).HasDefaultValue(0); builder.Property(x => x.Description).HasMaxLength(512); builder.HasIndex(x => new { x.QuotaType, x.IsActive, x.SortOrder }); } private static void ConfigureTenantQuotaPackagePurchase(EntityTypeBuilder builder) { builder.ToTable("tenant_quota_package_purchases"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.QuotaPackageId).IsRequired(); builder.Property(x => x.QuotaValue).HasPrecision(18, 2).IsRequired(); builder.Property(x => x.Price).HasPrecision(18, 2).IsRequired(); builder.Property(x => x.PurchasedAt).IsRequired(); builder.Property(x => x.Notes).HasMaxLength(512); builder.HasIndex(x => new { x.TenantId, x.QuotaPackageId, x.PurchasedAt }); } }