Files
docs/01-BUSINESS/discount-shop-business.md
T
masoodafar-web 119e870a26 feat: Complete overhaul of FourSat documentation structure and content
- Added FINAL-STATUS.md detailing project completion and key metrics
- Created QUICK-REFERENCE.md for quick access to essential documents
- Updated README.md with project overview and quick start guide
- Established STRUCTURE.md outlining the final documentation structure
- Organized and archived old files, ensuring a clean and efficient directory
- Enhanced documentation quality with comprehensive metrics and checklists
2025-12-04 17:32:31 +03:30

23 KiB
Raw Blame History

Club Discount Shop System - سیستم فروشگاه باشگاه مشتریان با تخفیف ترکیبی

تاریخ ایجاد: 2024-12-02
تاریخ آپدیت: 2024-12-02
وضعیت: طراحی (Phase 9)
اولویت: 🔴 بالا (یکی از دو فاز باقیمانده)


📋 فهرست

  1. مقدمه
  2. مفهوم اصلی: پرداخت ترکیبی
  3. تفاوت با Regular Shop
  4. معماری جداسازی
  5. Entity Design
  6. 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

namespace CMSMicroservice.Domain.Entities.DiscountShop;

/// <summary>
/// محصول فروشگاه تخفیفی
/// </summary>
public class DiscountProduct : BaseAuditableEntity
{
    /// <summary>
    /// عنوان محصول
    /// </summary>
    public string Title { get; set; }
    
    /// <summary>
    /// توضیحات مختصر
    /// </summary>
    public string ShortInfomation { get; set; }
    
    /// <summary>
    /// توضیحات کامل
    /// </summary>
    public string FullInformation { get; set; }
    
    /// <summary>
    /// قیمت (ریال)
    /// </summary>
    public long Price { get; set; }
    
    /// <summary>
    /// درصد تخفیف
    /// </summary>
    public int DiscountPercent { get; set; }
    
    /// <summary>
    /// امتیاز (0 تا 5)
    /// </summary>
    public int Rate { get; set; }
    
    /// <summary>
    /// آدرس تصویر اصلی
    /// </summary>
    public string ImagePath { get; set; }
    
    /// <summary>
    /// آدرس تصویر کوچک
    /// </summary>
    public string ThumbnailPath { get; set; }
    
    /// <summary>
    /// تعداد فروش
    /// </summary>
    public int SaleCount { get; set; }
    
    /// <summary>
    /// تعداد بازدید
    /// </summary>
    public int ViewCount { get; set; }
    
    /// <summary>
    /// موجودی انبار
    /// </summary>
    public int RemainingCount { get; set; }
    
    /// <summary>
    /// وضعیت فعال/غیرفعال
    /// </summary>
    public bool IsActive { get; set; }
    
    // Navigation Properties
    public virtual ICollection<DiscountShoppingCart> ShoppingCarts { get; set; }
    public virtual ICollection<DiscountOrderDetail> OrderDetails { get; set; }
    public virtual ICollection<DiscountProductCategory> ProductCategories { get; set; }
}

2️⃣ DiscountCategory

namespace CMSMicroservice.Domain.Entities.DiscountShop;

/// <summary>
/// دسته‌بندی فروشگاه تخفیفی
/// </summary>
public class DiscountCategory : BaseAuditableEntity
{
    /// <summary>
    /// نام لاتین (برای URL)
    /// </summary>
    public string Name { get; set; }
    
    /// <summary>
    /// عنوان فارسی
    /// </summary>
    public string Title { get; set; }
    
    /// <summary>
    /// توضیحات
    /// </summary>
    public string? Description { get; set; }
    
    /// <summary>
    /// آدرس تصویر
    /// </summary>
    public string? ImagePath { get; set; }
    
    /// <summary>
    /// شناسه والد (برای دسته‌بندی چند سطحی)
    /// </summary>
    public long? ParentId { get; set; }
    
    /// <summary>
    /// Parent Navigation Property
    /// </summary>
    public virtual DiscountCategory? Parent { get; set; }
    
    /// <summary>
    /// فعال/غیرفعال
    /// </summary>
    public bool IsActive { get; set; }
    
    /// <summary>
    /// ترتیب نمایش
    /// </summary>
    public int SortOrder { get; set; }
    
    // Navigation Properties
    public virtual ICollection<DiscountCategory> Children { get; set; }
    public virtual ICollection<DiscountProductCategory> ProductCategories { get; set; }
}

3️⃣ DiscountProductCategory (Many-to-Many)

namespace CMSMicroservice.Domain.Entities.DiscountShop;

/// <summary>
/// رابطه محصول و دسته‌بندی در فروشگاه تخفیفی
/// </summary>
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

namespace CMSMicroservice.Domain.Entities.DiscountShop;

