From 5ddad07658ce58d25e1716e9a36625a0c58e289d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BA=E7=88=B1=E6=B3=BD?= Date: Mon, 1 Dec 2025 13:26:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=A9=E5=B1=95=E9=A2=86=E5=9F=9F?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/dotnet-tools.json | 13 + Document/05_部署运维.md | 26 + .../appsettings.Development.json | 2 +- .../appsettings.Production.json | 2 +- .../Analytics/Entities/MetricAlertRule.cs | 35 + .../Analytics/Entities/MetricDefinition.cs | 34 + .../Analytics/Entities/MetricSnapshot.cs | 34 + .../Analytics/Enums/MetricAlertSeverity.cs | 22 + .../Coupons/Entities/Coupon.cs | 50 + .../Coupons/Entities/CouponTemplate.cs | 90 ++ .../Coupons/Entities/PromotionCampaign.cs | 55 + .../Coupons/Enums/CouponStatus.cs | 32 + .../Coupons/Enums/CouponTemplateStatus.cs | 22 + .../Coupons/Enums/CouponType.cs | 32 + .../Coupons/Enums/PromotionStatus.cs | 27 + .../Coupons/Enums/PromotionType.cs | 27 + .../CustomerService/Entities/ChatMessage.cs | 45 + .../CustomerService/Entities/ChatSession.cs | 50 + .../CustomerService/Entities/SupportTicket.cs | 55 + .../CustomerService/Entities/TicketComment.cs | 34 + .../Enums/ChatSessionStatus.cs | 27 + .../Enums/MessageSenderType.cs | 27 + .../CustomerService/Enums/TicketPriority.cs | 22 + .../CustomerService/Enums/TicketStatus.cs | 32 + .../Deliveries/Entities/DeliveryEvent.cs | 35 + .../Deliveries/Entities/DeliveryOrder.cs | 62 ++ .../Deliveries/Enums/DeliveryEventType.cs | 22 + .../Deliveries/Enums/DeliveryProvider.cs | 14 + .../Deliveries/Enums/DeliveryStatus.cs | 15 + .../Distribution/Entities/AffiliateOrder.cs | 45 + .../Distribution/Entities/AffiliatePartner.cs | 45 + .../Distribution/Entities/AffiliatePayout.cs | 40 + .../Enums/AffiliateChannelType.cs | 27 + .../Enums/AffiliateOrderStatus.cs | 27 + .../Distribution/Enums/AffiliateStatus.cs | 27 + .../Distribution/Enums/PayoutStatus.cs | 22 + .../Engagement/Entities/CheckInCampaign.cs | 45 + .../Engagement/Entities/CheckInRecord.cs | 34 + .../Engagement/Entities/CommunityComment.cs | 34 + .../Engagement/Entities/CommunityPost.cs | 45 + .../Engagement/Entities/CommunityReaction.cs | 30 + .../Engagement/Enums/CheckInCampaignStatus.cs | 27 + .../Engagement/Enums/PostStatus.cs | 27 + .../Engagement/Enums/ReactionType.cs | 22 + .../GroupBuying/Entities/GroupOrder.cs | 70 ++ .../GroupBuying/Entities/GroupParticipant.cs | 35 + .../GroupBuying/Enums/GroupOrderStatus.cs | 27 + .../Enums/GroupParticipantStatus.cs | 22 + .../Inventory/Entities/InventoryAdjustment.cs | 40 + .../Inventory/Entities/InventoryBatch.cs | 44 + .../Inventory/Entities/InventoryItem.cs | 49 + .../Enums/InventoryAdjustmentType.cs | 32 + .../Membership/Entities/MemberGrowthLog.cs | 34 + .../Membership/Entities/MemberPointLedger.cs | 45 + .../Membership/Entities/MemberProfile.cs | 60 ++ .../Membership/Entities/MemberTier.cs | 29 + .../Membership/Enums/MemberStatus.cs | 22 + .../Membership/Enums/PointChangeReason.cs | 32 + .../Merchants/Entities/Merchant.cs | 120 +++ .../Merchants/Entities/MerchantContract.cs | 55 + .../Merchants/Entities/MerchantDocument.cs | 50 + .../Merchants/Entities/MerchantStaff.cs | 55 + .../Merchants/Enums/ContractStatus.cs | 27 + .../Merchants/Enums/MerchantDocumentStatus.cs | 27 + .../Merchants/Enums/MerchantDocumentType.cs | 27 + .../Merchants/Enums/MerchantStatus.cs | 12 + .../Merchants/Enums/StaffRoleType.cs | 32 + .../Merchants/Enums/StaffStatus.cs | 22 + .../Navigation/Entities/MapLocation.cs | 39 + .../Navigation/Entities/NavigationRequest.cs | 35 + .../Navigation/Enums/NavigationChannel.cs | 22 + .../Navigation/Enums/NavigationTargetApp.cs | 32 + .../Ordering/Entities/CartItem.cs | 55 + .../Ordering/Entities/CartItemAddon.cs | 29 + .../Ordering/Entities/CheckoutSession.cs | 40 + .../Ordering/Entities/ShoppingCart.cs | 40 + .../Ordering/Enums/CartItemStatus.cs | 22 + .../Ordering/Enums/CheckoutSessionStatus.cs | 27 + .../Ordering/Enums/ShoppingCartStatus.cs | 22 + .../Orders/Entities/Order.cs | 111 ++ .../Orders/Entities/OrderItem.cs | 59 ++ .../Orders/Entities/OrderStatusHistory.cs | 35 + .../Orders/Entities/RefundRequest.cs | 50 + .../Orders/Enums/DeliveryType.cs | 11 + .../Orders/Enums/OrderChannel.cs | 14 + .../Orders/Enums/OrderStatus.cs | 14 + .../Orders/Enums/RefundStatus.cs | 27 + .../Payments/Entities/PaymentRecord.cs | 55 + .../Payments/Entities/PaymentRefundRecord.cs | 50 + .../Payments/Enums/PaymentMethod.cs | 14 + .../Payments/Enums/PaymentRefundStatus.cs | 27 + .../Payments/Enums/PaymentStatus.cs | 13 + .../Products/Entities/Product.cs | 100 ++ .../Products/Entities/ProductAddonGroup.cs | 45 + .../Products/Entities/ProductAddonOption.cs | 34 + .../Entities/ProductAttributeGroup.cs | 35 + .../Entities/ProductAttributeOption.cs | 34 + .../Products/Entities/ProductCategory.cs | 34 + .../Products/Entities/ProductMediaAsset.cs | 35 + .../Products/Entities/ProductPricingRule.cs | 45 + .../Products/Entities/ProductSku.cs | 49 + .../Products/Enums/AddonSelectionType.cs | 17 + .../Products/Enums/AttributeSelectionType.cs | 17 + .../Products/Enums/MediaAssetType.cs | 22 + .../Products/Enums/PricingRuleType.cs | 32 + .../Products/Enums/ProductStatus.cs | 12 + .../Queues/Entities/QueueTicket.cs | 52 + .../Queues/Enums/QueueStatus.cs | 13 + .../Reservations/Entities/Reservation.cs | 70 ++ .../Reservations/Enums/ReservationStatus.cs | 13 + .../Stores/Entities/Store.cs | 130 +++ .../Stores/Entities/StoreBusinessHour.cs | 45 + .../Stores/Entities/StoreDeliveryZone.cs | 39 + .../Stores/Entities/StoreEmployeeShift.cs | 45 + .../Stores/Entities/StoreHoliday.cs | 29 + .../Stores/Entities/StoreTable.cs | 45 + .../Stores/Entities/StoreTableArea.cs | 24 + .../Stores/Enums/BusinessHourType.cs | 27 + .../Stores/Enums/StoreStatus.cs | 27 + .../Stores/Enums/StoreTableStatus.cs | 27 + .../Tenants/Entities/Tenant.cs | 125 +++ .../Entities/TenantBillingStatement.cs | 50 + .../Tenants/Entities/TenantNotification.cs | 45 + .../Tenants/Entities/TenantPackage.cs | 70 ++ .../Tenants/Entities/TenantQuotaUsage.cs | 35 + .../Tenants/Entities/TenantSubscription.cs | 50 + .../Tenants/Enums/SubscriptionStatus.cs | 32 + .../Tenants/Enums/TenantBillingStatus.cs | 27 + .../Enums/TenantNotificationChannel.cs | 27 + .../Enums/TenantNotificationSeverity.cs | 22 + .../Tenants/Enums/TenantPackageType.cs | 27 + .../Tenants/Enums/TenantQuotaType.cs | 37 + .../Tenants/Enums/TenantStatus.cs | 32 + .../20251201044927_InitialApp.Designer.cs | 949 ++++++++++++++++++ .../Migrations/20251201044927_InitialApp.cs | 497 +++++++++ .../TakeoutAppDbContextModelSnapshot.cs | 946 +++++++++++++++++ .../App/Persistence/TakeoutAppDbContext.cs | 221 ++++ .../TakeoutAppDesignTimeDbContextFactory.cs | 24 + .../DesignTimeDbContextFactoryBase.cs | 68 ++ ...251201042346_InitialDictionary.Designer.cs | 172 ++++ .../20251201042346_InitialDictionary.cs | 101 ++ .../DictionaryDbContextModelSnapshot.cs | 169 ++++ .../DictionaryDesignTimeDbContextFactory.cs | 24 + ...20251201042324_InitialIdentity.Designer.cs | 152 +++ .../20251201042324_InitialIdentity.cs | 94 ++ .../IdentityDbContextModelSnapshot.cs | 149 +++ .../IdentityDesignTimeDbContextFactory.cs | 24 + .../TakeoutSaaS.Infrastructure.csproj | 4 + 148 files changed, 8519 insertions(+), 2 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricDefinition.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Analytics/Enums/MetricAlertSeverity.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Entities/CouponTemplate.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Entities/PromotionCampaign.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponTemplateStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/ChatSessionStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/MessageSenderType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketPriority.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryEventType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryProvider.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateChannelType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateOrderStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Distribution/Enums/PayoutStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInCampaign.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Enums/CheckInCampaignStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Enums/PostStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Engagement/Enums/ReactionType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupOrderStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupParticipantStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Inventory/Enums/InventoryAdjustmentType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberTier.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Membership/Enums/MemberStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Membership/Enums/PointChangeReason.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Enums/ContractStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffRoleType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationChannel.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationTargetApp.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CartItemStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CheckoutSessionStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Ordering/Enums/ShoppingCartStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Enums/DeliveryType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderChannel.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Enums/RefundStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentMethod.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentRefundStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Enums/AddonSelectionType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Enums/AttributeSelectionType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Enums/MediaAssetType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Enums/PricingRuleType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Enums/ProductStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Queues/Enums/QueueStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Reservations/Enums/ReservationStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Enums/BusinessHourType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreTableStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantBillingStatement.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantNotification.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantPackage.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantQuotaUsage.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/SubscriptionStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantBillingStatus.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationChannel.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationSeverity.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantPackageType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantQuotaType.cs create mode 100644 src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantStatus.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/TakeoutAppDbContextModelSnapshot.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.Designer.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/DictionaryDbContextModelSnapshot.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.Designer.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/IdentityDbContextModelSnapshot.cs create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..0d6d304 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.0", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/Document/05_部署运维.md b/Document/05_部署运维.md index 1ff6392..449ab67 100644 --- a/Document/05_部署运维.md +++ b/Document/05_部署运维.md @@ -198,6 +198,32 @@ dotnet ef database update dotnet run ``` +### 2.7 EF Core 迁移基线 + +现已内置 `dotnet-ef` 本地工具与设计时 DbContext 工厂,可直接在命令行生成/更新数据库。运行前可通过环境变量 `TAKEOUTSAAS_APP_CONNECTION`、`TAKEOUTSAAS_IDENTITY_CONNECTION` 覆盖默认连接串(默认指向本地 PostgreSQL)。 + +```powershell +# 业务主库(TakeoutAppDbContext,含租户/商户/门店/商品/订单等) +dotnet tool run dotnet-ef database update ` + --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj ` + --startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj ` + --context TakeoutSaaS.Infrastructure.App.Persistence.TakeoutAppDbContext + +# 身份库(IdentityDbContext) +dotnet tool run dotnet-ef database update ` + --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj ` + --startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj ` + --context TakeoutSaaS.Infrastructure.Identity.Persistence.IdentityDbContext + +# 业务/字典库(DictionaryDbContext,归属 AppDatabase) +dotnet tool run dotnet-ef database update ` + --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj ` + --startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj ` + --context TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext +``` + +> Hangfire 使用 Scheduler.ConnectionString 指向的数据库,首次启动服务会自动建表;只需提前创建空数据库并授予账号权限。 + ## 3. Docker部署 ### 3.1 创建Dockerfile diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json index f015bba..d858ad3 100644 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json +++ b/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json @@ -134,7 +134,7 @@ "PrefetchCount": 20 }, "Scheduler": { - "ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_saas_scheduler;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_saas_hangfire;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", "WorkerCount": 5, "DashboardEnabled": false, "DashboardPath": "/hangfire" diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json index f015bba..d858ad3 100644 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json +++ b/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json @@ -134,7 +134,7 @@ "PrefetchCount": 20 }, "Scheduler": { - "ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_saas_scheduler;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_saas_hangfire;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", "WorkerCount": 5, "DashboardEnabled": false, "DashboardPath": "/hangfire" diff --git a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs new file mode 100644 index 0000000..694dfca --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Analytics.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Analytics.Entities; + +/// +/// 指标告警规则。 +/// +public sealed class MetricAlertRule : MultiTenantEntityBase +{ + /// + /// 关联指标。 + /// + public Guid MetricDefinitionId { get; set; } + + /// + /// 触发条件 JSON。 + /// + public string ConditionJson { get; set; } = string.Empty; + + /// + /// 告警级别。 + /// + public MetricAlertSeverity Severity { get; set; } = MetricAlertSeverity.Warning; + + /// + /// 通知渠道。 + /// + public string NotificationChannels { get; set; } = "email"; + + /// + /// 是否启用。 + /// + public bool Enabled { get; set; } = true; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricDefinition.cs b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricDefinition.cs new file mode 100644 index 0000000..1180216 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricDefinition.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Analytics.Entities; + +/// +/// 指标定义,描述可观测的数据点。 +/// +public sealed class MetricDefinition : MultiTenantEntityBase +{ + /// + /// 指标编码。 + /// + public string Code { get; set; } = string.Empty; + + /// + /// 指标名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 说明。 + /// + public string? Description { get; set; } + + /// + /// 维度描述 JSON。 + /// + public string? DimensionsJson { get; set; } + + /// + /// 默认聚合方式。 + /// + public string DefaultAggregation { get; set; } = "sum"; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs new file mode 100644 index 0000000..cb0e726 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Analytics.Entities; + +/// +/// 指标快照,用于大盘展示。 +/// +public sealed class MetricSnapshot : MultiTenantEntityBase +{ + /// + /// 指标定义 ID。 + /// + public Guid MetricDefinitionId { get; set; } + + /// + /// 维度键(JSON)。 + /// + public string DimensionKey { get; set; } = string.Empty; + + /// + /// 统计时间窗口开始。 + /// + public DateTime WindowStart { get; set; } + + /// + /// 统计时间窗口结束。 + /// + public DateTime WindowEnd { get; set; } + + /// + /// 数值。 + /// + public decimal Value { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Analytics/Enums/MetricAlertSeverity.cs b/src/Domain/TakeoutSaaS.Domain/Analytics/Enums/MetricAlertSeverity.cs new file mode 100644 index 0000000..a1e79eb --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Analytics/Enums/MetricAlertSeverity.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Analytics.Enums; + +/// +/// 指标告警严重程度。 +/// +public enum MetricAlertSeverity +{ + /// + /// 信息提示。 + /// + Info = 0, + + /// + /// 告警。 + /// + Warning = 1, + + /// + /// 严重。 + /// + Critical = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs new file mode 100644 index 0000000..c327f0b --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.Coupons.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Coupons.Entities; + +/// +/// 用户领取的券。 +/// +public sealed class Coupon : MultiTenantEntityBase +{ + /// + /// 模板标识。 + /// + public Guid CouponTemplateId { get; set; } + + /// + /// 券码或序列号。 + /// + public string Code { get; set; } = string.Empty; + + /// + /// 归属用户。 + /// + public Guid UserId { get; set; } + + /// + /// 订单 ID(已使用时记录)。 + /// + public Guid? OrderId { get; set; } + + /// + /// 状态。 + /// + public CouponStatus Status { get; set; } = CouponStatus.Available; + + /// + /// 发放时间。 + /// + public DateTime IssuedAt { get; set; } = DateTime.UtcNow; + + /// + /// 使用时间。 + /// + public DateTime? UsedAt { get; set; } + + /// + /// 到期时间。 + /// + public DateTime ExpireAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/CouponTemplate.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/CouponTemplate.cs new file mode 100644 index 0000000..8f795a6 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/CouponTemplate.cs @@ -0,0 +1,90 @@ +using TakeoutSaaS.Domain.Coupons.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Coupons.Entities; + +/// +/// 优惠券模板。 +/// +public sealed class CouponTemplate : MultiTenantEntityBase +{ + /// + /// 模板名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 券类型。 + /// + public CouponType CouponType { get; set; } = CouponType.AmountOff; + + /// + /// 面值或折扣额度。 + /// + public decimal Value { get; set; } + + /// + /// 折扣上限(针对折扣券)。 + /// + public decimal? DiscountCap { get; set; } + + /// + /// 最低消费门槛。 + /// + public decimal? MinimumSpend { get; set; } + + /// + /// 可用开始时间。 + /// + public DateTime? ValidFrom { get; set; } + + /// + /// 可用结束时间。 + /// + public DateTime? ValidTo { get; set; } + + /// + /// 有效天数(相对发放时间)。 + /// + public int? RelativeValidDays { get; set; } + + /// + /// 总发放数量上限。 + /// + public int? TotalQuantity { get; set; } + + /// + /// 已领取数量。 + /// + public int ClaimedQuantity { get; set; } + + /// + /// 适用门店 ID 集合(JSON)。 + /// + public string? StoreScopeJson { get; set; } + + /// + /// 适用品类或商品范围(JSON)。 + /// + public string? ProductScopeJson { get; set; } + + /// + /// 发放渠道(JSON)。 + /// + public string? ChannelsJson { get; set; } + + /// + /// 是否允许叠加其他优惠。 + /// + public bool AllowStack { get; set; } + + /// + /// 状态。 + /// + public CouponTemplateStatus Status { get; set; } = CouponTemplateStatus.Draft; + + /// + /// 备注。 + /// + public string? Description { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/PromotionCampaign.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/PromotionCampaign.cs new file mode 100644 index 0000000..8cab151 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/PromotionCampaign.cs @@ -0,0 +1,55 @@ +using TakeoutSaaS.Domain.Coupons.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Coupons.Entities; + +/// +/// 营销活动配置。 +/// +public sealed class PromotionCampaign : MultiTenantEntityBase +{ + /// + /// 活动名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 活动类型。 + /// + public PromotionType PromotionType { get; set; } = PromotionType.Coupon; + + /// + /// 活动状态。 + /// + public PromotionStatus Status { get; set; } = PromotionStatus.Draft; + + /// + /// 开始时间。 + /// + public DateTime StartAt { get; set; } + + /// + /// 结束时间。 + /// + public DateTime EndAt { get; set; } + + /// + /// 预算金额。 + /// + public decimal? Budget { get; set; } + + /// + /// 活动规则 JSON。 + /// + public string RulesJson { get; set; } = string.Empty; + + /// + /// 目标人群描述。 + /// + public string? AudienceDescription { get; set; } + + /// + /// 营销素材(如 banner)。 + /// + public string? BannerUrl { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponStatus.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponStatus.cs new file mode 100644 index 0000000..7897089 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponStatus.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Coupons.Enums; + +/// +/// 券使用状态。 +/// +public enum CouponStatus +{ + /// + /// 可使用。 + /// + Available = 0, + + /// + /// 已锁定。 + /// + Locked = 1, + + /// + /// 已使用。 + /// + Redeemed = 2, + + /// + /// 已过期。 + /// + Expired = 3, + + /// + /// 已作废。 + /// + Voided = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponTemplateStatus.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponTemplateStatus.cs new file mode 100644 index 0000000..d5aefd5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponTemplateStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Coupons.Enums; + +/// +/// 券模板状态。 +/// +public enum CouponTemplateStatus +{ + /// + /// 草稿状态。 + /// + Draft = 0, + + /// + /// 已上线可发放。 + /// + Active = 1, + + /// + /// 已下架。 + /// + Archived = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponType.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponType.cs new file mode 100644 index 0000000..8206bd2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/CouponType.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Coupons.Enums; + +/// +/// 券类型。 +/// +public enum CouponType +{ + /// + /// 满减券。 + /// + AmountOff = 0, + + /// + /// 折扣券。 + /// + Percentage = 1, + + /// + /// 现金券/无门槛券。 + /// + Cash = 2, + + /// + /// 免配送费券。 + /// + DeliveryFee = 3, + + /// + /// 礼品/兑换券。 + /// + Gift = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionStatus.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionStatus.cs new file mode 100644 index 0000000..2b12ee1 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Coupons.Enums; + +/// +/// 营销活动状态。 +/// +public enum PromotionStatus +{ + /// + /// 草稿。 + /// + Draft = 0, + + /// + /// 进行中。 + /// + Active = 1, + + /// + /// 已结束。 + /// + Completed = 2, + + /// + /// 暂停。 + /// + Paused = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionType.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionType.cs new file mode 100644 index 0000000..e30893d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Enums/PromotionType.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Coupons.Enums; + +/// +/// 营销活动类型。 +/// +public enum PromotionType +{ + /// + /// 优惠券活动。 + /// + Coupon = 0, + + /// + /// 秒杀/限时购。 + /// + FlashSale = 1, + + /// + /// 满减活动。 + /// + FullReduction = 2, + + /// + /// 抽奖活动。 + /// + Lottery = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs new file mode 100644 index 0000000..56799b5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.CustomerService.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.CustomerService.Entities; + +/// +/// 会话消息。 +/// +public sealed class ChatMessage : MultiTenantEntityBase +{ + /// + /// 会话标识。 + /// + public Guid ChatSessionId { get; set; } + + /// + /// 发送方类型。 + /// + public MessageSenderType SenderType { get; set; } = MessageSenderType.Customer; + + /// + /// 发送方用户 ID。 + /// + public Guid? SenderUserId { get; set; } + + /// + /// 消息内容。 + /// + public string Content { get; set; } = string.Empty; + + /// + /// 消息类型(文字/图片/语音等)。 + /// + public string ContentType { get; set; } = "text/plain"; + + /// + /// 是否已读。 + /// + public bool IsRead { get; set; } + + /// + /// 读取时间。 + /// + public DateTime? ReadAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs new file mode 100644 index 0000000..d2528ea --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.CustomerService.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.CustomerService.Entities; + +/// +/// 客服会话。 +/// +public sealed class ChatSession : MultiTenantEntityBase +{ + /// + /// 会话编号。 + /// + public string SessionCode { get; set; } = string.Empty; + + /// + /// 顾客用户 ID。 + /// + public Guid CustomerUserId { get; set; } + + /// + /// 当前客服员工 ID。 + /// + public Guid? AgentUserId { get; set; } + + /// + /// 所属门店(可空为平台)。 + /// + public Guid? StoreId { get; set; } + + /// + /// 会话状态。 + /// + public ChatSessionStatus Status { get; set; } = ChatSessionStatus.Waiting; + + /// + /// 是否机器人接待中。 + /// + public bool IsBotActive { get; set; } + + /// + /// 开始时间。 + /// + public DateTime StartedAt { get; set; } = DateTime.UtcNow; + + /// + /// 结束时间。 + /// + public DateTime? EndedAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs new file mode 100644 index 0000000..931727d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs @@ -0,0 +1,55 @@ +using TakeoutSaaS.Domain.CustomerService.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.CustomerService.Entities; + +/// +/// 客服工单。 +/// +public sealed class SupportTicket : MultiTenantEntityBase +{ + /// + /// 工单编号。 + /// + public string TicketNo { get; set; } = string.Empty; + + /// + /// 客户用户 ID。 + /// + public Guid CustomerUserId { get; set; } + + /// + /// 关联订单(如有)。 + /// + public Guid? OrderId { get; set; } + + /// + /// 工单主题。 + /// + public string Subject { get; set; } = string.Empty; + + /// + /// 工单详情。 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 优先级。 + /// + public TicketPriority Priority { get; set; } = TicketPriority.Normal; + + /// + /// 状态。 + /// + public TicketStatus Status { get; set; } = TicketStatus.Open; + + /// + /// 指派的客服。 + /// + public Guid? AssignedAgentId { get; set; } + + /// + /// 关闭时间。 + /// + public DateTime? ClosedAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs new file mode 100644 index 0000000..f6abba6 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.CustomerService.Entities; + +/// +/// 工单评论/流转记录。 +/// +public sealed class TicketComment : MultiTenantEntityBase +{ + /// + /// 工单标识。 + /// + public Guid SupportTicketId { get; set; } + + /// + /// 评论人 ID。 + /// + public Guid? AuthorUserId { get; set; } + + /// + /// 评论内容。 + /// + public string Content { get; set; } = string.Empty; + + /// + /// 是否内部备注。 + /// + public bool IsInternal { get; set; } + + /// + /// 附件 JSON。 + /// + public string? AttachmentsJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/ChatSessionStatus.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/ChatSessionStatus.cs new file mode 100644 index 0000000..e774652 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/ChatSessionStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.CustomerService.Enums; + +/// +/// 客服会话状态。 +/// +public enum ChatSessionStatus +{ + /// + /// 等待客服接入。 + /// + Waiting = 0, + + /// + /// 聊天进行中。 + /// + Active = 1, + + /// + /// 已转人工排队。 + /// + Queueing = 2, + + /// + /// 已结束。 + /// + Closed = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/MessageSenderType.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/MessageSenderType.cs new file mode 100644 index 0000000..826c923 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/MessageSenderType.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.CustomerService.Enums; + +/// +/// 消息发送方类型。 +/// +public enum MessageSenderType +{ + /// + /// 顾客。 + /// + Customer = 0, + + /// + /// 客服人员。 + /// + Agent = 1, + + /// + /// 机器人。 + /// + Bot = 2, + + /// + /// 系统通知。 + /// + System = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketPriority.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketPriority.cs new file mode 100644 index 0000000..e875355 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketPriority.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.CustomerService.Enums; + +/// +/// 工单优先级。 +/// +public enum TicketPriority +{ + /// + /// 普通。 + /// + Normal = 0, + + /// + /// 高。 + /// + High = 1, + + /// + /// 紧急。 + /// + Urgent = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketStatus.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketStatus.cs new file mode 100644 index 0000000..40d6311 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Enums/TicketStatus.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.CustomerService.Enums; + +/// +/// 工单状态。 +/// +public enum TicketStatus +{ + /// + /// 新建未处理。 + /// + Open = 0, + + /// + /// 处理中。 + /// + InProgress = 1, + + /// + /// 等待客户反馈。 + /// + PendingCustomer = 2, + + /// + /// 已解决。 + /// + Resolved = 3, + + /// + /// 已关闭。 + /// + Closed = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs new file mode 100644 index 0000000..771f564 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Deliveries.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Deliveries.Entities; + +/// +/// 配送状态事件流水。 +/// +public sealed class DeliveryEvent : MultiTenantEntityBase +{ + /// + /// 配送单标识。 + /// + public Guid DeliveryOrderId { get; set; } + + /// + /// 事件类型。 + /// + public DeliveryEventType EventType { get; set; } = DeliveryEventType.Updated; + + /// + /// 事件描述。 + /// + public string Message { get; set; } = string.Empty; + + /// + /// 原始数据 JSON。 + /// + public string? Payload { get; set; } + + /// + /// 发生时间。 + /// + public DateTime OccurredAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs new file mode 100644 index 0000000..31dbfc1 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs @@ -0,0 +1,62 @@ +using TakeoutSaaS.Domain.Deliveries.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Deliveries.Entities; + +/// +/// 配送单。 +/// +public sealed class DeliveryOrder : MultiTenantEntityBase +{ + public Guid OrderId { get; set; } + + /// + /// 配送服务商。 + /// + public DeliveryProvider Provider { get; set; } = DeliveryProvider.InHouse; + + /// + /// 第三方配送单号。 + /// + public string? ProviderOrderId { get; set; } + + /// + /// 状态。 + /// + public DeliveryStatus Status { get; set; } = DeliveryStatus.Pending; + + /// + /// 配送费。 + /// + public decimal? DeliveryFee { get; set; } + + /// + /// 骑手姓名。 + /// + public string? CourierName { get; set; } + + /// + /// 骑手电话。 + /// + public string? CourierPhone { get; set; } + + /// + /// 下发时间。 + /// + public DateTime? DispatchedAt { get; set; } + + /// + /// 取餐时间。 + /// + public DateTime? PickedUpAt { get; set; } + + /// + /// 完成时间。 + /// + public DateTime? DeliveredAt { get; set; } + + /// + /// 异常原因。 + /// + public string? FailureReason { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryEventType.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryEventType.cs new file mode 100644 index 0000000..ce1914d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryEventType.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Deliveries.Enums; + +/// +/// 配送事件类型。 +/// +public enum DeliveryEventType +{ + /// + /// 状态更新。 + /// + Updated = 0, + + /// + /// 渠道回调。 + /// + Callback = 1, + + /// + /// 加价或异常通知。 + /// + Exception = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryProvider.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryProvider.cs new file mode 100644 index 0000000..70cff85 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryProvider.cs @@ -0,0 +1,14 @@ +namespace TakeoutSaaS.Domain.Deliveries.Enums; + +/// +/// 配送服务商类型。 +/// +public enum DeliveryProvider +{ + InHouse = 0, + Dada = 1, + FlashEx = 2, + Meituan = 3, + Eleme = 4, + Shunfeng = 5 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryStatus.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryStatus.cs new file mode 100644 index 0000000..7f6aaef --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Enums/DeliveryStatus.cs @@ -0,0 +1,15 @@ +namespace TakeoutSaaS.Domain.Deliveries.Enums; + +/// +/// 配送状态。 +/// +public enum DeliveryStatus +{ + Pending = 0, + Accepted = 1, + PickingUp = 2, + Delivering = 3, + Completed = 4, + Cancelled = 5, + Failed = 6 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs new file mode 100644 index 0000000..0b1bb24 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Distribution.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Distribution.Entities; + +/// +/// 分销订单记录。 +/// +public sealed class AffiliateOrder : MultiTenantEntityBase +{ + /// + /// 推广人标识。 + /// + public Guid AffiliatePartnerId { get; set; } + + /// + /// 关联订单。 + /// + public Guid OrderId { get; set; } + + /// + /// 用户 ID。 + /// + public Guid BuyerUserId { get; set; } + + /// + /// 订单金额。 + /// + public decimal OrderAmount { get; set; } + + /// + /// 预计佣金。 + /// + public decimal EstimatedCommission { get; set; } + + /// + /// 当前状态。 + /// + public AffiliateOrderStatus Status { get; set; } = AffiliateOrderStatus.Pending; + + /// + /// 结算完成时间。 + /// + public DateTime? SettledAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs new file mode 100644 index 0000000..d9ceaa1 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Distribution.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Distribution.Entities; + +/// +/// 分销/推广合作伙伴。 +/// +public sealed class AffiliatePartner : MultiTenantEntityBase +{ + /// + /// 用户 ID(如绑定平台账号)。 + /// + public Guid? UserId { get; set; } + + /// + /// 昵称或渠道名称。 + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// 联系电话。 + /// + public string? Phone { get; set; } + + /// + /// 渠道类型。 + /// + public AffiliateChannelType ChannelType { get; set; } = AffiliateChannelType.Personal; + + /// + /// 分成比例(0-1)。 + /// + public decimal CommissionRate { get; set; } + + /// + /// 当前状态。 + /// + public AffiliateStatus Status { get; set; } = AffiliateStatus.Pending; + + /// + /// 审核备注。 + /// + public string? Remarks { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs new file mode 100644 index 0000000..774c4e4 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs @@ -0,0 +1,40 @@ +using TakeoutSaaS.Domain.Distribution.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Distribution.Entities; + +/// +/// 佣金结算记录。 +/// +public sealed class AffiliatePayout : MultiTenantEntityBase +{ + /// + /// 合作伙伴标识。 + /// + public Guid AffiliatePartnerId { get; set; } + + /// + /// 结算周期描述。 + /// + public string Period { get; set; } = string.Empty; + + /// + /// 结算金额。 + /// + public decimal Amount { get; set; } + + /// + /// 状态。 + /// + public PayoutStatus Status { get; set; } = PayoutStatus.Pending; + + /// + /// 打款时间。 + /// + public DateTime? PaidAt { get; set; } + + /// + /// 备注。 + /// + public string? Remarks { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateChannelType.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateChannelType.cs new file mode 100644 index 0000000..963bd98 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateChannelType.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Distribution.Enums; + +/// +/// 推广渠道类型。 +/// +public enum AffiliateChannelType +{ + /// + /// 个人。 + /// + Personal = 0, + + /// + /// 自媒体。 + /// + Media = 1, + + /// + /// 门店地推。 + /// + Offline = 2, + + /// + /// 第三方联盟。 + /// + Alliance = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateOrderStatus.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateOrderStatus.cs new file mode 100644 index 0000000..a28f953 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateOrderStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Distribution.Enums; + +/// +/// 分销订单状态。 +/// +public enum AffiliateOrderStatus +{ + /// + /// 待成交通知。 + /// + Pending = 0, + + /// + /// 已完成待结算。 + /// + AwaitingPayout = 1, + + /// + /// 已结算。 + /// + Settled = 2, + + /// + /// 因退款失效。 + /// + Invalidated = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateStatus.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateStatus.cs new file mode 100644 index 0000000..7e25a50 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/AffiliateStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Distribution.Enums; + +/// +/// 合作伙伴状态。 +/// +public enum AffiliateStatus +{ + /// + /// 待审核。 + /// + Pending = 0, + + /// + /// 已激活。 + /// + Active = 1, + + /// + /// 已冻结。 + /// + Suspended = 2, + + /// + /// 已退出。 + /// + Closed = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/PayoutStatus.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/PayoutStatus.cs new file mode 100644 index 0000000..0f96e3d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Enums/PayoutStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Distribution.Enums; + +/// +/// 佣金结算状态。 +/// +public enum PayoutStatus +{ + /// + /// 待打款。 + /// + Pending = 0, + + /// + /// 已打款。 + /// + Paid = 1, + + /// + /// 已驳回。 + /// + Rejected = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInCampaign.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInCampaign.cs new file mode 100644 index 0000000..1e279d5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInCampaign.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Engagement.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Engagement.Entities; + +/// +/// 签到活动配置。 +/// +public sealed class CheckInCampaign : MultiTenantEntityBase +{ + /// + /// 活动名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 活动描述。 + /// + public string? Description { get; set; } + + /// + /// 开始日期。 + /// + public DateTime StartDate { get; set; } + + /// + /// 结束日期。 + /// + public DateTime EndDate { get; set; } + + /// + /// 支持补签次数。 + /// + public int AllowMakeupCount { get; set; } + + /// + /// 连签奖励 JSON。 + /// + public string RewardsJson { get; set; } = string.Empty; + + /// + /// 状态。 + /// + public CheckInCampaignStatus Status { get; set; } = CheckInCampaignStatus.Draft; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs new file mode 100644 index 0000000..9f41172 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Engagement.Entities; + +/// +/// 用户签到记录。 +/// +public sealed class CheckInRecord : MultiTenantEntityBase +{ + /// + /// 活动标识。 + /// + public Guid CheckInCampaignId { get; set; } + + /// + /// 用户标识。 + /// + public Guid UserId { get; set; } + + /// + /// 签到日期(本地)。 + /// + public DateTime CheckInDate { get; set; } + + /// + /// 是否补签。 + /// + public bool IsMakeup { get; set; } + + /// + /// 获得奖励 JSON。 + /// + public string RewardJson { get; set; } = string.Empty; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs new file mode 100644 index 0000000..3888bfd --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Engagement.Entities; + +/// +/// 社区评论。 +/// +public sealed class CommunityComment : MultiTenantEntityBase +{ + /// + /// 动态标识。 + /// + public Guid PostId { get; set; } + + /// + /// 评论人。 + /// + public Guid AuthorUserId { get; set; } + + /// + /// 评论内容。 + /// + public string Content { get; set; } = string.Empty; + + /// + /// 父级评论 ID。 + /// + public Guid? ParentId { get; set; } + + /// + /// 状态。 + /// + public bool IsDeleted { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs new file mode 100644 index 0000000..73c7e21 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Engagement.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Engagement.Entities; + +/// +/// 社区动态。 +/// +public sealed class CommunityPost : MultiTenantEntityBase +{ + /// + /// 作者用户 ID。 + /// + public Guid AuthorUserId { get; set; } + + /// + /// 标题。 + /// + public string? Title { get; set; } + + /// + /// 内容。 + /// + public string Content { get; set; } = string.Empty; + + /// + /// 媒体资源 JSON。 + /// + public string? MediaJson { get; set; } + + /// + /// 状态。 + /// + public PostStatus Status { get; set; } = PostStatus.PendingReview; + + /// + /// 点赞数。 + /// + public int LikeCount { get; set; } + + /// + /// 评论数。 + /// + public int CommentCount { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs new file mode 100644 index 0000000..b7d5c19 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs @@ -0,0 +1,30 @@ +using TakeoutSaaS.Domain.Engagement.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Engagement.Entities; + +/// +/// 社区互动反馈。 +/// +public sealed class CommunityReaction : MultiTenantEntityBase +{ + /// + /// 动态 ID。 + /// + public Guid PostId { get; set; } + + /// + /// 用户 ID。 + /// + public Guid UserId { get; set; } + + /// + /// 反应类型。 + /// + public ReactionType ReactionType { get; set; } = ReactionType.Like; + + /// + /// 时间戳。 + /// + public DateTime ReactedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/CheckInCampaignStatus.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/CheckInCampaignStatus.cs new file mode 100644 index 0000000..57a3f0c --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/CheckInCampaignStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Engagement.Enums; + +/// +/// 签到活动状态。 +/// +public enum CheckInCampaignStatus +{ + /// + /// 草稿。 + /// + Draft = 0, + + /// + /// 进行中。 + /// + Active = 1, + + /// + /// 已结束。 + /// + Completed = 2, + + /// + /// 已停用。 + /// + Disabled = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/PostStatus.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/PostStatus.cs new file mode 100644 index 0000000..9ce168b --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/PostStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Engagement.Enums; + +/// +/// 社区动态状态。 +/// +public enum PostStatus +{ + /// + /// 待审核。 + /// + PendingReview = 0, + + /// + /// 已发布。 + /// + Published = 1, + + /// + /// 已屏蔽。 + /// + Blocked = 2, + + /// + /// 已删除。 + /// + Deleted = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/ReactionType.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/ReactionType.cs new file mode 100644 index 0000000..7328bf7 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Enums/ReactionType.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Engagement.Enums; + +/// +/// 互动类型。 +/// +public enum ReactionType +{ + /// + /// 点赞。 + /// + Like = 0, + + /// + /// 收藏。 + /// + Favorite = 1, + + /// + /// 点踩。 + /// + Dislike = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs new file mode 100644 index 0000000..e5412de --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs @@ -0,0 +1,70 @@ +using TakeoutSaaS.Domain.GroupBuying.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.GroupBuying.Entities; + +/// +/// 拼单活动。 +/// +public sealed class GroupOrder : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 关联商品或套餐。 + /// + public Guid ProductId { get; set; } + + /// + /// 拼单编号。 + /// + public string GroupOrderNo { get; set; } = string.Empty; + + /// + /// 团长用户 ID。 + /// + public Guid LeaderUserId { get; set; } + + /// + /// 成团需要的人数。 + /// + public int TargetCount { get; set; } + + /// + /// 当前已参与人数。 + /// + public int CurrentCount { get; set; } + + /// + /// 拼团价格。 + /// + public decimal GroupPrice { get; set; } + + /// + /// 开始时间。 + /// + public DateTime StartAt { get; set; } + + /// + /// 结束时间。 + /// + public DateTime EndAt { get; set; } + + /// + /// 拼团状态。 + /// + public GroupOrderStatus Status { get; set; } = GroupOrderStatus.Open; + + /// + /// 成团时间。 + /// + public DateTime? SucceededAt { get; set; } + + /// + /// 取消时间。 + /// + public DateTime? CancelledAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs new file mode 100644 index 0000000..61e169a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.GroupBuying.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.GroupBuying.Entities; + +/// +/// 拼单参与者。 +/// +public sealed class GroupParticipant : MultiTenantEntityBase +{ + /// + /// 拼单活动标识。 + /// + public Guid GroupOrderId { get; set; } + + /// + /// 对应订单标识。 + /// + public Guid OrderId { get; set; } + + /// + /// 用户标识。 + /// + public Guid UserId { get; set; } + + /// + /// 参与状态。 + /// + public GroupParticipantStatus Status { get; set; } = GroupParticipantStatus.Joined; + + /// + /// 参与时间。 + /// + public DateTime JoinedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupOrderStatus.cs b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupOrderStatus.cs new file mode 100644 index 0000000..b5969fd --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupOrderStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.GroupBuying.Enums; + +/// +/// 拼单状态。 +/// +public enum GroupOrderStatus +{ + /// + /// 开放中。 + /// + Open = 0, + + /// + /// 已成团。 + /// + Succeeded = 1, + + /// + /// 已取消。 + /// + Cancelled = 2, + + /// + /// 超时失败。 + /// + Failed = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupParticipantStatus.cs b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupParticipantStatus.cs new file mode 100644 index 0000000..7b5f807 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Enums/GroupParticipantStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.GroupBuying.Enums; + +/// +/// 拼单参与者状态。 +/// +public enum GroupParticipantStatus +{ + /// + /// 已参团。 + /// + Joined = 0, + + /// + /// 因退款或取消退出。 + /// + Exited = 1, + + /// + /// 团失败待退款。 + /// + PendingRefund = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs new file mode 100644 index 0000000..36b7d59 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs @@ -0,0 +1,40 @@ +using TakeoutSaaS.Domain.Inventory.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Inventory.Entities; + +/// +/// 库存调整记录。 +/// +public sealed class InventoryAdjustment : MultiTenantEntityBase +{ + /// + /// 对应的库存记录标识。 + /// + public Guid InventoryItemId { get; set; } + + /// + /// 调整类型。 + /// + public InventoryAdjustmentType AdjustmentType { get; set; } = InventoryAdjustmentType.Manual; + + /// + /// 调整数量,正数增加,负数减少。 + /// + public int Quantity { get; set; } + + /// + /// 原因说明。 + /// + public string? Reason { get; set; } + + /// + /// 操作人标识。 + /// + public Guid? OperatorId { get; set; } + + /// + /// 发生时间。 + /// + public DateTime OccurredAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs new file mode 100644 index 0000000..8a764b3 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs @@ -0,0 +1,44 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Inventory.Entities; + +/// +/// SKU 批次信息。 +/// +public sealed class InventoryBatch : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// SKU 标识。 + /// + public Guid ProductSkuId { get; set; } + + /// + /// 批次编号。 + /// + public string BatchNumber { get; set; } = string.Empty; + + /// + /// 生产日期。 + /// + public DateTime? ProductionDate { get; set; } + + /// + /// 过期日期。 + /// + public DateTime? ExpireDate { get; set; } + + /// + /// 入库数量。 + /// + public int Quantity { get; set; } + + /// + /// 剩余数量。 + /// + public int RemainingQuantity { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs new file mode 100644 index 0000000..6432253 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs @@ -0,0 +1,49 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Inventory.Entities; + +/// +/// SKU 在门店的库存信息。 +/// +public sealed class InventoryItem : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// SKU 标识。 + /// + public Guid ProductSkuId { get; set; } + + /// + /// 批次编号,可为空表示混批。 + /// + public string? BatchNumber { get; set; } + + /// + /// 可用库存。 + /// + public int QuantityOnHand { get; set; } + + /// + /// 已锁定库存(订单占用)。 + /// + public int QuantityReserved { get; set; } + + /// + /// 安全库存阈值。 + /// + public int? SafetyStock { get; set; } + + /// + /// 储位或仓位信息。 + /// + public string? Location { get; set; } + + /// + /// 过期日期。 + /// + public DateTime? ExpireDate { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Enums/InventoryAdjustmentType.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Enums/InventoryAdjustmentType.cs new file mode 100644 index 0000000..58e0913 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Enums/InventoryAdjustmentType.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Inventory.Enums; + +/// +/// 库存调整类型。 +/// +public enum InventoryAdjustmentType +{ + /// + /// 手动盘点调整。 + /// + Manual = 0, + + /// + /// 采购入库。 + /// + Purchase = 1, + + /// + /// 退货回库。 + /// + Return = 2, + + /// + /// 报损。 + /// + Damage = 3, + + /// + /// 过期销毁。 + /// + Expiration = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs new file mode 100644 index 0000000..b06f23f --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Membership.Entities; + +/// +/// 成长值变动日志。 +/// +public sealed class MemberGrowthLog : MultiTenantEntityBase +{ + /// + /// 会员标识。 + /// + public Guid MemberId { get; set; } + + /// + /// 变动数量。 + /// + public int ChangeValue { get; set; } + + /// + /// 当前成长值。 + /// + public int CurrentValue { get; set; } + + /// + /// 备注。 + /// + public string? Notes { get; set; } + + /// + /// 发生时间。 + /// + public DateTime OccurredAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs new file mode 100644 index 0000000..07c4fbe --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Membership.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Membership.Entities; + +/// +/// 积分变动流水。 +/// +public sealed class MemberPointLedger : MultiTenantEntityBase +{ + /// + /// 会员标识。 + /// + public Guid MemberId { get; set; } + + /// + /// 变动数量,可为负值。 + /// + public int ChangeAmount { get; set; } + + /// + /// 变动后余额。 + /// + public int BalanceAfterChange { get; set; } + + /// + /// 变动原因。 + /// + public PointChangeReason Reason { get; set; } = PointChangeReason.Purchase; + + /// + /// 来源 ID(订单、活动等)。 + /// + public Guid? SourceId { get; set; } + + /// + /// 发生时间。 + /// + public DateTime OccurredAt { get; set; } = DateTime.UtcNow; + + /// + /// 过期时间(如适用)。 + /// + public DateTime? ExpireAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs new file mode 100644 index 0000000..7378ccd --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs @@ -0,0 +1,60 @@ +using TakeoutSaaS.Domain.Membership.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Membership.Entities; + +/// +/// 会员档案。 +/// +public sealed class MemberProfile : MultiTenantEntityBase +{ + /// + /// 用户标识。 + /// + public Guid UserId { get; set; } + + /// + /// 手机号。 + /// + public string Mobile { get; set; } = string.Empty; + + /// + /// 昵称。 + /// + public string? Nickname { get; set; } + + /// + /// 头像。 + /// + public string? AvatarUrl { get; set; } + + /// + /// 当前会员等级 ID。 + /// + public Guid? MemberTierId { get; set; } + + /// + /// 会员状态。 + /// + public MemberStatus Status { get; set; } = MemberStatus.Active; + + /// + /// 会员积分余额。 + /// + public int PointsBalance { get; set; } + + /// + /// 成长值/经验值。 + /// + public int GrowthValue { get; set; } + + /// + /// 生日。 + /// + public DateTime? BirthDate { get; set; } + + /// + /// 注册时间。 + /// + public DateTime JoinedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberTier.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberTier.cs new file mode 100644 index 0000000..bd40724 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberTier.cs @@ -0,0 +1,29 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Membership.Entities; + +/// +/// 会员等级定义。 +/// +public sealed class MemberTier : MultiTenantEntityBase +{ + /// + /// 等级名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 所需成长值。 + /// + public int RequiredGrowth { get; set; } + + /// + /// 等级权益(JSON)。 + /// + public string BenefitsJson { get; set; } = string.Empty; + + /// + /// 排序值。 + /// + public int SortOrder { get; set; } = 100; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Enums/MemberStatus.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Enums/MemberStatus.cs new file mode 100644 index 0000000..5383e3a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Membership/Enums/MemberStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Membership.Enums; + +/// +/// 会员状态。 +/// +public enum MemberStatus +{ + /// + /// 正常。 + /// + Active = 0, + + /// + /// 已冻结。 + /// + Frozen = 1, + + /// + /// 已注销。 + /// + Cancelled = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Enums/PointChangeReason.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Enums/PointChangeReason.cs new file mode 100644 index 0000000..61579b2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Membership/Enums/PointChangeReason.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Membership.Enums; + +/// +/// 积分变动原因。 +/// +public enum PointChangeReason +{ + /// + /// 正常消费获得。 + /// + Purchase = 0, + + /// + /// 活动奖励。 + /// + Promotion = 1, + + /// + /// 签到或任务。 + /// + Task = 2, + + /// + /// 管理员调整。 + /// + Manual = 3, + + /// + /// 抵扣消费。 + /// + Redeem = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs new file mode 100644 index 0000000..d05ae41 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/Merchant.cs @@ -0,0 +1,120 @@ +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Merchants.Entities; + +/// +/// 商户主体信息,承载入驻和资质审核结果。 +/// +public sealed class Merchant : MultiTenantEntityBase +{ + /// + /// 品牌名称(对外展示)。 + /// + public string BrandName { get; set; } = string.Empty; + + /// + /// 品牌简称或别名。 + /// + public string? BrandAlias { get; set; } + + /// + /// 品牌 Logo。 + /// + public string? LogoUrl { get; set; } + + /// + /// 品牌所属品类,如火锅、咖啡等。 + /// + public string? Category { get; set; } + + /// + /// 营业执照号。 + /// + public string? BusinessLicenseNumber { get; set; } + + /// + /// 营业执照扫描件地址。 + /// + public string? BusinessLicenseImageUrl { get; set; } + + /// + /// 税号/统一社会信用代码。 + /// + public string? TaxNumber { get; set; } + + /// + /// 法人或负责人姓名。 + /// + public string? LegalPerson { get; set; } + + /// + /// 联系电话。 + /// + public string ContactPhone { get; set; } = string.Empty; + + /// + /// 联系邮箱。 + /// + public string? ContactEmail { get; set; } + + /// + /// 客服电话。 + /// + public string? ServicePhone { get; set; } + + /// + /// 客服邮箱。 + /// + public string? SupportEmail { get; set; } + + /// + /// 所在省份。 + /// + public string? Province { get; set; } + + /// + /// 所在城市。 + /// + public string? City { get; set; } + + /// + /// 所在区县。 + /// + public string? District { get; set; } + + /// + /// 详细地址。 + /// + public string? Address { get; set; } + + /// + /// 经度信息。 + /// + public double? Longitude { get; set; } + + /// + /// 纬度信息。 + /// + public double? Latitude { get; set; } + + /// + /// 入驻状态。 + /// + public MerchantStatus Status { get; set; } = MerchantStatus.Pending; + + /// + /// 审核备注或驳回原因。 + /// + public string? ReviewRemarks { get; set; } + + /// + /// 入驻时间。 + /// + public DateTime? JoinedAt { get; set; } + + /// + /// 最近一次审核时间。 + /// + public DateTime? LastReviewedAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs new file mode 100644 index 0000000..e8a21bb --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs @@ -0,0 +1,55 @@ +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Merchants.Entities; + +/// +/// 商户合同记录。 +/// +public sealed class MerchantContract : MultiTenantEntityBase +{ + /// + /// 所属商户标识。 + /// + public Guid MerchantId { get; set; } + + /// + /// 合同编号。 + /// + public string ContractNumber { get; set; } = string.Empty; + + /// + /// 合同状态。 + /// + public ContractStatus Status { get; set; } = ContractStatus.Draft; + + /// + /// 合同开始时间。 + /// + public DateTime StartDate { get; set; } + + /// + /// 合同结束时间。 + /// + public DateTime EndDate { get; set; } + + /// + /// 合同文件存储地址。 + /// + public string FileUrl { get; set; } = string.Empty; + + /// + /// 签署时间。 + /// + public DateTime? SignedAt { get; set; } + + /// + /// 终止时间。 + /// + public DateTime? TerminatedAt { get; set; } + + /// + /// 终止原因。 + /// + public string? TerminationReason { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs new file mode 100644 index 0000000..572f718 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Merchants.Entities; + +/// +/// 商户提交的资质或证照材料。 +/// +public sealed class MerchantDocument : MultiTenantEntityBase +{ + /// + /// 所属商户标识。 + /// + public Guid MerchantId { get; set; } + + /// + /// 证照类型。 + /// + public MerchantDocumentType DocumentType { get; set; } = MerchantDocumentType.BusinessLicense; + + /// + /// 审核状态。 + /// + public MerchantDocumentStatus Status { get; set; } = MerchantDocumentStatus.Pending; + + /// + /// 证照文件链接。 + /// + public string FileUrl { get; set; } = string.Empty; + + /// + /// 证照编号。 + /// + public string? DocumentNumber { get; set; } + + /// + /// 签发日期。 + /// + public DateTime? IssuedAt { get; set; } + + /// + /// 到期日期。 + /// + public DateTime? ExpiresAt { get; set; } + + /// + /// 审核备注或驳回原因。 + /// + public string? Remarks { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs new file mode 100644 index 0000000..273054a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs @@ -0,0 +1,55 @@ +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Merchants.Entities; + +/// +/// 商户员工账号,支持门店维度分配。 +/// +public sealed class MerchantStaff : MultiTenantEntityBase +{ + /// + /// 所属商户标识。 + /// + public Guid MerchantId { get; set; } + + /// + /// 可选的关联门店 ID。 + /// + public Guid? StoreId { get; set; } + + /// + /// 员工姓名。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 手机号。 + /// + public string Phone { get; set; } = string.Empty; + + /// + /// 邮箱地址。 + /// + public string? Email { get; set; } + + /// + /// 登录账号 ID(指向统一身份体系)。 + /// + public Guid? IdentityUserId { get; set; } + + /// + /// 员工角色类型。 + /// + public StaffRoleType RoleType { get; set; } = StaffRoleType.FrontDesk; + + /// + /// 员工状态。 + /// + public StaffStatus Status { get; set; } = StaffStatus.Active; + + /// + /// 自定义权限(JSON)。 + /// + public string? PermissionsJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/ContractStatus.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/ContractStatus.cs new file mode 100644 index 0000000..c38ebff --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/ContractStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Merchants.Enums; + +/// +/// 商户合同状态。 +/// +public enum ContractStatus +{ + /// + /// 草拟中。 + /// + Draft = 0, + + /// + /// 已生效。 + /// + Active = 1, + + /// + /// 已到期。 + /// + Expired = 2, + + /// + /// 已解除。 + /// + Terminated = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentStatus.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentStatus.cs new file mode 100644 index 0000000..9d1bf46 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Merchants.Enums; + +/// +/// 证照审核状态。 +/// +public enum MerchantDocumentStatus +{ + /// + /// 等待审核。 + /// + Pending = 0, + + /// + /// 审核通过。 + /// + Approved = 1, + + /// + /// 审核驳回。 + /// + Rejected = 2, + + /// + /// 已过期待更新。 + /// + Expired = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentType.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentType.cs new file mode 100644 index 0000000..0dffa2d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantDocumentType.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Merchants.Enums; + +/// +/// 商户证照类型。 +/// +public enum MerchantDocumentType +{ + /// + /// 营业执照。 + /// + BusinessLicense = 0, + + /// + /// 餐饮服务许可证。 + /// + CateringPermit = 1, + + /// + /// 税务登记证。 + /// + TaxCertificate = 2, + + /// + /// 其他补充资质。 + /// + Other = 99 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantStatus.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantStatus.cs new file mode 100644 index 0000000..6ed8097 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/MerchantStatus.cs @@ -0,0 +1,12 @@ +namespace TakeoutSaaS.Domain.Merchants.Enums; + +/// +/// 商户入驻状态。 +/// +public enum MerchantStatus +{ + Pending = 0, + Approved = 1, + Rejected = 2, + Frozen = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffRoleType.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffRoleType.cs new file mode 100644 index 0000000..c5b3d33 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffRoleType.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Merchants.Enums; + +/// +/// 商户员工角色。 +/// +public enum StaffRoleType +{ + /// + /// 管理员。 + /// + Admin = 0, + + /// + /// 前台收银。 + /// + FrontDesk = 1, + + /// + /// 后厨制作。 + /// + Kitchen = 2, + + /// + /// 配送骑手。 + /// + Courier = 3, + + /// + /// 运营人员。 + /// + Operator = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffStatus.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffStatus.cs new file mode 100644 index 0000000..20ab741 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Enums/StaffStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Merchants.Enums; + +/// +/// 员工账号状态。 +/// +public enum StaffStatus +{ + /// + /// 正常在职。 + /// + Active = 0, + + /// + /// 停用。 + /// + Disabled = 1, + + /// + /// 已离职。 + /// + Resigned = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs new file mode 100644 index 0000000..74d9e14 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs @@ -0,0 +1,39 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Navigation.Entities; + +/// +/// 地图 POI 信息,用于门店定位和推荐。 +/// +public sealed class MapLocation : MultiTenantEntityBase +{ + /// + /// 关联门店 ID,可空表示独立 POI。 + /// + public Guid? StoreId { get; set; } + + /// + /// 名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 地址。 + /// + public string Address { get; set; } = string.Empty; + + /// + /// 经度。 + /// + public double Longitude { get; set; } + + /// + /// 纬度。 + /// + public double Latitude { get; set; } + + /// + /// 打车/导航落点描述。 + /// + public string? Landmark { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs new file mode 100644 index 0000000..284f1a5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Navigation.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Navigation.Entities; + +/// +/// 用户发起的导航请求日志。 +/// +public sealed class NavigationRequest : MultiTenantEntityBase +{ + /// + /// 用户 ID。 + /// + public Guid UserId { get; set; } + + /// + /// 门店 ID。 + /// + public Guid StoreId { get; set; } + + /// + /// 来源通道(小程序、H5 等)。 + /// + public NavigationChannel Channel { get; set; } = NavigationChannel.MiniProgram; + + /// + /// 跳转的地图应用。 + /// + public NavigationTargetApp TargetApp { get; set; } = NavigationTargetApp.WechatMap; + + /// + /// 请求时间。 + /// + public DateTime RequestedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationChannel.cs b/src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationChannel.cs new file mode 100644 index 0000000..ed60c26 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationChannel.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Navigation.Enums; + +/// +/// 导航请求来源渠道。 +/// +public enum NavigationChannel +{ + /// + /// 小程序。 + /// + MiniProgram = 0, + + /// + /// H5/公众号。 + /// + Web = 1, + + /// + /// App。 + /// + MobileApp = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationTargetApp.cs b/src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationTargetApp.cs new file mode 100644 index 0000000..7e9d834 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Navigation/Enums/NavigationTargetApp.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Navigation.Enums; + +/// +/// 导航目标应用。 +/// +public enum NavigationTargetApp +{ + /// + /// 微信地图。 + /// + WechatMap = 0, + + /// + /// 腾讯地图。 + /// + Tencent = 1, + + /// + /// 高德。 + /// + Amap = 2, + + /// + /// 百度地图。 + /// + Baidu = 3, + + /// + /// Apple Map。 + /// + Apple = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs new file mode 100644 index 0000000..d19d659 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs @@ -0,0 +1,55 @@ +using TakeoutSaaS.Domain.Ordering.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Ordering.Entities; + +/// +/// 购物车条目。 +/// +public sealed class CartItem : MultiTenantEntityBase +{ + /// + /// 所属购物车标识。 + /// + public Guid ShoppingCartId { get; set; } + + /// + /// 商品或 SKU 标识。 + /// + public Guid ProductId { get; set; } + + /// + /// SKU 标识。 + /// + public Guid? ProductSkuId { get; set; } + + /// + /// 商品名称快照。 + /// + public string ProductName { get; set; } = string.Empty; + + /// + /// 单价快照。 + /// + public decimal UnitPrice { get; set; } + + /// + /// 数量。 + /// + public int Quantity { get; set; } + + /// + /// 自定义备注(口味要求)。 + /// + public string? Remark { get; set; } + + /// + /// 状态。 + /// + public CartItemStatus Status { get; set; } = CartItemStatus.Normal; + + /// + /// 扩展 JSON(规格、加料选项等)。 + /// + public string? AttributesJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs new file mode 100644 index 0000000..eb99a0d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs @@ -0,0 +1,29 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Ordering.Entities; + +/// +/// 购物车条目的加料/附加项。 +/// +public sealed class CartItemAddon : MultiTenantEntityBase +{ + /// + /// 所属购物车条目。 + /// + public Guid CartItemId { get; set; } + + /// + /// 选项名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 附加价格。 + /// + public decimal ExtraPrice { get; set; } + + /// + /// 选项 ID(可对应 ProductAddonOption)。 + /// + public Guid? OptionId { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs new file mode 100644 index 0000000..a3beb7a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs @@ -0,0 +1,40 @@ +using TakeoutSaaS.Domain.Ordering.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Ordering.Entities; + +/// +/// 结账会话,记录校验上下文。 +/// +public sealed class CheckoutSession : MultiTenantEntityBase +{ + /// + /// 用户标识。 + /// + public Guid UserId { get; set; } + + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 会话 Token。 + /// + public string SessionToken { get; set; } = string.Empty; + + /// + /// 会话状态。 + /// + public CheckoutSessionStatus Status { get; set; } = CheckoutSessionStatus.Pending; + + /// + /// 校验结果明细 JSON。 + /// + public string ValidationResultJson { get; set; } = string.Empty; + + /// + /// 过期时间(UTC)。 + /// + public DateTime ExpiresAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs new file mode 100644 index 0000000..7928c1e --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs @@ -0,0 +1,40 @@ +using TakeoutSaaS.Domain.Ordering.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Ordering.Entities; + +/// +/// 用户购物车,按租户/门店隔离。 +/// +public sealed class ShoppingCart : MultiTenantEntityBase +{ + /// + /// 用户标识。 + /// + public Guid UserId { get; set; } + + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 购物车状态,包含正常/锁定。 + /// + public ShoppingCartStatus Status { get; set; } = ShoppingCartStatus.Active; + + /// + /// 桌码或场景标识(扫码点餐)。 + /// + public string? TableContext { get; set; } + + /// + /// 履约方式(堂食/自提/配送)缓存。 + /// + public string? DeliveryPreference { get; set; } + + /// + /// 最近一次修改时间(UTC)。 + /// + public DateTime LastModifiedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CartItemStatus.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CartItemStatus.cs new file mode 100644 index 0000000..f717b97 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CartItemStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Ordering.Enums; + +/// +/// 购物车条目状态。 +/// +public enum CartItemStatus +{ + /// + /// 正常可结算。 + /// + Normal = 0, + + /// + /// 不可售(售罄或下架)。 + /// + Unavailable = 1, + + /// + /// 已被删除。 + /// + Removed = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CheckoutSessionStatus.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CheckoutSessionStatus.cs new file mode 100644 index 0000000..fbb71c8 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/CheckoutSessionStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Ordering.Enums; + +/// +/// 结账会话状态。 +/// +public enum CheckoutSessionStatus +{ + /// + /// 等待用户提交或支付。 + /// + Pending = 0, + + /// + /// 校验失败。 + /// + Failed = 1, + + /// + /// 已用于创建订单。 + /// + Completed = 2, + + /// + /// 超时作废。 + /// + Expired = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/ShoppingCartStatus.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/ShoppingCartStatus.cs new file mode 100644 index 0000000..88d16af --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Enums/ShoppingCartStatus.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Ordering.Enums; + +/// +/// 购物车状态。 +/// +public enum ShoppingCartStatus +{ + /// + /// 可正常使用。 + /// + Active = 0, + + /// + /// 已锁定(进行结账中)。 + /// + Locked = 1, + + /// + /// 已清空或失效。 + /// + Cleared = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs new file mode 100644 index 0000000..9a1c295 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs @@ -0,0 +1,111 @@ +using TakeoutSaaS.Domain.Orders.Enums; +using TakeoutSaaS.Domain.Payments.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Orders.Entities; + +/// +/// 交易订单。 +/// +public sealed class Order : MultiTenantEntityBase +{ + /// + /// 订单号。 + /// + public string OrderNo { get; set; } = string.Empty; + + /// + /// 门店。 + /// + public Guid StoreId { get; set; } + + /// + /// 下单渠道。 + /// + public OrderChannel Channel { get; set; } = OrderChannel.MiniProgram; + + /// + /// 履约类型。 + /// + public DeliveryType DeliveryType { get; set; } = DeliveryType.DineIn; + + /// + /// 当前状态。 + /// + public OrderStatus Status { get; set; } = OrderStatus.PendingPayment; + + /// + /// 支付状态。 + /// + public PaymentStatus PaymentStatus { get; set; } = PaymentStatus.Unpaid; + + /// + /// 顾客姓名。 + /// + public string? CustomerName { get; set; } + + /// + /// 顾客手机号。 + /// + public string? CustomerPhone { get; set; } + + /// + /// 就餐桌号。 + /// + public string? TableNo { get; set; } + + /// + /// 排队号(如有)。 + /// + public string? QueueNumber { get; set; } + + /// + /// 预约 ID。 + /// + public Guid? ReservationId { get; set; } + + /// + /// 商品总额。 + /// + public decimal ItemsAmount { get; set; } + + /// + /// 优惠金额。 + /// + public decimal DiscountAmount { get; set; } + + /// + /// 应付金额。 + /// + public decimal PayableAmount { get; set; } + + /// + /// 实付金额。 + /// + public decimal PaidAmount { get; set; } + + /// + /// 支付时间。 + /// + public DateTime? PaidAt { get; set; } + + /// + /// 完成时间。 + /// + public DateTime? FinishedAt { get; set; } + + /// + /// 取消时间。 + /// + public DateTime? CancelledAt { get; set; } + + /// + /// 取消原因。 + /// + public string? CancelReason { get; set; } + + /// + /// 备注。 + /// + public string? Remark { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs new file mode 100644 index 0000000..83681ca --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs @@ -0,0 +1,59 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Orders.Entities; + +/// +/// 订单明细。 +/// +public sealed class OrderItem : MultiTenantEntityBase +{ + /// + /// 订单 ID。 + /// + public Guid OrderId { get; set; } + + /// + /// 商品 ID。 + /// + public Guid ProductId { get; set; } + + /// + /// 商品名称。 + /// + public string ProductName { get; set; } = string.Empty; + + /// + /// SKU/规格描述。 + /// + public string? SkuName { get; set; } + + /// + /// 单位。 + /// + public string? Unit { get; set; } + + /// + /// 数量。 + /// + public int Quantity { get; set; } + + /// + /// 单价。 + /// + public decimal UnitPrice { get; set; } + + /// + /// 折扣金额。 + /// + public decimal DiscountAmount { get; set; } + + /// + /// 小计。 + /// + public decimal SubTotal { get; set; } + + /// + /// 自定义属性 JSON。 + /// + public string? AttributesJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs new file mode 100644 index 0000000..26823f2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Orders.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Orders.Entities; + +/// +/// 订单状态流转记录。 +/// +public sealed class OrderStatusHistory : MultiTenantEntityBase +{ + /// + /// 订单标识。 + /// + public Guid OrderId { get; set; } + + /// + /// 变更后的状态。 + /// + public OrderStatus Status { get; set; } + + /// + /// 操作人标识(可为空表示系统)。 + /// + public Guid? OperatorId { get; set; } + + /// + /// 备注信息。 + /// + public string? Notes { get; set; } + + /// + /// 发生时间。 + /// + public DateTime OccurredAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs new file mode 100644 index 0000000..d18d4ab --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.Orders.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Orders.Entities; + +/// +/// 售后/退款申请。 +/// +public sealed class RefundRequest : MultiTenantEntityBase +{ + /// + /// 关联订单标识。 + /// + public Guid OrderId { get; set; } + + /// + /// 退款单号。 + /// + public string RefundNo { get; set; } = string.Empty; + + /// + /// 申请金额。 + /// + public decimal Amount { get; set; } + + /// + /// 申请原因。 + /// + public string Reason { get; set; } = string.Empty; + + /// + /// 退款状态。 + /// + public RefundStatus Status { get; set; } = RefundStatus.Pending; + + /// + /// 用户提交时间。 + /// + public DateTime RequestedAt { get; set; } = DateTime.UtcNow; + + /// + /// 审核完成时间。 + /// + public DateTime? ProcessedAt { get; set; } + + /// + /// 审核备注。 + /// + public string? ReviewNotes { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Enums/DeliveryType.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/DeliveryType.cs new file mode 100644 index 0000000..4a7249f --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/DeliveryType.cs @@ -0,0 +1,11 @@ +namespace TakeoutSaaS.Domain.Orders.Enums; + +/// +/// 履约/交付方式。 +/// +public enum DeliveryType +{ + DineIn = 0, + Pickup = 1, + Delivery = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderChannel.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderChannel.cs new file mode 100644 index 0000000..6908ac6 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderChannel.cs @@ -0,0 +1,14 @@ +namespace TakeoutSaaS.Domain.Orders.Enums; + +/// +/// 下单渠道。 +/// +public enum OrderChannel +{ + Unknown = 0, + MiniProgram = 1, + ScanToOrder = 2, + StaffConsole = 3, + PhoneReservation = 4, + ThirdPartyDelivery = 5 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderStatus.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderStatus.cs new file mode 100644 index 0000000..4132e46 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/OrderStatus.cs @@ -0,0 +1,14 @@ +namespace TakeoutSaaS.Domain.Orders.Enums; + +/// +/// 订单状态。 +/// +public enum OrderStatus +{ + PendingPayment = 0, + AwaitingPreparation = 1, + InProgress = 2, + Ready = 3, + Completed = 4, + Cancelled = 5 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Enums/RefundStatus.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/RefundStatus.cs new file mode 100644 index 0000000..3f2dc41 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Enums/RefundStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Orders.Enums; + +/// +/// 退款申请状态。 +/// +public enum RefundStatus +{ + /// + /// 等待审核。 + /// + Pending = 0, + + /// + /// 审核通过,待原路退款。 + /// + Approved = 1, + + /// + /// 已拒绝。 + /// + Rejected = 2, + + /// + /// 已完成退款。 + /// + Refunded = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs new file mode 100644 index 0000000..2d0923f --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs @@ -0,0 +1,55 @@ +using TakeoutSaaS.Domain.Payments.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Payments.Entities; + +/// +/// 支付流水。 +/// +public sealed class PaymentRecord : MultiTenantEntityBase +{ + /// + /// 关联订单。 + /// + public Guid OrderId { get; set; } + + /// + /// 支付方式。 + /// + public PaymentMethod Method { get; set; } = PaymentMethod.Unknown; + + /// + /// 支付状态。 + /// + public PaymentStatus Status { get; set; } = PaymentStatus.Unpaid; + + /// + /// 支付金额。 + /// + public decimal Amount { get; set; } + + /// + /// 平台交易号。 + /// + public string? TradeNo { get; set; } + + /// + /// 第三方渠道单号。 + /// + public string? ChannelTransactionId { get; set; } + + /// + /// 支付完成时间。 + /// + public DateTime? PaidAt { get; set; } + + /// + /// 错误/备注。 + /// + public string? Remark { get; set; } + + /// + /// 原始回调内容。 + /// + public string? Payload { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs new file mode 100644 index 0000000..9e91973 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.Payments.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Payments.Entities; + +/// +/// 支付渠道退款流水。 +/// +public sealed class PaymentRefundRecord : MultiTenantEntityBase +{ + /// + /// 原支付记录标识。 + /// + public Guid PaymentRecordId { get; set; } + + /// + /// 关联订单标识。 + /// + public Guid OrderId { get; set; } + + /// + /// 退款金额。 + /// + public decimal Amount { get; set; } + + /// + /// 渠道退款流水号。 + /// + public string? ChannelRefundId { get; set; } + + /// + /// 退款状态。 + /// + public PaymentRefundStatus Status { get; set; } = PaymentRefundStatus.Pending; + + /// + /// 退款请求时间。 + /// + public DateTime RequestedAt { get; set; } = DateTime.UtcNow; + + /// + /// 完成时间。 + /// + public DateTime? CompletedAt { get; set; } + + /// + /// 渠道返回的原始数据 JSON。 + /// + public string? Payload { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentMethod.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentMethod.cs new file mode 100644 index 0000000..2453be2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentMethod.cs @@ -0,0 +1,14 @@ +namespace TakeoutSaaS.Domain.Payments.Enums; + +/// +/// 支付方式。 +/// +public enum PaymentMethod +{ + Unknown = 0, + WeChatPay = 1, + Alipay = 2, + Cash = 3, + Card = 4, + Balance = 5 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentRefundStatus.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentRefundStatus.cs new file mode 100644 index 0000000..0ee24db --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentRefundStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Payments.Enums; + +/// +/// 支付退款状态。 +/// +public enum PaymentRefundStatus +{ + /// + /// 已提交至渠道。 + /// + Pending = 0, + + /// + /// 退款成功。 + /// + Succeeded = 1, + + /// + /// 退款失败。 + /// + Failed = 2, + + /// + /// 渠道处理中。 + /// + Processing = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentStatus.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentStatus.cs new file mode 100644 index 0000000..6b5912a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Payments/Enums/PaymentStatus.cs @@ -0,0 +1,13 @@ +namespace TakeoutSaaS.Domain.Payments.Enums; + +/// +/// 支付记录状态。 +/// +public enum PaymentStatus +{ + Unpaid = 0, + Paying = 1, + Paid = 2, + Failed = 3, + Refunded = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs new file mode 100644 index 0000000..c9617e2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs @@ -0,0 +1,100 @@ +using TakeoutSaaS.Domain.Products.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品(SPU)信息。 +/// +public sealed class Product : MultiTenantEntityBase +{ + /// + /// 所属门店。 + /// + public Guid StoreId { get; set; } + + /// + /// 所属分类。 + /// + public Guid CategoryId { get; set; } + + /// + /// 商品编码。 + /// + public string SpuCode { get; set; } = string.Empty; + + /// + /// 商品名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 副标题/卖点。 + /// + public string? Subtitle { get; set; } + + /// + /// 售卖单位(份/杯等)。 + /// + public string? Unit { get; set; } + + /// + /// 现价。 + /// + public decimal Price { get; set; } + + /// + /// 原价。 + /// + public decimal? OriginalPrice { get; set; } + + /// + /// 库存数量(可选)。 + /// + public int? StockQuantity { get; set; } + + /// + /// 最大每单限购。 + /// + public int? MaxQuantityPerOrder { get; set; } + + /// + /// 商品状态。 + /// + public ProductStatus Status { get; set; } = ProductStatus.Draft; + + /// + /// 主图。 + /// + public string? CoverImage { get; set; } + + /// + /// Gallery 图片逗号分隔。 + /// + public string? GalleryImages { get; set; } + + /// + /// 商品描述。 + /// + public string? Description { get; set; } + + /// + /// 支持堂食。 + /// + public bool EnableDineIn { get; set; } = true; + + /// + /// 支持自提。 + /// + public bool EnablePickup { get; set; } = true; + + /// + /// 支持配送。 + /// + public bool EnableDelivery { get; set; } = true; + + /// + /// 是否热门推荐。 + /// + public bool IsFeatured { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs new file mode 100644 index 0000000..dfd8dd5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Products.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 加料/做法分组。 +/// +public sealed class ProductAddonGroup : MultiTenantEntityBase +{ + /// + /// 所属商品。 + /// + public Guid ProductId { get; set; } + + /// + /// 分组名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 选择类型。 + /// + public AddonSelectionType SelectionType { get; set; } = AddonSelectionType.Single; + + /// + /// 最小选择数量。 + /// + public int? MinSelect { get; set; } + + /// + /// 最大选择数量。 + /// + public int? MaxSelect { get; set; } + + /// + /// 是否必选。 + /// + public bool IsRequired { get; set; } + + /// + /// 排序值。 + /// + public int SortOrder { get; set; } = 100; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs new file mode 100644 index 0000000..3d27185 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 加料选项。 +/// +public sealed class ProductAddonOption : MultiTenantEntityBase +{ + /// + /// 所属加料分组。 + /// + public Guid AddonGroupId { get; set; } + + /// + /// 选项名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 附加价格。 + /// + public decimal? ExtraPrice { get; set; } + + /// + /// 是否默认选项。 + /// + public bool IsDefault { get; set; } + + /// + /// 排序。 + /// + public int SortOrder { get; set; } = 100; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs new file mode 100644 index 0000000..62a9af1 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Products.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品规格/属性分组。 +/// +public sealed class ProductAttributeGroup : MultiTenantEntityBase +{ + /// + /// 关联门店,可为空表示所有门店共享。 + /// + public Guid? StoreId { get; set; } + + /// + /// 分组名称,例如“辣度”“份量”。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 选择类型(单选/多选)。 + /// + public AttributeSelectionType SelectionType { get; set; } = AttributeSelectionType.Single; + + /// + /// 是否必选。 + /// + public bool IsRequired { get; set; } + + /// + /// 显示排序。 + /// + public int SortOrder { get; set; } = 100; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs new file mode 100644 index 0000000..80332d3 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品规格选项。 +/// +public sealed class ProductAttributeOption : MultiTenantEntityBase +{ + /// + /// 所属规格组。 + /// + public Guid AttributeGroupId { get; set; } + + /// + /// 选项名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 附加价格。 + /// + public decimal? ExtraPrice { get; set; } + + /// + /// 排序。 + /// + public int SortOrder { get; set; } = 100; + + /// + /// 是否默认选中。 + /// + public bool IsDefault { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs new file mode 100644 index 0000000..4d055a2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs @@ -0,0 +1,34 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品分类。 +/// +public sealed class ProductCategory : MultiTenantEntityBase +{ + /// + /// 所属门店。 + /// + public Guid StoreId { get; set; } + + /// + /// 分类名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 分类描述。 + /// + public string? Description { get; set; } + + /// + /// 排序值。 + /// + public int SortOrder { get; set; } = 100; + + /// + /// 是否启用。 + /// + public bool IsEnabled { get; set; } = true; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs new file mode 100644 index 0000000..c07ef44 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Products.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品媒资素材。 +/// +public sealed class ProductMediaAsset : MultiTenantEntityBase +{ + /// + /// 商品标识。 + /// + public Guid ProductId { get; set; } + + /// + /// 媒体类型。 + /// + public MediaAssetType MediaType { get; set; } = MediaAssetType.Image; + + /// + /// 媒资链接。 + /// + public string Url { get; set; } = string.Empty; + + /// + /// 描述或标题。 + /// + public string? Caption { get; set; } + + /// + /// 排序。 + /// + public int SortOrder { get; set; } = 100; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs new file mode 100644 index 0000000..bace92a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Products.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品价格策略,支持会员价/时段价等。 +/// +public sealed class ProductPricingRule : MultiTenantEntityBase +{ + /// + /// 所属商品。 + /// + public Guid ProductId { get; set; } + + /// + /// 策略类型。 + /// + public PricingRuleType RuleType { get; set; } = PricingRuleType.Member; + + /// + /// 条件描述(JSON),如会员等级、渠道等。 + /// + public string ConditionsJson { get; set; } = string.Empty; + + /// + /// 特殊价格。 + /// + public decimal Price { get; set; } + + /// + /// 生效开始时间。 + /// + public DateTime? StartTime { get; set; } + + /// + /// 生效结束时间。 + /// + public DateTime? EndTime { get; set; } + + /// + /// 生效星期(JSON 数组)。 + /// + public string? WeekdaysJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs new file mode 100644 index 0000000..1c1e6ce --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs @@ -0,0 +1,49 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Products.Entities; + +/// +/// 商品 SKU,记录具体规格组合价格。 +/// +public sealed class ProductSku : MultiTenantEntityBase +{ + /// + /// 所属商品标识。 + /// + public Guid ProductId { get; set; } + + /// + /// SKU 编码。 + /// + public string SkuCode { get; set; } = string.Empty; + + /// + /// 条形码。 + /// + public string? Barcode { get; set; } + + /// + /// 售价。 + /// + public decimal Price { get; set; } + + /// + /// 原价。 + /// + public decimal? OriginalPrice { get; set; } + + /// + /// 可售库存。 + /// + public int? StockQuantity { get; set; } + + /// + /// 重量(千克)。 + /// + public decimal? Weight { get; set; } + + /// + /// 规格属性 JSON(记录选项 ID)。 + /// + public string AttributesJson { get; set; } = string.Empty; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Enums/AddonSelectionType.cs b/src/Domain/TakeoutSaaS.Domain/Products/Enums/AddonSelectionType.cs new file mode 100644 index 0000000..65bea66 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Enums/AddonSelectionType.cs @@ -0,0 +1,17 @@ +namespace TakeoutSaaS.Domain.Products.Enums; + +/// +/// 加料选择类型。 +/// +public enum AddonSelectionType +{ + /// + /// 单选。 + /// + Single = 0, + + /// + /// 多选。 + /// + Multiple = 1 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Enums/AttributeSelectionType.cs b/src/Domain/TakeoutSaaS.Domain/Products/Enums/AttributeSelectionType.cs new file mode 100644 index 0000000..9b5f69d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Enums/AttributeSelectionType.cs @@ -0,0 +1,17 @@ +namespace TakeoutSaaS.Domain.Products.Enums; + +/// +/// 规格/加料的选择方式。 +/// +public enum AttributeSelectionType +{ + /// + /// 单选。 + /// + Single = 0, + + /// + /// 多选。 + /// + Multiple = 1 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Enums/MediaAssetType.cs b/src/Domain/TakeoutSaaS.Domain/Products/Enums/MediaAssetType.cs new file mode 100644 index 0000000..27a0f2d --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Enums/MediaAssetType.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Products.Enums; + +/// +/// 商品媒资类型。 +/// +public enum MediaAssetType +{ + /// + /// 图片。 + /// + Image = 0, + + /// + /// 视频。 + /// + Video = 1, + + /// + /// PDF 或说明文档。 + /// + Document = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Enums/PricingRuleType.cs b/src/Domain/TakeoutSaaS.Domain/Products/Enums/PricingRuleType.cs new file mode 100644 index 0000000..613283a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Enums/PricingRuleType.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Products.Enums; + +/// +/// 价格策略类型。 +/// +public enum PricingRuleType +{ + /// + /// 会员价格。 + /// + Member = 0, + + /// + /// 不同门店价格。 + /// + Store = 1, + + /// + /// 时间段价格。 + /// + TimePeriod = 2, + + /// + /// 区域价格。 + /// + Region = 3, + + /// + /// 活动价格。 + /// + Promotion = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Enums/ProductStatus.cs b/src/Domain/TakeoutSaaS.Domain/Products/Enums/ProductStatus.cs new file mode 100644 index 0000000..8678fcc --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Products/Enums/ProductStatus.cs @@ -0,0 +1,12 @@ +namespace TakeoutSaaS.Domain.Products.Enums; + +/// +/// 商品状态。 +/// +public enum ProductStatus +{ + Draft = 0, + OnSale = 1, + OffShelf = 2, + Archived = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs b/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs new file mode 100644 index 0000000..f09c94e --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs @@ -0,0 +1,52 @@ +using TakeoutSaaS.Domain.Queues.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Queues.Entities; + +/// +/// 排队叫号。 +/// +public sealed class QueueTicket : MultiTenantEntityBase +{ + public Guid StoreId { get; set; } + + /// + /// 排队编号。 + /// + public string TicketNumber { get; set; } = string.Empty; + + /// + /// 就餐人数。 + /// + public int PartySize { get; set; } + + /// + /// 状态。 + /// + public QueueStatus Status { get; set; } = QueueStatus.Waiting; + + /// + /// 预计等待分钟。 + /// + public int? EstimatedWaitMinutes { get; set; } + + /// + /// 叫号时间。 + /// + public DateTime? CalledAt { get; set; } + + /// + /// 过号时间。 + /// + public DateTime? ExpiredAt { get; set; } + + /// + /// 取消时间。 + /// + public DateTime? CancelledAt { get; set; } + + /// + /// 备注。 + /// + public string? Remark { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Queues/Enums/QueueStatus.cs b/src/Domain/TakeoutSaaS.Domain/Queues/Enums/QueueStatus.cs new file mode 100644 index 0000000..f217a70 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Queues/Enums/QueueStatus.cs @@ -0,0 +1,13 @@ +namespace TakeoutSaaS.Domain.Queues.Enums; + +/// +/// 排队状态。 +/// +public enum QueueStatus +{ + Waiting = 0, + Calling = 1, + Completed = 2, + Cancelled = 3, + Expired = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs b/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs new file mode 100644 index 0000000..3214ee3 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs @@ -0,0 +1,70 @@ +using TakeoutSaaS.Domain.Reservations.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Reservations.Entities; + +/// +/// 预约/预订记录。 +/// +public sealed class Reservation : MultiTenantEntityBase +{ + /// + /// 门店。 + /// + public Guid StoreId { get; set; } + + /// + /// 预约号。 + /// + public string ReservationNo { get; set; } = string.Empty; + + /// + /// 客户姓名。 + /// + public string CustomerName { get; set; } = string.Empty; + + /// + /// 联系电话。 + /// + public string CustomerPhone { get; set; } = string.Empty; + + /// + /// 用餐人数。 + /// + public int PeopleCount { get; set; } + + /// + /// 预约时间(UTC)。 + /// + public DateTime ReservationTime { get; set; } + + /// + /// 状态。 + /// + public ReservationStatus Status { get; set; } = ReservationStatus.Pending; + + /// + /// 桌型/标签。 + /// + public string? TablePreference { get; set; } + + /// + /// 备注。 + /// + public string? Remark { get; set; } + + /// + /// 核销码/到店码。 + /// + public string? CheckInCode { get; set; } + + /// + /// 实际签到时间。 + /// + public DateTime? CheckedInAt { get; set; } + + /// + /// 取消时间。 + /// + public DateTime? CancelledAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Reservations/Enums/ReservationStatus.cs b/src/Domain/TakeoutSaaS.Domain/Reservations/Enums/ReservationStatus.cs new file mode 100644 index 0000000..2bf53a3 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Reservations/Enums/ReservationStatus.cs @@ -0,0 +1,13 @@ +namespace TakeoutSaaS.Domain.Reservations.Enums; + +/// +/// 预约状态。 +/// +public enum ReservationStatus +{ + Pending = 0, + Confirmed = 1, + CheckedIn = 2, + Cancelled = 3, + NoShow = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs new file mode 100644 index 0000000..5c82a91 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs @@ -0,0 +1,130 @@ +using TakeoutSaaS.Domain.Stores.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 门店信息,承载营业配置与能力。 +/// +public sealed class Store : MultiTenantEntityBase +{ + /// + /// 所属商户标识。 + /// + public Guid MerchantId { get; set; } + + /// + /// 门店编码,便于扫码及外部对接。 + /// + public string Code { get; set; } = string.Empty; + + /// + /// 门店名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 联系电话。 + /// + public string? Phone { get; set; } + + /// + /// 门店负责人姓名。 + /// + public string? ManagerName { get; set; } + + /// + /// 门店当前运营状态。 + /// + public StoreStatus Status { get; set; } = StoreStatus.Closed; + + /// + /// 所在国家或地区。 + /// + public string? Country { get; set; } + + /// + /// 所在省份。 + /// + public string? Province { get; set; } + + /// + /// 所在城市。 + /// + public string? City { get; set; } + + /// + /// 区县信息。 + /// + public string? District { get; set; } + + /// + /// 详细地址。 + /// + public string? Address { get; set; } + + /// + /// 高德/腾讯地图经度。 + /// + public double? Longitude { get; set; } + + /// + /// 纬度。 + /// + public double? Latitude { get; set; } + + /// + /// 门店描述或公告。 + /// + public string? Description { get; set; } + + /// + /// 门店营业时段描述(备用字符串)。 + /// + public string? BusinessHours { get; set; } + + /// + /// 是否支持堂食。 + /// + public bool SupportsDineIn { get; set; } = true; + + /// + /// 是否支持自提。 + /// + public bool SupportsPickup { get; set; } = true; + + /// + /// 是否支持配送。 + /// + public bool SupportsDelivery { get; set; } = true; + + /// + /// 支持预约。 + /// + public bool SupportsReservation { get; set; } + + /// + /// 支持排队叫号。 + /// + public bool SupportsQueueing { get; set; } + + /// + /// 默认配送半径(公里)。 + /// + public decimal DeliveryRadiusKm { get; set; } = 3m; + + /// + /// 门店公告。 + /// + public string? Announcement { get; set; } + + /// + /// 门店标签(逗号分隔)。 + /// + public string? Tags { get; set; } + + /// + /// 门店海报。 + /// + public string? CoverImageUrl { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs new file mode 100644 index 0000000..a85b0d8 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Stores.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 门店营业时段配置。 +/// +public sealed class StoreBusinessHour : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 星期几,0 表示周日。 + /// + public DayOfWeek DayOfWeek { get; set; } + + /// + /// 时段类型(正常营业、休息、预约等)。 + /// + public BusinessHourType HourType { get; set; } = BusinessHourType.Normal; + + /// + /// 开始时间(本地时间)。 + /// + public TimeSpan StartTime { get; set; } + + /// + /// 结束时间(本地时间)。 + /// + public TimeSpan EndTime { get; set; } + + /// + /// 最大接待容量或单量限制。 + /// + public int? CapacityLimit { get; set; } + + /// + /// 备注。 + /// + public string? Notes { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs new file mode 100644 index 0000000..e067e1f --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs @@ -0,0 +1,39 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 门店配送范围配置。 +/// +public sealed class StoreDeliveryZone : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 区域名称。 + /// + public string ZoneName { get; set; } = string.Empty; + + /// + /// GeoJSON 表示的多边形范围。 + /// + public string PolygonGeoJson { get; set; } = string.Empty; + + /// + /// 起送价。 + /// + public decimal? MinimumOrderAmount { get; set; } + + /// + /// 配送费。 + /// + public decimal? DeliveryFee { get; set; } + + /// + /// 预计送达分钟。 + /// + public int? EstimatedMinutes { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs new file mode 100644 index 0000000..a1bc39a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Merchants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 门店员工排班记录。 +/// +public sealed class StoreEmployeeShift : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 员工标识。 + /// + public Guid StaffId { get; set; } + + /// + /// 班次日期。 + /// + public DateTime ShiftDate { get; set; } + + /// + /// 开始时间。 + /// + public TimeSpan StartTime { get; set; } + + /// + /// 结束时间。 + /// + public TimeSpan EndTime { get; set; } + + /// + /// 排班角色。 + /// + public StaffRoleType RoleType { get; set; } = StaffRoleType.FrontDesk; + + /// + /// 备注。 + /// + public string? Notes { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs new file mode 100644 index 0000000..c464849 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs @@ -0,0 +1,29 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 门店休息日或特殊营业日。 +/// +public sealed class StoreHoliday : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 日期。 + /// + public DateTime Date { get; set; } + + /// + /// 是否全天闭店。 + /// + public bool IsClosed { get; set; } = true; + + /// + /// 说明内容。 + /// + public string? Reason { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs new file mode 100644 index 0000000..1b55549 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Stores.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 桌台信息与二维码绑定。 +/// +public sealed class StoreTable : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 所在区域 ID。 + /// + public Guid? AreaId { get; set; } + + /// + /// 桌码。 + /// + public string TableCode { get; set; } = string.Empty; + + /// + /// 可容纳人数。 + /// + public int Capacity { get; set; } + + /// + /// 桌台标签(堂食、快餐等)。 + /// + public string? Tags { get; set; } + + /// + /// 当前桌台状态。 + /// + public StoreTableStatus Status { get; set; } = StoreTableStatus.Idle; + + /// + /// 桌码二维码地址。 + /// + public string? QrCodeUrl { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs new file mode 100644 index 0000000..6255266 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs @@ -0,0 +1,24 @@ +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Stores.Entities; + +/// +/// 门店桌台区域配置。 +/// +public sealed class StoreTableArea : MultiTenantEntityBase +{ + /// + /// 门店标识。 + /// + public Guid StoreId { get; set; } + + /// + /// 区域名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 区域描述。 + /// + public string? Description { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Enums/BusinessHourType.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/BusinessHourType.cs new file mode 100644 index 0000000..9f545a0 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/BusinessHourType.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Stores.Enums; + +/// +/// 营业时段类型。 +/// +public enum BusinessHourType +{ + /// + /// 正常营业时段。 + /// + Normal = 0, + + /// + /// 预留给预约的时段。 + /// + ReservationOnly = 1, + + /// + /// 仅自提或配送的时段。 + /// + PickupOrDelivery = 2, + + /// + /// 休息时段。 + /// + Closed = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreStatus.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreStatus.cs new file mode 100644 index 0000000..df82fb9 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Stores.Enums; + +/// +/// 门店运营状态。 +/// +public enum StoreStatus +{ + /// + /// 未开业或休眠。 + /// + Closed = 0, + + /// + /// 准备营业。 + /// + Preparing = 1, + + /// + /// 正常营业中。 + /// + Operating = 2, + + /// + /// 暂停营业。 + /// + Suspended = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreTableStatus.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreTableStatus.cs new file mode 100644 index 0000000..073b832 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Stores/Enums/StoreTableStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Stores.Enums; + +/// +/// 桌台占用状态。 +/// +public enum StoreTableStatus +{ + /// + /// 空闲可用。 + /// + Idle = 0, + + /// + /// 已被占用。 + /// + Occupied = 1, + + /// + /// 正在清理。 + /// + Cleaning = 2, + + /// + /// 暂停使用。 + /// + Disabled = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs new file mode 100644 index 0000000..fd65958 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs @@ -0,0 +1,125 @@ +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Tenants.Entities; + +/// +/// 平台租户信息,描述租户的生命周期与基础资料。 +/// +public sealed class Tenant : AuditableEntityBase +{ + /// + /// 租户短编码,作为跨系统引用的唯一标识。 + /// + public string Code { get; set; } = string.Empty; + + /// + /// 租户全称或品牌名称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 对外展示的简称。 + /// + public string? ShortName { get; set; } + + /// + /// 法人或公司主体名称。 + /// + public string? LegalEntityName { get; set; } + + /// + /// 所属行业,如餐饮、零售等。 + /// + public string? Industry { get; set; } + + /// + /// LOGO 图片地址。 + /// + public string? LogoUrl { get; set; } + + /// + /// 品牌海报或封面图。 + /// + public string? CoverImageUrl { get; set; } + + /// + /// 官网或主要宣传链接。 + /// + public string? Website { get; set; } + + /// + /// 所在国家/地区。 + /// + public string? Country { get; set; } + + /// + /// 所在省份或州。 + /// + public string? Province { get; set; } + + /// + /// 所在城市。 + /// + public string? City { get; set; } + + /// + /// 详细地址信息。 + /// + public string? Address { get; set; } + + /// + /// 主联系人姓名。 + /// + public string? ContactName { get; set; } + + /// + /// 主联系人电话。 + /// + public string? ContactPhone { get; set; } + + /// + /// 主联系人邮箱。 + /// + public string? ContactEmail { get; set; } + + /// + /// 系统内对应的租户所有者账号 ID。 + /// + public Guid? PrimaryOwnerUserId { get; set; } + + /// + /// 租户当前状态,涵盖审核、启用、停用等场景。 + /// + public TenantStatus Status { get; set; } = TenantStatus.PendingReview; + + /// + /// 服务生效时间(UTC)。 + /// + public DateTime? EffectiveFrom { get; set; } + + /// + /// 服务到期时间(UTC)。 + /// + public DateTime? EffectiveTo { get; set; } + + /// + /// 最近一次暂停服务时间。 + /// + public DateTime? SuspendedAt { get; set; } + + /// + /// 暂停或终止的原因说明。 + /// + public string? SuspensionReason { get; set; } + + /// + /// 业务标签集合(逗号分隔)。 + /// + public string? Tags { get; set; } + + /// + /// 备注信息,用于运营记录特殊说明。 + /// + public string? Remarks { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantBillingStatement.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantBillingStatement.cs new file mode 100644 index 0000000..4fb50e2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantBillingStatement.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Tenants.Entities; + +/// +/// 租户账单,用于呈现周期性收费。 +/// +public sealed class TenantBillingStatement : MultiTenantEntityBase +{ + /// + /// 账单编号,供对账查询。 + /// + public string StatementNo { get; set; } = string.Empty; + + /// + /// 账单周期开始时间。 + /// + public DateTime PeriodStart { get; set; } + + /// + /// 账单周期结束时间。 + /// + public DateTime PeriodEnd { get; set; } + + /// + /// 应付金额。 + /// + public decimal AmountDue { get; set; } + + /// + /// 实付金额。 + /// + public decimal AmountPaid { get; set; } + + /// + /// 当前付款状态。 + /// + public TenantBillingStatus Status { get; set; } = TenantBillingStatus.Pending; + + /// + /// 到期日。 + /// + public DateTime DueDate { get; set; } + + /// + /// 账单明细 JSON,记录各项费用。 + /// + public string? LineItemsJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantNotification.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantNotification.cs new file mode 100644 index 0000000..8d2b881 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantNotification.cs @@ -0,0 +1,45 @@ +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Tenants.Entities; + +/// +/// 面向租户的站内通知或消息推送。 +/// +public sealed class TenantNotification : MultiTenantEntityBase +{ + /// + /// 通知标题。 + /// + public string Title { get; set; } = string.Empty; + + /// + /// 通知正文。 + /// + public string Message { get; set; } = string.Empty; + + /// + /// 发布通道(站内、邮件、短信等)。 + /// + public TenantNotificationChannel Channel { get; set; } = TenantNotificationChannel.InApp; + + /// + /// 通知重要级别。 + /// + public TenantNotificationSeverity Severity { get; set; } = TenantNotificationSeverity.Info; + + /// + /// 推送时间。 + /// + public DateTime SentAt { get; set; } = DateTime.UtcNow; + + /// + /// 租户是否已阅读。 + /// + public DateTime? ReadAt { get; set; } + + /// + /// 附加元数据 JSON。 + /// + public string? MetadataJson { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantPackage.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantPackage.cs new file mode 100644 index 0000000..807f159 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantPackage.cs @@ -0,0 +1,70 @@ +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Tenants.Entities; + +/// +/// 平台提供的租户套餐定义。 +/// +public sealed class TenantPackage : AuditableEntityBase +{ + /// + /// 套餐名称,展示给租户的简称。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 套餐描述,包含适用场景、权益等。 + /// + public string? Description { get; set; } + + /// + /// 套餐分类(试用、标准、旗舰等)。 + /// + public TenantPackageType PackageType { get; set; } = TenantPackageType.Standard; + + /// + /// 月付价格,单位:人民币元。 + /// + public decimal? MonthlyPrice { get; set; } + + /// + /// 年付价格,单位:人民币元。 + /// + public decimal? YearlyPrice { get; set; } + + /// + /// 允许的最大门店数。 + /// + public int? MaxStoreCount { get; set; } + + /// + /// 允许创建的最大账号数。 + /// + public int? MaxAccountCount { get; set; } + + /// + /// 存储容量上限(GB)。 + /// + public int? MaxStorageGb { get; set; } + + /// + /// 每月短信额度上限。 + /// + public int? MaxSmsCredits { get; set; } + + /// + /// 每月可调用的配送单数量上限。 + /// + public int? MaxDeliveryOrders { get; set; } + + /// + /// 权益明细 JSON,记录自定义特性开关。 + /// + public string? FeaturePoliciesJson { get; set; } + + /// + /// 是否仍可售卖。 + /// + public bool IsActive { get; set; } = true; +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantQuotaUsage.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantQuotaUsage.cs new file mode 100644 index 0000000..5b2f4c5 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantQuotaUsage.cs @@ -0,0 +1,35 @@ +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Tenants.Entities; + +/// +/// 租户配额使用情况快照。 +/// +public sealed class TenantQuotaUsage : MultiTenantEntityBase +{ + /// + /// 配额类型,例如门店数、短信条数等。 + /// + public TenantQuotaType QuotaType { get; set; } + + /// + /// 当前配额上限。 + /// + public decimal LimitValue { get; set; } + + /// + /// 已消耗的数量。 + /// + public decimal UsedValue { get; set; } + + /// + /// 配额刷新周期描述(如月、年)。 + /// + public string? ResetCycle { get; set; } + + /// + /// 最近一次重置时间。 + /// + public DateTime? LastResetAt { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs new file mode 100644 index 0000000..a54fce2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs @@ -0,0 +1,50 @@ +using TakeoutSaaS.Domain.Tenants.Enums; +using TakeoutSaaS.Shared.Abstractions.Entities; + +namespace TakeoutSaaS.Domain.Tenants.Entities; + +/// +/// 租户套餐订阅记录。 +/// +public sealed class TenantSubscription : MultiTenantEntityBase +{ + /// + /// 当前订阅关联的套餐标识。 + /// + public Guid TenantPackageId { get; set; } + + /// + /// 订阅生效时间(UTC)。 + /// + public DateTime EffectiveFrom { get; set; } + + /// + /// 订阅到期时间(UTC)。 + /// + public DateTime EffectiveTo { get; set; } + + /// + /// 下一个计费时间,配合自动续费使用。 + /// + public DateTime? NextBillingDate { get; set; } + + /// + /// 订阅当前状态。 + /// + public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Pending; + + /// + /// 是否开启自动续费。 + /// + public bool AutoRenew { get; set; } + + /// + /// 若已排期升降配,对应的新套餐 ID。 + /// + public Guid? ScheduledPackageId { get; set; } + + /// + /// 运营备注信息。 + /// + public string? Notes { get; set; } +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/SubscriptionStatus.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/SubscriptionStatus.cs new file mode 100644 index 0000000..d1d1a49 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/SubscriptionStatus.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 订阅状态。 +/// +public enum SubscriptionStatus +{ + /// + /// 尚未支付或等待审批。 + /// + Pending = 0, + + /// + /// 订阅已生效。 + /// + Active = 1, + + /// + /// 已到期但仍保留数据。 + /// + GracePeriod = 2, + + /// + /// 已取消。 + /// + Cancelled = 3, + + /// + /// 因欠费被暂停。 + /// + Suspended = 4 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantBillingStatus.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantBillingStatus.cs new file mode 100644 index 0000000..11671d2 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantBillingStatus.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 账单状态。 +/// +public enum TenantBillingStatus +{ + /// + /// 等待付款。 + /// + Pending = 0, + + /// + /// 已付款结清。 + /// + Paid = 1, + + /// + /// 已逾期。 + /// + Overdue = 2, + + /// + /// 已取消或作废。 + /// + Cancelled = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationChannel.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationChannel.cs new file mode 100644 index 0000000..25a3a36 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationChannel.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 通知推送渠道。 +/// +public enum TenantNotificationChannel +{ + /// + /// 站内消息。 + /// + InApp = 0, + + /// + /// 邮件推送。 + /// + Email = 1, + + /// + /// 短信提醒。 + /// + Sms = 2, + + /// + /// 管理后台弹窗。 + /// + Portal = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationSeverity.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationSeverity.cs new file mode 100644 index 0000000..7947059 --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantNotificationSeverity.cs @@ -0,0 +1,22 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 租户通知的重要程度。 +/// +public enum TenantNotificationSeverity +{ + /// + /// 普通提示。 + /// + Info = 0, + + /// + /// 需要关注的提醒。 + /// + Warning = 1, + + /// + /// 影响业务的严重事件。 + /// + Critical = 2 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantPackageType.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantPackageType.cs new file mode 100644 index 0000000..111ae2c --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantPackageType.cs @@ -0,0 +1,27 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 套餐类型枚举。 +/// +public enum TenantPackageType +{ + /// + /// 免费试用套餐。 + /// + Trial = 0, + + /// + /// 标准商业套餐。 + /// + Standard = 1, + + /// + /// 面向成长型商户的高级套餐。 + /// + Professional = 2, + + /// + /// 提供完整能力的旗舰套餐。 + /// + Enterprise = 3 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantQuotaType.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantQuotaType.cs new file mode 100644 index 0000000..5392bca --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantQuotaType.cs @@ -0,0 +1,37 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 配额类型,覆盖容量及调用次数。 +/// +public enum TenantQuotaType +{ + /// + /// 门店数量限制。 + /// + StoreCount = 0, + + /// + /// 员工账号数量限制。 + /// + AccountCount = 1, + + /// + /// 存储空间限制。 + /// + Storage = 2, + + /// + /// 短信额度。 + /// + SmsCredits = 3, + + /// + /// 配送订单数量限制。 + /// + DeliveryOrders = 4, + + /// + /// 营销活动并发数量。 + /// + PromotionSlots = 5 +} diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantStatus.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantStatus.cs new file mode 100644 index 0000000..369ed3a --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Enums/TenantStatus.cs @@ -0,0 +1,32 @@ +namespace TakeoutSaaS.Domain.Tenants.Enums; + +/// +/// 租户服务状态。 +/// +public enum TenantStatus +{ + /// + /// 已提交信息,等待审核。 + /// + PendingReview = 0, + + /// + /// 审核通过并正常运营。 + /// + Active = 1, + + /// + /// 因欠费或违规被暂时停用。 + /// + Suspended = 2, + + /// + /// 服务到期尚未续费。 + /// + Expired = 3, + + /// + /// 主动或被动注销,数据进入归档状态。 + /// + Closed = 4 +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs new file mode 100644 index 0000000..56454dc --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs @@ -0,0 +1,949 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.App.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.App.Migrations +{ + [DbContext(typeof(TakeoutAppDbContext))] + [Migration("20251201044927_InitialApp")] + partial class InitialApp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CourierName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CourierPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeliveredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeliveryFee") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("DispatchedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FailureReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("PickedUpAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("ProviderOrderId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId") + .IsUnique(); + + b.ToTable("delivery_orders", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.Merchant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BrandAlias") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BusinessLicenseNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("City") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ContactPhone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("District") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("LegalPerson") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OnboardedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Province") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ReviewRemarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("merchants", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CancelReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Channel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CustomerName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CustomerPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeliveryType") + .HasColumnType("integer"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("FinishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ItemsAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("OrderNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PayableAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PaymentStatus") + .HasColumnType("integer"); + + b.Property("QueueNumber") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Remark") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ReservationId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TableNo") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderNo") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId", "Status"); + + b.ToTable("orders", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributesJson") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("ProductName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("SkuName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SubTotal") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Unit") + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("TenantId", "OrderId"); + + b.ToTable("order_items", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ChannelTransactionId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Method") + .HasColumnType("integer"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TradeNo") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId"); + + b.ToTable("payment_records", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("CoverImage") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("EnableDelivery") + .HasColumnType("boolean"); + + b.Property("EnableDineIn") + .HasColumnType("boolean"); + + b.Property("EnablePickup") + .HasColumnType("boolean"); + + b.Property("GalleryImages") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("IsFeatured") + .HasColumnType("boolean"); + + b.Property("MaxQuantityPerOrder") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("OriginalPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("SpuCode") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StockQuantity") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("Subtitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Unit") + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SpuCode") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("products", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("product_categories", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Queues.Entities.QueueTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("EstimatedWaitMinutes") + .HasColumnType("integer"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PartySize") + .HasColumnType("integer"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TicketNumber") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.HasIndex("TenantId", "StoreId", "TicketNumber") + .IsUnique(); + + b.ToTable("queue_tickets", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Reservations.Entities.Reservation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CheckInCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CheckedInAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CustomerName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CustomerPhone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("PeopleCount") + .HasColumnType("integer"); + + b.Property("Remark") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ReservationNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ReservationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TablePreference") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ReservationNo") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("reservations", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Announcement") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("BusinessHours") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("City") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeliveryRadiusKm") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("District") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Latitude") + .HasColumnType("double precision"); + + b.Property("Longitude") + .HasColumnType("double precision"); + + b.Property("ManagerName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MerchantId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Province") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("QueueEnabled") + .HasColumnType("boolean"); + + b.Property("ReservationEnabled") + .HasColumnType("boolean"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SupportsDelivery") + .HasColumnType("boolean"); + + b.Property("SupportsDineIn") + .HasColumnType("boolean"); + + b.Property("SupportsPickup") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MerchantId"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.HasIndex("TenantId", "MerchantId"); + + b.ToTable("stores", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ContactName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("Industry") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("LogoUrl") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Remarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ShortName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b => + { + b.HasOne("TakeoutSaaS.Domain.Orders.Entities.Order", null) + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b => + { + b.HasOne("TakeoutSaaS.Domain.Merchants.Entities.Merchant", "Merchant") + .WithMany() + .HasForeignKey("MerchantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Merchant"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.cs new file mode 100644 index 0000000..f322c41 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.cs @@ -0,0 +1,497 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.App.Migrations +{ + /// + public partial class InitialApp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "delivery_orders", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + Provider = table.Column(type: "integer", nullable: false), + ProviderOrderId = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + Status = table.Column(type: "integer", nullable: false), + DeliveryFee = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + CourierName = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + CourierPhone = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + DispatchedAt = table.Column(type: "timestamp with time zone", nullable: true), + PickedUpAt = table.Column(type: "timestamp with time zone", nullable: true), + DeliveredAt = table.Column(type: "timestamp with time zone", nullable: true), + FailureReason = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_delivery_orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "merchants", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + BrandName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + BrandAlias = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + LegalPerson = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + BusinessLicenseNumber = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + ContactPhone = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + ContactEmail = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + Province = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + City = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + District = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + Address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Status = table.Column(type: "integer", nullable: false), + ReviewRemarks = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + OnboardedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_merchants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "orders", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderNo = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + StoreId = table.Column(type: "uuid", nullable: false), + Channel = table.Column(type: "integer", nullable: false), + DeliveryType = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "integer", nullable: false), + PaymentStatus = table.Column(type: "integer", nullable: false), + CustomerName = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + CustomerPhone = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + TableNo = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + QueueNumber = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + ReservationId = table.Column(type: "uuid", nullable: true), + ItemsAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + DiscountAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + PayableAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + PaidAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + PaidAt = table.Column(type: "timestamp with time zone", nullable: true), + FinishedAt = table.Column(type: "timestamp with time zone", nullable: true), + CancelledAt = table.Column(type: "timestamp with time zone", nullable: true), + CancelReason = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Remark = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "payment_records", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + Method = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "integer", nullable: false), + Amount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + TradeNo = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + ChannelTransactionId = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + PaidAt = table.Column(type: "timestamp with time zone", nullable: true), + Remark = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Payload = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_payment_records", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "product_categories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + StoreId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Description = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + SortOrder = table.Column(type: "integer", nullable: false), + IsEnabled = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_product_categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "products", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + StoreId = table.Column(type: "uuid", nullable: false), + CategoryId = table.Column(type: "uuid", nullable: false), + SpuCode = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Subtitle = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Unit = table.Column(type: "character varying(16)", maxLength: 16, nullable: true), + Price = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + OriginalPrice = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true), + StockQuantity = table.Column(type: "integer", nullable: true), + MaxQuantityPerOrder = table.Column(type: "integer", nullable: true), + Status = table.Column(type: "integer", nullable: false), + CoverImage = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + GalleryImages = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + Description = table.Column(type: "text", nullable: true), + EnableDineIn = table.Column(type: "boolean", nullable: false), + EnablePickup = table.Column(type: "boolean", nullable: false), + EnableDelivery = table.Column(type: "boolean", nullable: false), + IsFeatured = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_products", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "queue_tickets", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + StoreId = table.Column(type: "uuid", nullable: false), + TicketNumber = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + PartySize = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "integer", nullable: false), + EstimatedWaitMinutes = table.Column(type: "integer", nullable: true), + CalledAt = table.Column(type: "timestamp with time zone", nullable: true), + ExpiredAt = table.Column(type: "timestamp with time zone", nullable: true), + CancelledAt = table.Column(type: "timestamp with time zone", nullable: true), + Remark = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_queue_tickets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "reservations", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + StoreId = table.Column(type: "uuid", nullable: false), + ReservationNo = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + CustomerName = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + CustomerPhone = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + PeopleCount = table.Column(type: "integer", nullable: false), + ReservationTime = table.Column(type: "timestamp with time zone", nullable: false), + Status = table.Column(type: "integer", nullable: false), + TablePreference = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + Remark = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + CheckInCode = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + CheckedInAt = table.Column(type: "timestamp with time zone", nullable: true), + CancelledAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_reservations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "tenants", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + ShortName = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + ContactName = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + ContactPhone = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + ContactEmail = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + Industry = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + LogoUrl = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EffectiveFrom = table.Column(type: "timestamp with time zone", nullable: true), + EffectiveTo = table.Column(type: "timestamp with time zone", nullable: true), + Status = table.Column(type: "integer", nullable: false), + Remarks = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_tenants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "stores", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + MerchantId = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Phone = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + ManagerName = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + Status = table.Column(type: "integer", nullable: false), + Province = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + City = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + District = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + Address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Longitude = table.Column(type: "double precision", nullable: true), + Latitude = table.Column(type: "double precision", nullable: true), + BusinessHours = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + SupportsDineIn = table.Column(type: "boolean", nullable: false), + SupportsPickup = table.Column(type: "boolean", nullable: false), + SupportsDelivery = table.Column(type: "boolean", nullable: false), + DeliveryRadiusKm = table.Column(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false), + QueueEnabled = table.Column(type: "boolean", nullable: false), + ReservationEnabled = table.Column(type: "boolean", nullable: false), + Announcement = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stores", x => x.Id); + table.ForeignKey( + name: "FK_stores_merchants_MerchantId", + column: x => x.MerchantId, + principalTable: "merchants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "order_items", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + ProductName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + SkuName = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + Unit = table.Column(type: "character varying(16)", maxLength: 16, nullable: true), + Quantity = table.Column(type: "integer", nullable: false), + UnitPrice = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + DiscountAmount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + SubTotal = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + AttributesJson = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_order_items", x => x.Id); + table.ForeignKey( + name: "FK_order_items_orders_OrderId", + column: x => x.OrderId, + principalTable: "orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_delivery_orders_TenantId_OrderId", + table: "delivery_orders", + columns: new[] { "TenantId", "OrderId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_merchants_TenantId", + table: "merchants", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_order_items_OrderId", + table: "order_items", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_order_items_TenantId_OrderId", + table: "order_items", + columns: new[] { "TenantId", "OrderId" }); + + migrationBuilder.CreateIndex( + name: "IX_orders_TenantId_OrderNo", + table: "orders", + columns: new[] { "TenantId", "OrderNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_orders_TenantId_StoreId_Status", + table: "orders", + columns: new[] { "TenantId", "StoreId", "Status" }); + + migrationBuilder.CreateIndex( + name: "IX_payment_records_TenantId_OrderId", + table: "payment_records", + columns: new[] { "TenantId", "OrderId" }); + + migrationBuilder.CreateIndex( + name: "IX_product_categories_TenantId_StoreId", + table: "product_categories", + columns: new[] { "TenantId", "StoreId" }); + + migrationBuilder.CreateIndex( + name: "IX_products_TenantId_SpuCode", + table: "products", + columns: new[] { "TenantId", "SpuCode" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_products_TenantId_StoreId", + table: "products", + columns: new[] { "TenantId", "StoreId" }); + + migrationBuilder.CreateIndex( + name: "IX_queue_tickets_TenantId_StoreId", + table: "queue_tickets", + columns: new[] { "TenantId", "StoreId" }); + + migrationBuilder.CreateIndex( + name: "IX_queue_tickets_TenantId_StoreId_TicketNumber", + table: "queue_tickets", + columns: new[] { "TenantId", "StoreId", "TicketNumber" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_reservations_TenantId_ReservationNo", + table: "reservations", + columns: new[] { "TenantId", "ReservationNo" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_reservations_TenantId_StoreId", + table: "reservations", + columns: new[] { "TenantId", "StoreId" }); + + migrationBuilder.CreateIndex( + name: "IX_stores_MerchantId", + table: "stores", + column: "MerchantId"); + + migrationBuilder.CreateIndex( + name: "IX_stores_TenantId_Code", + table: "stores", + columns: new[] { "TenantId", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_stores_TenantId_MerchantId", + table: "stores", + columns: new[] { "TenantId", "MerchantId" }); + + migrationBuilder.CreateIndex( + name: "IX_tenants_Code", + table: "tenants", + column: "Code", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "delivery_orders"); + + migrationBuilder.DropTable( + name: "order_items"); + + migrationBuilder.DropTable( + name: "payment_records"); + + migrationBuilder.DropTable( + name: "product_categories"); + + migrationBuilder.DropTable( + name: "products"); + + migrationBuilder.DropTable( + name: "queue_tickets"); + + migrationBuilder.DropTable( + name: "reservations"); + + migrationBuilder.DropTable( + name: "stores"); + + migrationBuilder.DropTable( + name: "tenants"); + + migrationBuilder.DropTable( + name: "orders"); + + migrationBuilder.DropTable( + name: "merchants"); + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/TakeoutAppDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/TakeoutAppDbContextModelSnapshot.cs new file mode 100644 index 0000000..d0c482a --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/TakeoutAppDbContextModelSnapshot.cs @@ -0,0 +1,946 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.App.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.App.Migrations +{ + [DbContext(typeof(TakeoutAppDbContext))] + partial class TakeoutAppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CourierName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CourierPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeliveredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeliveryFee") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("DispatchedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FailureReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("PickedUpAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("ProviderOrderId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId") + .IsUnique(); + + b.ToTable("delivery_orders", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.Merchant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BrandAlias") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("BrandName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BusinessLicenseNumber") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("City") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ContactPhone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("District") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("LegalPerson") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OnboardedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Province") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ReviewRemarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("merchants", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CancelReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Channel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CustomerName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CustomerPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeliveryType") + .HasColumnType("integer"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("FinishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ItemsAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("OrderNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("PaidAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PayableAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("PaymentStatus") + .HasColumnType("integer"); + + b.Property("QueueNumber") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Remark") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ReservationId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TableNo") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderNo") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId", "Status"); + + b.ToTable("orders", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttributesJson") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DiscountAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("ProductName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("SkuName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SubTotal") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Unit") + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("UnitPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("TenantId", "OrderId"); + + b.ToTable("order_items", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ChannelTransactionId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Method") + .HasColumnType("integer"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TradeNo") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "OrderId"); + + b.ToTable("payment_records", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("CoverImage") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("EnableDelivery") + .HasColumnType("boolean"); + + b.Property("EnableDineIn") + .HasColumnType("boolean"); + + b.Property("EnablePickup") + .HasColumnType("boolean"); + + b.Property("GalleryImages") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("IsFeatured") + .HasColumnType("boolean"); + + b.Property("MaxQuantityPerOrder") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("OriginalPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Price") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("SpuCode") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StockQuantity") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("Subtitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Unit") + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "SpuCode") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("products", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("product_categories", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Queues.Entities.QueueTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CalledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("EstimatedWaitMinutes") + .HasColumnType("integer"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PartySize") + .HasColumnType("integer"); + + b.Property("Remark") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("TicketNumber") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "StoreId"); + + b.HasIndex("TenantId", "StoreId", "TicketNumber") + .IsUnique(); + + b.ToTable("queue_tickets", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Reservations.Entities.Reservation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CheckInCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CheckedInAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CustomerName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CustomerPhone") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("PeopleCount") + .HasColumnType("integer"); + + b.Property("Remark") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ReservationNo") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ReservationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("TablePreference") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ReservationNo") + .IsUnique(); + + b.HasIndex("TenantId", "StoreId"); + + b.ToTable("reservations", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Announcement") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("BusinessHours") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("City") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeliveryRadiusKm") + .HasPrecision(6, 2) + .HasColumnType("numeric(6,2)"); + + b.Property("District") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Latitude") + .HasColumnType("double precision"); + + b.Property("Longitude") + .HasColumnType("double precision"); + + b.Property("ManagerName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MerchantId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Province") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("QueueEnabled") + .HasColumnType("boolean"); + + b.Property("ReservationEnabled") + .HasColumnType("boolean"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SupportsDelivery") + .HasColumnType("boolean"); + + b.Property("SupportsDineIn") + .HasColumnType("boolean"); + + b.Property("SupportsPickup") + .HasColumnType("boolean"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MerchantId"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.HasIndex("TenantId", "MerchantId"); + + b.ToTable("stores", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactEmail") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ContactName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactPhone") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveTo") + .HasColumnType("timestamp with time zone"); + + b.Property("Industry") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("LogoUrl") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Remarks") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ShortName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b => + { + b.HasOne("TakeoutSaaS.Domain.Orders.Entities.Order", null) + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b => + { + b.HasOne("TakeoutSaaS.Domain.Merchants.Entities.Merchant", "Merchant") + .WithMany() + .HasForeignKey("MerchantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Merchant"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs new file mode 100644 index 0000000..328a3a2 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs @@ -0,0 +1,221 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using TakeoutSaaS.Domain.Deliveries.Entities; +using TakeoutSaaS.Domain.Merchants.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.Tenants.Entities; +using TakeoutSaaS.Infrastructure.Common.Persistence; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.App.Persistence; + +/// +/// 业务主库 DbContext。 +/// +public sealed class TakeoutAppDbContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor? currentUserAccessor = null) + : TenantAwareDbContext(options, tenantProvider, currentUserAccessor) +{ + public DbSet Tenants => Set(); + public DbSet Merchants => Set(); + public DbSet Stores => Set(); + public DbSet ProductCategories => Set(); + public DbSet Products => Set(); + public DbSet Orders => Set(); + public DbSet OrderItems => Set(); + public DbSet PaymentRecords => Set(); + public DbSet Reservations => Set(); + public DbSet QueueTickets => Set(); + public DbSet DeliveryOrders => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + ConfigureTenant(modelBuilder.Entity()); + ConfigureMerchant(modelBuilder.Entity()); + ConfigureStore(modelBuilder.Entity()); + ConfigureProductCategory(modelBuilder.Entity()); + ConfigureProduct(modelBuilder.Entity()); + ConfigureOrder(modelBuilder.Entity()); + ConfigureOrderItem(modelBuilder.Entity()); + ConfigurePaymentRecord(modelBuilder.Entity()); + ConfigureReservation(modelBuilder.Entity()); + ConfigureQueueTicket(modelBuilder.Entity()); + ConfigureDelivery(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).HasMaxLength(256); + builder.Property(x => x.Remarks).HasMaxLength(512); + builder.HasIndex(x => x.Code).IsUnique(); + } + + 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.HasIndex(x => x.TenantId); + } + + 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.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(); + } + + 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(); + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs new file mode 100644 index 0000000..b56f2d0 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.App.Persistence; + +/// +/// 设计时工厂,供 EF CLI 使用。 +/// +internal sealed class TakeoutAppDesignTimeDbContextFactory + : DesignTimeDbContextFactoryBase +{ + public TakeoutAppDesignTimeDbContextFactory() + : base("TAKEOUTSAAS_APP_CONNECTION", "takeout_saas_app") + { + } + + protected override TakeoutAppDbContext CreateContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor) + => new(options, tenantProvider, currentUserAccessor); +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs new file mode 100644 index 0000000..88ba403 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs @@ -0,0 +1,68 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using TakeoutSaaS.Infrastructure.Common.Persistence; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime; + +/// +/// EF Core 设计时 DbContext 工厂基类,提供统一的连接串与依赖替身。 +/// +internal abstract class DesignTimeDbContextFactoryBase : IDesignTimeDbContextFactory + where TContext : TenantAwareDbContext +{ + private readonly string _connectionStringEnvVar; + private readonly string _defaultDatabase; + + protected DesignTimeDbContextFactoryBase(string connectionStringEnvVar, string defaultDatabase) + { + _connectionStringEnvVar = connectionStringEnvVar; + _defaultDatabase = defaultDatabase; + } + + public TContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql( + ResolveConnectionString(), + npgsql => + { + npgsql.CommandTimeout(30); + npgsql.EnableRetryOnFailure(); + }); + + return CreateContext( + optionsBuilder.Options, + new DesignTimeTenantProvider(), + new DesignTimeCurrentUserAccessor()); + } + + protected abstract TContext CreateContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor); + + private string ResolveConnectionString() + { + var env = Environment.GetEnvironmentVariable(_connectionStringEnvVar); + if (!string.IsNullOrWhiteSpace(env)) + { + return env; + } + + return $"Host=localhost;Port=5432;Database={_defaultDatabase};Username=postgres;Password=postgres"; + } + + private sealed class DesignTimeTenantProvider : ITenantProvider + { + public Guid GetCurrentTenantId() => Guid.Empty; + } + + private sealed class DesignTimeCurrentUserAccessor : ICurrentUserAccessor + { + public Guid UserId => Guid.Empty; + public bool IsAuthenticated => false; + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.Designer.cs new file mode 100644 index 0000000..ed76f4c --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.Designer.cs @@ -0,0 +1,172 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.Dictionary.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations +{ + [DbContext(typeof(DictionaryDbContext))] + [Migration("20251201042346_InitialDictionary")] + partial class InitialDictionary + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Scope") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.ToTable("dictionary_groups", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(100); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("GroupId", "Key") + .IsUnique(); + + b.ToTable("dictionary_items", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b => + { + b.HasOne("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", "Group") + .WithMany("Items") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.cs new file mode 100644 index 0000000..3e0084a --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.cs @@ -0,0 +1,101 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations +{ + /// + public partial class InitialDictionary : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "dictionary_groups", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Scope = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + IsEnabled = table.Column(type: "boolean", nullable: false, defaultValue: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_dictionary_groups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "dictionary_items", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + GroupId = table.Column(type: "uuid", nullable: false), + Key = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Value = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + IsDefault = table.Column(type: "boolean", nullable: false), + IsEnabled = table.Column(type: "boolean", nullable: false, defaultValue: true), + SortOrder = table.Column(type: "integer", nullable: false, defaultValue: 100), + Description = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_dictionary_items", x => x.Id); + table.ForeignKey( + name: "FK_dictionary_items_dictionary_groups_GroupId", + column: x => x.GroupId, + principalTable: "dictionary_groups", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_dictionary_groups_TenantId", + table: "dictionary_groups", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_dictionary_groups_TenantId_Code", + table: "dictionary_groups", + columns: new[] { "TenantId", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_dictionary_items_GroupId_Key", + table: "dictionary_items", + columns: new[] { "GroupId", "Key" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_dictionary_items_TenantId", + table: "dictionary_items", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "dictionary_items"); + + migrationBuilder.DropTable( + name: "dictionary_groups"); + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/DictionaryDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/DictionaryDbContextModelSnapshot.cs new file mode 100644 index 0000000..0c08edf --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/DictionaryDbContextModelSnapshot.cs @@ -0,0 +1,169 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.Dictionary.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations +{ + [DbContext(typeof(DictionaryDbContext))] + partial class DictionaryDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Scope") + .HasColumnType("integer"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "Code") + .IsUnique(); + + b.ToTable("dictionary_groups", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(100); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("GroupId", "Key") + .IsUnique(); + + b.ToTable("dictionary_items", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b => + { + b.HasOne("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", "Group") + .WithMany("Items") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs new file mode 100644 index 0000000..c69074c --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.Dictionary.Persistence; + +/// +/// 设计时 DictionaryDbContext 工厂。 +/// +internal sealed class DictionaryDesignTimeDbContextFactory + : DesignTimeDbContextFactoryBase +{ + public DictionaryDesignTimeDbContextFactory() + : base("TAKEOUTSAAS_APP_CONNECTION", "takeout_saas_app") + { + } + + protected override DictionaryDbContext CreateContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor) + => new(options, tenantProvider, currentUserAccessor); +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.Designer.cs new file mode 100644 index 0000000..9ea6285 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.Designer.cs @@ -0,0 +1,152 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.Identity.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(IdentityDbContext))] + [Migration("20251201042324_InitialIdentity")] + partial class InitialIdentity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Account") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Avatar") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MerchantId") + .HasColumnType("uuid"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("text"); + + b.Property("Roles") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "Account") + .IsUnique(); + + b.ToTable("identity_users", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Avatar") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Nickname") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OpenId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UnionId") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "OpenId") + .IsUnique(); + + b.ToTable("mini_users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.cs new file mode 100644 index 0000000..5a5a2c7 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.cs @@ -0,0 +1,94 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Identity.Migrations +{ + /// + public partial class InitialIdentity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "identity_users", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Account = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + DisplayName = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + PasswordHash = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + MerchantId = table.Column(type: "uuid", nullable: true), + Roles = table.Column(type: "text", nullable: false), + Permissions = table.Column(type: "text", nullable: false), + Avatar = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_identity_users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "mini_users", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OpenId = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + UnionId = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + Nickname = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Avatar = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + DeletedBy = table.Column(type: "uuid", nullable: true), + TenantId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_mini_users", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_identity_users_TenantId", + table: "identity_users", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_identity_users_TenantId_Account", + table: "identity_users", + columns: new[] { "TenantId", "Account" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_mini_users_TenantId", + table: "mini_users", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_mini_users_TenantId_OpenId", + table: "mini_users", + columns: new[] { "TenantId", "OpenId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "identity_users"); + + migrationBuilder.DropTable( + name: "mini_users"); + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/IdentityDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/IdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..385d79c --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/IdentityDbContextModelSnapshot.cs @@ -0,0 +1,149 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.Identity.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(IdentityDbContext))] + partial class IdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Account") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Avatar") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MerchantId") + .HasColumnType("uuid"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("text"); + + b.Property("Roles") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "Account") + .IsUnique(); + + b.ToTable("identity_users", (string)null); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Avatar") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Nickname") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OpenId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UnionId") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.HasIndex("TenantId", "OpenId") + .IsUnique(); + + b.ToTable("mini_users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs new file mode 100644 index 0000000..48667fb --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.Identity.Persistence; + +/// +/// 设计时 IdentityDbContext 工厂,供 EF Core CLI 生成迁移使用。 +/// +internal sealed class IdentityDesignTimeDbContextFactory + : DesignTimeDbContextFactoryBase +{ + public IdentityDesignTimeDbContextFactory() + : base("TAKEOUTSAAS_IDENTITY_CONNECTION", "takeout_saas_identity") + { + } + + protected override IdentityDbContext CreateContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor) + => new(options, tenantProvider, currentUserAccessor); +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj b/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj index c3b590c..90cd078 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj @@ -8,6 +8,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +