using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using MassTransit; 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.Common.Enums; using TakeoutSaaS.Domain.Engagement.Entities; using TakeoutSaaS.Domain.Finance.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; namespace TakeoutSaaS.Infrastructure.App.Persistence; /// /// 业务主库 DbContext。 /// public sealed class TakeoutAppDbContext( DbContextOptions options, ITenantProvider tenantProvider, ICurrentUserAccessor? currentUserAccessor = null, IIdGenerator? idGenerator = null) : TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator) { /// /// 租户聚合根。 /// 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 TenantVisibilityRoleRules => Set(); /// /// 租户发票设置。 /// public DbSet TenantInvoiceSettings => Set(); /// /// 租户发票记录。 /// public DbSet TenantInvoiceRecords => Set(); /// /// 成本录入汇总。 /// public DbSet FinanceCostEntries => Set(); /// /// 成本录入明细。 /// public DbSet FinanceCostEntryItems => 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 StoreDeliverySettings => Set(); /// /// 门店堂食设置。 /// public DbSet StoreDineInSettings => Set(); /// /// 门店桌台区域。 /// public DbSet StoreTableAreas => Set(); /// /// 门店桌台。 /// public DbSet StoreTables => Set(); /// /// 门店员工班次。 /// public DbSet StoreEmployeeShifts => Set(); /// /// 自提配置。 /// public DbSet StorePickupSettings => Set(); /// /// 自提时间段。 /// public DbSet StorePickupSlots => Set(); /// /// 门店班次模板。 /// public DbSet StoreStaffTemplates => Set(); /// /// 门店每周排班。 /// public DbSet StoreStaffWeeklySchedules => Set(); /// /// 商品分类。 /// public DbSet ProductCategories => Set(); /// /// 商品。 /// public DbSet Products => Set(); /// /// 商品属性组。 /// public DbSet ProductAttributeGroups => Set(); /// /// 商品属性项。 /// public DbSet ProductAttributeOptions => Set(); /// /// 门店规格做法模板。 /// public DbSet ProductSpecTemplates => Set(); /// /// 门店规格做法模板选项。 /// public DbSet ProductSpecTemplateOptions => Set(); /// /// 门店规格做法模板关联商品。 /// public DbSet ProductSpecTemplateProducts => Set(); /// /// 商品标签。 /// public DbSet ProductLabels => Set(); /// /// 标签与商品关联。 /// public DbSet ProductLabelProducts => Set(); /// /// 商品时段规则。 /// public DbSet ProductSchedules => Set(); /// /// 时段规则与商品关联。 /// public DbSet ProductScheduleProducts => Set(); /// /// SKU 实体。 /// public DbSet ProductSkus => Set(); /// /// SKU 异步保存任务。 /// public DbSet ProductSkuSaveJobs => Set(); /// /// 套餐分组。 /// public DbSet ProductComboGroups => Set(); /// /// 套餐分组商品。 /// public DbSet ProductComboGroupItems => 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 NewCustomerGiftSettings => Set(); /// /// 新客有礼券规则。 /// public DbSet NewCustomerCouponRules => Set(); /// /// 新客邀请记录。 /// public DbSet NewCustomerInviteRecords => Set(); /// /// 新客成长记录。 /// public DbSet NewCustomerGrowthRecords => Set(); /// /// 次卡模板。 /// public DbSet PunchCardTemplates => Set(); /// /// 次卡实例。 /// public DbSet PunchCardInstances => Set(); /// /// 次卡使用记录。 /// public DbSet PunchCardUsageRecords => Set(); /// /// 会员档案。 /// public DbSet MemberProfiles => Set(); /// /// 会员等级。 /// public DbSet MemberTiers => Set(); /// /// 会员标签。 /// public DbSet MemberProfileTags => Set(); /// /// 会员日设置。 /// public DbSet MemberDaySettings => Set(); /// /// 积分流水。 /// public DbSet MemberPointLedgers => Set(); /// /// 积分商城规则。 /// public DbSet MemberPointMallRules => Set(); /// /// 积分商城兑换商品。 /// public DbSet MemberPointMallProducts => Set(); /// /// 积分商城兑换记录。 /// public DbSet MemberPointMallRecords => Set(); /// /// 会员储值方案。 /// public DbSet MemberStoredCardPlans => Set(); /// /// 会员储值充值记录。 /// public DbSet MemberStoredCardRechargeRecords => Set(); /// /// 会员消息触达记录。 /// public DbSet MemberReachMessages => Set(); /// /// 会员消息模板。 /// public DbSet MemberMessageTemplates => Set(); /// /// 会员消息触达收件明细。 /// public DbSet MemberReachRecipients => 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()); ConfigureTenantVisibilityRoleRule(modelBuilder.Entity()); ConfigureTenantInvoiceSetting(modelBuilder.Entity()); ConfigureTenantInvoiceRecord(modelBuilder.Entity()); ConfigureFinanceCostEntry(modelBuilder.Entity()); ConfigureFinanceCostEntryItem(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()); ConfigureStoreDeliverySetting(modelBuilder.Entity()); ConfigureStoreDineInSetting(modelBuilder.Entity()); ConfigureStoreTableArea(modelBuilder.Entity()); ConfigureStoreTable(modelBuilder.Entity()); ConfigureStoreEmployeeShift(modelBuilder.Entity()); ConfigureStorePickupSetting(modelBuilder.Entity()); ConfigureStorePickupSlot(modelBuilder.Entity()); ConfigureStoreStaffTemplate(modelBuilder.Entity()); ConfigureStoreStaffWeeklySchedule(modelBuilder.Entity()); ConfigureProductCategory(modelBuilder.Entity()); ConfigureProduct(modelBuilder.Entity()); ConfigureProductAttributeGroup(modelBuilder.Entity()); ConfigureProductAttributeOption(modelBuilder.Entity()); ConfigureProductSpecTemplate(modelBuilder.Entity()); ConfigureProductSpecTemplateOption(modelBuilder.Entity()); ConfigureProductSpecTemplateProduct(modelBuilder.Entity()); ConfigureProductLabel(modelBuilder.Entity()); ConfigureProductLabelProduct(modelBuilder.Entity()); ConfigureProductSchedule(modelBuilder.Entity()); ConfigureProductScheduleProduct(modelBuilder.Entity()); ConfigureProductSku(modelBuilder.Entity()); ConfigureProductSkuSaveJob(modelBuilder.Entity()); ConfigureProductComboGroup(modelBuilder.Entity()); ConfigureProductComboGroupItem(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()); ConfigureNewCustomerGiftSetting(modelBuilder.Entity()); ConfigureNewCustomerCouponRule(modelBuilder.Entity()); ConfigureNewCustomerInviteRecord(modelBuilder.Entity()); ConfigureNewCustomerGrowthRecord(modelBuilder.Entity()); ConfigurePunchCardTemplate(modelBuilder.Entity()); ConfigurePunchCardInstance(modelBuilder.Entity()); ConfigurePunchCardUsageRecord(modelBuilder.Entity()); ConfigureMemberProfile(modelBuilder.Entity()); ConfigureMemberTier(modelBuilder.Entity()); ConfigureMemberProfileTag(modelBuilder.Entity()); ConfigureMemberDaySetting(modelBuilder.Entity()); ConfigureMemberPointLedger(modelBuilder.Entity()); ConfigureMemberPointMallRule(modelBuilder.Entity()); ConfigureMemberPointMallProduct(modelBuilder.Entity()); ConfigureMemberPointMallRecord(modelBuilder.Entity()); ConfigureMemberStoredCardPlan(modelBuilder.Entity()); ConfigureMemberStoredCardRechargeRecord(modelBuilder.Entity()); ConfigureMemberReachMessage(modelBuilder.Entity()); ConfigureMemberMessageTemplate(modelBuilder.Entity()); ConfigureMemberReachRecipient(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()); modelBuilder.AddInboxStateEntity(); modelBuilder.AddOutboxMessageEntity(); modelBuilder.AddOutboxStateEntity(); // 3. 应用多租户全局查询过滤器 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.GeoStatus).HasConversion().HasDefaultValue(GeoLocationStatus.Pending); builder.Property(x => x.GeoFailReason).HasMaxLength(500); builder.Property(x => x.GeoRetryCount).HasDefaultValue(0); 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(); builder.HasIndex(x => new { x.GeoStatus, x.GeoNextRetryAt }); builder.HasIndex(x => new { x.Longitude, x.Latitude }) .HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL"); } 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 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.GeoStatus).HasConversion().HasDefaultValue(GeoLocationStatus.Pending); builder.Property(x => x.GeoFailReason).HasMaxLength(500); builder.Property(x => x.GeoRetryCount).HasDefaultValue(0); builder.Property(x => x.IsFrozen).HasDefaultValue(false); builder.Property(x => x.FrozenReason).HasMaxLength(500); builder.Property(x => x.ClaimedByName).HasMaxLength(100); builder.Ignore(x => x.RowVersion); builder.Property("xmin") .HasColumnName("xmin") .HasColumnType("xid") .ValueGeneratedOnAddOrUpdate() .IsConcurrencyToken(); builder.HasIndex(x => x.TenantId); builder.HasIndex(x => new { x.TenantId, x.Status }); builder.HasIndex(x => x.ClaimedBy); builder.HasIndex(x => new { x.TenantId, x.GeoStatus, x.GeoNextRetryAt }); builder.HasIndex(x => new { x.Longitude, x.Latitude }) .HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL"); } 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.Property(x => x.GeoStatus).HasConversion().HasDefaultValue(GeoLocationStatus.Pending); builder.Property(x => x.GeoFailReason).HasMaxLength(500); builder.Property(x => x.GeoRetryCount).HasDefaultValue(0); 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.TenantId, x.MerchantId, x.GeoStatus, x.GeoNextRetryAt }); 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.OrderPackagingFeeMode).HasConversion(); builder.Property(x => x.FixedPackagingFee).HasPrecision(10, 2); builder.Property(x => x.PackagingFeeTiersJson).HasColumnType("text"); builder.Property(x => x.FreeDeliveryThreshold).HasPrecision(10, 2); builder.Property(x => x.CutleryFeeEnabled).HasDefaultValue(false); builder.Property(x => x.CutleryFeeAmount).HasPrecision(10, 2); builder.Property(x => x.RushFeeEnabled).HasDefaultValue(false); builder.Property(x => x.RushFeeAmount).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.Property(x => x.Icon).HasMaxLength(512); builder.Property(x => x.ChannelsJson).HasColumnType("text").HasDefaultValue("[\"wm\"]"); 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.Kind).HasConversion().HasDefaultValue(Domain.Products.Enums.ProductKind.Single); builder.Property(x => x.SalesMonthly).HasDefaultValue(0); builder.Property(x => x.TagsJson).HasColumnType("text").HasDefaultValue("[]"); builder.Property(x => x.SoldoutMode).HasConversion(); builder.Property(x => x.SoldoutReason).HasMaxLength(256); builder.Property(x => x.SyncToPlatform).HasDefaultValue(true); builder.Property(x => x.NotifyManager).HasDefaultValue(false); builder.Property(x => x.CoverImage).HasMaxLength(256); builder.Property(x => x.GalleryImages).HasMaxLength(1024); builder.Property(x => x.Description).HasColumnType("text"); builder.Property(x => x.SortWeight).HasDefaultValue(0); builder.Property(x => x.PackingFee).HasPrecision(18, 2); 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 ConfigureTenantVisibilityRoleRule(EntityTypeBuilder builder) { builder.ToTable("tenant_visibility_role_rules"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.QuotaVisibleRoleCodes).HasColumnType("text[]"); builder.Property(x => x.BillingVisibleRoleCodes).HasColumnType("text[]"); builder.Property(x => x.UpdatedBy).IsRequired(); builder.Property(x => x.UpdatedAt).IsRequired(); builder.HasIndex(x => x.TenantId).IsUnique(); } private static void ConfigureTenantInvoiceSetting(EntityTypeBuilder builder) { builder.ToTable("finance_invoice_settings"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.CompanyName).HasMaxLength(128).IsRequired(); builder.Property(x => x.TaxpayerNumber).HasMaxLength(64).IsRequired(); builder.Property(x => x.RegisteredAddress).HasMaxLength(256); builder.Property(x => x.RegisteredPhone).HasMaxLength(32); builder.Property(x => x.BankName).HasMaxLength(128); builder.Property(x => x.BankAccount).HasMaxLength(64); builder.Property(x => x.EnableElectronicNormalInvoice).IsRequired(); builder.Property(x => x.EnableElectronicSpecialInvoice).IsRequired(); builder.Property(x => x.EnableAutoIssue).IsRequired(); builder.Property(x => x.AutoIssueMaxAmount).HasPrecision(18, 2).IsRequired(); builder.HasIndex(x => x.TenantId).IsUnique(); } private static void ConfigureTenantInvoiceRecord(EntityTypeBuilder builder) { builder.ToTable("finance_invoice_records"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.InvoiceNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.ApplicantName).HasMaxLength(64).IsRequired(); builder.Property(x => x.CompanyName).HasMaxLength(128).IsRequired(); builder.Property(x => x.TaxpayerNumber).HasMaxLength(64); builder.Property(x => x.InvoiceType).HasConversion().IsRequired(); builder.Property(x => x.Amount).HasPrecision(18, 2).IsRequired(); builder.Property(x => x.OrderNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.ContactEmail).HasMaxLength(128); builder.Property(x => x.ContactPhone).HasMaxLength(32); builder.Property(x => x.ApplyRemark).HasMaxLength(256); builder.Property(x => x.Status).HasConversion().IsRequired(); builder.Property(x => x.AppliedAt).IsRequired(); builder.Property(x => x.IssueRemark).HasMaxLength(256); builder.Property(x => x.VoidReason).HasMaxLength(256); builder.HasIndex(x => new { x.TenantId, x.InvoiceNo }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.OrderNo }); builder.HasIndex(x => new { x.TenantId, x.Status, x.AppliedAt }); builder.HasIndex(x => new { x.TenantId, x.Status, x.IssuedAt }); builder.HasIndex(x => new { x.TenantId, x.InvoiceType, x.AppliedAt }); } private static void ConfigureFinanceCostEntry(EntityTypeBuilder builder) { builder.ToTable("finance_cost_entries"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.Dimension).HasConversion().IsRequired(); builder.Property(x => x.StoreId); builder.Property(x => x.CostMonth).IsRequired(); builder.Property(x => x.Category).HasConversion().IsRequired(); builder.Property(x => x.TotalAmount).HasPrecision(18, 2); builder.HasIndex(x => new { x.TenantId, x.Dimension, x.StoreId, x.CostMonth, x.Category }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.Dimension, x.StoreId, x.CostMonth }); } private static void ConfigureFinanceCostEntryItem(EntityTypeBuilder builder) { builder.ToTable("finance_cost_entry_items"); builder.HasKey(x => x.Id); builder.Property(x => x.TenantId).IsRequired(); builder.Property(x => x.EntryId).IsRequired(); builder.Property(x => x.Dimension).HasConversion().IsRequired(); builder.Property(x => x.StoreId); builder.Property(x => x.CostMonth).IsRequired(); builder.Property(x => x.Category).HasConversion().IsRequired(); builder.Property(x => x.ItemName).HasMaxLength(64).IsRequired(); builder.Property(x => x.Amount).HasPrecision(18, 2); builder.Property(x => x.Quantity).HasPrecision(18, 2); builder.Property(x => x.UnitPrice).HasPrecision(18, 2); builder.Property(x => x.SortOrder).HasDefaultValue(100); builder.HasOne() .WithMany() .HasForeignKey(x => x.EntryId) .OnDelete(DeleteBehavior.Cascade); builder.HasIndex(x => x.EntryId); builder.HasIndex(x => new { x.TenantId, x.Dimension, x.StoreId, x.CostMonth, x.Category, x.SortOrder }); } 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.Property(x => x.Color).HasMaxLength(32); builder.Property(x => x.Priority).HasDefaultValue(100); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ZoneName }); } private static void ConfigureStoreDeliverySetting(EntityTypeBuilder builder) { builder.ToTable("store_delivery_settings"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Mode).HasConversion(); builder.Property(x => x.FreeDeliveryThreshold).HasPrecision(10, 2); builder.Property(x => x.MaxDeliveryDistance).HasPrecision(10, 2); builder.Property(x => x.RadiusTiersJson).HasColumnType("text"); builder.Property(x => x.RadiusCenterLatitude).HasPrecision(10, 7); builder.Property(x => x.RadiusCenterLongitude).HasPrecision(10, 7); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } private static void ConfigureStoreDineInSetting(EntityTypeBuilder builder) { builder.ToTable("store_dinein_settings"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Enabled).HasDefaultValue(true); builder.Property(x => x.DefaultDiningMinutes).HasDefaultValue(90); builder.Property(x => x.OvertimeReminderMinutes).HasDefaultValue(10); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } 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.Mode).HasConversion(); builder.Property(x => x.FineRuleJson).HasColumnType("text"); builder.Property(x => x.RowVersion) .IsRequired() .IsConcurrencyToken() .ValueGeneratedNever(); 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) .IsRequired() .IsConcurrencyToken() .ValueGeneratedNever(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }); } private static void ConfigureStoreStaffTemplate(EntityTypeBuilder builder) { builder.ToTable("store_staff_templates"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.MorningStartTime).IsRequired(); builder.Property(x => x.MorningEndTime).IsRequired(); builder.Property(x => x.EveningStartTime).IsRequired(); builder.Property(x => x.EveningEndTime).IsRequired(); builder.Property(x => x.FullStartTime).IsRequired(); builder.Property(x => x.FullEndTime).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } private static void ConfigureStoreStaffWeeklySchedule(EntityTypeBuilder builder) { builder.ToTable("store_staff_weekly_schedules"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.StaffId).IsRequired(); builder.Property(x => x.DayOfWeek).IsRequired(); builder.Property(x => x.ShiftType).HasConversion(); builder.Property(x => x.StartTime); builder.Property(x => x.EndTime); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.StaffId, x.DayOfWeek }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.DayOfWeek }); } 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 ConfigureProductSpecTemplate(EntityTypeBuilder builder) { builder.ToTable("product_spec_templates"); 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).IsRequired(); builder.Property(x => x.TemplateType).HasConversion(); builder.Property(x => x.SelectionType).HasConversion(); builder.Property(x => x.MinSelect).IsRequired(); builder.Property(x => x.MaxSelect).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.Property(x => x.IsEnabled).IsRequired(); builder.Property(x => x.IsRequired).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.TemplateType, x.Name }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.TemplateType, x.IsEnabled }); } private static void ConfigureProductSpecTemplateOption(EntityTypeBuilder builder) { builder.ToTable("product_spec_template_options"); builder.HasKey(x => x.Id); builder.Property(x => x.TemplateId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.ExtraPrice).HasPrecision(18, 2); builder.Property(x => x.Stock).IsRequired(); builder.Property(x => x.IsEnabled).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.TemplateId, x.Name }).IsUnique(); } private static void ConfigureProductSpecTemplateProduct(EntityTypeBuilder builder) { builder.ToTable("product_spec_template_products"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.TemplateId).IsRequired(); builder.Property(x => x.ProductId).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.TemplateId, x.ProductId }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductId }); } private static void ConfigureProductLabel(EntityTypeBuilder builder) { builder.ToTable("product_labels"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Color).HasMaxLength(32).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.Property(x => x.IsEnabled).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.IsEnabled, x.SortOrder }); } private static void ConfigureProductLabelProduct(EntityTypeBuilder builder) { builder.ToTable("product_label_products"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.LabelId).IsRequired(); builder.Property(x => x.ProductId).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.LabelId, x.ProductId }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductId }); } private static void ConfigureProductSchedule(EntityTypeBuilder builder) { builder.ToTable("product_schedules"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.StartTime).IsRequired(); builder.Property(x => x.EndTime).IsRequired(); builder.Property(x => x.WeekDaysMask).IsRequired(); builder.Property(x => x.IsEnabled).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.IsEnabled }); } private static void ConfigureProductScheduleProduct(EntityTypeBuilder builder) { builder.ToTable("product_schedule_products"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.ScheduleId).IsRequired(); builder.Property(x => x.ProductId).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ScheduleId, x.ProductId }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductId }); } 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.Property(x => x.IsEnabled).HasDefaultValue(true); builder.HasIndex(x => new { x.TenantId, x.ProductId }); builder.HasIndex(x => new { x.TenantId, x.SkuCode }).IsUnique(); } private static void ConfigureProductSkuSaveJob(EntityTypeBuilder builder) { builder.ToTable("product_sku_save_jobs"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.Mode).HasMaxLength(16).IsRequired(); builder.Property(x => x.PayloadJson).HasColumnType("text").IsRequired(); builder.Property(x => x.ProgressTotal).IsRequired(); builder.Property(x => x.ProgressProcessed).IsRequired(); builder.Property(x => x.FailedCount).IsRequired(); builder.Property(x => x.ErrorMessage).HasMaxLength(2000); builder.Property(x => x.HangfireJobId).HasMaxLength(64); builder.HasIndex(x => new { x.TenantId, x.ProductId, x.CreatedAt }); builder.HasIndex(x => new { x.TenantId, x.Status, x.CreatedAt }); } private static void ConfigureProductComboGroup(EntityTypeBuilder builder) { builder.ToTable("product_combo_groups"); builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.MinSelect).IsRequired(); builder.Property(x => x.MaxSelect).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.ProductId, x.SortOrder }); builder.HasIndex(x => new { x.TenantId, x.ProductId, x.Name }); } private static void ConfigureProductComboGroupItem(EntityTypeBuilder builder) { builder.ToTable("product_combo_group_items"); builder.HasKey(x => x.Id); builder.Property(x => x.ComboGroupId).IsRequired(); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.Quantity).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.ComboGroupId, x.SortOrder }); builder.HasIndex(x => new { x.TenantId, x.ComboGroupId, x.ProductId }).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.PerUserLimit); 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 ConfigureNewCustomerGiftSetting(EntityTypeBuilder builder) { builder.ToTable("new_customer_gift_settings"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.GiftType).HasConversion(); builder.Property(x => x.DirectReduceAmount).HasPrecision(18, 2); builder.Property(x => x.DirectMinimumSpend).HasPrecision(18, 2); builder.Property(x => x.ShareChannelsJson).HasColumnType("text").IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } private static void ConfigureNewCustomerCouponRule(EntityTypeBuilder builder) { builder.ToTable("new_customer_coupon_rules"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Scene).HasConversion(); builder.Property(x => x.CouponType).HasConversion(); builder.Property(x => x.Value).HasPrecision(18, 2); builder.Property(x => x.MinimumSpend).HasPrecision(18, 2); builder.Property(x => x.ValidDays).IsRequired(); builder.Property(x => x.SortOrder).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Scene, x.SortOrder }); } private static void ConfigureNewCustomerInviteRecord(EntityTypeBuilder builder) { builder.ToTable("new_customer_invite_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.InviterName).HasMaxLength(64).IsRequired(); builder.Property(x => x.InviteeName).HasMaxLength(64).IsRequired(); builder.Property(x => x.InviteTime).IsRequired(); builder.Property(x => x.OrderStatus).HasConversion(); builder.Property(x => x.RewardStatus).HasConversion(); builder.Property(x => x.SourceChannel).HasMaxLength(32); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.InviteTime }); } private static void ConfigureNewCustomerGrowthRecord(EntityTypeBuilder builder) { builder.ToTable("new_customer_growth_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.CustomerKey).HasMaxLength(64).IsRequired(); builder.Property(x => x.CustomerName).HasMaxLength(64); builder.Property(x => x.RegisteredAt).IsRequired(); builder.Property(x => x.SourceChannel).HasMaxLength(32); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.CustomerKey }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RegisteredAt }); } private static void ConfigurePunchCardTemplate(EntityTypeBuilder builder) { builder.ToTable("punch_card_templates"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.CoverImageUrl).HasMaxLength(512); builder.Property(x => x.SalePrice).HasPrecision(18, 2); builder.Property(x => x.OriginalPrice).HasPrecision(18, 2); builder.Property(x => x.TotalTimes).IsRequired(); builder.Property(x => x.ValidityType).HasConversion(); builder.Property(x => x.ValidityDays); builder.Property(x => x.ValidFrom); builder.Property(x => x.ValidTo); builder.Property(x => x.ScopeType).HasConversion(); builder.Property(x => x.ScopeCategoryIdsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.ScopeTagIdsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.ScopeProductIdsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.UsageMode).HasConversion(); builder.Property(x => x.UsageCapAmount).HasPrecision(18, 2); builder.Property(x => x.DailyLimit); builder.Property(x => x.PerOrderLimit); builder.Property(x => x.PerUserPurchaseLimit); builder.Property(x => x.AllowTransfer).IsRequired(); builder.Property(x => x.ExpireStrategy).HasConversion(); builder.Property(x => x.Description).HasMaxLength(512); builder.Property(x => x.NotifyChannelsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Status }); } private static void ConfigurePunchCardInstance(EntityTypeBuilder builder) { builder.ToTable("punch_card_instances"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.PunchCardTemplateId).IsRequired(); builder.Property(x => x.InstanceNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.MemberName).HasMaxLength(64).IsRequired(); builder.Property(x => x.MemberPhoneMasked).HasMaxLength(32).IsRequired(); builder.Property(x => x.PurchasedAt).IsRequired(); builder.Property(x => x.ExpiresAt); builder.Property(x => x.TotalTimes).IsRequired(); builder.Property(x => x.RemainingTimes).IsRequired(); builder.Property(x => x.PaidAmount).HasPrecision(18, 2); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.InstanceNo }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PunchCardTemplateId }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Status, x.ExpiresAt }); } private static void ConfigurePunchCardUsageRecord(EntityTypeBuilder builder) { builder.ToTable("punch_card_usage_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.PunchCardTemplateId).IsRequired(); builder.Property(x => x.PunchCardInstanceId).IsRequired(); builder.Property(x => x.RecordNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.ProductName).HasMaxLength(128).IsRequired(); builder.Property(x => x.UsedAt).IsRequired(); builder.Property(x => x.UsedTimes).IsRequired(); builder.Property(x => x.RemainingTimesAfterUse).IsRequired(); builder.Property(x => x.StatusAfterUse).HasConversion(); builder.Property(x => x.ExtraPayAmount).HasPrecision(18, 2); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RecordNo }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PunchCardTemplateId, x.UsedAt }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PunchCardInstanceId, x.UsedAt }); } 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.StoredBalance).HasPrecision(18, 2); builder.Property(x => x.StoredRechargeBalance).HasPrecision(18, 2); builder.Property(x => x.StoredGiftBalance).HasPrecision(18, 2); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.Mobile }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.MemberTierId }); } 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.IconKey).HasMaxLength(32).IsRequired(); builder.Property(x => x.ColorHex).HasMaxLength(16).IsRequired(); builder.Property(x => x.UpgradeRuleType).HasMaxLength(16).IsRequired(); builder.Property(x => x.UpgradeAmountThreshold).HasPrecision(18, 2); builder.Property(x => x.BenefitsJson).HasColumnType("text"); builder.HasIndex(x => new { x.TenantId, x.Name }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.SortOrder }); } private static void ConfigureMemberProfileTag(EntityTypeBuilder builder) { builder.ToTable("member_profile_tags"); builder.HasKey(x => x.Id); builder.Property(x => x.MemberProfileId).IsRequired(); builder.Property(x => x.TagName).HasMaxLength(32).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.MemberProfileId, x.TagName }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.MemberProfileId }); } private static void ConfigureMemberDaySetting(EntityTypeBuilder builder) { builder.ToTable("member_day_settings"); builder.HasKey(x => x.Id); builder.Property(x => x.IsEnabled).IsRequired(); builder.Property(x => x.Weekday).IsRequired(); builder.Property(x => x.ExtraDiscountRate).HasPrecision(5, 2); builder.HasIndex(x => x.TenantId).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 ConfigureMemberPointMallRule(EntityTypeBuilder builder) { builder.ToTable("member_point_mall_rules"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.IsConsumeRewardEnabled).IsRequired(); builder.Property(x => x.ConsumeAmountPerStep).IsRequired(); builder.Property(x => x.ConsumeRewardPointsPerStep).IsRequired(); builder.Property(x => x.IsReviewRewardEnabled).IsRequired(); builder.Property(x => x.ReviewRewardPoints).IsRequired(); builder.Property(x => x.IsRegisterRewardEnabled).IsRequired(); builder.Property(x => x.RegisterRewardPoints).IsRequired(); builder.Property(x => x.IsSigninRewardEnabled).IsRequired(); builder.Property(x => x.SigninRewardPoints).IsRequired(); builder.Property(x => x.ExpiryMode).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } private static void ConfigureMemberPointMallProduct(EntityTypeBuilder builder) { builder.ToTable("member_point_mall_products"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.ImageUrl).HasMaxLength(512); builder.Property(x => x.RedeemType).HasConversion(); builder.Property(x => x.ProductId); builder.Property(x => x.CouponTemplateId); builder.Property(x => x.PhysicalName).HasMaxLength(64); builder.Property(x => x.PickupMethod).HasConversion(); builder.Property(x => x.Description).HasMaxLength(512); builder.Property(x => x.ExchangeType).HasConversion(); builder.Property(x => x.RequiredPoints).IsRequired(); builder.Property(x => x.CashAmount).HasPrecision(18, 2); builder.Property(x => x.StockTotal).IsRequired(); builder.Property(x => x.StockAvailable).IsRequired(); builder.Property(x => x.PerMemberLimit); builder.Property(x => x.NotifyChannelsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Status }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductId }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.CouponTemplateId }); } private static void ConfigureMemberPointMallRecord(EntityTypeBuilder builder) { builder.ToTable("member_point_mall_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.RecordNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.PointMallProductId).IsRequired(); builder.Property(x => x.MemberId).IsRequired(); builder.Property(x => x.MemberName).HasMaxLength(64).IsRequired(); builder.Property(x => x.MemberMobileMasked).HasMaxLength(32).IsRequired(); builder.Property(x => x.ProductName).HasMaxLength(128).IsRequired(); builder.Property(x => x.RedeemType).HasConversion(); builder.Property(x => x.ExchangeType).HasConversion(); builder.Property(x => x.UsedPoints).IsRequired(); builder.Property(x => x.CashAmount).HasPrecision(18, 2); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.RedeemedAt).IsRequired(); builder.Property(x => x.IssuedAt); builder.Property(x => x.VerifiedAt); builder.Property(x => x.VerifyMethod).HasConversion(); builder.Property(x => x.VerifyRemark).HasMaxLength(256); builder.Property(x => x.VerifiedBy); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RecordNo }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PointMallProductId, x.RedeemedAt }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.MemberId, x.RedeemedAt }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Status, x.RedeemedAt }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RedeemedAt }); } private static void ConfigureMemberStoredCardPlan(EntityTypeBuilder builder) { builder.ToTable("member_stored_card_plans"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.RechargeAmount).HasPrecision(18, 2); builder.Property(x => x.GiftAmount).HasPrecision(18, 2); builder.Property(x => x.SortOrder).IsRequired(); builder.Property(x => x.Status).HasConversion(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RechargeAmount, x.GiftAmount }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.SortOrder }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Status }); } private static void ConfigureMemberStoredCardRechargeRecord(EntityTypeBuilder builder) { builder.ToTable("member_stored_card_recharge_records"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId).IsRequired(); builder.Property(x => x.MemberId).IsRequired(); builder.Property(x => x.PlanId); builder.Property(x => x.RecordNo).HasMaxLength(32).IsRequired(); builder.Property(x => x.MemberName).HasMaxLength(64).IsRequired(); builder.Property(x => x.MemberMobileMasked).HasMaxLength(32).IsRequired(); builder.Property(x => x.RechargeAmount).HasPrecision(18, 2); builder.Property(x => x.GiftAmount).HasPrecision(18, 2); builder.Property(x => x.ArrivedAmount).HasPrecision(18, 2); builder.Property(x => x.PaymentMethod).HasConversion(); builder.Property(x => x.Remark).HasMaxLength(256); builder.Property(x => x.RechargedAt).IsRequired(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RecordNo }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.MemberId, x.RechargedAt }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.PlanId, x.RechargedAt }); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.RechargedAt }); } private static void ConfigureMemberReachMessage(EntityTypeBuilder builder) { builder.ToTable("member_reach_messages"); builder.HasKey(x => x.Id); builder.Property(x => x.StoreId); builder.Property(x => x.TemplateId); builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); builder.Property(x => x.Content).HasColumnType("text").IsRequired(); builder.Property(x => x.ChannelsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.AudienceType).HasConversion(); builder.Property(x => x.AudienceTagsJson).HasColumnType("text").IsRequired(); builder.Property(x => x.EstimatedReachCount).IsRequired(); builder.Property(x => x.ScheduleType).HasConversion(); builder.Property(x => x.ScheduledAt); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.SentAt); builder.Property(x => x.SentCount).IsRequired(); builder.Property(x => x.ReadCount).IsRequired(); builder.Property(x => x.ConvertedCount).IsRequired(); builder.Property(x => x.HangfireJobId).HasMaxLength(64); builder.Property(x => x.LastError).HasMaxLength(1024); builder.HasIndex(x => new { x.TenantId, x.Status, x.ScheduledAt }); builder.HasIndex(x => new { x.TenantId, x.CreatedAt }); } private static void ConfigureMemberMessageTemplate(EntityTypeBuilder builder) { builder.ToTable("member_message_templates"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).HasMaxLength(64).IsRequired(); builder.Property(x => x.Category).HasConversion(); builder.Property(x => x.Content).HasColumnType("text").IsRequired(); builder.Property(x => x.UsageCount).IsRequired(); builder.Property(x => x.LastUsedAt); builder.HasIndex(x => new { x.TenantId, x.Name }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.Category, x.UsageCount }); } private static void ConfigureMemberReachRecipient(EntityTypeBuilder builder) { builder.ToTable("member_reach_recipients"); builder.HasKey(x => x.Id); builder.Property(x => x.MessageId).IsRequired(); builder.Property(x => x.MemberId).IsRequired(); builder.Property(x => x.Channel).HasConversion(); builder.Property(x => x.Mobile).HasMaxLength(32); builder.Property(x => x.OpenId).HasMaxLength(128); builder.Property(x => x.Status).HasConversion(); builder.Property(x => x.SentAt); builder.Property(x => x.ReadAt); builder.Property(x => x.ConvertedAt); builder.Property(x => x.ErrorMessage).HasMaxLength(512); builder.HasIndex(x => new { x.TenantId, x.MessageId, x.MemberId, x.Channel }).IsUnique(); builder.HasIndex(x => new { x.TenantId, x.MessageId, x.Status }); } 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 }); } }