- 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
23 KiB
Club Discount Shop System - سیستم فروشگاه باشگاه مشتریان با تخفیف ترکیبی
تاریخ ایجاد: 2024-12-02
تاریخ آپدیت: 2024-12-02
وضعیت: طراحی (Phase 9)
اولویت: 🔴 بالا (یکی از دو فاز باقیمانده)
📋 فهرست
- مقدمه
- مفهوم اصلی: پرداخت ترکیبی
- تفاوت با Regular Shop
- معماری جداسازی
- Entity Design
- Business Rules
- تسکهای پیادهسازی
🎯 مقدمه
هدف:
ایجاد فروشگاه باشگاه مشتریان که در آن کاربران میتوانند با پرداخت ترکیبی خرید کنند:
🔑 قانون اصلی:
- کاربر نمیتواند کل محصول را فقط با
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 روز)
-
ایجاد namespace جدید:
CMSMicroservice.Domain/Entities/DiscountShop/
-
ایجاد Entityها:
DiscountProductDiscountCategoryDiscountProductCategoryDiscountShoppingCartDiscountOrderDiscountOrderDetail
-
ایجاد Configurationها:
DiscountProductConfigurationDiscountCategoryConfiguration- و غیره...
-
بهروزرسانی
DbContext:public DbSet<DiscountProduct> DiscountProducts { get; set; } public DbSet<DiscountCategory> DiscountCategories { get; set; } // ... -
ایجاد Migration:
dotnet ef migrations add AddDiscountShopTables
Phase 2: Commands & Queries (3 روز)
DiscountProduct CRUD:
CreateDiscountProductCommandUpdateDiscountProductCommandDeleteDiscountProductCommandGetDiscountProductByIdQueryGetDiscountProductsListQuery
DiscountCategory CRUD:
CreateDiscountCategoryCommandUpdateDiscountCategoryCommandDeleteDiscountCategoryCommandGetDiscountCategoriesTreeQuery
Shopping Cart:
AddToDiscountCartCommandRemoveFromDiscountCartCommandGetDiscountCartQuery
Order:
CreateDiscountOrderCommand(Checkout)GetDiscountOrderByIdQueryGetMyDiscountOrdersQuery(برای کاربر)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 روز)
صفحات مدیریت:
- لیست محصولات تخفیفی + CRUD
- دستهبندیها (Tree View) + CRUD
- سفارشات تخفیفی + تغییر وضعیت ارسال
- گزارش فروش Discount Shop
Phase 6: FrontOffice UI (3 روز)
صفحات کاربر:
- لیست محصولات تخفیفی (با فیلتر دستهبندی)
- جزئیات محصول تخفیفی
- سبد خرید تخفیفی
- Checkout (با نمایش
DiscountBalance) - لیست سفارشات تخفیفی کاربر
Phase 7: Unit Tests (2 روز)
- تست CRUD محصولات تخفیفی
- تست AddToDiscountCart
- تست 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
وضعیت: ✅ تایید شده توسط کاربر