# Club Discount Shop System - سیستم فروشگاه باشگاه مشتریان با تخفیف ترکیبی **تاریخ ایجاد:** 2024-12-02 **تاریخ آپدیت:** 2024-12-02 **وضعیت:** طراحی (Phase 9) **اولویت:** 🔴 بالا (یکی از دو فاز باقیمانده) --- ## 📋 فهرست 1. [مقدمه](#مقدمه) 2. [مفهوم اصلی: پرداخت ترکیبی](#مفهوم-اصلی-پرداخت-ترکیبی) 3. [تفاوت با Regular Shop](#تفاوت-با-regular-shop) 4. [معماری جداسازی](#معماری-جداسازی) 5. [Entity Design](#entity-design) 6. [Business Rules](#business-rules) 7. [تسک‌های پیاده‌سازی](#تسک-های-پیاده-سازی) --- ## 🎯 مقدمه ### هدف: ایجاد **فروشگاه باشگاه مشتریان** که در آن کاربران می‌توانند با **پرداخت ترکیبی** خرید کنند: **🔑 قانون اصلی**: - کاربر **نمی‌تواند** کل محصول را فقط با `DiscountBalance` بخرد - کاربر می‌تواند **درصدی از قیمت** را با `DiscountBalance` پرداخت کند - **مابقی مبلغ** باید از طریق **درگاه پرداخت واقعی در Gateway/PYMS** پرداخت شود (نه در CMS) ### مثال عملی: ``` قیمت محصول: 1,000,000 تومان حداکثر تخفیف مجاز: 30% DiscountBalance کاربر: 500,000 تومان محاسبه: - حداکثر تخفیف قابل استفاده: 1,000,000 × 30% = 300,000 تومان - DiscountBalance کاربر: 500,000 تومان (بیشتر از 300,000) - مبلغ تخفیف نهایی: 300,000 تومان (محدود به 30%) - مبلغ قابل پرداخت از درگاه: 1,000,000 - 300,000 = 700,000 تومان نتیجه: ✅ کسر از DiscountBalance: 300,000 تومان ✅ پرداخت از درگاه: 700,000 تومان ✅ DiscountBalance باقیمانده: 200,000 تومان ``` --- ## 🔄 مفهوم اصلی: پرداخت ترکیبی ### Flow خرید: ``` 1. کاربر محصول را انتخاب می‌کند 2. سیستم چک می‌کند: - قیمت محصول: X تومان - حداکثر تخفیف مجاز: Y% - DiscountBalance کاربر: Z تومان 3. محاسبه تخفیف: MaxDiscountAmount = X × (Y / 100) ActualDiscountAmount = Min(Z, MaxDiscountAmount) 4. محاسبه مبلغ درگاه: GatewayAmount = X - ActualDiscountAmount 5. ریدایرکت به درگاه پرداخت (GatewayAmount) 6. بعد از بازگشت موفق از درگاه: - Verify payment از درگاه - کسر ActualDiscountAmount از DiscountBalance - ثبت سفارش با دو مبلغ جدا - ارسال اطلاعیه به کاربر ``` ### مزایا: ✅ کاربر نمی‌تواند کل محصول را با تخفیف بخرد (محدودیت درصد) ✅ کاربر می‌تواند از موجودی تخفیف خود استفاده کند ✅ فروشنده مطمئن است مبلغی واقعی دریافت می‌کند ✅ سیستم از سوء‌استفاده جلوگیری می‌کند --- ## 🔄 تفاوت با Regular Shop | ویژگی | فروشگاه عادی (Regular) | فروشگاه تخفیفی (Club Discount) | |-------|------------------------|---------------------------| | **نوع کیف پول** | `UserWallet.Balance` | `UserWallet.DiscountBalance` + درگاه | | **نحوه پرداخت** | 100% از Balance یا IPG | **ترکیبی**: X% از DiscountBalance + مابقی از IPG | | **محدودیت تخفیف** | ندارد | **دارد** (MaxDiscountPercent per product) | | **نحوه شارژ** | خرید پکیج طلایی (56M) | کمیسیون برداشت Diamond | | **ارتباط با باشگاه** | ✅ دارد | ✅ دارد (اعضای باشگاه) | | **محصولات** | `Products` | `DiscountProduct` (یا flag در Products) | | **سفارش** | `UserOrder` | `DiscountOrder` (با دو مبلغ جدا) | | **پرداخت** | یک مرحله‌ای | **دو مرحله‌ای**: 1) Verify IPG، 2) Deduct DiscountBalance | | **TransactionType** | `DepositIpg` | `DiscountPurchase` (hybrid) | --- ## 🏗️ معماری جداسازی ### اصل طراحی: > **"همه چیز جدا، جز درگاه پرداخت و کیف پول"** ``` ┌─────────────────────────────────────────────────────────────────┐ │ User │ │ - Id │ │ - FirstName, LastName, Mobile │ │ - PackagePurchaseMethod │ └────────────┬────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────┐ │ │ ▼ ▼ ┌────────────────────────────┐ ┌──────────────────────────┐ │ UserWallet │ │ Transactions (مشترک) │ │ - Balance │ │ - Type │ │ - DiscountBalance │ │ - RefId │ │ - NetworkBalance │ │ - Amount │ └────────────┬───────────────┘ └──────────────────────────┘ │ ├──────────────────────────────────────────┐ │ │ ▼ ▼ ┌────────────────────────────┐ ┌──────────────────────────┐ │ Regular Shop │ │ Discount Shop │ │ - Products │ │ - DiscountProduct │ │ - Category │ │ - DiscountCategory │ │ - UserCarts │ │ - DiscountShoppingCart │ │ - UserOrder │ │ - DiscountOrder │ │ - FactorDetails │ │ - DiscountOrderDetail │ └────────────────────────────┘ └──────────────────────────┘ ``` --- ## 🗄️ Entity Design ### 1️⃣ `DiscountProduct` ```csharp namespace CMSMicroservice.Domain.Entities.DiscountShop; /// /// محصول فروشگاه تخفیفی /// public class DiscountProduct : BaseAuditableEntity { /// /// عنوان محصول /// public string Title { get; set; } /// /// توضیحات مختصر /// public string ShortInfomation { get; set; } /// /// توضیحات کامل /// public string FullInformation { get; set; } /// /// قیمت (ریال) /// public long Price { get; set; } /// /// درصد تخفیف /// public int DiscountPercent { get; set; } /// /// امتیاز (0 تا 5) /// public int Rate { get; set; } /// /// آدرس تصویر اصلی /// public string ImagePath { get; set; } /// /// آدرس تصویر کوچک /// public string ThumbnailPath { get; set; } /// /// تعداد فروش /// public int SaleCount { get; set; } /// /// تعداد بازدید /// public int ViewCount { get; set; } /// /// موجودی انبار /// public int RemainingCount { get; set; } /// /// وضعیت فعال/غیرفعال /// public bool IsActive { get; set; } // Navigation Properties public virtual ICollection ShoppingCarts { get; set; } public virtual ICollection OrderDetails { get; set; } public virtual ICollection ProductCategories { get; set; } } ``` --- ### 2️⃣ `DiscountCategory` ```csharp namespace CMSMicroservice.Domain.Entities.DiscountShop; /// /// دسته‌بندی فروشگاه تخفیفی /// public class DiscountCategory : BaseAuditableEntity { /// /// نام لاتین (برای URL) /// public string Name { get; set; } /// /// عنوان فارسی /// public string Title { get; set; } /// /// توضیحات /// public string? Description { get; set; } /// /// آدرس تصویر /// public string? ImagePath { get; set; } /// /// شناسه والد (برای دسته‌بندی چند سطحی) /// public long? ParentId { get; set; } /// /// Parent Navigation Property /// public virtual DiscountCategory? Parent { get; set; } /// /// فعال/غیرفعال /// public bool IsActive { get; set; } /// /// ترتیب نمایش /// public int SortOrder { get; set; } // Navigation Properties public virtual ICollection Children { get; set; } public virtual ICollection ProductCategories { get; set; } } ``` --- ### 3️⃣ `DiscountProductCategory` (Many-to-Many) ```csharp namespace CMSMicroservice.Domain.Entities.DiscountShop; /// /// رابطه محصول و دسته‌بندی در فروشگاه تخفیفی /// public class DiscountProductCategory : BaseAuditableEntity { public long DiscountProductId { get; set; } public virtual DiscountProduct DiscountProduct { get; set; } public long DiscountCategoryId { get; set; } public virtual DiscountCategory DiscountCategory { get; set; } } ``` --- ### 4️⃣ `DiscountShoppingCart` ```csharp namespace CMSMicroservice.Domain.Entities.DiscountShop; /// /// سبد خرید فروشگاه تخفیفی /// public class DiscountShoppingCart : BaseAuditableEntity { /// /// شناسه کاربر /// public long UserId { get; set; } /// /// User Navigation Property /// public virtual User User { get; set; } /// /// شناسه محصول /// public long DiscountProductId { get; set; } /// /// DiscountProduct Navigation Property /// public virtual DiscountProduct DiscountProduct { get; set; } /// /// تعداد /// public int Count { get; set; } /// /// قیمت واحد در زمان افزودن به سبد /// public long UnitPrice { get; set; } } ``` --- ### 5️⃣ `DiscountOrder` ```csharp namespace CMSMicroservice.Domain.Entities.DiscountShop; /// /// سفارش از فروشگاه تخفیفی /// public class DiscountOrder : BaseAuditableEntity { /// /// شناسه کاربر /// public long UserId { get; set; } /// /// User Navigation Property /// public virtual User User { get; set; } /// /// مبلغ کل سفارش /// public long TotalAmount { get; set; } /// /// مبلغ تخفیف /// public long DiscountAmount { get; set; } /// /// مبلغ قابل پرداخت /// public long PayableAmount { get; set; } /// /// وضعیت پرداخت /// public PaymentStatus PaymentStatus { get; set; } /// /// تاریخ پرداخت /// public DateTime? PaymentDate { get; set; } /// /// شناسه تراکنش (اگر پرداخت موفق باشد) /// public long? TransactionId { get; set; } /// /// Transaction Navigation Property /// public virtual Transactions? Transaction { get; set; } /// /// شناسه آدرس کاربر /// public long UserAddressId { get; set; } /// /// UserAddress Navigation Property /// public virtual UserAddress UserAddress { get; set; } /// /// وضعیت ارسال /// public DeliveryStatus DeliveryStatus { get; set; } /// /// کد رهگیری مرسوله /// public string? TrackingCode { get; set; } /// /// توضیحات وضعیت ارسال /// public string? DeliveryDescription { get; set; } // Navigation Properties public virtual ICollection OrderDetails { get; set; } } ``` --- ### 6️⃣ `DiscountOrderDetail` ```csharp namespace CMSMicroservice.Domain.Entities.DiscountShop; /// /// جزئیات سفارش از فروشگاه تخفیفی /// public class DiscountOrderDetail : BaseAuditableEntity { /// /// شناسه سفارش /// public long DiscountOrderId { get; set; } /// /// DiscountOrder Navigation Property /// public virtual DiscountOrder DiscountOrder { get; set; } /// /// شناسه محصول /// public long DiscountProductId { get; set; } /// /// DiscountProduct Navigation Property /// public virtual DiscountProduct DiscountProduct { get; set; } /// /// تعداد /// public int Quantity { get; set; } /// /// قیمت واحد در زمان ثبت سفارش /// public long UnitPrice { get; set; } /// /// درصد تخفیف در زمان ثبت سفارش /// public int DiscountPercent { get; set; } /// /// مبلغ کل این آیتم (بعد از تخفیف) /// public long TotalPrice { get; set; } } ``` --- ## 📐 Business Rules ### قانون 1: خرید از Discount Shop فقط با DiscountBalance ```csharp // در زمان Checkout از Discount Shop: var wallet = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == userId); if (wallet.DiscountBalance < order.PayableAmount) { throw new ValidationException( $"موجودی کیف پول تخفیفی شما کافی نیست. " + $"موجودی فعلی: {wallet.DiscountBalance:N0} تومان، " + $"مبلغ مورد نیاز: {order.PayableAmount:N0} تومان" ); } ``` --- ### قانون 2: خرید از Regular Shop فقط با Balance ```csharp // در زمان Checkout از Regular Shop: var wallet = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == userId); if (wallet.Balance < order.Amount) { throw new ValidationException( $"موجودی کیف پول اصلی شما کافی نیست. " + $"موجودی فعلی: {wallet.Balance:N0} تومان، " + $"مبلغ مورد نیاز: {order.Amount:N0} تومان" ); } ``` --- ### قانون 3: شارژ DiscountBalance از طریق درگاه ```csharp // در VerifyDiscountWalletChargeCommand: wallet.DiscountBalance += amount; var transaction = new Transactions { Type = TransactionType.DiscountWalletCharge, Amount = amount, RefId = verifyResult.RefId }; ``` --- ### قانون 4: محصولات Discount Shop جدا از Regular Shop - یک محصول **نمی‌تواند** هم در `Products` باشد، هم در `DiscountProduct` - Admin باید محصولات را جداگانه مدیریت کند - هیچ رابطه‌ای بین `Products` و `DiscountProduct` نیست --- ## 🔄 Flow Diagram: خرید از Discount Shop ``` کاربر → مشاهده محصولات Discount Shop ↓ افزودن به DiscountShoppingCart ↓ Checkout (بررسی DiscountBalance) ↓ ثبت DiscountOrder (PaymentStatus: Pending) ↓ کم کردن DiscountBalance از کیف پول ↓ ثبت Transaction (Type: Buy) ← این تراکنش برای خرید است ↓ ثبت DiscountOrderDetail برای هر محصول ↓ به‌روزرسانی DiscountOrder (PaymentStatus: Success) ↓ خالی کردن DiscountShoppingCart ↓ نمایش پیام موفقیت + کد رهگیری ``` **نکته:** در این فلو از درگاه استفاده **نمی‌شود** چون موجودی از قبل شارژ شده است. --- ## 📝 تسک‌های پیاده‌سازی ### Phase 1: Entity Creation (2 روز) 1. **ایجاد namespace جدید**: - `CMSMicroservice.Domain/Entities/DiscountShop/` 2. **ایجاد Entity‌ها**: - `DiscountProduct` - `DiscountCategory` - `DiscountProductCategory` - `DiscountShoppingCart` - `DiscountOrder` - `DiscountOrderDetail` 3. **ایجاد Configuration‌ها**: - `DiscountProductConfiguration` - `DiscountCategoryConfiguration` - و غیره... 4. **به‌روزرسانی `DbContext`**: ```csharp public DbSet DiscountProducts { get; set; } public DbSet DiscountCategories { get; set; } // ... ``` 5. **ایجاد Migration**: ```bash dotnet ef migrations add AddDiscountShopTables ``` --- ### Phase 2: Commands & Queries (3 روز) #### DiscountProduct CRUD: - `CreateDiscountProductCommand` - `UpdateDiscountProductCommand` - `DeleteDiscountProductCommand` - `GetDiscountProductByIdQuery` - `GetDiscountProductsListQuery` #### DiscountCategory CRUD: - `CreateDiscountCategoryCommand` - `UpdateDiscountCategoryCommand` - `DeleteDiscountCategoryCommand` - `GetDiscountCategoriesTreeQuery` #### Shopping Cart: - `AddToDiscountCartCommand` - `RemoveFromDiscountCartCommand` - `GetDiscountCartQuery` #### Order: - `CreateDiscountOrderCommand` (Checkout) - `GetDiscountOrderByIdQuery` - `GetMyDiscountOrdersQuery` (برای کاربر) - `UpdateDiscountOrderDeliveryCommand` (برای Admin) --- ### Phase 3: BackOffice.BFF APIs (1 روز) **Proto file**: `DiscountShopContract.proto` ```protobuf service DiscountShopContract { // Product rpc CreateDiscountProduct(CreateDiscountProductRequest) returns (CreateDiscountProductResponse); rpc UpdateDiscountProduct(UpdateDiscountProductRequest) returns (UpdateDiscountProductResponse); rpc GetDiscountProducts(GetDiscountProductsRequest) returns (GetDiscountProductsResponse); // Category rpc CreateDiscountCategory(CreateDiscountCategoryRequest) returns (CreateDiscountCategoryResponse); rpc GetDiscountCategoriesTree(Empty) returns (GetDiscountCategoriesTreeResponse); // Orders rpc GetDiscountOrders(GetDiscountOrdersRequest) returns (GetDiscountOrdersResponse); rpc UpdateDiscountOrderDelivery(UpdateDiscountOrderDeliveryRequest) returns (UpdateDiscountOrderDeliveryResponse); } ``` --- ### Phase 4: FrontOffice.BFF APIs (1 روز) **Proto file**: `DiscountShopContract.proto` (در FrontOffice.BFF) ```protobuf service DiscountShopContract { // Browse rpc GetDiscountProducts(GetDiscountProductsRequest) returns (GetDiscountProductsResponse); rpc GetDiscountProductById(GetDiscountProductByIdRequest) returns (GetDiscountProductByIdResponse); // Cart rpc AddToDiscountCart(AddToDiscountCartRequest) returns (AddToDiscountCartResponse); rpc GetMyDiscountCart(Empty) returns (GetMyDiscountCartResponse); rpc RemoveFromDiscountCart(RemoveFromDiscountCartRequest) returns (RemoveFromDiscountCartResponse); // Order rpc CheckoutDiscountCart(CheckoutDiscountCartRequest) returns (CheckoutDiscountCartResponse); rpc GetMyDiscountOrders(Empty) returns (GetMyDiscountOrdersResponse); } ``` --- ### Phase 5: BackOffice UI (3 روز) **صفحات مدیریت:** 1. **لیست محصولات تخفیفی** + CRUD 2. **دسته‌بندی‌ها** (Tree View) + CRUD 3. **سفارشات تخفیفی** + تغییر وضعیت ارسال 4. **گزارش فروش** Discount Shop --- ### Phase 6: FrontOffice UI (3 روز) **صفحات کاربر:** 1. **لیست محصولات تخفیفی** (با فیلتر دسته‌بندی) 2. **جزئیات محصول تخفیفی** 3. **سبد خرید تخفیفی** 4. **Checkout** (با نمایش `DiscountBalance`) 5. **لیست سفارشات تخفیفی کاربر** --- ### Phase 7: Unit Tests (2 روز) 1. تست **CRUD محصولات تخفیفی** 2. تست **AddToDiscountCart** 3. تست **CheckoutDiscountCart**: - کاربر با موجودی کافی → موفق - کاربر با موجودی ناکافی → خطا --- ### Phase 8: Documentation (0.5 روز) - به‌روزرسانی `implementation-progress.md` - لینک از `REMAINING-TASKS-CONSOLIDATED.md` --- ## 📊 خلاصه Timeline | Phase | عنوان | زمان | |-------|-------|------| | 1 | Entity Creation | 2 روز | | 2 | Commands & Queries (CMS) | 3 روز | | 3 | BackOffice.BFF APIs | 1 روز | | 4 | FrontOffice.BFF APIs | 1 روز | | 5 | BackOffice UI | 3 روز | | 6 | FrontOffice UI | 3 روز | | 7 | Unit Tests | 2 روز | | 8 | Documentation | 0.5 روز | | **جمع** | | **15.5 روز** (~3 هفته) | --- ## 🔗 مراجع - [Package Purchase System](./package-purchase-system.md) - [Manual Payment System](./manual-payment-system.md) - [Implementation Progress](./implementation-progress.md) - [REMAINING-TASKS](../REMAINING-TASKS-CONSOLIDATED.md) --- **تاریخ آخرین به‌روزرسانی:** 2024-12-02 **نویسنده:** GitHub Copilot **وضعیت:** ✅ تایید شده توسط کاربر