docs: update BUSINESS-02, BUSINESS-03, TECH-03
BUSINESS-02: - فرمول هایبرید: حذف MIN، اضافه validation کیفپول اعتباری - فلوی خرید: اضافه مرحله بررسی موجودی + UserWalletChangeLog - نامگذاری جدید کیفپولها: اصلی، اعتباری، پاداش تیمی - جدول وضعیت: اضافه WalletChangeLog + Validation BUSINESS-03: - بخش ۹ جدید: ExpirePendingOrdersService (۱۵ دقیقه) - دیاگرام Mermaid فلوی انقضا TECH-03: - فیکس URL پروداکشن (kbs1→kbs2) + هشدار - ۴ کامیت جدید در بخش ۹.۲ - بخش ۹.۳ فیکس URL پروداکشن - بخش ۹.۴ نامگذاری کیفپولها
This commit is contained in:
@@ -0,0 +1,554 @@
|
||||
# 📦 سیستم مبتنی بر پکیج (Package-Based System)
|
||||
|
||||
> **وضعیت:** تحلیل و بررسی — منتظر تایید
|
||||
> **تاریخ:** اسفند ۱۴۰۴
|
||||
> **تاثیرگذاری:** زیاد — بخشهای متعدد سیستم تحت تاثیر قرار میگیرد
|
||||
|
||||
---
|
||||
|
||||
## ۱. خلاصه فیچر
|
||||
|
||||
**وضعیت فعلی:** سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن میچرخد.
|
||||
|
||||
**وضعیت هدف:** سیستم چندین پکیج با قیمتها و ویژگیهای متفاوت پشتیبانی میکند. هر پکیج روشهای پرداخت، محاسبه پورسانت، شارژ کیف پول و فیچرهای مختص خود را دارد.
|
||||
|
||||
```
|
||||
مثال پکیجها:
|
||||
┌──────────────┬──────────────┬──────────────┬──────────────┐
|
||||
│ 🥈 نقرهای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │
|
||||
│ ۵.۶M تومان │ ۵۶M تومان │ ؟؟ تومان │ ؟؟ تومان │
|
||||
│ │ (پکیج پایه) │ │ │
|
||||
│ فقط مستقیم │ دایا+مستقیم │ فقط مستقیم │ فقط مستقیم │
|
||||
│ فیچر محدود │ همه فیچرها │ همه فیچرها │ همه+اختصاصی │
|
||||
└──────────────┴──────────────┴──────────────┴──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۲. وضعیت فعلی سیستم (AS-IS)
|
||||
|
||||
### ۲.۱ فلوی فعلی فعالسازی
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["کاربر وارد سیستم میشود"] --> B{"روش پرداخت"}
|
||||
B -->|"خرید الماس دایا"| C["DayaLoan — ۵۶M"]
|
||||
B -->|"پرداخت مستقیم"| D["درگاه بانکی — ۵۶M"]
|
||||
C --> E["بررسی موفقیت پرداخت"]
|
||||
D --> E
|
||||
E --> F["شارژ کیف پول"]
|
||||
F --> G["مدال قرارداد باشگاه مشتریان"]
|
||||
G --> H["تایید OTP + امضا"]
|
||||
H --> I["فعالسازی عضویت باشگاه"]
|
||||
I --> J["اختصاص فیچرها"]
|
||||
I --> K["ایجاد Cycle"]
|
||||
I --> L["اضافه به Commission Pool"]
|
||||
J --> M["✅ کاربر فعال — لینک معرف"]
|
||||
```
|
||||
|
||||
### ۲.۲ جریان پول فعلی
|
||||
|
||||
```
|
||||
کاربر ۵۶M پرداخت میکند
|
||||
│
|
||||
├── Balance (کیف پول عادی) += ۵۶,۰۰۰,۰۰۰ ریال
|
||||
├── DiscountBalance (اعتباری) += ۱۱۲,۰۰۰,۰۰۰ ریال (×۲)
|
||||
│
|
||||
└── Club Activation:
|
||||
├── CommissionPool += ۲۵,۲۰۰,۰۰۰ ریال (ClubActivationFee)
|
||||
└── GiftValue = ۲۵,۲۰۰,۰۰۰ ریال (اطلاعرسانی)
|
||||
```
|
||||
|
||||
### ۲.۳ مقادیر Hardcoded فعلی (`SystemConstants.cs`)
|
||||
|
||||
| ثابت | مقدار | کاربرد |
|
||||
|------|-------|--------|
|
||||
| `BasePackageAmount` | ۵۶,۰۰۰,۰۰۰ | قیمت پکیج |
|
||||
| `DayaLoanAmount` | ۵۶,۰۰۰,۰۰۰ | مبلغ وام دایا |
|
||||
| `ClubActivationFee` | ۲۵,۲۰۰,۰۰۰ | سهم هفتگی Commission Pool |
|
||||
| `ClubMembershipGiftValue` | ۲۵,۲۰۰,۰۰۰ | ارزش هدیه حق عضویت |
|
||||
| `MagicWalletMultiplier` | ×۲.۵ | ضریب کیف پول جادویی |
|
||||
|
||||
### ۲.۴ مشکلات فعلی
|
||||
|
||||
| # | مشکل | فایل |
|
||||
|---|------|------|
|
||||
| ۱ | پکیج ID=4 **hardcoded** در `InitiateBasePackagePaymentCommandHandler` | Application/Commands |
|
||||
| ۲ | مبلغ ۵۶M **hardcoded** در `SystemConstants` و چندین handler | Domain/Common |
|
||||
| ۳ | فیچرها **همه یکجا** assign میشن (۴ فیچر ثابت: چتیکا، بیمه، تریپ، لرن) | ActivateClubMembershipHandler |
|
||||
| ۴ | Commission Pool فقط با `ClubActivationFee` ثابت پر میشه | ActivateClubMembershipHandler |
|
||||
| ۵ | `DiscountBalance = Amount × 2` — ضریب hardcoded | VerifyPayment handlers |
|
||||
| ۶ | فرانتاند فقط یک مسیر خرید نشون میده | FrontOffice pages |
|
||||
|
||||
---
|
||||
|
||||
## ۳. طراحی پیشنهادی (TO-BE)
|
||||
|
||||
### ۳.۱ فلوی جدید فعالسازی
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["کاربر وارد سیستم"] --> B["صفحه پکیجها<br/>(کاشیهای نقرهای/طلایی/الماسی/...)"]
|
||||
|
||||
B -->|"کلیک روی پکیج"| C{"نوع پکیج"}
|
||||
|
||||
C -->|"پکیج پایه (طلایی)"| D["مدال با دو گزینه:<br/>۱. خرید الماس دایا<br/>۲. پرداخت مستقیم"]
|
||||
C -->|"پکیجهای دیگر"| E["مدال با یک گزینه:<br/>فقط پرداخت مستقیم<br/>+ توضیحات + قیمت"]
|
||||
|
||||
D -->|"دایا"| F["فلوی دایا"]
|
||||
D -->|"مستقیم"| G["درگاه پرداخت"]
|
||||
E --> G
|
||||
|
||||
F --> H["پرداخت موفق"]
|
||||
G --> H
|
||||
|
||||
H --> I["شارژ کیف پول<br/>(متناسب با قیمت پکیج)"]
|
||||
I --> J["مدال قرارداد باشگاه"]
|
||||
J --> K["OTP + امضا"]
|
||||
K --> L["فعالسازی<br/>+ اختصاص فیچرهای پکیج"]
|
||||
L --> M["✅ کاربر فعال"]
|
||||
```
|
||||
|
||||
### ۳.۲ تغییرات Entity — Package
|
||||
|
||||
**فعلی:**
|
||||
```csharp
|
||||
public class Package : BaseAuditableEntity
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string ImagePath { get; set; }
|
||||
public long Price { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**پیشنهادی:**
|
||||
```csharp
|
||||
public class Package : BaseAuditableEntity
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string ImagePath { get; set; }
|
||||
public long Price { get; set; } // قیمت پکیج (ریال)
|
||||
|
||||
// === فیلدهای جدید ===
|
||||
public int SortOrder { get; set; } // ترتیب نمایش
|
||||
public bool IsActive { get; set; } = true; // فعال/غیرفعال
|
||||
public bool IsBasePackage { get; set; } // آیا پکیج پایه است؟
|
||||
public bool SupportsDayaPurchase { get; set; } // پشتیبانی از خرید دایا
|
||||
public bool SupportsDirectPurchase { get; set; } = true; // پشتیبانی از پرداخت مستقیم
|
||||
|
||||
// === محاسبات مالی ===
|
||||
public long ActivationFee { get; set; } // سهم Commission Pool
|
||||
public long GiftValue { get; set; } // ارزش هدیه
|
||||
public decimal DiscountMultiplier { get; set; } = 2.0m; // ضریب شارژ DiscountBalance
|
||||
|
||||
// === Navigation ===
|
||||
public virtual ICollection<PackageFeature> PackageFeatures { get; set; }
|
||||
public virtual ICollection<UserPackagePurchase> Purchases { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### ۳.۳ Entity جدید — PackageFeature (پل بین پکیج و فیچر)
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// مشخص میکند هر پکیج چه فیچرهایی را فعال میکند
|
||||
/// </summary>
|
||||
public class PackageFeature : BaseAuditableEntity
|
||||
{
|
||||
public long PackageId { get; set; }
|
||||
public virtual Package Package { get; set; }
|
||||
|
||||
public long ClubFeatureId { get; set; }
|
||||
public virtual ClubFeature ClubFeature { get; set; }
|
||||
|
||||
public bool IsIncluded { get; set; } = true; // آیا این فیچر در پکیج هست؟
|
||||
}
|
||||
```
|
||||
|
||||
### ۳.۴ تغییرات Entity — ClubMembership
|
||||
|
||||
```csharp
|
||||
public class ClubMembership : BaseAuditableEntity
|
||||
{
|
||||
// ... فیلدهای فعلی حفظ میشوند ...
|
||||
|
||||
// === فیلد جدید ===
|
||||
public long PackageId { get; set; } // کدام پکیج خریداری شده
|
||||
public virtual Package Package { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### ۳.۵ تغییرات Entity — ClubMembershipCycle
|
||||
|
||||
```csharp
|
||||
public class ClubMembershipCycle : BaseAuditableEntity
|
||||
{
|
||||
// ... فیلدهای فعلی حفظ میشوند ...
|
||||
|
||||
// === فیلد جدید ===
|
||||
public long PackageId { get; set; } // پکیج این سایکل
|
||||
public virtual Package Package { get; set; }
|
||||
// PackageAmount قبلاً وجود دارد — از Package.Price پر میشود
|
||||
}
|
||||
```
|
||||
|
||||
### ۳.۶ تغییرات Entity — WeeklyCommissionPool
|
||||
|
||||
```csharp
|
||||
public class WeeklyCommissionPool : BaseAuditableEntity
|
||||
{
|
||||
// ... فیلدهای فعلی حفظ میشوند ...
|
||||
|
||||
// === فیلد جدید ===
|
||||
public long PackageId { get; set; } // Pool جداگانه برای هر پکیج
|
||||
public virtual Package Package { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### ۳.۷ جریان پول جدید
|
||||
|
||||
```
|
||||
پکیج نقرهای (۵.۶M):
|
||||
├── Balance += ۵,۶۰۰,۰۰۰
|
||||
├── DiscountBalance += ۱۱,۲۰۰,۰۰۰ (×۲)
|
||||
└── CommissionPool += ActivationFee مخصوص نقرهای
|
||||
|
||||
پکیج طلایی/پایه (۵۶M):
|
||||
├── Balance += ۵۶,۰۰۰,۰۰۰
|
||||
├── DiscountBalance += ۱۱۲,۰۰۰,۰۰۰ (×۲)
|
||||
└── CommissionPool += ۲۵,۲۰۰,۰۰۰
|
||||
|
||||
پکیج الماسی (??M):
|
||||
├── Balance += ??
|
||||
├── DiscountBalance += ?? (×۲)
|
||||
└── CommissionPool += ActivationFee مخصوص الماسی
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۴. محاسبه پورسانت — تغییرات
|
||||
|
||||
### ۴.۱ وضعیت فعلی
|
||||
|
||||
```
|
||||
یک WeeklyCommissionPool برای کل هفته
|
||||
↓
|
||||
TotalAmount = مجموع ActivationFee همه فعالسازیها
|
||||
↓
|
||||
ValuePerBalance = TotalAmount ÷ مجموع Balanceها
|
||||
↓
|
||||
همه یکسان محاسبه میشوند
|
||||
```
|
||||
|
||||
### ۴.۲ وضعیت هدف
|
||||
|
||||
```
|
||||
برای هر پکیج، یک WeeklyCommissionPool جداگانه:
|
||||
|
||||
Pool_نقرهای:
|
||||
TotalAmount = مجموع ActivationFee خریداران نقرهای این هفته
|
||||
Balanceها = فقط از شبکه خریداران نقرهای
|
||||
ValuePerBalance = Pool_نقرهای ÷ Balance_نقرهای
|
||||
|
||||
Pool_طلایی:
|
||||
TotalAmount = مجموع ActivationFee خریداران طلایی این هفته
|
||||
Balanceها = فقط از شبکه خریداران طلایی
|
||||
ValuePerBalance = Pool_طلایی ÷ Balance_طلایی
|
||||
```
|
||||
|
||||
### ۴.۳ نکته مهم: ساختار شبکه یکی است
|
||||
|
||||
```
|
||||
[Ali]
|
||||
/ \
|
||||
[Sara] [Reza] ← شبکه باینری یکیست
|
||||
/ \ / \
|
||||
[M1] [M2] [M3] [M4]
|
||||
|
||||
ولی محاسبات جدا:
|
||||
- Ali با پکیج طلایی → پورسانت از Pool طلایی
|
||||
- Sara با پکیج نقرهای → پورسانت از Pool نقرهای
|
||||
- Reza با پکیج طلایی → پورسانت از Pool طلایی
|
||||
```
|
||||
|
||||
### ۴.۴ تغییرات Stored Procedure
|
||||
|
||||
**`sp_CalculateWeeklyBalances`** باید:
|
||||
- پارامتر `@PackageId` بگیرد
|
||||
- فقط کاربرانی که این پکیج را خریدهاند فیلتر کند
|
||||
- برای هر پکیج جداگانه اجرا شود
|
||||
|
||||
**`sp_CalculateWeeklyCommissionPool`** باید:
|
||||
- پارامتر `@PackageId` بگیرد
|
||||
- Pool مخصوص آن پکیج را بخواند
|
||||
- پرداختها فقط به خریداران آن پکیج اختصاص یابد
|
||||
|
||||
---
|
||||
|
||||
## ۵. فیچرهای باشگاه مشتریان بر اساس پکیج
|
||||
|
||||
### ۵.۱ وضعیت فعلی
|
||||
|
||||
وقتی کاربر فعال میشود، **همه ۴ فیچر** یکجا assign میشوند:
|
||||
```csharp
|
||||
// ActivateClubMembershipCommandHandler — خط ~350
|
||||
var allFeatureIds = ClubFeatureTypeExtensions.GetAllFeatureIds();
|
||||
foreach (var featureId in allFeatureIds)
|
||||
{
|
||||
userClubFeatures.Add(new UserClubFeature { ... });
|
||||
}
|
||||
```
|
||||
|
||||
### ۵.۲ وضعیت هدف
|
||||
|
||||
فیچرها بر اساس جدول `PackageFeature` تعیین میشوند:
|
||||
|
||||
| فیچر | نقرهای | طلایی (پایه) | الماسی |
|
||||
|------|---------|-------------|--------|
|
||||
| چتیکا | ❌ | ✅ | ✅ |
|
||||
| بیمه | ❌ | ✅ | ✅ |
|
||||
| تریپ | ✅ | ✅ | ✅ |
|
||||
| لرن | ✅ | ✅ | ✅ |
|
||||
| فیچر VIP | ❌ | ❌ | ✅ |
|
||||
|
||||
*مقادیر بالا نمونهای هستند — قابل تنظیم از BackOffice*
|
||||
|
||||
### ۵.۳ تغییر در ActivateClubMembershipHandler
|
||||
|
||||
```
|
||||
قبلی:
|
||||
GetAllFeatureIds() → assign all
|
||||
|
||||
جدید:
|
||||
Package.PackageFeatures
|
||||
.Where(pf => pf.IsIncluded)
|
||||
.Select(pf => pf.ClubFeatureId)
|
||||
→ assign only included features
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۶. تغییرات UI — FrontOffice
|
||||
|
||||
### ۶.۱ صفحه پکیجها (کاشیها)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ انتخاب پکیج باشگاه مشتریان │
|
||||
├─────────────┬──────────────┬──────────────┬─────────┤
|
||||
│ │ │ │ │
|
||||
│ 🥈 نقرهای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │
|
||||
│ ۵.۶M │ ۵۶M │ ؟؟M │ ؟؟M │
|
||||
│ │ │ │ │
|
||||
│ ● لرن │ ● چتیکا │ ● همه │ ● همه │
|
||||
│ ● تریپ │ ● بیمه │ ● + VIP │ ● +... │
|
||||
│ │ ● تریپ │ │ │
|
||||
│ │ ● لرن │ │ │
|
||||
│ │ │ │ │
|
||||
│ [انتخاب] │ [انتخاب] │ [انتخاب] │[انتخاب]│
|
||||
└─────────────┴──────────────┴──────────────┴─────────┘
|
||||
```
|
||||
|
||||
### ۶.۲ مدال پرداخت — پکیج پایه (طلایی)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ خرید پکیج طلایی — ۵۶M تومان │
|
||||
│ │
|
||||
│ توضیحات: ... │
|
||||
│ │
|
||||
│ روشهای پرداخت: │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ 💎 خرید از طریق الماس دایا │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ 💳 پرداخت مستقیم (درگاه بانکی) │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### ۶.۳ مدال پرداخت — پکیجهای دیگر (نقرهای و بالاتر)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ خرید پکیج نقرهای — ۵.۶M تومان │
|
||||
│ │
|
||||
│ توضیحات: ... │
|
||||
│ ویژگیها: لرن، تریپ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ 💳 پرداخت و فعالسازی │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۷. بخشهای تحت تاثیر (Impact Analysis)
|
||||
|
||||
### ۷.۱ جدول تاثیرپذیری
|
||||
|
||||
| # | لایه | فایل/بخش | نوع تغییر | شدت |
|
||||
|---|------|----------|-----------|-----|
|
||||
| ۱ | **Domain** | `Package.cs` | اضافه فیلد | 🟡 متوسط |
|
||||
| ۲ | **Domain** | `PackageFeature.cs` — **جدید** | Entity جدید | 🔴 زیاد |
|
||||
| ۳ | **Domain** | `ClubMembership.cs` | اضافه `PackageId` | 🟡 متوسط |
|
||||
| ۴ | **Domain** | `ClubMembershipCycle.cs` | اضافه `PackageId` | 🟡 متوسط |
|
||||
| ۵ | **Domain** | `WeeklyCommissionPool.cs` | اضافه `PackageId` | 🔴 زیاد |
|
||||
| ۶ | **Domain** | `SystemConstants.cs` | حذف hardcodeها → خوانش از Package | 🟡 متوسط |
|
||||
| ۷ | **Application** | `ActivateClubMembershipCommandHandler` | فیچر بر اساس پکیج | 🔴 زیاد |
|
||||
| ۸ | **Application** | `InitiateBasePackagePaymentCommandHandler` | حذف ID=4 hardcoded | 🟡 متوسط |
|
||||
| ۹ | **Application** | `VerifyBasePackagePaymentCommandHandler` | شارژ متناسب با پکیج | 🔴 زیاد |
|
||||
| ۱۰ | **Application** | `VerifyPackagePurchasePaymentCommandHandler` | شارژ متناسب با پکیج | 🔴 زیاد |
|
||||
| ۱۱ | **Application** | `ManualPaymentCommandHandler` | شارژ متناسب با پکیج | 🟡 متوسط |
|
||||
| ۱۲ | **Application** | `CustomerPurchasePackageCommandHandler` | پشتیبانی روشهای پرداخت پکیج | 🟡 متوسط |
|
||||
| ۱۳ | **Infra** | `sp_CalculateWeeklyBalances` | پارامتر PackageId | 🔴 زیاد |
|
||||
| ۱۴ | **Infra** | `sp_CalculateWeeklyCommissionPool` | Pool جداگانه هر پکیج | 🔴 زیاد |
|
||||
| ۱۵ | **Infra** | `WeeklyCommissionCalculationService` | Loop روی پکیجها | 🟡 متوسط |
|
||||
| ۱۶ | **Infra** | EF Configurations | جدول جدید + FKها | 🟡 متوسط |
|
||||
| ۱۷ | **Infra** | Database Migration | schema changes | 🟡 متوسط |
|
||||
| ۱۸ | **Proto** | `package.proto` | فیلدهای جدید پکیج | 🟢 کم |
|
||||
| ۱۹ | **Proto** | `clubmembership.proto` | PackageId در response | 🟢 کم |
|
||||
| ۲۰ | **Proto** | `commission.proto` | PackageId در pool/payout | 🟢 کم |
|
||||
| ۲۱ | **FrontOffice** | صفحه انتخاب پکیج | UI جدید (کاشیها) | 🔴 زیاد |
|
||||
| ۲۲ | **FrontOffice** | مدال پرداخت | دو مدال متفاوت | 🔴 زیاد |
|
||||
| ۲۳ | **FrontOffice** | `MyPackages.razor` | نمایش نوع پکیج | 🟡 متوسط |
|
||||
| ۲۴ | **FrontOffice** | `ActivateClubDialog.razor` | ارتباط با پکیج | 🟡 متوسط |
|
||||
| ۲۵ | **BackOffice** | صفحه مدیریت پکیجها | CRUD فیلدهای جدید | 🟡 متوسط |
|
||||
| ۲۶ | **BackOffice** | صفحه فیچر پکیجها — **جدید** | ماتریس پکیج×فیچر | 🔴 زیاد |
|
||||
| ۲۷ | **BackOffice** | `ActivateClubDialog.razor` | انتخاب پکیج | 🟡 متوسط |
|
||||
|
||||
### ۷.۲ ریسکها
|
||||
|
||||
| ریسک | احتمال | شدت | راهحل |
|
||||
|------|--------|-----|--------|
|
||||
| دادههای فعلی — کاربران بدون PackageId | قطعی | زیاد | Migration: کاربران فعلی → PackageId = پکیج پایه |
|
||||
| Commission Pool فعلی بدون PackageId | قطعی | زیاد | Migration: Poolهای موجود → PackageId = پکیج پایه |
|
||||
| SP تغییر → محاسبات اشتباه | متوسط | بحرانی | تست جامع + محیط staging |
|
||||
| مبالغ hardcoded در جاهای پراکنده | زیاد | متوسط | Audit کامل کدبیس |
|
||||
| عدم سازگاری FrontOffice/BackOffice | متوسط | متوسط | تست end-to-end |
|
||||
|
||||
---
|
||||
|
||||
## ۸. فازبندی پیادهسازی
|
||||
|
||||
### فاز ۱ — زیرساخت (Domain + DB) ≈ ۳-۴ روز
|
||||
|
||||
| تسک | شرح |
|
||||
|-----|------|
|
||||
| T1.1 | بروزرسانی `Package` entity (فیلدهای جدید) |
|
||||
| T1.2 | ایجاد `PackageFeature` entity + EF Configuration |
|
||||
| T1.3 | اضافه کردن `PackageId` به `ClubMembership` |
|
||||
| T1.4 | اضافه کردن `PackageId` به `ClubMembershipCycle` |
|
||||
| T1.5 | اضافه کردن `PackageId` به `WeeklyCommissionPool` |
|
||||
| T1.6 | Database Migration + Seed data (پکیج پایه + فیچرها) |
|
||||
| T1.7 | Migration: کاربران/Poolهای فعلی → PackageId = پکیج پایه |
|
||||
| T1.8 | بروزرسانی Protoها |
|
||||
|
||||
### فاز ۲ — منطق کسبوکار (Application) ≈ ۴-۵ روز
|
||||
|
||||
| تسک | شرح |
|
||||
|-----|------|
|
||||
| T2.1 | بروزرسانی `ActivateClubMembershipCommandHandler` — فیچر بر اساس پکیج |
|
||||
| T2.2 | بروزرسانی Verify handlers — شارژ کیف پول متناسب با پکیج |
|
||||
| T2.3 | حذف مقادیر hardcoded از `SystemConstants` → خوانش از Package |
|
||||
| T2.4 | بروزرسانی `InitiateBasePackagePayment` → Generic `InitiatePackagePayment` |
|
||||
| T2.5 | بروزرسانی `ManualPaymentCommandHandler` — پشتیبانی پکیج متغیر |
|
||||
| T2.6 | CRUD پکیج با فیلدهای جدید (gRPC handlers) |
|
||||
| T2.7 | CRUD `PackageFeature` (ماتریس پکیج×فیچر) |
|
||||
|
||||
### فاز ۳ — محاسبه پورسانت ≈ ۳-۴ روز
|
||||
|
||||
| تسک | شرح |
|
||||
|-----|------|
|
||||
| T3.1 | بروزرسانی `sp_CalculateWeeklyBalances` — فیلتر بر اساس PackageId |
|
||||
| T3.2 | بروزرسانی `sp_CalculateWeeklyCommissionPool` — Pool جداگانه |
|
||||
| T3.3 | بروزرسانی `WeeklyCommissionCalculationService` — Loop روی پکیجها |
|
||||
| T3.4 | تست محاسبات با داده واقعی |
|
||||
|
||||
### فاز ۴ — UI (FrontOffice + BackOffice) ≈ ۴-۵ روز
|
||||
|
||||
| تسک | شرح |
|
||||
|-----|------|
|
||||
| T4.1 | صفحه کاشیهای پکیج (FrontOffice) |
|
||||
| T4.2 | مدال پرداخت پکیج پایه (دایا + مستقیم) |
|
||||
| T4.3 | مدال پرداخت پکیجهای دیگر (فقط مستقیم) |
|
||||
| T4.4 | بروزرسانی `MyPackages.razor` — نمایش نوع پکیج |
|
||||
| T4.5 | بروزرسانی `ActivateClubDialog.razor` — ارتباط با پکیج |
|
||||
| T4.6 | BackOffice: CRUD پکیج با فیلدهای جدید |
|
||||
| T4.7 | BackOffice: صفحه ماتریس فیچرهای پکیج |
|
||||
| T4.8 | BackOffice: `ActivateClubDialog` — انتخاب پکیج |
|
||||
|
||||
### فاز ۵ — تست و استقرار ≈ ۲-۳ روز
|
||||
|
||||
| تسک | شرح |
|
||||
|-----|------|
|
||||
| T5.1 | تست end-to-end فلوی خرید هر پکیج |
|
||||
| T5.2 | تست محاسبه پورسانت جداگانه |
|
||||
| T5.3 | تست migration دادههای فعلی |
|
||||
| T5.4 | Deploy به staging + تست |
|
||||
| T5.5 | Deploy به production |
|
||||
|
||||
---
|
||||
|
||||
## ۹. Seed Data — پکیجهای اولیه
|
||||
|
||||
```sql
|
||||
-- Migration: Seed packages
|
||||
INSERT INTO Packages (Title, Description, Price, IsActive, IsBasePackage,
|
||||
SupportsDayaPurchase, SupportsDirectPurchase, ActivationFee, GiftValue,
|
||||
DiscountMultiplier, SortOrder)
|
||||
VALUES
|
||||
('نقرهای', 'پکیج نقرهای باشگاه مشتریان', 5600000, 1, 0,
|
||||
0, 1, ???, ???, 2.0, 1),
|
||||
('طلایی', 'پکیج طلایی باشگاه مشتریان (پایه)', 56000000, 1, 1,
|
||||
1, 1, 25200000, 25200000, 2.0, 2);
|
||||
|
||||
-- Migration: ربط فیچرها به پکیجها
|
||||
INSERT INTO PackageFeatures (PackageId, ClubFeatureId, IsIncluded) VALUES
|
||||
-- نقرهای: فقط تریپ و لرن
|
||||
(@silverId, @tripId, 1),
|
||||
(@silverId, @learnId, 1),
|
||||
-- طلایی: همه فیچرها
|
||||
(@goldId, @chatikaId, 1),
|
||||
(@goldId, @bimeId, 1),
|
||||
(@goldId, @tripId, 1),
|
||||
(@goldId, @learnId, 1);
|
||||
|
||||
-- Migration: کاربران فعلی → پکیج پایه
|
||||
UPDATE ClubMemberships SET PackageId = @goldId WHERE PackageId IS NULL;
|
||||
UPDATE ClubMembershipCycles SET PackageId = @goldId WHERE PackageId IS NULL;
|
||||
UPDATE WeeklyCommissionPools SET PackageId = @goldId WHERE PackageId IS NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۱۰. سوالات باز (نیاز به تصمیمگیری)
|
||||
|
||||
| # | سوال | گزینهها |
|
||||
|---|------|---------|
|
||||
| ۱ | `ActivationFee` و `GiftValue` پکیج نقرهای چقدر باشد؟ | نسبت به قیمت؟ مقدار ثابت؟ |
|
||||
| ۲ | آیا کاربر میتواند بعداً پکیج خود را ارتقا دهد (upgrade)؟ | بله → فقط مابهالتفاوت / خیر |
|
||||
| ۳ | `DiscountMultiplier` برای همه پکیجها ×۲ باشد؟ | یکسان / متفاوت به ازای هر پکیج |
|
||||
| ۴ | ضریب `MagicWallet` (×۲.۵) برای پکیجهای کوچکتر هم همان باشد؟ | بله / خیر |
|
||||
| ۵ | فیچرهای پکیج نقرهای دقیقاً کدامها هستند؟ | لرن+تریپ؟ فقط لرن؟ |
|
||||
| ۶ | آیا یک کاربر میتواند چند پکیج همزمان داشته باشد؟ | فقط یکی / امکان خرید چندتا |
|
||||
| ۷ | نام و تعداد دقیق پکیجها چیست؟ | نقرهای+طلایی؟ بیشتر؟ |
|
||||
| ۸ | کاربرانی که با دایا فعال شدن، چه پکیجی دارند؟ | طلایی (پایه) |
|
||||
|
||||
---
|
||||
|
||||
## ۱۱. تخمین زمانی
|
||||
|
||||
| فاز | مدت | وابستگی |
|
||||
|-----|------|---------|
|
||||
| فاز ۱ — زیرساخت | ۳-۴ روز | — |
|
||||
| فاز ۲ — منطق | ۴-۵ روز | فاز ۱ |
|
||||
| فاز ۳ — پورسانت | ۳-۴ روز | فاز ۱ |
|
||||
| فاز ۴ — UI | ۴-۵ روز | فاز ۲ |
|
||||
| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ |
|
||||
| **مجموع** | **~۱۶-۲۱ روز کاری** | |
|
||||
|
||||
> فازهای ۲ و ۳ قابل موازیسازی هستند.
|
||||
@@ -1,7 +1,7 @@
|
||||
# 💰 سیستم مالی، پرداخت و درگاهها
|
||||
|
||||
> **منابع ادغامشده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
|
||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فعالسازی درگاه ZarinPal + تنظیمات محیطی)
|
||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس WalletChangeLog + validation کیفپول اعتباری + نامگذاری جدید کیفپولها)
|
||||
|
||||
---
|
||||
|
||||
@@ -24,9 +24,9 @@ flowchart TD
|
||||
PYMS --> WALLETS
|
||||
|
||||
subgraph WALLETS["3 Wallet System"]
|
||||
W1["💰 Balance\nنقدی"]
|
||||
W2["🌟 NetworkBalance\nشبکه"]
|
||||
W3["🏷️ DiscountBalance\nاعتباری"]
|
||||
W1["💰 Balance\nکیف پول اصلی"]
|
||||
W2["🌟 NetworkBalance\nپاداش تیمی"]
|
||||
W3["🏷️ DiscountBalance\nکیف پول اعتباری"]
|
||||
end
|
||||
```
|
||||
|
||||
@@ -137,7 +137,11 @@ flowchart TD
|
||||
قیمت محصول = 1,000,000 ریال
|
||||
MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخصوص خود را دارد)
|
||||
|
||||
سهم تخفیف = MIN(1,000,000 × 40%, DiscountBalanceکاربر) = 400,000
|
||||
سهم تخفیف = قیمت × MaxDiscountPercent% = 400,000
|
||||
|
||||
⚠️ اگر DiscountBalance < سهم تخفیف → خطا: «موجودی کیف پول اعتباری کافی نیست»
|
||||
(دیگر MIN استفاده نمیشود — کاربر باید موجودی کافی داشته باشد)
|
||||
|
||||
باقیمانده → ZarinPal IPG = 1,000,000 - 400,000 = 600,000
|
||||
─────────
|
||||
مجموع = 1,000,000
|
||||
@@ -151,11 +155,15 @@ MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخص
|
||||
flowchart TD
|
||||
A["کاربر عضو باشگاه\nمشاهده محصول"] --> B["قیمت تخفیفخورده نمایش داده میشود"]
|
||||
B --> C["افزودن به سبد\nمحاسبه MaxDiscount% هر محصول"]
|
||||
C --> D["سهم تخفیف = MIN(قیمت×MaxDiscount%, DiscountBalance)"]
|
||||
D --> E["باقیمانده = مجموع - سهم تخفیف"]
|
||||
C --> D["سهم تخفیف = قیمت × MaxDiscount%"]
|
||||
D --> V{"DiscountBalance >= سهم تخفیف?"}
|
||||
V -->|خیر| X["❌ خطا: موجودی کیف پول اعتباری کافی نیست"]
|
||||
V -->|بله| E["باقیمانده = مجموع - سهم تخفیف"]
|
||||
E --> F{"باقیمانده > 0?"}
|
||||
F -->|بله| G["کسر DiscountBalance\n+ Redirect → ZarinPal IPG\nباقیمانده + 10% VAT"]
|
||||
F -->|خیر| H["فقط کسر از DiscountBalance\nبدون درگاه → ثبت مستقیم"]
|
||||
G --> LOG["ثبت UserWalletChangeLog"]
|
||||
H --> LOG
|
||||
```
|
||||
|
||||
### ۵.۳ دسترسی فروشگاه اعتباری
|
||||
@@ -164,8 +172,21 @@ flowchart TD
|
||||
|------|--------|
|
||||
| `IsClubMember = true` | دسترسی به Discount Store |
|
||||
| `IsClubMember = false` | فقط Regular Store |
|
||||
| `DiscountBalance > 0` | میتواند از تخفیف استفاده کند |
|
||||
| `DiscountBalance = 0` | پرداخت ۱۰۰% از طریق ZarinPal IPG |
|
||||
| `DiscountBalance >= سهم تخفیف` | خرید مجاز |
|
||||
| `DiscountBalance < سهم تخفیف` | ❌ خطا: موجودی کیف پول اعتباری کافی نیست |
|
||||
|
||||
### ۵.۴ UserWalletChangeLog (اسفند ۱۴۰۴ — فیکس)
|
||||
|
||||
> **باگ:** هنگام خرید از فروشگاه اعتباری، `DiscountBalance` در دیتابیس کم میشد ولی هیچ
|
||||
> `UserWalletChangeLog` ثبت نمیشد → کاربر در تاریخچه کیفپول چیزی نمیدید.
|
||||
|
||||
فیکس در ۳ هندلر:
|
||||
|
||||
| هندلر | سناریو | فیکس |
|
||||
|--------|---------|------|
|
||||
| `PlaceOrderCommandHandler` | پرداخت کامل با DiscountBalance (بدون درگاه) | ✅ ثبت log با `ChangeDiscountValue = -amount` |
|
||||
| `CompleteOrderPaymentCommandHandler` | پرداخت ترکیبی (درگاه + DiscountBalance) | ✅ ثبت log بعد از verify موفق درگاه |
|
||||
| `VerifyDiscountWalletChargeCommandHandler` | شارژ کیفپول اعتباری | ✅ ثبت log با `ChangeDiscountValue = +amount` |
|
||||
|
||||
---
|
||||
|
||||
@@ -221,6 +242,18 @@ service PaymentService {
|
||||
| وام دایا | ✅ کامل | Mock mode فعال در staging |
|
||||
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
|
||||
| Pool هفتگی | ✅ کامل | SP + Hangfire |
|
||||
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیفپول در ۳ هندلر اضافه شد |
|
||||
| Validation کیفپول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
|
||||
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
|
||||
| Refund | ⬜ طراحی | فقط در PYMS تعریفشده |
|
||||
| کیفپول جادویی (Magic) | ✅ کامل | فاز 1-6 پیادهسازی شده — Production فعال |
|
||||
|
||||
### ۸.۱ نامگذاری استاندارد کیفپولها (اسفند ۱۴۰۴)
|
||||
|
||||
| فیلد دیتابیس | نام قدیم (UI) | نام جدید (UI) |
|
||||
|-------------|--------------|---------------|
|
||||
| `Balance` | عادی / اعتباری / نقدی | **کیف پول اصلی** |
|
||||
| `DiscountBalance` | تخفیفی / تخفیف | **کیف پول اعتباری** |
|
||||
| `NetworkBalance` | شبکه / طلایی / پورسانت | **پاداش تیمی** |
|
||||
|
||||
> تغییرات UI در ۱۲ فایل (FrontOffice: 5, BackOffice: 7) اعمال شد.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 🛒 فروشگاه، موجودی و محصولات
|
||||
|
||||
> **منابع ادغامشده:** `discount-shop-business.md`, `DISCOUNT-STORE-STATUS.md`, `package-purchase-system.md`, `INVENTORY-IMPROVEMENTS.md`, `INVENTORY-REFACTORING-STATUS.md`, `PRODUCT-BUNDLE-FEATURE.md`, `SHOP-UNIFICATION.md`
|
||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: VAT 10%)
|
||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: ExpirePendingOrders ۱۵ دقیقه + فروشگاه اعتباری نامگذاری)
|
||||
|
||||
---
|
||||
|
||||
@@ -226,5 +226,44 @@ graph TD
|
||||
| Lazy Loading | ✅ کامل | 100% |
|
||||
| موجودی خودکار | ✅ کامل | 100% |
|
||||
| تصاویر مربعی | ✅ کامل | 100% |
|
||||
| انقضای سفارشات Pending | ✅ کامل | 100% |
|
||||
| باندل محصولات | ⬜ طراحی | 30% |
|
||||
| مقایسه محصول | ⬜ ایده | 0% |
|
||||
|
||||
---
|
||||
|
||||
## ۹. انقضای خودکار سفارشات Pending (ExpirePendingOrdersService)
|
||||
|
||||
> سرویس پسزمینهای که سفارشات فروشگاه اعتباری را بعد از ۱۵ دقیقه منقضی میکند.
|
||||
|
||||
### ۹.۱ پارامترها
|
||||
|
||||
| پارامتر | مقدار | توضیح |
|
||||
|---------|-------|-------|
|
||||
| `ExpirationTime` | **۱۵ دقیقه** | مدت زمان مجاز برای پرداخت |
|
||||
| `CheckInterval` | ۵ دقیقه | فاصله بررسی |
|
||||
|
||||
### ۹.۲ عملکرد
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["هر ۵ دقیقه\nExpirePendingOrdersService"] --> B["جستجوی DiscountOrders\nPaymentStatus=Pending\nCreated < (now - 15 min)"]
|
||||
B --> C{"سفارشی یافت شد?"}
|
||||
C -->|خیر| A
|
||||
C -->|بله| D["آزادسازی رزرو موجودی\nReleaseReservationAsync"]
|
||||
D --> E["PaymentStatus → Reject\nDeliveryStatus → Cancelled"]
|
||||
E --> F["Transaction.PaymentStatus → Reject"]
|
||||
F --> G["Log: Expired order #X"]
|
||||
G --> A
|
||||
```
|
||||
|
||||
### ۹.۳ فایل
|
||||
|
||||
```
|
||||
CMS/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs
|
||||
```
|
||||
|
||||
رجیستر شده در `ConfigureServices.cs`:
|
||||
```csharp
|
||||
services.AddHostedService<ExpirePendingOrdersService>();
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 🚀 استقرار، CI/CD و زیرساخت
|
||||
|
||||
> **منابع ادغامشده:** `CICD-PIPELINE-GUIDE.md`, `DEPLOYMENT-README.md`, `INFRASTRUCTURE-GUIDE.md`, `INGRESS-NGINX-WARNING.md`, `OFFLINE-DEPLOYMENT-GUIDE.md`, `SERVER-MIRRORS-CONFIG.md`
|
||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: PVC آپلود + K8s Secret برای config دائمی + جدا کردن appsettings هر برنچ)
|
||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس URL پروداکشن + چریپیک فیکسهای WalletChangeLog/Validation/Expiry)
|
||||
|
||||
---
|
||||
|
||||
@@ -473,21 +473,55 @@ flowchart TD
|
||||
| `9288d06` | feat: externalize appsettings to K8s Secret — config persists independently |
|
||||
| `e72673c` | chore(staging): remove appsettings.Production.json (فقط kub-stage) |
|
||||
| `3ebe0f9` | chore(production): remove appsettings.Staging.json (فقط production) |
|
||||
| `f3ac5ad` | fix: add missing UserWalletChangeLog for discount shop purchases |
|
||||
| `0457ef6` | fix: validate discount wallet balance before applying discount |
|
||||
| `e206b71` | fix: reduce discount order expiry from 30 to 15 minutes |
|
||||
| `2620a24` | fix: correct production URLs from kbs1 to kbs2 in cms-config |
|
||||
|
||||
> کامیتهای PVC و Secret به هر دو شاخه push شدهاند.
|
||||
> ⚠️ کامیتهای حذف appsettings فقط به برنچ مربوطه push شده — cherry-pick نکنید!
|
||||
|
||||
### ۹.۳ فیکس URL پروداکشن (اسفند ۱۴۰۴)
|
||||
|
||||
> **مشکل:** در `cms-config.yaml` پروداکشن، URLها به اشتباه `kbs1.ir` (استیج) بودند.
|
||||
> زرینپال callback را به سرور استیج میفرستاد → خطای 401 → `Code=-1` (خطای ناشناخته).
|
||||
|
||||
| فیلد | مقدار اشتباه | مقدار صحیح |
|
||||
|------|-------------|------------|
|
||||
| `CmsBaseUrl` | `https://cms.kbs1.ir` | `https://cms.kbs2.ir` |
|
||||
| `FrontOfficeBaseUrl` | `https://kbs1.ir` | `https://kbs2.ir` |
|
||||
|
||||
```bash
|
||||
# فیکس مستقیم روی سرور (بدون نیاز به rebuild)
|
||||
kubectl apply -f cms-config.yaml
|
||||
kubectl rollout restart deployment/cms
|
||||
```
|
||||
|
||||
### ۹.۴ نامگذاری کیفپولها (اسفند ۱۴۰۴)
|
||||
|
||||
> تغییر عنوان کیفپولها در تمام UI (FrontOffice: 5 فایل، BackOffice: 7 فایل):
|
||||
|
||||
| فیلد | نام قدیم | نام جدید |
|
||||
|------|---------|----------|
|
||||
| `Balance` | عادی / نقدی | **کیف پول اصلی** |
|
||||
| `DiscountBalance` | تخفیفی / تخفیف | **کیف پول اعتباری** |
|
||||
| `NetworkBalance` | شبکه / طلایی | **پاداش تیمی** |
|
||||
|
||||
**تنظیمات محیطی Production (`appsettings.Production.json`):**
|
||||
|
||||
| تنظیم | مقدار |
|
||||
|--------|-------|
|
||||
| `ZarinPal.MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
|
||||
| `ZarinPal.UseSandbox` | `false` |
|
||||
| `CmsBaseUrl` | `https://cms.kbs2.ir` |
|
||||
| `FrontOfficeBaseUrl` | `https://kbs2.ir` |
|
||||
| `SeedWorkers.MagicWalletCycleSeed.Enabled` | `true` |
|
||||
| `Kestrel.Endpoints.Grpc.Protocols` | `Http2` |
|
||||
| `Seq.ServerUrl` | `http://seq-svc:5341` |
|
||||
| `ConnectionStrings.Default` | `Server=mssql-svc;Database=KBS` |
|
||||
|
||||
> ⚠️ **مهم:** URLها باید `kbs2.ir` باشند نه `kbs1.ir` — اشتباه در URL باعث خطای 401 زرینپال میشود.
|
||||
|
||||
```bash
|
||||
# k8s-health-check.sh (namespace = default)
|
||||
kubectl get pods
|
||||
|
||||
Reference in New Issue
Block a user