/// <summary>
/// سبد خرید فروشگاه تخفیفی
/// </summary>
public class DiscountShoppingCart : BaseAuditableEntity
{
    /// <summary>
    /// شناسه کاربر
    /// </summary>
    public long UserId { get; set; }
    
    /// <summary>
    /// User Navigation Property
    /// </summary>
    public virtual User User { get; set; }
    
    /// <summary>
    /// شناسه محصول
    /// </summary>
    public long DiscountProductId { get; set; }
    
    /// <summary>
    /// DiscountProduct Navigation Property
    /// </summary>
    public virtual DiscountProduct DiscountProduct { get; set; }
    
    /// <summary>
    /// تعداد
    /// </summary>
    public int Count { get; set; }
    
    /// <summary>
    /// قیمت واحد در زمان افزودن به سبد
    /// </summary>
    public long UnitPrice { get; set; }
}

5️⃣ DiscountOrder

namespace CMSMicroservice.Domain.Entities.DiscountShop;

/// <summary>
/// سفارش از فروشگاه تخفیفی
/// </summary>
public class DiscountOrder : BaseAuditableEntity
{
    /// <summary>
    /// شناسه کاربر
    /// </summary>
    public long UserId { get; set; }
    
    /// <summary>
    /// User Navigation Property
    /// </summary>
    public virtual User User { get; set; }
    
    /// <summary>
    /// مبلغ کل سفارش
    /// </summary>
    public long TotalAmount { get; set; }
    
    /// <summary>
    /// مبلغ تخفیف
    /// </summary>
    public long DiscountAmount { get; set; }
    
    /// <summary>
    /// مبلغ قابل پرداخت
    /// </summary>
    public long PayableAmount { get; set; }
    
    /// <summary>
    /// وضعیت پرداخت
    /// </summary>
    public PaymentStatus PaymentStatus { get; set; }
    
    /// <summary>
    /// تاریخ پرداخت
    /// </summary>
    public DateTime? PaymentDate { get; set; }
    
    /// <summary>
    /// شناسه تراکنش (اگر پرداخت موفق باشد)
    /// </summary>
    public long? TransactionId { get; set; }
    
    /// <summary>
    /// Transaction Navigation Property
    /// </summary>
    public virtual Transactions? Transaction { get; set; }
    
    /// <summary>
    /// شناسه آدرس کاربر
    /// </summary>
    public long UserAddressId { get; set; }
    
    /// <summary>
    /// UserAddress Navigation Property
    /// </summary>
    public virtual UserAddress UserAddress { get; set; }
    
    /// <summary>
    /// وضعیت ارسال
    /// </summary>
    public DeliveryStatus DeliveryStatus { get; set; }
    
    /// <summary>
    /// کد رهگیری مرسوله
    /// </summary>
    public string? TrackingCode { get; set; }
    
    /// <summary>
    /// توضیحات وضعیت ارسال
    /// </summary>
    public string? DeliveryDescription { get; set; }
    
    // Navigation Properties
    public virtual ICollection<DiscountOrderDetail> OrderDetails { get; set; }
}

6️⃣ DiscountOrderDetail

namespace CMSMicroservice.Domain.Entities.DiscountShop;

/// <summary>
/// جزئیات سفارش از فروشگاه تخفیفی
/// </summary>
public class DiscountOrderDetail : BaseAuditableEntity
{
    /// <summary>
    /// شناسه سفارش
    /// </summary>
    public long DiscountOrderId { get; set; }
    
    /// <summary>
    /// DiscountOrder Navigation Property
    /// </summary>
    public virtual DiscountOrder DiscountOrder { get; set; }
    
    /// <summary>
    /// شناسه محصول
    /// </summary>
    public long DiscountProductId { get; set; }
    
    /// <summary>
    /// DiscountProduct Navigation Property
    /// </summary>
    public virtual DiscountProduct DiscountProduct { get; set; }
    
    /// <summary>
    /// تعداد
    /// </summary>
    public int Quantity { get; set; }
    
    /// <summary>
    /// قیمت واحد در زمان ثبت سفارش
    /// </summary>
    public long UnitPrice { get; set; }
    
    /// <summary>
    /// درصد تخفیف در زمان ثبت سفارش
    /// </summary>
    public int DiscountPercent { get; set; }
    
    /// <summary>
    /// مبلغ کل این آیتم (بعد از تخفیف)
    /// </summary>
    public long TotalPrice { get; set; }
}

📐 Business Rules

قانون 1: خرید از Discount Shop فقط با DiscountBalance

// در زمان 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

// در زمان 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 از طریق درگاه

// در 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:

    public DbSet<DiscountProduct> DiscountProducts { get; set; }
    public DbSet<DiscountCategory> DiscountCategories { get; set; }
    // ...
    
  5. ایجاد Migration:

    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

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)

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 هفته)

🔗 مراجع


تاریخ آخرین به‌روزرسانی: 2024-12-02
نویسنده: GitHub Copilot
وضعیت: تایید شده توسط کاربر