# 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
**وضعیت:** ✅ تایید شده توسط کاربر