Compare commits
24 Commits
c78850f86e
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4218d08597 | |||
| 1db77b1a1b | |||
| e3850f9dd8 | |||
| 39590d2cbe | |||
| de69bf862c | |||
| 6fc15b474e | |||
| e9f1fb9911 | |||
| 085583c274 | |||
| f8908d8e2b | |||
| ba10b6485b | |||
| df1affa46e | |||
| 38aababc1a | |||
| e6d086b559 | |||
| a61a987b57 | |||
| d94b09878a | |||
| 0aa126af1c | |||
| 37330a3e45 | |||
| 977ef69e26 | |||
| 1885fcbd3b | |||
| 33d5ae9305 | |||
| 01244f426e | |||
| 3575e483b9 | |||
| 09b8b804d4 | |||
| dd5a2617cf |
@@ -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 | ۴-۵ روز | فاز ۲ |
|
||||||
|
| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ |
|
||||||
|
| **مجموع** | **~۱۶-۲۱ روز کاری** | |
|
||||||
|
|
||||||
|
> فازهای ۲ و ۳ قابل موازیسازی هستند.
|
||||||
+1617
-387
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
# 💰 سیستم مالی، پرداخت و درگاهها
|
# 💰 سیستم مالی، پرداخت و درگاهها
|
||||||
|
|
||||||
> **منابع ادغامشده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
|
> **منابع ادغامشده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس WalletChangeLog + validation کیفپول اعتباری + نامگذاری جدید کیفپولها)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: تصحیح مدل تومان/ریال + فیکس ZarinPal Verify + امنیت Callback URL)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -47,21 +47,41 @@ flowchart TD
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
|
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
|
||||||
B --> C["PYMS → ZarinPal API\nدریافت Authority"]
|
B --> C["PYMS → ZarinPal API\nمبلغ ×۱۰ (تومان→ریال)\nدریافت Authority"]
|
||||||
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
|
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
|
||||||
D --> E["بازگشت با Authority\nCMS VerifyPayment"]
|
D --> E["بازگشت با Authority\nCMS VerifyPayment (مبلغ ×۱۰)"]
|
||||||
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیفپول"]
|
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیفپول"]
|
||||||
E -->|ناموفق| G["❌ نمایش پیام خطا"]
|
E -->|ناموفق| G["❌ نمایش پیام خطا"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **✅ فیکس ZarinPal Verify (اسفند ۱۴۰۴ — `721661a`):**
|
||||||
|
> - **باگ:** `VerifyPaymentAsync(authority)` با ۲ آرگومان → amount=0 → ZarinPal Code=-1
|
||||||
|
> - **فیکس:** lookup `PaymentTransaction.Amount` از DB + استفاده از overload ۳ آرگومانه `VerifyPaymentAsync(authority, orderId, amount)`
|
||||||
|
> - `IPaymentGatewayService` — default impl ۳ آرگومانه اضافه شد
|
||||||
|
> - ۷ فایل تغییر: PackageService, TransactionsService, VerifyDiscountWalletChargeCommandHandler, VerifyPackagePurchaseCommandHandler, IPaymentGatewayService, MockPaymentGatewayService, DayaPaymentService
|
||||||
|
|
||||||
### ۲.۲ تنظیمات ZarinPal
|
### ۲.۲ تنظیمات ZarinPal
|
||||||
|
|
||||||
| پارامتر | مقدار |
|
| پارامتر | مقدار |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
|
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
|
||||||
| `CallbackUrl` | `/payment/callback` |
|
| `CallbackUrl` | از `appsettings.json` خوانده میشود (نه از ورودی کاربر) |
|
||||||
| `Sandbox` | `true` (staging) / `false` (production) |
|
| `Sandbox` | `true` (staging) / `false` (production) |
|
||||||
| `Currency` | IRR (ریال → تبدیل به تومان در UI) |
|
| `Currency` | DB: تومان — ZarinPal: ریال (×۱۰ هنگام ارسال) |
|
||||||
|
|
||||||
|
> **✅ مدل ارزی (تصحیح اسفند ۱۴۰۴):**
|
||||||
|
> - **DB:** `Package.Price` و همه مبالغ مالی به **تومان** ذخیره میشوند
|
||||||
|
> - **CMS → ZarinPal:** `ZarinPalPaymentService` مبلغ را ×۱۰ میکند (`amountInRials = amount * 10`)
|
||||||
|
> - **FrontOffice UI:** مبالغ مستقیم به تومان نمایش داده میشوند (بدون تبدیل)
|
||||||
|
> - **FrontOffice → CMS:** مبالغ به تومان ارسال میشوند (FO هیچ تبدیلی انجام نمیدهد)
|
||||||
|
> - **باگ قبلی ۱:** FO مبلغ تومان را ×۱۰ تبدیل میکرد + CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر (فیکس: `2f9ef15`)
|
||||||
|
> - **باگ قبلی ۲:** `FormattedPrice = Price / 10` اشتباه بود — Price از قبل تومان است (فیکس: `3c1a8ff` اصلاح شد)
|
||||||
|
|
||||||
|
> **✅ امنیت Callback URL (اسفند ۱۴۰۴):**
|
||||||
|
> - هیچ callback URL از ورودی کاربر خوانده نمیشود — همه از `appsettings.json` خوانده میشوند
|
||||||
|
> - `PackageService` و `TransactionsService`: از `FrontOfficeBaseUrl` config
|
||||||
|
> - `MagicWallet` و `DiscountWallet`: از `CmsBaseUrl` config
|
||||||
|
> - جلوگیری از حمله Open Redirect
|
||||||
|
|
||||||
> **تنظیمات محیطی:**
|
> **تنظیمات محیطی:**
|
||||||
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
|
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
|
||||||
@@ -239,11 +259,16 @@ service PaymentService {
|
|||||||
| ماژول | وضعیت | یادداشت |
|
| ماژول | وضعیت | یادداشت |
|
||||||
|-------|--------|---------|
|
|-------|--------|---------|
|
||||||
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
|
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
|
||||||
|
| ZarinPal Verify | ✅ فیکس شده | رفع amount=0 با overload ۳ آرگومانه (`721661a`) |
|
||||||
|
| Callback URL امنیت | ✅ فیکس شده | همه از config خوانده میشوند — جلوگیری از Open Redirect |
|
||||||
| وام دایا | ✅ کامل | Mock mode فعال در staging |
|
| وام دایا | ✅ کامل | Mock mode فعال در staging |
|
||||||
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
|
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
|
||||||
| Pool هفتگی | ✅ کامل | SP + Hangfire |
|
| Pool هفتگی | ✅ کامل | SP + Hangfire |
|
||||||
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیفپول در ۳ هندلر اضافه شد |
|
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیفپول در ۳ هندلر اضافه شد |
|
||||||
| Validation کیفپول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
|
| Validation کیفپول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
|
||||||
|
| Toman/Rial مدل | ✅ تصحیح شده | DB=تومان، فقط ZarinPal ریال (×۱۰) — FO بدون تبدیل |
|
||||||
|
| صفحه موفقیت پرداخت | ✅ بهبود | TransactionId + موجودی واقعی + دکمه بازگشت |
|
||||||
|
| PackagePurchaseDialog | ✅ کامل | دیالوگ داینامیک کاشیای با انتخاب روش پرداخت |
|
||||||
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
|
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
|
||||||
| Refund | ⬜ طراحی | فقط در PYMS تعریفشده |
|
| Refund | ⬜ طراحی | فقط در PYMS تعریفشده |
|
||||||
| کیفپول جادویی (Magic) | ✅ کامل | فاز 1-6 پیادهسازی شده — Production فعال |
|
| کیفپول جادویی (Magic) | ✅ کامل | فاز 1-6 پیادهسازی شده — Production فعال |
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# آدیت سرویسهای gRPC — CMS
|
||||||
|
|
||||||
|
> تاریخ: ۱۴۰۴/۰۴
|
||||||
|
> آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶
|
||||||
|
> هدف: شناسایی RPCهایی که از هیچکدام از فرانتها (FrontOffice مشتری + BackOffice ادمین) فراخوانی نمیشوند + تصمیمگیری نگهداری vs آرشیو
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 خلاصه آمار
|
||||||
|
|
||||||
|
| متریک | تعداد |
|
||||||
|
|--------|-------|
|
||||||
|
| کل فایلهای proto | 43 (بدون google/) |
|
||||||
|
| کل سرویسهای gRPC | 42 |
|
||||||
|
| **کل RPC متدها** | **342** |
|
||||||
|
| استفادهشده در FrontOffice | 92 |
|
||||||
|
| استفادهشده در BackOffice | 159 |
|
||||||
|
| **استفادهشده (مجموع یکتا)** | **217** |
|
||||||
|
| **استفادهنشده از فرانتها** | **125** |
|
||||||
|
| ↳ استفادهشده داخلی CMS | 101 |
|
||||||
|
| ↳ **کد مُرده واقعی** | **24** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴 بخش ۱ — تحلیل ۲۴ RPC مُرده: نگهداری vs آرشیو
|
||||||
|
|
||||||
|
### ✅ نگهداری (آیندهنگرانه — ۱۲ عدد)
|
||||||
|
|
||||||
|
> این RPCها پیادهسازی کامل دارند و در نقشهراه آینده محصول کاربرد دارند.
|
||||||
|
|
||||||
|
| # | RPC | فایل Proto | کیفیت کد | دلیل نگهداری |
|
||||||
|
|---|-----|-----------|---------|-------------|
|
||||||
|
| 1 | `CustomerReorderPreviousOrder` | userorder.proto | ✅ **کامل** — آیتمهای سفارش قبلی به سبد اضافه میشود | UX حیاتی: «تکرار سفارش قبلی» — فیچر رایج فروشگاهی، فقط نیاز به دکمه در FrontOffice |
|
||||||
|
| 2 | `CustomerTrackOrder` | userorder.proto | ✅ **کامل** — وضعیت + TrackingCode + DeliveryInfo | UX حیاتی: «ردیابی سفارش» — وقتی ارسال پستی فعال شود ضروری است |
|
||||||
|
| 3 | `CalculateOrderPV` | userorder.proto | ✅ **کامل** — PV هر آیتم + جمع کل | سیستم MLM: محاسبه PV (Point Value) سفارش — برای فاز بعدی کمیسیون بر اساس خرید |
|
||||||
|
| 4 | `GetInventorySummary` | inventory.proto | ✅ **کامل** — آمار تعداد + ارزش کل | داشبورد ادمین: خلاصه موجودی انبار — نیاز به کارت در BackOffice Dashboard |
|
||||||
|
| 5 | `GetStockValueReport` | inventory.proto | ✅ **کامل** — گزارش ارزش ریالی موجودی | گزارش مالی: ارزش دارایی انبار — برای حسابداری ضروری |
|
||||||
|
| 6 | `BulkAddStock` | inventory.proto | ✅ **کامل** — loop با error handling | عملیات انبوه: افزودن موجودی دستهای — بعد از ورود کالای فیزیکی |
|
||||||
|
| 7 | `BulkUpdateProductStock` | products.proto | ✅ **کامل** — Set/Add/Subtract با error handling | عملیات انبوه: بروزرسانی دستهای موجودی محصول |
|
||||||
|
| 8 | `GetConfigurationByKey` | configuration.proto | ✅ **کامل** — خواندن از SystemConstants | API مفید: دریافت یک تنظیم خاص بدون بارگذاری همه — performance بهتر |
|
||||||
|
| 9 | `UpdateCustomerSettings` | user.proto | ✅ **کامل** — Email/SMS/Push notifications | تنظیمات اعلانها: وقتی پنل تنظیمات مشتری ساخته شود |
|
||||||
|
| 10 | `ChangeNetworkParent` | networkmembership.proto | ✅ **CQRS کامل** → MoveInNetworkCommand | مدیریت شبکه: جابجایی کاربر در درخت — ابزار ادمین ضروری |
|
||||||
|
| 11 | `AssignFeatureToMembership` | clubmembership.proto | ✅ **CQRS کامل** → AssignClubFeatureCommand | مدیریت عضویت: اختصاص فیچر به عضویت — برای فاز بستهبندی پویا |
|
||||||
|
| 12 | `GetLowStockProducts` | products.proto | ✅ **کامل** — فیلتر threshold + pagination | هشدار موجودی: مکمل GetLowStockItems — فیلتر ClubExclusive اضافه دارد |
|
||||||
|
|
||||||
|
### 🗑️ آرشیو (حذف امن — ۱۲ عدد)
|
||||||
|
|
||||||
|
> این RPCها یا stub خالی هستند، یا جایگزین بهتری دارند، یا هرگز ساخته نشدند.
|
||||||
|
|
||||||
|
| # | RPC | فایل Proto | وضعیت کد | دلیل آرشیو |
|
||||||
|
|---|-----|-----------|---------|-----------|
|
||||||
|
| 1 | `CreateNewFileInfo` | fms.proto | ❌ **۰ رفرنس** — هیچ Service/Handler ندارد | سرویس FMS هرگز طراحی نشد — فایلها از imageresolver استفاده میکنند |
|
||||||
|
| 2 | `DeleteFileInfo` | fms.proto | ❌ **۰ رفرنس** — هیچ Service/Handler ندارد | همان — کل fms.proto حذفشدنی |
|
||||||
|
| 3 | `BulkAdjustStock` | inventory.proto | ❌ **۰ رفرنس** — حتی Service method ندارد | هرگز پیادهسازی نشد — از AdjustStock تکی استفاده میشود |
|
||||||
|
| 4 | `CreateNewOrderForCustomer` | userorder.proto | ❌ **Stub خالی** — `return new()` | مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد — تکراری |
|
||||||
|
| 5 | `SubmitOrderForCustomer` | userorder.proto | ❌ **Stub خالی** — `return new()` | مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد — تکراری |
|
||||||
|
| 6 | `DeactivateConfiguration` | configuration.proto | ❌ **throw میکند** — «تنظیمات فقط خواندنی هستند» | عمداً غیرفعال شده — SystemConstants ثابت هستند |
|
||||||
|
| 7 | `GetConfigurationHistory` | configuration.proto | ❌ **خالی برمیگرداند** — `return new()` | SystemConstants تاریخچه ندارند — بیمعنی |
|
||||||
|
| 8 | `DeleteCity` | City.proto | ✅ کامل ولی **بینیاز** | شهرها seed دیتا هستند — حذف شهر باعث خرابی آدرسها میشود |
|
||||||
|
| 9 | `UpdateCity` | City.proto | ✅ کامل ولی **بینیاز** | شهرها از سرویس خارجی seed شدهاند — ویرایش دستی نیاز نیست |
|
||||||
|
| 10 | `GetOrdersByDateRange` | userorder.proto | ✅ کامل ولی **تکراری** | `GetAllUserOrderByFilter` همین قابلیت + فیلترهای بیشتر دارد |
|
||||||
|
| 11 | `GetServiceHealth` | health.proto | ✅ کامل ولی **تکراری** | `GetSystemHealth` کل سیستم را برمیگرداند — فیلتر client-side کافی است |
|
||||||
|
| 12 | `GetCategoryByIdForCustomer` | category.proto | ✅ کامل ولی **تکراری** | `GetCategory` (admin) + `GetAllCategoriesForCustomer` کافی است |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟡 بخش ۲ — استفاده داخلی CMS (Internal Only — ۱۰۱ عدد)
|
||||||
|
|
||||||
|
> این RPCها از فرانتها فراخوانی نمیشوند ولی **در کد بکند CMS فعال هستند** (background services, handlers, internal flows). **حذف نشوند!**
|
||||||
|
|
||||||
|
### B1. احراز هویت و کاربر (user.proto)
|
||||||
|
|
||||||
|
| RPC | رفرنس CMS | علت |
|
||||||
|
|-----|-----------|-----|
|
||||||
|
| `CreateNewUser` | 175 | ثبتنام کاربر — اصلیترین فلو |
|
||||||
|
| `GetJwtToken` | 38 | صدور توکن JWT |
|
||||||
|
| `AdminGetJwtToken` | 19 | لاگین ادمین |
|
||||||
|
| `SetPasswordForUser` | 24 | تنظیم رمز عبور |
|
||||||
|
| `ChangeCustomerPassword` | 5 | تغییر رمز مشتری |
|
||||||
|
| `UploadCustomerAvatar` | 6 | آپلود آواتار |
|
||||||
|
| `GetCustomerProfile` | 13 | پروفایل مشتری |
|
||||||
|
| `GetCustomerReferrals` | 13 | لیست معرفیشدگان |
|
||||||
|
| `GetCustomerSettings` | 13 | تنظیمات مشتری |
|
||||||
|
|
||||||
|
### B2. بستهها و پرداخت (package.proto / manualpayment.proto)
|
||||||
|
|
||||||
|
| RPC | رفرنس CMS | علت |
|
||||||
|
|-----|-----------|-----|
|
||||||
|
| `PurchaseGoldenPackage` | 21 | خرید بسته طلایی — فلو فعال |
|
||||||
|
| `VerifyGoldenPackagePurchase` | 22 | تأیید خرید بسته طلایی |
|
||||||
|
| `CustomerPurchasePackage` | 3 | خرید مشتری (proto-generated + service) |
|
||||||
|
| `CustomerVerifyPackagePurchase` | 3 | تأیید خرید مشتری |
|
||||||
|
| `GetCustomerPurchaseHistory` | 13 | تاریخچه خرید |
|
||||||
|
| `ProcessManualMembershipPayment` | 20 | پرداخت دستی عضویت |
|
||||||
|
|
||||||
|
### B3. شبکه و عضویت (networkmembership.proto / clubmembership.proto)
|
||||||
|
|
||||||
|
| RPC | رفرنس CMS | علت |
|
||||||
|
|-----|-----------|-----|
|
||||||
|
| `JoinNetwork` | 14 | پیوستن به شبکه — فراخوانی خودکار |
|
||||||
|
| `RemoveFromNetwork` | 14 | حذف از شبکه |
|
||||||
|
|
||||||
|
### B4. کمیسیون (commission.proto)
|
||||||
|
|
||||||
|
| RPC | رفرنس CMS | علت |
|
||||||
|
|-----|-----------|-----|
|
||||||
|
| `CalculateWeeklyBalances` | 26 | سرویس پسزمینه هفتگی |
|
||||||
|
| `CalculateWeeklyCommissionPool` | 22 | سرویس پسزمینه هفتگی |
|
||||||
|
| `ProcessUserPayouts` | 15 | پردازش پرداختها |
|
||||||
|
| `GetCommissionPayoutHistory` | 20 | تاریخچه پرداخت کمیسیون |
|
||||||
|
|
||||||
|
### B5. انبارداری (inventory.proto)
|
||||||
|
|
||||||
|
| RPC | رفرنس CMS | علت |
|
||||||
|
|-----|-----------|-----|
|
||||||
|
| `ConfirmSale` | 10 | تأیید فروش — فلو سفارش |
|
||||||
|
| `ReserveStock` | 12 | رزرو موجودی — فلو سفارش |
|
||||||
|
| `ReleaseReservation` | 10 | آزادسازی رزرو |
|
||||||
|
| `ProcessReturn` | 6 | پردازش مرجوعی |
|
||||||
|
| `DeleteWarehouse` | 18 | حذف انبار |
|
||||||
|
| `GetInventoryByProduct` | 26 | موجودی بر اساس محصول |
|
||||||
|
| `GetInventoryItem` | 19 | آیتم انبار |
|
||||||
|
| `GetWarehouse` | 18 | دریافت انبار |
|
||||||
|
| `SetDefaultWarehouse` | 18 | تنظیم انبار پیشفرض |
|
||||||
|
| `UpdateWarehouse` | 18 | بروزرسانی انبار |
|
||||||
|
| `GetStockMovementsByInventoryItem` | 17 | حرکات موجودی |
|
||||||
|
|
||||||
|
### B6. تراکنشها (transactions.proto)
|
||||||
|
|
||||||
|
| RPC | رفرنس CMS | علت |
|
||||||
|
|-----|-----------|-----|
|
||||||
|
| `CreateNewTransactions` | 26 | ایجاد تراکنش — فلو پرداخت |
|
||||||
|
| `DeleteTransactions` | 23 | حذف تراکنش |
|
||||||
|
| `GetAllTransactionsByFilter` | 25 | لیست تراکنشها |
|
||||||
|
| `CustomerPaymentVerification` | 3 | تأیید پرداخت مشتری |
|
||||||
|
| `GetCustomerTransaction` | 27 | تراکنش مشتری |
|
||||||
|
| `GetCustomerTransactionsByFilter` | 14 | فیلتر تراکنشها |
|
||||||
|
| `RefundTransaction` | 31 | استرداد تراکنش |
|
||||||
|
| `UpdateTransactions` | 23 | بروزرسانی تراکنش |
|
||||||
|
| `VerifyTransaction` | 24 | تأیید تراکنش |
|
||||||
|
|
||||||
|
### B7. سایر CRUD داخلی (خلاصه)
|
||||||
|
|
||||||
|
> ۵۸ RPC در فایلهای contract, usercontract, factordetails, productcategory, productgalleries, productimages, userwallet, userwalletchangelog, otptoken, usercarts, discountproduct, public_messages, category, products, producttag, tag, City, useraddress, userorder — همه CRUD داخلی با ≥3 رفرنس در CMS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟢 بخش ۳ — سرویسهای کاملاً مورد استفاده
|
||||||
|
|
||||||
|
### فایلهای proto که تمام RPCهایشان استفاده میشود:
|
||||||
|
|
||||||
|
| فایل Proto | کل RPC | استفاده FO | استفاده BO |
|
||||||
|
|-----------|--------|-----------|-----------|
|
||||||
|
| appversion.proto | 3 | ✅ 1 | ✅ 3 |
|
||||||
|
| blogcategory.proto | 6 | ✅ 2 | ✅ 6 |
|
||||||
|
| blogpost.proto | 11 | ✅ 5 | ✅ 9 |
|
||||||
|
| blogpostimage.proto | 4 | ✅ 0 | ✅ 4 |
|
||||||
|
| discountcategory.proto | 4 | ✅ 1 | ✅ 4 |
|
||||||
|
| discountorder.proto | 7 | ✅ 3 | ✅ 4 |
|
||||||
|
| discountshoppingcart.proto | 5 | ✅ 5 | ✅ 0 |
|
||||||
|
| manualpayment.proto | 5 | ✅ 0 | ✅ 4 |
|
||||||
|
| public_messages.proto | 8 | ✅ 0 | ✅ 7 |
|
||||||
|
| role.proto | 5 | ✅ 0 | ✅ 5 |
|
||||||
|
| sitepage.proto | 10 | ✅ 2 | ✅ 10 |
|
||||||
|
| sitepagesettings.proto | 5 | ✅ 1 | ✅ 5 |
|
||||||
|
| tag.proto | 6 | ✅ 0 | ✅ 5 |
|
||||||
|
| userrole.proto | 5 | ✅ 0 | ✅ 5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 بخش ۴ — خلاصه تصمیمات
|
||||||
|
|
||||||
|
### ماتریکس نهایی ۲۴ RPC مُرده
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ نگهداری (12): CustomerReorderPreviousOrder, CustomerTrackOrder,
|
||||||
|
CalculateOrderPV, GetInventorySummary, GetStockValueReport,
|
||||||
|
BulkAddStock, BulkUpdateProductStock, GetConfigurationByKey,
|
||||||
|
UpdateCustomerSettings, ChangeNetworkParent,
|
||||||
|
AssignFeatureToMembership, GetLowStockProducts
|
||||||
|
|
||||||
|
🗑️ آرشیو (12): CreateNewFileInfo, DeleteFileInfo, BulkAdjustStock,
|
||||||
|
CreateNewOrderForCustomer, SubmitOrderForCustomer,
|
||||||
|
DeactivateConfiguration, GetConfigurationHistory,
|
||||||
|
DeleteCity, UpdateCity, GetOrdersByDateRange,
|
||||||
|
GetServiceHealth, GetCategoryByIdForCustomer
|
||||||
|
```
|
||||||
|
|
||||||
|
### فایلهای proto آرشیوشدنی (کامل)
|
||||||
|
|
||||||
|
| فایل | وضعیت | اقدام |
|
||||||
|
|------|-------|-------|
|
||||||
|
| **fms.proto** | کل فایل مُرده (2 RPC) | حذف از csproj — ساخته نشود |
|
||||||
|
|
||||||
|
### RPCهای آرشیوشدنی (جزئی — داخل فایلهای فعال)
|
||||||
|
|
||||||
|
| فایل Proto | RPCهای آرشیو | RPCهای فعال |
|
||||||
|
|-----------|-------------|-------------|
|
||||||
|
| inventory.proto | `BulkAdjustStock` (1) | 23 فعال |
|
||||||
|
| userorder.proto | `CreateNewOrderForCustomer`, `SubmitOrderForCustomer` (2) | 18 فعال |
|
||||||
|
| configuration.proto | `DeactivateConfiguration`, `GetConfigurationHistory` (2) | 5 فعال |
|
||||||
|
| City.proto | `DeleteCity`, `UpdateCity` (2) | 6 فعال |
|
||||||
|
| health.proto | `GetServiceHealth` (1) | 1 فعال |
|
||||||
|
| category.proto | `GetCategoryByIdForCustomer` (1) | 7 فعال |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ نکات مهم
|
||||||
|
|
||||||
|
1. **آرشیو ≠ حذف!** — RPCهای آرشیوشده با `[Obsolete]` + `#region [ARCHIVED]` علامتگذاری شدند (کامیت `13dd0f5`)
|
||||||
|
2. RPCهای دسته B (Internal — ۱۰۱ عدد) **حیاتی** هستند — بدون آنها سیستم از کار میافتد
|
||||||
|
3. RPCهای «نگهداری» (۱۲ عدد) کد **کامل و آماده** دارند — بکلاگ فیچر: [FEATURE-BACKLOG.md](../roadmap/FEATURE-BACKLOG.md)
|
||||||
|
4. **fms.proto** از csproj اکسکلود شد (کامیت `13dd0f5`)
|
||||||
|
5. نقشهراه تحول پکیجبیس: [PACKAGE-TRANSFORMATION-TASKS.md](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md)
|
||||||
|
6. قبل از هر تغییر، حتماً `grep -rn "RpcName" CMS/src/` بزنید تا مطمئن شوید
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶*
|
||||||
@@ -0,0 +1,700 @@
|
|||||||
|
# 📦 راهنمای مهاجرت سیستم پکیجبیس — خلاصه تغییرات و پلن استقرار
|
||||||
|
|
||||||
|
> **وضعیت:** آماده تست و استقرار — **Q1-Q30 تکمیلشده ✅ | F1-F11 تکمیلشده ✅**
|
||||||
|
> **تاریخ:** ۸ اسفند ۱۴۰۴ (27 Feb 2026) — آپدیت ۱۰ اسفند
|
||||||
|
> **نسخه NuGet:** v0.0.189
|
||||||
|
> **تعداد کامیتها:** ۵۱+ کامیت در ۴ ریپازیتوری (۲۱ CMS + ۹ FO + ۷ BO + ۱۴+ docs)
|
||||||
|
> **مدت پیادهسازی:** ۶ روز (۲۴ فوریه – ۱ مارس ۲۰۲۶)
|
||||||
|
> **ریپوها:** CMS (`gitea`/`kub-stage`) · FrontOffice (`kub-stage`) · BackOffice (`kub-stage`) · totalDoc (`foursatDocs`/`main`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [خلاصه اجرایی](#1-خلاصه-اجرایی)
|
||||||
|
2. [چه چیزی تغییر کرده؟ — نمای بیزینسی](#2-چه-چیزی-تغییر-کرده--نمای-بیزینسی)
|
||||||
|
3. [بخشهای تحت تاثیر سیستم](#3-بخشهای-تحت-تاثیر-سیستم)
|
||||||
|
4. [جزئیات تغییرات هر ریپو](#4-جزئیات-تغییرات-هر-ریپو)
|
||||||
|
5. [پلن مهاجرت مرحلهبهمرحله](#5-پلن-مهاجرت-مرحلهبهمرحله)
|
||||||
|
6. [Rollback Plan](#6-rollback-plan)
|
||||||
|
7. [چکلیست تست قبل از Production](#7-چکلیست-تست-قبل-از-production)
|
||||||
|
8. [ریسکها و نکات بحرانی](#8-ریسکها-و-نکات-بحرانی)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. خلاصه اجرایی
|
||||||
|
|
||||||
|
### قبل (سیستم تکپکیج):
|
||||||
|
- فقط **یک پکیج پایه** (۵۶ میلیون تومان) وجود داشت
|
||||||
|
- تمام مقادیر مالی (قیمت، هزینه فعالسازی، ضرایب، سقفها) **hardcoded** در کد بودند
|
||||||
|
- خرید مجدد پکیج **غیرممکن** بود (حتی بعد تکمیل چرخه)
|
||||||
|
- پورسانت فقط از **یک Pool واحد** محاسبه میشد
|
||||||
|
- همه کاربران **همه فیچرها** را دریافت میکردند
|
||||||
|
|
||||||
|
### بعد (سیستم چندپکیجی):
|
||||||
|
- سیستم **N پکیج** با قیمت و ویژگیهای متفاوت پشتیبانی میکند
|
||||||
|
- تمام مقادیر مالی از **دیتابیس (Package entity)** خوانده میشوند
|
||||||
|
- خرید مجدد بعد تکمیل چرخه Magic Wallet **فعال** شده
|
||||||
|
- هر پکیج **Commission Pool مستقل** خود را دارد
|
||||||
|
- فیچرها **per-package** هستند و با الگوریتم **DIFF** مدیریت میشوند
|
||||||
|
- قرارداد باشگاه **فقط یک بار** (اولین خرید) امضا میشود
|
||||||
|
|
||||||
|
### آمار تغییرات:
|
||||||
|
|
||||||
|
| شاخص | مقدار |
|
||||||
|
|-------|-------|
|
||||||
|
| فایلهای تغییریافته | **۲۲۷+ فایل** |
|
||||||
|
| خطوط اضافهشده | **+۱۷,۰۰۰+** |
|
||||||
|
| خطوط حذفشده | **−۲,۶۶۰+** |
|
||||||
|
| تصمیمات بیزینسی پیادهشده | **۳۰ تصمیم** (Q1–Q30) |
|
||||||
|
| باگهای فیکسشده | **۶ باگ بحرانی** |
|
||||||
|
| مقادیر hardcoded حذفشده | **۱۵+ مورد** |
|
||||||
|
| Handlerهای deprecated حذفشده | **۴ handler** (۱۲ فایل) |
|
||||||
|
| RPCهای deprecated حذفشده | **۴ RPC** + ۸ message type |
|
||||||
|
| فایلهای rename شده | **۳۴ فایل** + ۱۱ دایرکتوری (UserWalletChangeLog → UserWalletHistory) |
|
||||||
|
| History Tables جدید | **۳ جدول** (PackageHistories, ClubMembershipCycleHistories, UserWalletHistories) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. چه چیزی تغییر کرده؟ — نمای بیزینسی
|
||||||
|
|
||||||
|
### 2.1 🏪 مدل فروش پکیج
|
||||||
|
|
||||||
|
| قابلیت | قبل | بعد |
|
||||||
|
|--------|-----|------|
|
||||||
|
| تعداد پکیج | ۱ (پایه ۵۶M) | **N پکیج** (پایه ۵۶M + نقرهای ۵.۶M + ...) |
|
||||||
|
| قیمتگذاری | hardcoded `56_000_000` | از `Package.Price` در دیتابیس |
|
||||||
|
| هزینه فعالسازی | hardcoded `25_200_000` | از `Package.ActivationFee` |
|
||||||
|
| ضریب تخفیف | hardcoded `× 2` | از `Package.DiscountMultiplier` |
|
||||||
|
| پشتیبانی دایا | فقط پکیج پایه | بر اساس `Package.SupportsDayaPurchase` |
|
||||||
|
| پرداخت مستقیم | همه | بر اساس `Package.SupportsDirectPurchase` |
|
||||||
|
|
||||||
|
### 2.2 🔄 چرخه خرید مجدد (Re-Purchase)
|
||||||
|
|
||||||
|
| مرحله | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| تکمیل چرخه Magic | کاربر در بنبست | `PackagePurchaseMethod = None` ریست میشود |
|
||||||
|
| خرید مجدد | **مسدود** (guard G1-G3) | **مجاز** — بعد تکمیل چرخه Magic |
|
||||||
|
| قرارداد باشگاه | هر بار | **فقط یک بار** — خرید مجدد Skip (Q19) |
|
||||||
|
| فیچرها | همه فیچرها بدون توجه به پکیج | **DIFF/تفاضل** — فقط اختلاف اعمال میشود (Q20) |
|
||||||
|
| تاریخچه | فقط `ActivatedAt` | `FirstActivationDate` + `LastActivationDate` (Q21) |
|
||||||
|
|
||||||
|
### 2.3 💰 پورسانت و تعادلها
|
||||||
|
|
||||||
|
| ویژگی | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| Commission Pool | ۱ Pool واحد | **Pool جداگانه هر پکیج** |
|
||||||
|
| تعادل هفتگی | ۱ رکورد per user/week | **N رکورد** per user/week/package |
|
||||||
|
| MaxBalancesPerLeg | hardcoded `300` | per-package (پایه=۳۰۰, نقرهای=۳۰) |
|
||||||
|
| MaxNetworkLevel | hardcoded `15` | per-package از دیتابیس |
|
||||||
|
| Carryover | یکپارچه | **per-downline-package** — بر اساس پکیج زیرمجموعهها (تغییر پکیج خود کاربر تاثیری ندارد) |
|
||||||
|
| Stored Procedure | پارامترهای ثابت | پارامترهای داینامیک از Package entity |
|
||||||
|
| گزارش مشتری | بدون تفکیک | **breakdown per-package** |
|
||||||
|
| گزارش ادمین | بدون فیلتر | **فیلتر بر اساس پکیج** |
|
||||||
|
|
||||||
|
### 2.4 🪄 کیف پول جادویی (Magic Wallet)
|
||||||
|
|
||||||
|
| ویژگی | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| ضریب جادویی | hardcoded `× 2.5` | از `Package.MagicWalletMultiplier` |
|
||||||
|
| سقف واریز | hardcoded `1,000,000,000` | از `Package.MagicWalletMaxDeposit` |
|
||||||
|
| سقف اعتبار | hardcoded `2,500,000,000` | از `Package.MagicWalletMaxCredit` |
|
||||||
|
| شرط EXIT | بررسی سقف global | بررسی سقف **per-package** |
|
||||||
|
|
||||||
|
### 2.5 📋 فیچرهای باشگاه
|
||||||
|
|
||||||
|
| ویژگی | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| تخصیص فیچر | `GetAllFeatureIds()` — همه فیچرها | از `Package.PackageFeatures` — per-package |
|
||||||
|
| خرید مجدد | — | الگوریتم **DIFF**: مقایسه فیچرهای فعلی با پکیج جدید |
|
||||||
|
| مدیریت ادمین | — | ماتریس checkbox پکیج × فیچر در BackOffice |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. بخشهای تحت تاثیر سیستم
|
||||||
|
|
||||||
|
### 3.1 نقشه تاثیرگذاری
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 🏗️ سیستم پکیجبیس — Impact Map │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─── CMS (Backend) ──────────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📦 Domain Layer (Entity تغییرات) │ │
|
||||||
|
│ │ ├── Package.cs ← +۱۱ فیلد جدید │ │
|
||||||
|
│ │ ├── PackageFeature.cs ← Entity کاملاً جدید │ │
|
||||||
|
│ │ ├── ClubMembership.cs ← ActivatedAt → ۴ فیلد First/Last │ │
|
||||||
|
│ │ ├── ClubMembershipCycle.cs ← +PackageId │ │
|
||||||
|
│ │ ├── WeeklyCommissionPool.cs ← +PackageId │ │
|
||||||
|
│ │ ├── UserCommissionPayout.cs ← +PackageId │ │
|
||||||
|
│ │ ├── NetworkWeeklyBalance.cs ← +PackageId │ │
|
||||||
|
│ │ └── SystemConstants.cs ← حذف ۹ ثابت منسوخ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ⚙️ Application Layer (Handler تغییرات) │ │
|
||||||
|
│ │ ├── ActivateClubMembershipCommandHandler ← فیچر DIFF + re-activate│ │
|
||||||
|
│ │ ├── AcceptClubMembershipContractCommandHandler ← فیچر DIFF │ │
|
||||||
|
│ │ ├── VerifyPackagePurchaseCommandHandler ← حذف fallback 2.0m │ │
|
||||||
|
│ │ ├── CustomerPurchasePackage/Verify ← Generic purchase flow │ │
|
||||||
|
│ │ ├── ChargeMagicWalletCommandHandler ← سقف per-package │ │
|
||||||
|
│ │ ├── VerifyMagicWalletChargeCommandHandler ← ضریب per-package │ │
|
||||||
|
│ │ ├── UserOrderService (EXIT Magic) ← ریست + سقف per-package │ │
|
||||||
|
│ │ ├── CreateManualPaymentCommandHandler ← ضریب از Package │ │
|
||||||
|
│ │ └── CheckAndProcessDayaLoansCommandHandler ← حذف ID=4 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 🔌 Infrastructure Layer │ │
|
||||||
|
│ │ ├── sp_CalculateWeeklyBalances ← @PackageId + @Max params │ │
|
||||||
|
│ │ ├── sp_CalculateWeeklyCommissionPool ← @PackageId │ │
|
||||||
|
│ │ ├── WeeklyCommissionCalculationService ← Loop per-package │ │
|
||||||
|
│ │ ├── OrmCommissionCalculationStrategy ← فیلتر PackageId │ │
|
||||||
|
│ │ └── SpCommissionCalculationStrategy ← پارامترهای داینامیک │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📡 Proto/gRPC Layer │ │
|
||||||
|
│ │ ├── package.proto ← ۱۱ فیلد + PackageFeature CRUD │ │
|
||||||
|
│ │ ├── commission.proto ← package_id/title در ۴ model + فیلتر │ │
|
||||||
|
│ │ ├── حذف ۴ RPC deprecated (Golden/Base) │ │
|
||||||
|
│ │ └── حذف ۸ message type deprecated │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─── FrontOffice (مشتری) ─────────────────────────────────────────────┐│
|
||||||
|
│ │ ├── Packages.razor ← کاشیهای داینامیک (نه hardcoded) ││
|
||||||
|
│ │ ├── PackageDetail.razor ← فیچرها از API (نه ثابت) ││
|
||||||
|
│ │ ├── Checkout.razor ← پرداخت شرطی (دایا/مستقیم) ││
|
||||||
|
│ │ ├── MyPackages.razor ← خرید مجدد + پیشرفت Magic ││
|
||||||
|
│ │ ├── ActivationSection.razor ← قیمت داینامیک (نه ۵۶M hardcoded) ││
|
||||||
|
│ │ ├── ClubMembershipContractDialog ← متن قرارداد داینامیک ││
|
||||||
|
│ │ ├── CommissionDashboard ← فیلتر + ستون پکیج ││
|
||||||
|
│ │ ├── WeeklyBalancePage ← فیلتر per-package ││
|
||||||
|
│ │ ├── PaymentCallback ← مهاجرت به Customer* RPCs ││
|
||||||
|
│ │ └── حذف "پکیج طلایی" hardcoded (۵+ جا) ││
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ ┌─── BackOffice (ادمین) ──────────────────────────────────────────────┐│
|
||||||
|
│ │ ├── Package CRUD ← +۱۲ فیلد جدید در Create/Update ││
|
||||||
|
│ │ ├── PackageFeature Matrix ← checkbox فیچرها ││
|
||||||
|
│ │ ├── ManualPaymentDialog ← حذف ۵۶M hardcoded + Amount editable ││
|
||||||
|
│ │ ├── ChangeParentDialog ← جابجایی در شبکه (جدید) ││
|
||||||
|
│ │ ├── UserPayouts ← فیلتر + ستون پکیج ││
|
||||||
|
│ │ ├── BalancesReport ← فیلتر + ستون پکیج ││
|
||||||
|
│ │ ├── PackageSelect Component ← dropdown قابل استفاده مجدد ││
|
||||||
|
│ │ └── حذف "پکیج طلایی" → "خرید پکیج" ││
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ ┌─── Database ────────────────────────────────────────────────────────┐│
|
||||||
|
│ │ ├── Packages ← ۱۱ ستون جدید + Seed نقرهای ││
|
||||||
|
│ │ ├── PackageFeatures ← جدول جدید ││
|
||||||
|
│ │ ├── ClubMemberships ← ۴ ستون First/Last + حذف ActivatedAt ││
|
||||||
|
│ │ ├── ClubMembershipCycles ← +PackageId ││
|
||||||
|
│ │ ├── WeeklyCommissionPools ← +PackageId + Unique ││
|
||||||
|
│ │ ├── UserCommissionPayouts ← +PackageId + Unique ││
|
||||||
|
│ │ ├── NetworkWeeklyBalances ← +PackageId + Unique ││
|
||||||
|
│ │ └── EF Migration + Data Backfill ││
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 خلاصه آماری per-repo
|
||||||
|
|
||||||
|
| ریپو | کامیت | فایل | اضافه | حذف | شرح اصلی |
|
||||||
|
|-------|-------|------|-------|-----|-----------|
|
||||||
|
| **CMS** | ۲۰ | ۱۷۹+ | +۱۳,۵۸۶ | −۲,۳۲۷ | Domain + Business + Commission + Proto + History + Rename + Interceptor |
|
||||||
|
| **FrontOffice** | ۸ | ۲۹ | +۵۵۰ | −۱۳۶ | Dynamic UI + Customer RPCs + Per-package Reports + UI Guidance |
|
||||||
|
| **BackOffice** | ۶ | ۲۴ | +۵۸۰ | −۳۱ | Package CRUD + Feature Matrix + Per-package Reports + UI Guidance |
|
||||||
|
| **totalDoc** | ۱۴ | ۱۳ | +۲,۷۰۰ | −۱۹۴ | مستندات بیزینسی + تکنیکال + Phase 9 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. جزئیات تغییرات هر ریپو
|
||||||
|
|
||||||
|
### 4.1 CMS — ۲۰ کامیت
|
||||||
|
|
||||||
|
| فاز | کامیت | شرح |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **Phase 0** | `8b9c317` | فیکس ۴ باگ بحرانی: DiscountBalance + UserPackagePurchase |
|
||||||
|
| **Phase 0** | `fe3edd1` | فیکس EXIT Magic Mode — ریست PackagePurchaseMethod + بستن چرخه |
|
||||||
|
| **Phase 1** | `ae92ab8` | زیرساخت Domain: Package +۱۱ فیلد، PackageFeature entity، FKهای جدید |
|
||||||
|
| **Phase 1.5** | `a9cd2fd` | EF Migration + Seed Data + Data Backfill |
|
||||||
|
| **Phase 2** | `8e5c7c5` | جایگزینی همه SystemConstants با Package entity reads |
|
||||||
|
| **Phase 3** | `ccb938e` | بازسازی لایه Package + Proto enhancement + باگفیکس |
|
||||||
|
| **Phase 4** | `0002a5a` | CRUD DTOs + Legacy fixes |
|
||||||
|
| **Phase 5** | `607f791` | پورسانت per-package + حذف ref طلایی |
|
||||||
|
| **SP Fix** | `7176fe4` | فیکس SP: `cm.PackageId` → `cm.LastPackageId` |
|
||||||
|
| **Phase 6** | `d19c569` | Deprecation cleanup + ConfigurationService MagicWallet |
|
||||||
|
| **Phase 7a** | `469d97b` | Cosmetic cleanup + حذف orphan handler |
|
||||||
|
| **Phase 7b** | `161f796` | Embed orderId در callback URL |
|
||||||
|
| **Phase 7c** | `8446e0e` | حذف ۴ handler deprecated (۱۴ فایل، −۱,۱۲۵ خط) |
|
||||||
|
| **Phase 8b** | `ce8e248` | NuGet bump → 0.0.185 |
|
||||||
|
| **Phase 8d** | `7554d70` | حذف ۴ RPC + ۸ message deprecated از Proto |
|
||||||
|
| **Phase 8e** | `aaaf7fc` | Per-package filtering در Commission queries |
|
||||||
|
| **Phase 8f** | `dcd1135` | PackageFeature CRUD support |
|
||||||
|
| **Audit** | `1ac2366` | Compliance audit — Feature DIFF + حذف fallbackهای hardcoded |
|
||||||
|
| **Phase 9a** | `a1024a3` | Q24: آستانه موجودی `≤1M` ریال + Q26: SP Worker auto-deploy (IHostedService + checksum) |
|
||||||
|
| **Phase 9b** | `fdbb91d` | Q27: PackageHistory + ClubMembershipCycleHistory entities + enums + EF configs |
|
||||||
|
| **Phase 9d** | `10d2ca2` | Rename UserWalletChangeLog→UserWalletHistory (86 فایل) + IHasHistory + Interceptor + Migration |
|
||||||
|
|
||||||
|
### 4.2 FrontOffice — ۸ کامیت
|
||||||
|
|
||||||
|
| فاز | کامیت | شرح |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **Phase 7a** | `b82cac4` | حذف "پکیج طلایی" + PackageTitle در DTO |
|
||||||
|
| **Phase 7b** | `71f391a` | مهاجرت به Customer* RPCs |
|
||||||
|
| **Phase 8a** | `0bbc11e` | Checkout wire-up به Customer RPCs |
|
||||||
|
| **Phase 8c** | `d71d463` | صفحات پکیج — فیچرهای داینامیک |
|
||||||
|
| **Phase 8d** | `40882c8` | NuGet bump Proto cleanup |
|
||||||
|
| **Phase 8e** | `a956cb9` | Per-package filtering در Commission pages |
|
||||||
|
| **Phase 8f** | `3bffc13` | T4.2+T4.3+F3: پرداخت شرطی + خرید مجدد + PV |
|
||||||
|
| **Audit** | `816dcb7` | حذف ۵۶M hardcoded — قیمتگذاری داینامیک |
|
||||||
|
| **Phase 9c** | `474d364` | Q28: UI Guidance alerts (G1-G7) — ۷ صفحه MudAlert آموزشی |
|
||||||
|
|
||||||
|
### 4.3 BackOffice — ۶ کامیت
|
||||||
|
|
||||||
|
| فاز | کامیت | شرح |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **Phase 7a** | `f1b0085` | تغییر label "پکیج طلایی" → "خرید پکیج" |
|
||||||
|
| **Phase 8b** | `89f5241` | Package CRUD expansion — ۱۲ فیلد جدید |
|
||||||
|
| **Phase 8d** | `c96377a` | NuGet bump Proto cleanup |
|
||||||
|
| **Phase 8e** | `8be98ae` | Per-package commission filtering + PackageSelect component |
|
||||||
|
| **Phase 8f** | `e020354` | ChangeParentDialog + PackageFeature checkbox matrix |
|
||||||
|
| **Audit** | `e6cf90e` | ManualPaymentDialog — حذف ۵۶M + Amount editable |
|
||||||
|
| **Phase 9c** | `6939780` | Q28: UI Guidance alerts (G8-G13) — ۶ صفحه MudAlert |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. پلن مهاجرت مرحلهبهمرحله
|
||||||
|
|
||||||
|
### 📋 پیشنیازها
|
||||||
|
|
||||||
|
- [ ] بکاپ کامل از دیتابیس Production
|
||||||
|
- [ ] بکاپ از stateهای Kubernetes (Deployments, ConfigMaps)
|
||||||
|
- [ ] اطمینان از دسترسی به Container Registry (تصاویر فعلی)
|
||||||
|
- [ ] زمانبندی Maintenance Window (ترجیحاً شب یا آخر هفته)
|
||||||
|
- [ ] اطلاعرسانی به کاربران (در صورت نیاز به downtime)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۱ از ۶: بکاپ و آمادهسازی محیط 🛡️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۳۰ دقیقه
|
||||||
|
|
||||||
|
```
|
||||||
|
1.1 بکاپ کامل دیتابیس
|
||||||
|
└── pg_dump -Fc cms_db > cms_backup_pre_package_migration.dump
|
||||||
|
|
||||||
|
1.2 بکاپ دیتابیس BO (اگر جداست)
|
||||||
|
└── pg_dump -Fc bo_db > bo_backup_pre_package_migration.dump
|
||||||
|
|
||||||
|
1.3 ثبت وضعیت فعلی
|
||||||
|
└── تعداد رکوردها:
|
||||||
|
• ClubMemberships: SELECT COUNT(*) ...
|
||||||
|
• ClubMembershipCycles: SELECT COUNT(*) ...
|
||||||
|
• WeeklyCommissionPools: SELECT COUNT(*) ...
|
||||||
|
• UserCommissionPayouts: SELECT COUNT(*) ...
|
||||||
|
• NetworkWeeklyBalances: SELECT COUNT(*) ...
|
||||||
|
• Packages: SELECT COUNT(*) ...
|
||||||
|
|
||||||
|
1.4 ذخیره نسخه فعلی Docker images
|
||||||
|
└── docker tag <current-cms> cms:rollback-point
|
||||||
|
└── docker tag <current-fo> fo:rollback-point
|
||||||
|
└── docker tag <current-bo> bo:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** بکاپها ذخیره شدهاند و قابل restore هستند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۲ از ۶: استقرار CMS (Backend) 🏗️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۴۵ دقیقه
|
||||||
|
> ⚠️ **ترتیب بحرانی:** CMS باید **اول** deploy شود چون FO و BO به آن وابستهاند.
|
||||||
|
|
||||||
|
```
|
||||||
|
2.1 Build CMS Docker image
|
||||||
|
└── cd CMS/src
|
||||||
|
└── docker build -t cms:package-based .
|
||||||
|
|
||||||
|
2.2 اجرای EF Migration
|
||||||
|
└── این migration شامل:
|
||||||
|
• ۱۱ ستون جدید به جدول Packages
|
||||||
|
• جدول جدید PackageFeatures
|
||||||
|
• ستون PackageId به ۵ جدول (ClubMemberships, Cycles, Pools, Payouts, Balances)
|
||||||
|
• ۴ ستون First/Last به ClubMemberships
|
||||||
|
• Unique Indexها
|
||||||
|
|
||||||
|
⚠️ Migration خودکار اجرا میشود در startup اگر EF auto-migration فعال باشد.
|
||||||
|
✅ اگر دستی: dotnet ef database update
|
||||||
|
|
||||||
|
2.3 Data Backfill — مقداردهی پکیج پایه
|
||||||
|
└── اسکریپت SQL:
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ -- مشخص کردن ID پکیج پایه │
|
||||||
|
│ DO $$ │
|
||||||
|
│ DECLARE base_pkg_id BIGINT; │
|
||||||
|
│ BEGIN │
|
||||||
|
│ SELECT "Id" INTO base_pkg_id │
|
||||||
|
│ FROM "CMS"."Packages" │
|
||||||
|
│ WHERE "IsBasePackage" = true LIMIT 1; │
|
||||||
|
│ │
|
||||||
|
│ -- ClubMemberships │
|
||||||
|
│ UPDATE "CMS"."ClubMemberships" │
|
||||||
|
│ SET "FirstActivationDate" = "ActivatedAt", │
|
||||||
|
│ "LastActivationDate" = "ActivatedAt", │
|
||||||
|
│ "FirstPackageId" = base_pkg_id, │
|
||||||
|
│ "LastPackageId" = base_pkg_id │
|
||||||
|
│ WHERE "FirstActivationDate" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- ClubMembershipCycles │
|
||||||
|
│ UPDATE "CMS"."ClubMembershipCycles" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- WeeklyCommissionPools │
|
||||||
|
│ UPDATE "CMS"."WeeklyCommissionPools" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- UserCommissionPayouts │
|
||||||
|
│ UPDATE "CMS"."UserCommissionPayouts" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- NetworkWeeklyBalances │
|
||||||
|
│ UPDATE "CMS"."NetworkWeeklyBalances" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ RAISE NOTICE 'Migration done: PackageId=%', │
|
||||||
|
│ base_pkg_id; │
|
||||||
|
│ END $$; │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
2.4 Verification — بررسی migration
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ SELECT 'ClubMemberships' AS tbl, COUNT(*) │
|
||||||
|
│ FROM "CMS"."ClubMemberships" │
|
||||||
|
│ WHERE "LastPackageId" IS NULL │
|
||||||
|
│ UNION ALL │
|
||||||
|
│ SELECT 'Cycles', COUNT(*) │
|
||||||
|
│ FROM "CMS"."ClubMembershipCycles" │
|
||||||
|
│ WHERE "PackageId" IS NULL │
|
||||||
|
│ UNION ALL │
|
||||||
|
│ SELECT 'Pools', COUNT(*) │
|
||||||
|
│ FROM "CMS"."WeeklyCommissionPools" │
|
||||||
|
│ WHERE "PackageId" IS NULL │
|
||||||
|
│ UNION ALL │
|
||||||
|
│ SELECT 'Payouts', COUNT(*) │
|
||||||
|
│ FROM "CMS"."UserCommissionPayouts" │
|
||||||
|
│ WHERE "PackageId" IS NULL │
|
||||||
|
│ UNION ALL │
|
||||||
|
│ SELECT 'Balances', COUNT(*) │
|
||||||
|
│ FROM "CMS"."NetworkWeeklyBalances" │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- ✅ همه باید 0 باشند! │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
2.5 Seed پکیج نقرهای (اگر توسط EF Seed انجام نشده)
|
||||||
|
└── INSERT پکیج نقرهای + PackageFeatures
|
||||||
|
|
||||||
|
2.5b اجرای Migration دوم: Q27_HistoryTables_And_RenameWalletHistory
|
||||||
|
└── این migration شامل:
|
||||||
|
• RenameTable: UserWalletChangeLogs → UserWalletHistories (حفظ دادهها!)
|
||||||
|
• RenameIndex × 2 + sp_rename PK + FK × 2
|
||||||
|
• CreateTable: PackageHistories (فیلدهای Old*/New*)
|
||||||
|
• CreateTable: ClubMembershipCycleHistories (فیلدهای Old*/New*)
|
||||||
|
⚠️ دادههای قبلی UserWalletChangeLogs حفظ میشوند (RenameTable نه DropTable)
|
||||||
|
|
||||||
|
2.6 Deploy CMS به Kubernetes
|
||||||
|
└── kubectl set image deployment/cms cms=cms:package-based
|
||||||
|
└── kubectl rollout status deployment/cms
|
||||||
|
|
||||||
|
2.7 Health Check
|
||||||
|
└── curl http://cms-service/health
|
||||||
|
└── بررسی لاگها: kubectl logs deployment/cms --tail=100
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** CMS جدید بالا آمده، migration اجرا شده، همه رکوردها PackageId دارند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۳ از ۶: استقرار FrontOffice 🖥️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۲۰ دقیقه
|
||||||
|
> پیشنیاز: CMS باید بالا و سالم باشد
|
||||||
|
|
||||||
|
```
|
||||||
|
3.1 Build FrontOffice Docker image
|
||||||
|
└── cd FrontOffice/src
|
||||||
|
└── docker build -t fo:package-based .
|
||||||
|
|
||||||
|
3.2 Deploy به Kubernetes
|
||||||
|
└── kubectl set image deployment/frontoffice fo=fo:package-based
|
||||||
|
└── kubectl rollout status deployment/frontoffice
|
||||||
|
|
||||||
|
3.3 Smoke Test
|
||||||
|
└── ✅ صفحه پکیجها باز میشود (کاشیهای داینامیک)
|
||||||
|
└── ✅ جزئیات پکیج — فیچرها نمایش داده میشود
|
||||||
|
└── ✅ صفحه پاداشها — فیلتر پکیج کار میکند
|
||||||
|
└── ✅ صفحه تعادلها — per-package نمایش داده میشود
|
||||||
|
└── ✅ متن قرارداد — مبلغ داینامیک (نه ۵۶M hardcoded)
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** FrontOffice جدید بالا آمده و صفحات اصلی کار میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۴ از ۶: استقرار BackOffice 🛠️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۲۰ دقیقه
|
||||||
|
> پیشنیاز: CMS باید بالا و سالم باشد
|
||||||
|
|
||||||
|
```
|
||||||
|
4.1 Build BackOffice Docker image
|
||||||
|
└── cd BackOffice/src
|
||||||
|
└── docker build -t bo:package-based .
|
||||||
|
|
||||||
|
4.2 Deploy به Kubernetes
|
||||||
|
└── kubectl set image deployment/backoffice bo=bo:package-based
|
||||||
|
└── kubectl rollout status deployment/backoffice
|
||||||
|
|
||||||
|
4.3 Smoke Test
|
||||||
|
└── ✅ CRUD پکیج — ۱۲ فیلد جدید نمایش داده میشود
|
||||||
|
└── ✅ ماتریس فیچر — checkboxها load میشوند
|
||||||
|
└── ✅ گزارش تعادلها — فیلتر پکیج کار میکند
|
||||||
|
└── ✅ گزارش پرداختها — ستون پکیج نمایش داده میشود
|
||||||
|
└── ✅ ManualPayment — مبلغ editable (نه ۵۶M disabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** BackOffice جدید بالا آمده و CRUD + گزارشات کار میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۵ از ۶: بررسی پورسانت (بحرانی!) 💰
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۳۰ دقیقه
|
||||||
|
> ⚠️ پورسانت = پول واقعی — دقت مضاعف لازم است
|
||||||
|
|
||||||
|
```
|
||||||
|
5.1 بررسی SP پارامترها
|
||||||
|
└── محاسبه پورسانت هفته تستی (staging)
|
||||||
|
└── بررسی: هر پکیج Pool جداگانه دارد
|
||||||
|
└── بررسی: MaxBalancesPerLeg صحیح (پایه=۳۰۰, نقرهای=۳۰)
|
||||||
|
└── بررسی: MaxNetworkLevel صحیح
|
||||||
|
|
||||||
|
5.2 مقایسه نتایج
|
||||||
|
└── اجرای محاسبه در staging
|
||||||
|
└── مقایسه Pool مبلغ با محاسبه دستی
|
||||||
|
└── ✅ تفاوت < ۱% قابل قبول
|
||||||
|
|
||||||
|
5.3 بررسی carryover
|
||||||
|
└── ✅ carryover فقط per-package
|
||||||
|
└── ✅ تغییر پکیج → ریست carryover
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** محاسبات پورسانت per-package صحیح هستند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۶ از ۶: تنظیمات نهایی و بررسی سلامت ✅
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۱۵ دقیقه
|
||||||
|
|
||||||
|
```
|
||||||
|
6.1 بررسی PackageFeatures seed شدهاند
|
||||||
|
└── SELECT * FROM "CMS"."PackageFeatures";
|
||||||
|
└── پکیج پایه: همه فیچرها ✅
|
||||||
|
└── پکیج نقرهای: فیچرهای تعیینشده ✅
|
||||||
|
|
||||||
|
6.2 بررسی JWT Claims (اختیاری)
|
||||||
|
└── لاگین یک کاربر تست → decode JWT
|
||||||
|
└── ✅ PackageId وجود دارد
|
||||||
|
└── ✅ CanRepurchase صحیح
|
||||||
|
|
||||||
|
6.3 غیرفعال کردن Maintenance Mode (اگر فعال بود)
|
||||||
|
|
||||||
|
6.4 مانیتورینگ ۲۴ ساعته
|
||||||
|
└── بررسی لاگ خطاها
|
||||||
|
└── بررسی response timeها
|
||||||
|
└── بررسی پرداختهای جدید
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ مهاجرت تکمیل شد!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Rollback Plan
|
||||||
|
|
||||||
|
### سناریو ۱: مشکل در Migration دیتابیس
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore از بکاپ
|
||||||
|
pg_restore -d cms_db cms_backup_pre_package_migration.dump
|
||||||
|
|
||||||
|
# Rollback CMS image
|
||||||
|
kubectl set image deployment/cms cms=cms:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
### سناریو ۲: مشکل در CMS (بعد Migration موفق)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ⚠️ نکته: migration undo ممکن نیست (ستونهای جدید اضافه شدهاند)
|
||||||
|
# اما کد قدیمی با ستونهای nullable مشکلی ندارد
|
||||||
|
|
||||||
|
# Rollback فقط CMS image
|
||||||
|
kubectl set image deployment/cms cms=cms:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
### سناریو ۳: مشکل در FO/BO
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# FO و BO مستقل از هم هستند — هرکدام جداگانه rollback
|
||||||
|
kubectl set image deployment/frontoffice fo=fo:rollback-point
|
||||||
|
kubectl set image deployment/backoffice bo=bo:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
### نکته مهم Rollback:
|
||||||
|
- ستونهای جدید **nullable** هستند → کد قدیمی بدون مشکل کار میکند
|
||||||
|
- جدول `PackageFeatures` جدید است → کد قدیمی آن را ignore میکند
|
||||||
|
- **فقط Data Backfill** غیرقابلبرگشت است (ولی ضرری ندارد — فقط NULL → مقدار)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. چکلیست تست قبل از Production
|
||||||
|
|
||||||
|
### 🛒 خرید و فعالسازی
|
||||||
|
|
||||||
|
| # | تست | روش | نتیجه مورد انتظار |
|
||||||
|
|---|------|------|-------------------|
|
||||||
|
| 1 | خرید پکیج نقرهای (ZarinPal) | از FO → پکیجها → نقرهای → پرداخت | Balance = ۵.۶M, Discount = ۱۱.۲M |
|
||||||
|
| 2 | خرید پکیج پایه (ZarinPal) | از FO → پکیجها → پایه → پرداخت | Balance = ۵۶M, Discount = ۱۱۲M |
|
||||||
|
| 3 | خرید پکیج پایه (Daya Loan) | از FO → پکیجها → پایه → دایا | Balance = ۵۶M + loan created |
|
||||||
|
| 4 | پرداخت دستی (BO) | از BO → ManualPayment → مبلغ دلخواه | Amount editable, not hardcoded |
|
||||||
|
| 5 | فعالسازی با نقرهای | فعالسازی باشگاه بعد خرید نقرهای | فقط فیچرهای نقرهای فعال (نه همه) |
|
||||||
|
| 6 | فعالسازی با پایه | فعالسازی باشگاه بعد خرید پایه | همه فیچرها فعال |
|
||||||
|
|
||||||
|
### 🔄 چرخه Magic + خرید مجدد
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 7 | تکمیل چرخه Magic → ریست | PackagePurchaseMethod = None |
|
||||||
|
| 8 | خرید مجدد همان پکیج | بدون قرارداد مجدد، فقط شارژ wallet |
|
||||||
|
| 9 | خرید مجدد پکیج متفاوت (پایه → نقرهای) | DIFF اجرا: فیچرهای اضافی غیرفعال |
|
||||||
|
|
||||||
|
### 💰 پورسانت per-package
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 10 | Pool جداگانه هر پکیج | WeeklyCommissionPool با PackageId متفاوت |
|
||||||
|
| 11 | MaxBalancesPerLeg متفاوت | پایه=۳۰۰, نقرهای=۳۰ |
|
||||||
|
| 12 | Carryover per-downline-package | تغییر پکیج خود کاربر → carryover حفظ (بر اساس زیرمجموعهها) |
|
||||||
|
| 13 | SP پارامترها از Package | بدون hardcoded ۳۰۰/۱۵ |
|
||||||
|
|
||||||
|
### 📊 گزارشات per-package
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 14 | FO — فیلتر dropdown پکیج | فیلتر عملکرد صحیح |
|
||||||
|
| 15 | FO — breakdown پاداش per-package | مبالغ صحیح به تفکیک |
|
||||||
|
| 16 | BO — فیلتر پکیج در تعادلها | فیلتر عملکرد صحیح |
|
||||||
|
| 17 | BO — ستون پکیج در پرداختها | نام پکیج نمایش داده میشود |
|
||||||
|
|
||||||
|
### 📋 UI / قرارداد
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 18 | متن قرارداد — مبلغ داینامیک | مبلغ و نام پکیج صحیح (نه ۵۶M hardcoded) |
|
||||||
|
| 19 | ActivationSection — قیمت | از API خوانده میشود |
|
||||||
|
| 20 | BO — ManualPayment editable | مبلغ قابل ویرایش با validation |
|
||||||
|
| 21 | BO — Package CRUD ۱۲ فیلد | همه فیلدهای جدید ذخیره/بارگذاری |
|
||||||
|
| 22 | BO — Feature Matrix | checkboxها sync با DB |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. ریسکها و نکات بحرانی
|
||||||
|
|
||||||
|
### 🔴 ریسکهای بحرانی
|
||||||
|
|
||||||
|
| # | ریسک | احتمال | تاثیر | کاهشدهنده |
|
||||||
|
|---|-------|--------|-------|------------|
|
||||||
|
| R1 | Migration دیتابیس — PackageId اشتباه | کم | **فاجعه** | Verification query (مرحله 2.4) + بکاپ |
|
||||||
|
| R2 | SP تغییریافته → محاسبات مالی اشتباه | متوسط | **فاجعه** | تست staging + مقایسه دستی |
|
||||||
|
| R3 | Magic Wallet EXIT — سقف global بهجای per-package | متوسط | **بالا** | بررسی MW1-MW3 در CMS handlers |
|
||||||
|
| R4 | قرارداد حقوقی — مبلغ اشتباه | کم | **حقوقی** | متن قرارداد داینامیک ✅ فیکس شده |
|
||||||
|
|
||||||
|
### 🟡 ریسکهای متوسط
|
||||||
|
|
||||||
|
| # | ریسک | کاهشدهنده |
|
||||||
|
|---|-------|------------|
|
||||||
|
| R5 | Proto breaking change | Field numberها backward compatible (فقط اضافه) |
|
||||||
|
| R6 | NuGet version mismatch بین repos | همه روی v0.0.189 ✅ |
|
||||||
|
| R7 | JWT claims — cache invalidation | کاربران باید re-login کنند |
|
||||||
|
| R8 | ~~Validator hardcoded 1B~~ | ✅ فیکس شد — `SystemConstants.WalletMaxSafeAmount` (10B) حصار ایمنی |
|
||||||
|
|
||||||
|
### ⚠️ تغییرات آینده (هنوز پیادهنشده — Phase بعدی)
|
||||||
|
|
||||||
|
این موارد در BIZ spec شناسایی شدهاند ولی **هنوز پیاده نشدهاند**:
|
||||||
|
|
||||||
|
| # | مورد | شدت | شرح |
|
||||||
|
|---|------|------|------|
|
||||||
|
| ~~F1~~ | ~~WalletChangeLog + PackageId~~ | ✅ انجامشده | CMS:`e5bc3a9` — PackageId در UserWalletHistory |
|
||||||
|
| ~~F2~~ | ~~Notification + PackageId~~ | ✅ انجامشده | CMS:`61b7e4f` — SmsTemplates+IUserNotificationService+UserNotificationService با packageName |
|
||||||
|
| ~~F3~~ | ~~Background Services + PackageId~~ | ✅ بررسیشده | بدون تغییر — هر ۳ worker از قبل per-package صحیح کار میکنند |
|
||||||
|
| ~~F4~~ | ~~CSV exports + ستون پکیج~~ | ✅ انجامشده | CMS:`61b7e4f` BO:`92c9922` — proto+handler+CSV برای ManualPayments/WithdrawalRequests |
|
||||||
|
| ~~F5~~ | ~~SystemConfiguration per-package~~ | ✅ بررسیشده | بدون تغییر — مقادیر per-package قبلاً به Package entity منتقل شدهاند |
|
||||||
|
| ~~F6~~ | ~~MagicWalletChargePage hardcoded~~ | ✅ انجامشده | CMS:`61b7e4f` FO:`ecc4f44` — magic_multiplier+magic_max_credit از API، داشبورد "شارژ چندبرابری" |
|
||||||
|
| ~~F7~~ | ~~Validators async per-package~~ | ✅ انجامشده | CMS:`61b7e4f` FO:`ecc4f44` — SystemConstants.WalletMaxSafeAmount (10B) حصار ایمنی، سقف واقعی per-package در هندلر |
|
||||||
|
| ~~F8~~ | ~~آستانه موجودی ورود به Magic (Q24)~~ | ✅ انجامشده | CMS:`a1024a3` — `Balance <= 1_000_000` |
|
||||||
|
| ~~F9~~ | ~~SP Worker — مدیریت خودکار SP (Q26)~~ | ✅ انجامشده | CMS:`a1024a3` — `StoredProcedureDeploymentService` |
|
||||||
|
| ~~F10~~ | ~~History Tables — یکسانسازی + خودکار (Q27)~~ | ✅ انجامشده | CMS:`fdbb91d`+`10d2ca2` — IHasHistory + Interceptor + RenameTable migration |
|
||||||
|
| ~~F11~~ | ~~UI Guidance — آموزش و هشدار (Q28)~~ | ✅ انجامشده | FO:`474d364` BO:`6939780` — ۱۳ صفحه MudAlert |
|
||||||
|
|
||||||
|
> ✅ **F1-F11 همه پیادهسازی شدند.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ضمیمه: ۳۰ تصمیم بیزینسی (Q1–Q30)
|
||||||
|
|
||||||
|
### پیادهشده (Q1–Q23):
|
||||||
|
|
||||||
|
| # | تصمیم | وضعیت |
|
||||||
|
|---|-------|-------|
|
||||||
|
| Q1 | باگ DiscountBalance → فیکس | ✅ `8b9c317` |
|
||||||
|
| Q2 | ادغام ۳ مسیر پرداخت → Generic | ✅ `ccb938e` + `8446e0e` |
|
||||||
|
| Q3 | پکیج نقرهای + پایه — داینامیک | ✅ `ae92ab8` + `a9cd2fd` |
|
||||||
|
| Q4 | ActivationFee یک فیلد (حذف GiftValue) | ✅ `ae92ab8` |
|
||||||
|
| Q5 | DiscountMultiplier داینامیک | ✅ `8e5c7c5` |
|
||||||
|
| Q6 | Migration کاربران فعلی → پکیج پایه | ✅ `a9cd2fd` |
|
||||||
|
| Q7 | خرید N بار بعد تکمیل چرخه | ✅ `fe3edd1` + `8e5c7c5` |
|
||||||
|
| Q8 | Commission Pool جدا per-package | ✅ `607f791` |
|
||||||
|
| Q9 | MagicWallet Multiplier داینامیک | ✅ `8e5c7c5` |
|
||||||
|
| Q10 | دایا = پکیج پایه (نه طلایی) | ✅ `ccb938e` |
|
||||||
|
| Q11 | فیچرها داینامیک per-package | ✅ `dcd1135` |
|
||||||
|
| Q12 | MaxBalancesPerLeg per-package | ✅ `607f791` |
|
||||||
|
| Q13 | MaxNetworkLevel per-package | ✅ `607f791` |
|
||||||
|
| Q14 | MagicWalletMaxDeposit per-package | ✅ `ae92ab8` |
|
||||||
|
| Q15 | MagicWalletMaxCredit per-package | ✅ `ae92ab8` |
|
||||||
|
| Q16 | NetworkWeeklyBalance + PackageId | ✅ `ae92ab8` |
|
||||||
|
| Q17 | گزارش FO breakdown per-package | ✅ `a956cb9` |
|
||||||
|
| Q18 | گزارش BO فیلتر per-package | ✅ `8be98ae` |
|
||||||
|
| Q19 | قرارداد فقط یک بار | ✅ `1ac2366` |
|
||||||
|
| Q20 | فیچر DIFF/تفاضل | ✅ `1ac2366` |
|
||||||
|
| Q21 | First/Last ActivationDate | ✅ `ae92ab8` |
|
||||||
|
| Q22 | تشخیص هفته از LastActivationDate | ✅ `607f791` |
|
||||||
|
| Q23 | Carryover strictly per-package | ✅ `607f791` |
|
||||||
|
|
||||||
|
### تصمیمات v6 (Q24–Q30) — ✅ تکمیلشده:
|
||||||
|
|
||||||
|
| # | تصمیم | وضعیت | کامیت |
|
||||||
|
|---|-------|-------|-------|
|
||||||
|
| Q24 | آستانه موجودی ≤ ۱,۰۰۰,۰۰۰ ریال (ورود Magic + خرید مجدد) | ✅ | CMS:`a1024a3` |
|
||||||
|
| Q25 | DayaLoans فقط پکیج پایه — تایید (بدون تغییر کد) | ✅ تایید | — |
|
||||||
|
| Q26 | SP Worker — auto-deploy با checksum (IHostedService) | ✅ | CMS:`a1024a3` |
|
||||||
|
| Q27 | History Tables — PackageHistory + CycleHistory + IHasHistory + Interceptor + Rename UserWalletChangeLog→UserWalletHistory | ✅ | CMS:`fdbb91d`+`10d2ca2` |
|
||||||
|
| Q28 | UI Guidance — ۱۳ صفحه MudAlert آموزشی/هشداری در FO/BO | ✅ | FO:`474d364` BO:`6939780` |
|
||||||
|
| Q29 | شرط EXIT Magic — تایید: آخرین پکیج فعال (بدون تغییر کد) | ✅ تایید | — |
|
||||||
|
| Q30 | Carryover — تایید: توضیح مستند شد (بدون تغییر کد) | ✅ تایید | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*آخرین بروزرسانی: ۱۰ اسفند ۱۴۰۴ — v7: F1-F7 همه تکمیلشده ✅ | ۵۱+ کامیت (۲۱ CMS + ۹ FO + ۷ BO + ۱۴+ docs) | NuGet v0.0.189 | Notifications+PackageName, CSV ستون پکیج, Dynamic MagicWallet, SystemConstants validators*
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
# گزارش ممیزی صحت دادههای دیتابیس CMS
|
||||||
|
|
||||||
|
**تاریخ بررسی:** 1405/01/28 (2026-04-17)
|
||||||
|
**فایل بکاپ:** `dbbkup/CMS-20260417.sql` (4.9MB, 21,397 خط)
|
||||||
|
**تعداد جداول:** ~47 جدول | **تعداد کاربران:** 115 | **تعداد سفارشات:** 72
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [خلاصه اجرایی](#خلاصه-اجرایی)
|
||||||
|
2. [اصلاحیه مهم — PaymentStatus](#اصلاحیه-مهم)
|
||||||
|
3. [دسته ۱ — ورود دستی / مهاجرت دیتا](#دسته-۱--ورود-دستی--مهاجرت-دیتا)
|
||||||
|
4. [دسته ۲ — باگهای کد](#دسته-۲--باگهای-کد)
|
||||||
|
5. [دسته ۳ — وضعیت Stored Procedureها](#دسته-۳--وضعیت-stored-procedureها)
|
||||||
|
6. [آمار کلی جداول](#آمار-کلی-جداول)
|
||||||
|
7. [خلاصه مالی](#خلاصه-مالی)
|
||||||
|
8. [اقدامات پیشنهادی](#اقدامات-پیشنهادی)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه اجرایی
|
||||||
|
|
||||||
|
بکاپ دیتابیس CMS در تاریخ 17 آوریل 2026 تحلیل شد. تحلیل شامل بررسی صحت دادهها، ارجاعات خارجی (FK)، زنجیره مالی، و تطبیق با کد سورس C# و Stored Procedureها بود.
|
||||||
|
|
||||||
|
**وضعیت کلی:** سیستم در حال مهاجرت از ساختار استاتیک به پکیجمحور بوده. بخش عمده مشکلات ناشی از ورود دستی داده و مهاجرت سیستم دایا است. چند باگ کد نیز در SP کمیسیون و Worker دایا شناسایی شد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اصلاحیه مهم
|
||||||
|
|
||||||
|
> **`PaymentStatus=0` در enum کد یعنی `Success` نه `Pending`!**
|
||||||
|
>
|
||||||
|
> ```csharp
|
||||||
|
> // PaymentStatus.cs
|
||||||
|
> Success = 0,
|
||||||
|
> Reject = 1,
|
||||||
|
> Pending = 2
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> بنابراین تمام 72 سفارش واقعاً **موفق** هستند. این مشکل نیست.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## دسته ۱ — ورود دستی / مهاجرت دیتا
|
||||||
|
|
||||||
|
### 1A) موجودی 56M بدون فلگ `HasReceivedDayaCredit`
|
||||||
|
|
||||||
|
**شدت:** 🟠 متوسط
|
||||||
|
**علت:** ورود دستی / مهاجرت
|
||||||
|
|
||||||
|
- **23 کاربر** دقیقاً 56,000,000 ریال در `Balance` دارند ولی `HasReceivedDayaCredit = 0`
|
||||||
|
- فقط **16 کاربر** از طریق Daya Worker صحیح اعتبار گرفتند (`HasReceivedDayaCredit = 1`)
|
||||||
|
- بقیه احتمالاً دستی شارژ شدند بدون ثبت تاریخچه
|
||||||
|
|
||||||
|
**کاربران آسیبپذیر:**
|
||||||
|
|
||||||
|
| UserId | نام | Balance | HasReceivedDayaCredit |
|
||||||
|
|--------|-----|---------|----------------------|
|
||||||
|
| 51 | مرتضی اینالو | 56,000,000 | 0 |
|
||||||
|
| 58 | وحید حقگو | 56,000,000 | 0 |
|
||||||
|
| 87 | امیررضا محمدی | 56,000,000 | 0 |
|
||||||
|
| 43 | کریم خادمی | 56,000,000 | 0 |
|
||||||
|
| 52 | حمیدرضا اسمعیلی | 56,000,000 | 0 |
|
||||||
|
| 88 | کریم رعیتپیشه | 56,000,000 | 0 |
|
||||||
|
| 91 | رحیم رعیتپیشه | 56,000,000 | 0 |
|
||||||
|
| 93 | ریحانه سادات هاشمینصر | 56,000,000 | 0 |
|
||||||
|
| 99 | هستی خادمی | 56,000,000 | 0 |
|
||||||
|
| 110 | علی وفائی | 56,000,000 | 0 |
|
||||||
|
| 113 | کاوس بیگاینالو | 56,000,000 | 0 |
|
||||||
|
| 119 | علیرضا کریمیپیروز | 56,000,000 | 0 |
|
||||||
|
| 122 | ابوالقاسم عابدی | 56,000,000 | 0 |
|
||||||
|
| 123 | ناصر کریمیپیروز | 56,000,000 | 0 |
|
||||||
|
| 124 | محمدرضا باغجری | 56,000,000 | 0 |
|
||||||
|
| 126 | امیرعباس میرزایی | 56,000,000 | 0 |
|
||||||
|
| 138 | سیما اکبرزاده | 56,000,000 | 0 |
|
||||||
|
| 139 | مسعود توسلیان | 56,000,000 | 0 |
|
||||||
|
| 142 | صغری شبانکاره | 56,000,000 | 0 |
|
||||||
|
| 170 | لیلا خدارحمی | 56,000,000 | 0 |
|
||||||
|
| 172 | مهرافشان زاهدنیا | 56,000,000 | 0 |
|
||||||
|
| 175 | ناهید حسنزاده | 56,000,000 | 0 |
|
||||||
|
| 176 | مهریدخت میکانیکی | 56,000,000 | 0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1B) کد ملی تکراری — اکانتهای تستی
|
||||||
|
|
||||||
|
**شدت:** 🟡 پایین
|
||||||
|
**علت:** ورود دستی / تست
|
||||||
|
|
||||||
|
| کد ملی | تعداد اکانت | UserIdها | نام |
|
||||||
|
|--------|------------|----------|-----|
|
||||||
|
| مشترک #1 | 6 | 9, 11, 12, 13, 40, 41 | مهدی مرجانی |
|
||||||
|
| مشترک #2 | 3 | 7, 8, 10 | مهدی صیفی |
|
||||||
|
| مشترک #3 | 2 | 42, 43 | کریم خادمی |
|
||||||
|
| مشترک #4 | 2 | 50, 51 | مرتضی اینالو |
|
||||||
|
| مشترک #5 | 2 | 120, 190 | عسلی |
|
||||||
|
|
||||||
|
- شماره موبایل تکراری: `09038888074` بین کاربران 120 و 190
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1C) `NetworkInfos` خالی — مهاجرت صحیح انجام شده
|
||||||
|
|
||||||
|
**شدت:** ✅ مشکل نیست
|
||||||
|
|
||||||
|
جدول `NetworkInfos` خالی است چون دادههای شبکه به فیلدهای مستقیم `Users` مهاجرت شدند:
|
||||||
|
- `Users.NetworkParentId` ← FK به والد شبکه
|
||||||
|
- `Users.LegPosition` ← Left(0) / Right(1)
|
||||||
|
|
||||||
|
مهاجرت در `20250601_MigrateParentIdToNetworkParentId.sql` انجام شده. entity `NetworkInfo` در C# وجود ندارد. SPها هم از `Users.NetworkParentId` استفاده میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1D) `PasswordHash = NULL` برای تمام 115 کاربر
|
||||||
|
|
||||||
|
**شدت:** 🟡 نیاز به بررسی
|
||||||
|
**علت:** احتمالاً بکاپ شامل فیلد پسورد نشده، یا سیستم OTP/موبایل استفاده میکند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1E) دورههای عضویت باشگاه — `PaidAmount = 0`
|
||||||
|
|
||||||
|
**شدت:** 🟡 نیاز به بررسی
|
||||||
|
|
||||||
|
- 87 دوره `ClubMembershipCycles` همه `PaidAmount = 0`
|
||||||
|
- ممکن است عضویت باشگاه خودکار با خرید پکیج فعال شود (نه پرداخت جداگانه)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## دسته ۲ — باگهای کد
|
||||||
|
|
||||||
|
### 2A) تراکنشهای تکراری دایا — Race Condition در `CheckAndProcessDayaLoansCommandHandler`
|
||||||
|
|
||||||
|
**شدت:** 🔴 بحرانی
|
||||||
|
**فایل:** `CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs`
|
||||||
|
|
||||||
|
**یافتهها:**
|
||||||
|
- **109 رکورد `DayaLoanContracts`** ولی فقط **16 کاربر** `HasReceivedDayaCredit=1`
|
||||||
|
- **64 تراکنش** با «دریافت اعتبار دایا» ساخته شده ولی فقط **11 رکورد `UserPackagePurchases`**
|
||||||
|
- تراکنشها با `RefId` منحصربهفرد ساخته شدند (مثل `C4_T8579002`) — همه در `2025-11-19 01:24:24` ایجاد شدند
|
||||||
|
|
||||||
|
**تحلیل ریشهای:**
|
||||||
|
- Daya Worker (Hangfire هر 15 دقیقه) احتمالاً برای بعضی کاربران **چند بار** اجرا شده
|
||||||
|
- `CreateTransaction` و `DayaLoanContract` ساخته شده ولی `HasReceivedDayaCredit=true` ست نشده (exception بعد از SaveChanges اول ولی قبل از SaveChanges دوم)
|
||||||
|
- یا: چون همه در یک لحظه ساخته شدند (`2025-11-19 01:24:24`)، ممکن است **یک بار bulk import دستی** بوده
|
||||||
|
|
||||||
|
**ریسک:** کاربرانی که `HasReceivedDayaCredit=0` دارند ممکن است **دوباره** از Worker اعتبار بگیرند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2B) SP `sp_CalculateWeeklyCommissionPool` — `DistributedAmount` آپدیت نمیشود
|
||||||
|
|
||||||
|
**شدت:** 🔴 بحرانی
|
||||||
|
**فایل:** `dbbkup/CMS-20260417.sql` خط ~18830
|
||||||
|
|
||||||
|
در Step 10 (آپدیت نهایی Pool):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- کد فعلی (باگدار):
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET
|
||||||
|
IsCalculated = 1,
|
||||||
|
CalculatedAt = @CalculatedAt,
|
||||||
|
TotalBalances = @TotalBalances,
|
||||||
|
ValuePerBalance = @ValuePerBalance,
|
||||||
|
LastModified = @CalculatedAt,
|
||||||
|
LastModifiedBy = 'SP'
|
||||||
|
WHERE Id = @PoolId;
|
||||||
|
```
|
||||||
|
|
||||||
|
**مشکل:** فیلد `DistributedAmount` **هرگز مقداردهی نمیشود** و 0 باقی میماند.
|
||||||
|
|
||||||
|
**نتیجه در دیتا:**
|
||||||
|
- 15 استخر، مجموع `TotalPoolAmount = 2,016,000,000` ریال
|
||||||
|
- همه `DistributedAmount = 0`
|
||||||
|
- ولی 46 پرداخت واقعاً ثبت و به `NetworkBalance` اضافه شدند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2C) `UserWalletChangeLogs` خالی
|
||||||
|
|
||||||
|
**شدت:** 🟠 متوسط
|
||||||
|
**فایل:** SP Step 9 + `CalculateWeeklyCommissionPoolCommandHandler.cs`
|
||||||
|
|
||||||
|
- SP باید در Step 9 لاگ تغییرات کیفپول را در `UserWalletChangeLogs` ذخیره کند
|
||||||
|
- جدول **صفر رکورد** دارد
|
||||||
|
- **احتمال 1:** SP هرگز Step 9 را درست اجرا نکرده
|
||||||
|
- **احتمال 2:** ORM Strategy (نه SP) استفاده شده و آن `UserWalletChangeLogs` نمینویسد
|
||||||
|
- **احتمال 3:** لاگها در حین ForceRecalculate حذف شدند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2D) `WalletHistory.ChangeType = NULL` در تمام 239 رکورد
|
||||||
|
|
||||||
|
**شدت:** 🟠 متوسط
|
||||||
|
**فایل:** `UserOrderService.cs` و `PackageService.cs` — هرجا `UserWalletHistory` ساخته میشود
|
||||||
|
|
||||||
|
- فیلد `ChangeType` هرگز ست نمیشود
|
||||||
|
- کد از `IsIncrease` (bool) برای تفکیک واریز/برداشت استفاده میکند
|
||||||
|
- `ChangeType` احتمالاً فیلد قدیمی deprecated شده
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2E) `NetworkWeeklyBalances.WeeklyCommissionPoolId = NULL` (805 رکورد)
|
||||||
|
|
||||||
|
**شدت:** 🟡 پایین
|
||||||
|
|
||||||
|
- SP مقدار `WeeklyPoolContribution = 0` ثبت میکند و PoolId ست نمیشود
|
||||||
|
- ارتباط بین `NetworkWeeklyBalances` و `WeeklyCommissionPools` از طریق `WeekDefinitionId` برقرار است، نه FK مستقیم
|
||||||
|
- **عملاً مشکل عملکردی ایجاد نمیکند** ولی tracking سختتر میشود
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2F) 30 سفارش کیفپولی — بررسی WalletHistory
|
||||||
|
|
||||||
|
**شدت:** 🟠 نیاز به تأیید
|
||||||
|
|
||||||
|
- 30 سفارش با `PaymentMethod=1` (Wallet) ثبت شدند
|
||||||
|
- اولین بررسی نشان داد «هیچ برداشتی ثبت نشده» — **اما** این بررسی بر اساس `ChangeType` بود که همه NULL هستند
|
||||||
|
- **باید بر اساس `IsIncrease=0` (false = decrease)** دوباره بررسی شود
|
||||||
|
- `SubmitShopBuyOrder()` در کد `UserWalletHistory` میسازد — احتمالاً رکوردها وجود دارند ولی `ChangeType` NULL است
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## دسته ۳ — وضعیت Stored Procedureها
|
||||||
|
|
||||||
|
### `GetNetworkTree`
|
||||||
|
|
||||||
|
| آیتم | وضعیت |
|
||||||
|
|------|--------|
|
||||||
|
| از `Users.NetworkParentId` استفاده میکند | ✅ صحیح (بعد از مهاجرت) |
|
||||||
|
| JOIN با `ClubMemberships` | ✅ صحیح |
|
||||||
|
| `MAXRECURSION 0` | ✅ صحیح |
|
||||||
|
| فیلتر `IsDeleted = 0` | ✅ صحیح |
|
||||||
|
|
||||||
|
### `sp_CalculateWeeklyBalances`
|
||||||
|
|
||||||
|
| آیتم | وضعیت |
|
||||||
|
|------|--------|
|
||||||
|
| پارامتر `@PackageId` از `Packages.IsBasePackage` | ✅ صحیح |
|
||||||
|
| `MaxBalancesPerLeg` و `MaxNetworkLevel` از Package | ✅ صحیح |
|
||||||
|
| Carryover از هفته قبل | ✅ صحیح |
|
||||||
|
| CTE recursive برای چپ/راست | ✅ صحیح |
|
||||||
|
| `TotalBalances = MIN(left, right)` | ✅ صحیح |
|
||||||
|
| `SubordinateBalances` محاسبه | ✅ صحیح |
|
||||||
|
| 805 رکورد تولید شده | ✅ کار میکند |
|
||||||
|
|
||||||
|
### `sp_CalculateWeeklyCommissionPool`
|
||||||
|
|
||||||
|
| آیتم | وضعیت |
|
||||||
|
|------|--------|
|
||||||
|
| `ValuePerBalance = TotalPoolAmount / TotalBalances` | ✅ صحیح |
|
||||||
|
| ایجاد `UserCommissionPayouts` | ✅ صحیح (46 رکورد) |
|
||||||
|
| ثبت `CommissionPayoutHistories` | ✅ صحیح |
|
||||||
|
| شارژ `NetworkBalance` کیفپول | ✅ صحیح |
|
||||||
|
| آپدیت `DistributedAmount` در Pool | ❌ **انجام نمیشود** |
|
||||||
|
| ثبت `UserWalletChangeLogs` | ⚠️ نامشخص |
|
||||||
|
| ForceRecalculate — Revert | ✅ منطق صحیح |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## آمار کلی جداول
|
||||||
|
|
||||||
|
| جدول | تعداد | وضعیت |
|
||||||
|
|------|--------|--------|
|
||||||
|
| Users | 115 | |
|
||||||
|
| UserWallets | 115 | |
|
||||||
|
| UserWalletHistories | 239 | ChangeType همه NULL |
|
||||||
|
| UserWalletChangeLogs | 0 | ⚠️ خالی |
|
||||||
|
| UserOrders | 72 | همه PaymentStatus=0 (Success) |
|
||||||
|
| FactorDetails | 177 | |
|
||||||
|
| Transactions | 175 | 64 تراکنش دایا |
|
||||||
|
| PaymentTransactions | 22 | فقط DiscountOrders + شارژ |
|
||||||
|
| Products | 163 | |
|
||||||
|
| Categories | 14 | |
|
||||||
|
| InventoryItems | 175 | |
|
||||||
|
| StockMovements | 242 | 11 chain issue |
|
||||||
|
| ClubMemberships | 88 | همه IsActive=1 |
|
||||||
|
| ClubMembershipCycles | 87 | همه PaidAmount=0 |
|
||||||
|
| UserClubFeatures | 249 | |
|
||||||
|
| NetworkInfos | 0 | ✅ deprecated — مهاجرت شده |
|
||||||
|
| NetworkWeeklyBalances | 805 | PoolId همه NULL |
|
||||||
|
| WeeklyCommissionPools | 15 | DistributedAmount همه 0 |
|
||||||
|
| UserCommissionPayouts | 46 | Status=3, مبالغ کلان |
|
||||||
|
| WeekDefinitions | 59 | |
|
||||||
|
| Packages | 2 | Base=56M, Secondary=5.6M |
|
||||||
|
| DayaLoanContracts | 109 | |
|
||||||
|
| UserPackagePurchases | 11 | |
|
||||||
|
| DiscountOrders | 13 | |
|
||||||
|
| DiscountOrderDetails | 14 | |
|
||||||
|
| DiscountCategories | 8 | |
|
||||||
|
| OrderVATs | 44 | ✅ محاسبات صحیح |
|
||||||
|
| UserAddresses | 127 | |
|
||||||
|
| Roles | 3 | user, admin, Administrator |
|
||||||
|
| UserRoles | 119 | 115 user + 2 admin + 2 Administrator |
|
||||||
|
| ProductImages | 4 | |
|
||||||
|
| ShippingMethods | 0 | ⚠️ خالی |
|
||||||
|
| SitePages | 0 | ⚠️ خالی |
|
||||||
|
| SystemConfigurations | 0 | ⚠️ خالی |
|
||||||
|
| Coupons | 0 | ⚠️ خالی |
|
||||||
|
| ProductProperties | 0 | ⚠️ خالی |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه مالی
|
||||||
|
|
||||||
|
### موجودیهای کل سیستم
|
||||||
|
|
||||||
|
| فیلد | مبلغ (ریال) |
|
||||||
|
|------|-------------|
|
||||||
|
| مجموع `Balance` کل کیفپولها | 2,548,684,394 |
|
||||||
|
| مجموع `NetworkBalance` (کمیسیون) | 453,599,993 |
|
||||||
|
| مجموع `DiscountBalance` (تخفیف) | 7,326,400,000 |
|
||||||
|
| مجموع سفارشات (UserOrders) | 1,039,410,812 |
|
||||||
|
| مجموع استخر کمیسیون (WeeklyPools) | 2,016,000,000 |
|
||||||
|
|
||||||
|
### پکیجها
|
||||||
|
|
||||||
|
| Package | قیمت | IsBase | MaxBalancesPerLeg | MaxNetworkLevel | DiscountMultiplier |
|
||||||
|
|---------|-------|--------|-------------------|-----------------|-------------------|
|
||||||
|
| Package 1 | 56,000,000 | ✅ | 300 | 1,000,000 | 2.0 |
|
||||||
|
| Package 4 | 5,600,000 | ❌ | 30 | 1,000,000 | 2.0 |
|
||||||
|
|
||||||
|
### ارجاعات شکسته (FK)
|
||||||
|
|
||||||
|
| ارجاع | تعداد |
|
||||||
|
|-------|--------|
|
||||||
|
| FactorDetails → OrderId ناموجود | 2 (DetailId=22,23 → OrderId=21) |
|
||||||
|
| InventoryItems ≠ آخرین StockMovement | 3 |
|
||||||
|
| StockMovement chain breaks | 11 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اقدامات پیشنهادی
|
||||||
|
|
||||||
|
### اولویت بالا (انجام ندهید تا بررسی بیشتر)
|
||||||
|
|
||||||
|
1. **فیکس SP `sp_CalculateWeeklyCommissionPool`:** اضافه کردن `DistributedAmount` به UPDATE نهایی
|
||||||
|
2. **بررسی Daya Worker:** race condition در `CheckAndProcessDayaLoansCommandHandler` — ممکن است تراکنش تکراری بسازد
|
||||||
|
3. **23 کاربر با 56M بدون فلگ دایا:** تعیین اینکه آیا دستی شارژ شدند یا از Worker — سپس اصلاح `HasReceivedDayaCredit`
|
||||||
|
|
||||||
|
### اولویت متوسط
|
||||||
|
|
||||||
|
4. **WalletHistory ChangeType:** تأیید اینکه deprecated شده و `IsIncrease` جایگزین است
|
||||||
|
5. **UserWalletChangeLogs خالی:** بررسی اینکه ORM Strategy استفاده شده یا SP Strategy
|
||||||
|
6. **اکانتهای تکراری:** تصمیمگیری درباره 13 اکانت تکراری (حذف/ادغام)
|
||||||
|
|
||||||
|
### اولویت پایین
|
||||||
|
|
||||||
|
7. **جداول خالی:** SystemConfigurations, ShippingMethods, SitePages — آیا باید از seed پر شوند؟
|
||||||
|
8. **StockMovement chain issues:** 11 ناسازگاری — آیا از ورود دستی موجودی بوده؟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*این گزارش فقط مستندات یافتهها است. هیچ تغییری در کد یا دیتابیس اعمال نشده است.*
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
> **فهرست کامل ۱۵ فایل مستند پروژه FourSat (کارا بازار سلامت)**
|
> **فهرست کامل ۱۵ فایل مستند پروژه FourSat (کارا بازار سلامت)**
|
||||||
> **تاریخ تجمیع:** اسفند ۱۴۰۴
|
> **تاریخ تجمیع:** اسفند ۱۴۰۴
|
||||||
> **تعداد فایلهای مبدأ:** ۵۳ فایل (~۳۲,۰۰۰ خط)
|
> **تعداد فایلهای مبدأ:** ۵۳ فایل (~۳۲,۰۰۰ خط)
|
||||||
> **تعداد فایلهای نهایی:** ۱۷ فایل (۱۵ اصلی + ۲ roadmap)
|
> **تعداد فایلهای نهایی:** ۲۲ فایل (۱۵ اصلی + ۷ roadmap/business)
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (فعالسازی درگاه + مرج Production + بهبود UI)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (فاز ۱۰: DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes | NuGet v0.0.189)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -35,7 +35,10 @@ totalDoc/
|
|||||||
│
|
│
|
||||||
└── 📁 roadmap/ (فیچرهای جدید — در حال توسعه)
|
└── 📁 roadmap/ (فیچرهای جدید — در حال توسعه)
|
||||||
├── MAGIC-WALLET-SPEC.md مشخصات کیفپول جادویی
|
├── MAGIC-WALLET-SPEC.md مشخصات کیفپول جادویی
|
||||||
└── MAGIC-WALLET-PLAN.md پلن پیادهسازی + checklist
|
├── MAGIC-WALLET-PLAN.md پلن پیادهسازی + checklist
|
||||||
|
├── PACKAGE-TRANSFORMATION-TASKS.md تسکهای تحول پکیجبیس (فاز 0-5)
|
||||||
|
├── PACKAGE-TRANSFORMATION-UX.md تاثیر بر UX فرانتها
|
||||||
|
└── FEATURE-BACKLOG.md بکلاگ ۱۲ RPC آماده
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -77,7 +80,11 @@ totalDoc/
|
|||||||
| # | فایل | موضوع | خلاصه |
|
| # | فایل | موضوع | خلاصه |
|
||||||
|---|------|--------|--------|
|
|---|------|--------|--------|
|
||||||
| R1 | [MAGIC-WALLET-SPEC](../roadmap/MAGIC-WALLET-SPEC.md) | کیفپول جادویی — مشخصات | State Machine، ضریب ×2.5، سقف 100M، قوانین، API، مدل داده |
|
| R1 | [MAGIC-WALLET-SPEC](../roadmap/MAGIC-WALLET-SPEC.md) | کیفپول جادویی — مشخصات | State Machine، ضریب ×2.5، سقف 100M، قوانین، API، مدل داده |
|
||||||
| R2 | [MAGIC-WALLET-PLAN](../roadmap/MAGIC-WALLET-PLAN.md) | کیفپول جادویی — پلن | ۶ فاز، **فاز 1-5 تکمیل ✅**، فاز 6 باقی |
|
| R2 | [MAGIC-WALLET-PLAN](../roadmap/MAGIC-WALLET-PLAN.md) | کیفپول جادویی — پلن | ۶ فاز، **فاز 1-6 تکمیل ✅** |
|
||||||
|
| R3 | [PACKAGE-TRANSFORMATION-TASKS](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md) | تحول پکیجبیس — تسکها | ۱۰ فاز، **فاز 0-10 تکمیل ✅**، تست + deploy در انتظار |
|
||||||
|
| R4 | [PACKAGE-TRANSFORMATION-UX](../roadmap/PACKAGE-TRANSFORMATION-UX.md) | تاثیر بر UX | تحلیل تاثیر بر FrontOffice + BackOffice |
|
||||||
|
| R5 | [FEATURE-BACKLOG](../roadmap/FEATURE-BACKLOG.md) | بکلاگ فیچرها | ۱۲ RPC آماده بدون UI، اولویتبندیشده |
|
||||||
|
| R6 | [BIZ-PACKAGE-BASED-SYSTEM](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی سیستم پکیجبیس | v6، ۳۰ تصمیم (Q1-Q30) + ۵۱ تغییر + ۴۴ سایدافکت |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 📜 تاریخچه کارهای انجامشده
|
# 📜 تاریخچه کارهای انجامشده
|
||||||
|
|
||||||
> **همه فعالیتهای پروژه به صورت بولت با توضیح یکخطی و درصد تکمیل**
|
> **همه فعالیتهای پروژه به صورت بولت با توضیح یکخطی و درصد تکمیل**
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فعالسازی درگاه + مرج پروداکشن + بهبود UI ادمین)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱ — فیکسهای پرداخت ZarinPal + امنیت Callback URL + اصلاح تومان/ریال)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,12 +9,13 @@
|
|||||||
|
|
||||||
| حوزه | تعداد آیتم | تکمیلشده | درصد کل |
|
| حوزه | تعداد آیتم | تکمیلشده | درصد کل |
|
||||||
|------|-----------|----------|---------|
|
|------|-----------|----------|---------|
|
||||||
| **BackOffice** | 61 | 60 | **98%** |
|
| **BackOffice** | 67 | 67 | **100%** |
|
||||||
| **FrontOffice** | 40 | 38 | **95%** |
|
| **FrontOffice** | 60 | 60 | **100%** |
|
||||||
| **CMS Core** | 58 | 56 | **97%** |
|
| **CMS Core** | 89 | 89 | **100%** |
|
||||||
|
| **Package-Based System** | 52 | 52 | **100%** |
|
||||||
| **Deployment** | 22 | 21 | **95%** |
|
| **Deployment** | 22 | 21 | **95%** |
|
||||||
| **Migration** | 16 | 16 | **100%** |
|
| **Migration** | 21 | 21 | **100%** |
|
||||||
| **مجموع** | **197** | **191** | **97%** |
|
| **مجموع** | **311** | **310** | **99.5%** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -66,6 +67,13 @@
|
|||||||
- ✅ فیلتر کاربر در صفحه ClubMembers — UserAutoComplete در تولبار جستجو
|
- ✅ فیلتر کاربر در صفحه ClubMembers — UserAutoComplete در تولبار جستجو
|
||||||
- ✅ نمایش نام کاربر در WalletManagementPage — TemplateColumn با UserName + ID
|
- ✅ نمایش نام کاربر در WalletManagementPage — TemplateColumn با UserName + ID
|
||||||
|
|
||||||
|
### Phase 8b: CRUD پکیج کامل (اسفند ۱۴۰۴)
|
||||||
|
- ✅ CreateDialog: اضافه ۱۲ فیلد جدید — SortOrder, ActivationFee, DiscountMultiplier, MagicWallet*, MaxBalancesPerLeg, MaxNetworkLevel, IsActive, IsBasePackage, SupportsDirectPurchase, SupportsDayaPurchase
|
||||||
|
- ✅ UpdateDialog: همان ۱۲ فیلد جدید — ایجاد فرم کامل ادمین
|
||||||
|
- ✅ PackageMainPage Grid: ۴ ستون جدید — قیمت (N0), ترتیب, وضعیت (فعال/غیرفعال chip), نوع (پایه/عادی chip)
|
||||||
|
- ✅ فیکس `HasPurchasedGoldenPackage` → `HasPurchasedPackage` — UserNetworkInfo.razor
|
||||||
|
- ✅ NuGet bump 0.0.184 → 0.0.186 + local feed source
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۲. FrontOffice — فازها (95% کامل)
|
## ۲. FrontOffice — فازها (95% کامل)
|
||||||
@@ -97,8 +105,90 @@
|
|||||||
- ✅ دکمه پرداخت شارژ کیفپول اعتباری — ChargeDiscountWallet.razor فعال شد (حذف «بزودی»)
|
- ✅ دکمه پرداخت شارژ کیفپول اعتباری — ChargeDiscountWallet.razor فعال شد (حذف «بزودی»)
|
||||||
- ✅ دکمه پرداخت شارژ کیفپول جادویی — MagicWallet.razor فعال شد (حذف «بزودی»)
|
- ✅ دکمه پرداخت شارژ کیفپول جادویی — MagicWallet.razor فعال شد (حذف «بزودی»)
|
||||||
- ✅ دکمههای پرداخت مستقیم خرید پکیج — Index.razor هر دو شاخه فعال شدند (حذف «بزودی»)
|
- ✅ دکمههای پرداخت مستقیم خرید پکیج — Index.razor هر دو شاخه فعال شدند (حذف «بزودی»)
|
||||||
|
|
||||||
|
### فیکسهای پرداخت و UX (اسفند ۱۴۰۴ — Phase 11)
|
||||||
|
- ✅ **صفحه موفقیت پرداخت** — `TransactionId` بجای `RefId` + موجودی واقعی + `Href="/profile"` (FO:`5ded91a`)
|
||||||
|
- ✅ **حذف دوبار ×۱۰** — FO مستقیم تومان ارسال میکند، CMS/ZarinPal ×۱۰ میکند (FO:`2f9ef15`)
|
||||||
|
- ✅ **حذف CallbackUrl از درخواست** — `Index.razor.cs` و `Checkout.razor.cs` دیگر URL ارسال نمیکنند (FO:`2b1dc47`)
|
||||||
|
- ✅ ۳ کامیت، ۷ فایل تغییر
|
||||||
|
|
||||||
|
### Phase 8a+8c: Checkout + Package Pages (اسفند ۱۴۰۴)
|
||||||
|
- ✅ Checkout wire-up — مهاجرت به `CustomerPurchasePackageAsync` (حذف dead code قدیمی)
|
||||||
|
- ✅ PackageDetail: `GetPackageAsync` → `GetCustomerPackageDetailsAsync` — features/specs از API (نه hardcoded)
|
||||||
|
- ✅ Packages.razor: un-exclude از build + dynamic feature bullets از `CustomerPackageModel`
|
||||||
|
- ✅ PackageService: `PackageDto` غنیشده با ۸ فیلد جدید + `GetUserPackageStatusAsync` متصل به RPC واقعی
|
||||||
|
- ✅ NuGet bump 0.0.182 → 0.0.186 + local feed source
|
||||||
- ✅ فیکس GwUrl پروداکشن — تصحیح از cms.kbs1.ir به cms.kbs2.ir
|
- ✅ فیکس GwUrl پروداکشن — تصحیح از cms.kbs1.ir به cms.kbs2.ir
|
||||||
|
|
||||||
|
### Phase 10a: PackagePurchaseDialog — دیالوگ داینامیک خرید پکیج (اسفند ۱۴۰۴)
|
||||||
|
- ✅ `PackagePurchaseDialog.razor` — دیالوگ ۲ مرحلهای: مرحله ۱ = کاشیهای پکیج (responsive grid)، مرحله ۲ = انتخاب روش پرداخت
|
||||||
|
- ✅ حذف دیالوگ inline خرید «پکیج پایه» از `Index.razor` — جایگزین با دیالوگ داینامیک
|
||||||
|
- ✅ بارگذاری پکیجها از `PackageService.GetAllPackagesAsync()` — نمایش عنوان + قیمت + ویژگیها
|
||||||
|
- ✅ پشتیبانی از ۲ روش پرداخت: مستقیم (درگاه بانکی) + اعتبار دایا (فقط پکیج پایه + دور اول)
|
||||||
|
- ✅ CSS کلاسهای جدید: `.pkg-tile`, `.pkg-tile-badge`, `.pkg-payment-option`
|
||||||
|
- ✅ کامیت: `a3681a8` (FO)
|
||||||
|
|
||||||
|
### Phase 10b: ۴ فیکس UI پکیج (اسفند ۱۴۰۴)
|
||||||
|
- ✅ **Toman/Rial**: قیمت از سرور به ریال ← `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
|
||||||
|
- ✅ **لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیجها)
|
||||||
|
- ✅ **دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
|
||||||
|
- ✅ **HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
|
||||||
|
- ✅ کامیت: `3c1a8ff` (FO)
|
||||||
|
|
||||||
|
### Phase 11: فیکسهای پرداخت + تومان/ریال + امنیت Callback URL (اسفند ۱۴۰۴)
|
||||||
|
|
||||||
|
#### 11a: اصلاح مدل تومان/ریال (CMS+FO)
|
||||||
|
> **تصحیح مهم:** دیتابیس به **تومان** ذخیره میکند نه ریال. فقط درگاه ZarinPal ریال نیاز دارد (×۱۰).
|
||||||
|
- ✅ `ZarinPalPaymentService.InitiatePaymentAsync` — مبلغ ×۱۰ تبدیل به ریال فقط هنگام ارسال به ZarinPal
|
||||||
|
- ✅ `ZarinPalPaymentService.VerifyPaymentWithAmountAsync` — مبلغ ×۱۰ هنگام verify
|
||||||
|
- ✅ FrontOffice نمایش مستقیم مبلغ تومان (بدون `/10`) — فیکس `MagicWallet.razor`, `ChargeDiscountWallet.razor.cs`
|
||||||
|
- ✅ حذف `Price / 10` اضافی در `ClubMembershipContractDialog.razor`
|
||||||
|
|
||||||
|
#### 11b: فیکس ZarinPal Verify — رفع خطای Code=-1 (CMS:`721661a`)
|
||||||
|
> **باگ:** `VerifyPaymentAsync` با ۲ آرگومان مبلغ صفر (0) ارسال میکرد → ZarinPal Code=-1 برمیگرداند
|
||||||
|
- ✅ `PackageService` — lookup `PaymentTransaction.Amount` + استفاده از overload ۳ آرگومانه
|
||||||
|
- ✅ `TransactionsService` — همان فیکس
|
||||||
|
- ✅ `VerifyDiscountWalletChargeCommandHandler` — مبلغ از `PaymentTransaction` + رفع کپیپیست باگ
|
||||||
|
- ✅ `VerifyPackagePurchaseCommandHandler` — مبلغ از `PaymentTransaction`
|
||||||
|
- ✅ `IPaymentGatewayService` — default impl ۳ آرگومانه با `NotImplementedException`
|
||||||
|
- ✅ `MockPaymentGatewayService` + `DayaPaymentService` — اضافه overload ۳ آرگومانه
|
||||||
|
- ✅ ۷ فایل تغییر
|
||||||
|
|
||||||
|
#### 11c: بهبود صفحه موفقیت پرداخت (FO:`5ded91a`)
|
||||||
|
- ✅ `PaymentCallback.razor` — نمایش `TransactionId` بجای `RefId` برای کد رهگیری
|
||||||
|
- ✅ نمایش موجودی واقعی کیفپول از `WalletService.GetBalancesAsync()` (نه مقدار ثابت)
|
||||||
|
- ✅ دکمه بازگشت: `Href="/profile"` بجای `history.back()` (جلوگیری از حلقه بازگشت به درگاه)
|
||||||
|
|
||||||
|
#### 11d: حذف دوبار ×۱۰ شارژ کیفپول (FO:`2f9ef15`)
|
||||||
|
> **باگ:** FO مبلغ تومان را ×۱۰ تبدیل به ریال میکرد، سپس CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر
|
||||||
|
- ✅ `MagicWallet.razor.cs` — حذف تبدیل ×۱۰ (ارسال مستقیم تومان)
|
||||||
|
- ✅ `MagicWallet.razor` — فیکس Max و فیلتر preset مبالغ
|
||||||
|
- ✅ `ChargeDiscountWallet.razor.cs` — حذف تبدیل ×۱۰
|
||||||
|
- ✅ `ClubMembershipContractDialog.razor` — حذف `Price/10` اضافی
|
||||||
|
- ✅ ۴ فایل تغییر
|
||||||
|
|
||||||
|
#### 11e: فیکس مسیر Callback کیفپول (CMS:`ed2b20a`)
|
||||||
|
- ✅ `PaymentCallbackController` — مسیر redirect از `/magic-wallet` به `/profile/magic-wallet`
|
||||||
|
- ✅ ایجاد `appsettings.Development.json` — URLهای محلی (`localhost:32846` و `localhost:5268`)
|
||||||
|
- ✅ تصحیح کامنتهای proto: «ریال» → «تومان»
|
||||||
|
|
||||||
|
#### 11f: امنیت Callback URL — حذف از ورودی کاربر (CMS:`0107308`, FO:`2b1dc47`)
|
||||||
|
> **اصلاح امنیتی:** هیچ callback URL نباید از ورودی کاربر بیاید — همه از `appsettings.json` خوانده شوند
|
||||||
|
- ✅ `PackageService` — خواندن `FrontOfficeBaseUrl` از `IConfiguration` بجای `request.CallbackUrl`
|
||||||
|
- ✅ `TransactionsService` — همان فیکس، خواندن از config
|
||||||
|
- ✅ تأیید: `MagicWallet` و `DiscountWallet` از قبل از `CmsBaseUrl` config میخوانند ✅
|
||||||
|
- ✅ تأیید: `DiscountShop PlaceOrder` از قبل از `CmsBaseUrl` config میخواند ✅
|
||||||
|
- ✅ FO: حذف `CallbackUrl` از `Index.razor.cs` و `Checkout.razor.cs`
|
||||||
|
- ✅ جدول Callback URLها:
|
||||||
|
|
||||||
|
| فلو | Callback URL | منبع |
|
||||||
|
|-----|-------------|------|
|
||||||
|
| خرید پکیج | `FrontOfficeBaseUrl/profile/payment-callback?orderId=X` | config |
|
||||||
|
| کیفپول جادویی | `CmsBaseUrl/api/wallet/verify-magic-charge` | config |
|
||||||
|
| کیفپول اعتباری | `CmsBaseUrl/api/wallet/verify-discount-charge` | config |
|
||||||
|
| فروشگاه اعتباری | `CmsBaseUrl/api/payment/discount-order/callback?orderId=X` | config |
|
||||||
|
| تراکنش عمومی | `FrontOfficeBaseUrl/profile/payment-callback` | config |
|
||||||
|
|
||||||
### محتوا و ناوبری
|
### محتوا و ناوبری
|
||||||
- ✅ بلاگ — لیست + جزئیات + pagination
|
- ✅ بلاگ — لیست + جزئیات + pagination
|
||||||
- ✅ صفحات سایت — About, Contact, FAQ, Terms, Privacy, Licenses
|
- ✅ صفحات سایت — About, Contact, FAQ, Terms, Privacy, Licenses
|
||||||
@@ -161,6 +251,14 @@
|
|||||||
- ✅ اغنای پاسخ UserWalletService — join با جدول Users برای نمایش نام کاربر
|
- ✅ اغنای پاسخ UserWalletService — join با جدول Users برای نمایش نام کاربر
|
||||||
- ✅ ارتقای Proto NuGet به نسخه 0.0.183
|
- ✅ ارتقای Proto NuGet به نسخه 0.0.183
|
||||||
|
|
||||||
|
### فیکسهای پرداخت و امنیت (اسفند ۱۴۰۴ — Phase 11)
|
||||||
|
- ✅ **ZarinPal Verify fix** — رفع باگ amount=0 در VerifyPaymentAsync (Code=-1) — ۳ آرگومانه overload
|
||||||
|
- ✅ **تصحیح مدل تومان/ریال** — DB به تومان ذخیره میکند، فقط ZarinPal ریال (×۱۰) نیاز دارد
|
||||||
|
- ✅ **Callback URL از config** — `PackageService` و `TransactionsService` از `FrontOfficeBaseUrl` config میخوانند (نه از ورودی)
|
||||||
|
- ✅ **فیکس مسیر redirect** — `/magic-wallet` → `/profile/magic-wallet` در PaymentCallbackController
|
||||||
|
- ✅ **appsettings.Development.json** — URLهای محلی برای توسعه (CmsBaseUrl + FrontOfficeBaseUrl)
|
||||||
|
- ✅ کامیتها: `721661a` → `ed2b20a` → `0107308`
|
||||||
|
|
||||||
### محتوا
|
### محتوا
|
||||||
- ✅ Blog CRUD — با pagination
|
- ✅ Blog CRUD — با pagination
|
||||||
- ✅ SitePage Settings — JSON typed
|
- ✅ SitePage Settings — JSON typed
|
||||||
@@ -204,6 +302,21 @@
|
|||||||
- ✅ Proto Package Unification — یک package مشترک
|
- ✅ Proto Package Unification — یک package مشترک
|
||||||
- ✅ Binary Tree Reconstruction — از سیستم قدیم
|
- ✅ Binary Tree Reconstruction — از سیستم قدیم
|
||||||
|
|
||||||
|
### DataMigration Tool (اسفند ۱۴۰۴)
|
||||||
|
- ✅ ابزار مستقل مهاجرت داده — .NET 9 Console + Dapper + Polly + Serilog
|
||||||
|
- ✅ Smart Retry Policy — فقط خطاهای transient SQL (deadlock, timeout, transport) — نه خطاهای منطقی
|
||||||
|
- ✅ FK Disable/Enable — `NOCHECK`/`CHECK` حول مهاجرت برای حل FK violation
|
||||||
|
- ✅ TruncateTargetTables — حل مشکل duplicate key (IX_ClubMembership_UserId)
|
||||||
|
- ✅ Fallback Table Name — اگر جدول مقصد rename شده (`UserWalletChangeLogs` → `UserWalletHistories`)
|
||||||
|
- ✅ PostMigration SQL — همه مراحل با `IF COL_LENGTH` / `OBJECT_ID` guard شده
|
||||||
|
- ✅ کامیتها: `0e8c6fd` → `8385c90` → `31cc464`
|
||||||
|
|
||||||
|
### EF Migration — Staging (اسفند ۱۴۰۴)
|
||||||
|
- ✅ اعمال migrations روی DB استیجینگ KBS (`185.252.31.42,2019/KBS`) — موفق
|
||||||
|
- ✅ اعمال migrations روی DB اپلیکیشن (`194.5.195.53,31433/Foursat`) — موفق
|
||||||
|
- ✅ آخرین migration: `20260227024734_Q27_HistoryTables_And_RenameWalletHistory` (۵۵ migration مجموع)
|
||||||
|
- ✅ حل خطای لاگین `Invalid column name 'FirstActivationDate'` — دو DB مختلف بودند
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۶. مستندات (100% کامل)
|
## ۶. مستندات (100% کامل)
|
||||||
@@ -217,7 +330,157 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۷. Timeline (جدول زمانی)
|
## ۷. تحول سیستم پکیجبیس (Package-Based Transformation) — 90%
|
||||||
|
|
||||||
|
> 📦 تبدیل سیستم تکپکیجی hardcoded به معماری چندپکیجی داینامیک
|
||||||
|
> مرجع: [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | [PACKAGE-TRANSFORMATION-TASKS.md](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md)
|
||||||
|
|
||||||
|
### Phase 0 — فیکس باگهای فوری ✅ (`8b9c317`, `fe3edd1`)
|
||||||
|
- ✅ B1: DiscountBalance شارژ نمیشد در VerifyGoldenPackagePurchase — اضافه `DiscountBalance += Amount × 2` + WalletChangeLog
|
||||||
|
- ✅ B2: UserPackagePurchase ساخته نمیشد در VerifyGoldenPackagePurchase — ساخت record بعد verify
|
||||||
|
- ✅ B3: UserPackagePurchase ساخته نمیشد در VerifyPackagePurchase — ساخت record
|
||||||
|
- ✅ B4: UserPackagePurchase ساخته نمیشد در VerifyBasePackagePayment — ساخت record
|
||||||
|
- ✅ B6: EXIT Magic Mode — ریست PackagePurchaseMethod + بستن چرخه فعلی
|
||||||
|
|
||||||
|
### Phase 1 — زیرساخت Domain ✅ (`ae92ab8`)
|
||||||
|
- ✅ T1.1: Package Entity — ۱۱ فیلد جدید (SortOrder, IsActive, IsBasePackage, DiscountMultiplier, MagicWalletMultiplier, ...)
|
||||||
|
- ✅ T1.2: PackageFeature Entity — رابطه M:N بین Package و ClubFeature
|
||||||
|
- ✅ T1.3: ClubMembership — ۴ فیلد First/Last Activation + PackageId
|
||||||
|
- ✅ T1.4-T1.6: اضافه PackageId به ClubMembershipCycle, WeeklyCommissionPool, UserCommissionPayout, NetworkWeeklyBalance, UserWalletChangeLog
|
||||||
|
- ✅ T1.7: علامتگذاری ۹ SystemConstants بهعنوان [Obsolete]
|
||||||
|
- ✅ T1.8: EF Configurations — Index, Precision, FK relations
|
||||||
|
|
||||||
|
### Phase 1.5 — Migration + Data Seed ✅ (`a9cd2fd`)
|
||||||
|
- ✅ EF Migration `AddPackageBasedSystem` — ستونها + جداول + ایندکسها
|
||||||
|
- ✅ Golden Package Seed (Id=1) — Price=56M, ActivationFee=25.2M, DiscountMultiplier=2.0, MagicWalletMultiplier=2.5
|
||||||
|
- ✅ Data Backfill — تمام رکوردهای موجود → PackageId=1
|
||||||
|
- ✅ فیکس nullable DateTime/long در ClubMembership
|
||||||
|
- ✅ فیکس Shadow FK PackageId1
|
||||||
|
|
||||||
|
### Phase 2 — منطق کسبوکار ✅ (`8e5c7c5`)
|
||||||
|
- ✅ T2.1: ActivateClubMembership — ActivationFee از Package entity
|
||||||
|
- ✅ T2.2: AcceptClubMembershipContract — Package features از DB
|
||||||
|
- ✅ T2.3: CalculateWeeklyBalances — MaxBalancesPerLeg per-package
|
||||||
|
- ✅ T2.4: ProcessUserPayouts — PackageId tracking
|
||||||
|
- ✅ T2.5: DayaLoans — Package.Price بجای hardcoded
|
||||||
|
- ✅ T2.6: ManualPayment — Package.Price بجای hardcoded
|
||||||
|
- ✅ T2.7: InitiateBasePackage/VerifyBasePackage — Package-based
|
||||||
|
- ✅ T2.8: ChargeMagicWallet/VerifyMagicWalletCharge — MagicWalletMultiplier per-package
|
||||||
|
- ✅ T2.9: OrmCommissionCalculationStrategy — MaxBalancesPerLeg/MaxNetworkLevel per-package
|
||||||
|
- ✅ T2.10: ConfigurationService/UserOrderService/UserWalletService — Package reads
|
||||||
|
- ✅ **نتیجه:** صفر مصرف SystemConstants deprecated باقی مانده
|
||||||
|
|
||||||
|
### Phase 3 — بازسازی لایه Package ✅ (`ccb938e`)
|
||||||
|
- ✅ Proto: ۱۱ فیلد جدید در ۵ message (CreateNewPackageRequest, UpdatePackageRequest, GetPackageResponse, ...)
|
||||||
|
- ✅ GetUserPackageStatus — پیادهسازی (قبلاً NotImplementedException بود!)
|
||||||
|
- ✅ CustomerVerifyPackagePurchase — شارژ کیفپول اضافه شد (قبلاً missing بود!)
|
||||||
|
- ✅ VerifyGoldenPackagePurchase — `package.DiscountMultiplier` بجای hardcoded ×2
|
||||||
|
- ✅ GetAllPackageByFilter — فیلتر IsDeleted
|
||||||
|
- ✅ GetCustomerPackages — فیلتر IsDeleted + IncludeInactive + SortOrder
|
||||||
|
- ✅ GetCustomerPackageDetails — PackageFeatures از DB
|
||||||
|
- ✅ GetCustomerPurchaseHistory — Include Transaction
|
||||||
|
- ✅ UpdatePackageCommand — ۱۲ فیلد جدید
|
||||||
|
|
||||||
|
### Phase 4 — تکمیل CRUD + Legacy Fixes ✅ (`0002a5a`)
|
||||||
|
- ✅ CreateNewPackageCommand — ۱۲ فیلد جدید با defaultهای مناسب
|
||||||
|
- ✅ GetPackageResponseDto — ۱۲ فیلد جدید (Mapster auto-map)
|
||||||
|
- ✅ GetAllPackageByFilterResponseModel — ۱۲ فیلد جدید
|
||||||
|
- ✅ PurchaseGoldenPackage — حذف Title string match شکننده (`"طلایی"/"golden"`) → `IsDeleted/IsActive/SupportsDirectPurchase`
|
||||||
|
- ✅ VerifyPackagePurchase — حذف hardcoded `order.Amount × 2` → `package.DiscountMultiplier` از DB
|
||||||
|
|
||||||
|
### Phase 5 — پورسانت per-package + پاکسازی golden ✅ (`607f791`, `7176fe4`)
|
||||||
|
- ✅ ORM Commission: per-user-package calculation via `ClubMembership.LastPackageId`
|
||||||
|
- userPackageMap، per-user maxBalancesPerLeg/maxNetworkLevel
|
||||||
|
- Carryover keyed by (UserId, PackageId) tuple
|
||||||
|
- ✅ SP Commission: loop over packages، pass `@PackageId/@InputMaxBalancesPerLeg/@InputMaxNetworkLevel`
|
||||||
|
- ✅ sp_CalculateWeeklyBalances: ۳ پارامتر جدید، فیلتر `cm.LastPackageId = @PackageId`، ستون PackageId در INSERT
|
||||||
|
- ✅ Fix: `cm.PackageId` → `cm.LastPackageId` — match actual DB column name
|
||||||
|
- ✅ Fix golden/طلایی string refs in user-facing messages (ActivateClubMembership)
|
||||||
|
- ✅ Rename `HasPurchasedGoldenPackage` → `HasPurchasedPackage` (DTO + Handler + Proto + Mapping)
|
||||||
|
|
||||||
|
### Phase 6 — Deprecation cleanup + ConfigurationService ✅ (`d19c569`)
|
||||||
|
- ✅ Mark `PurchaseGoldenPackage`/`VerifyGoldenPackagePurchase` RPCs as `deprecated = true`
|
||||||
|
- ✅ Mark `InitiateBasePackagePayment`/`VerifyBasePackagePayment` RPCs as `deprecated = true`
|
||||||
|
- ✅ Remove deprecated SystemConstants from `GetAllAsDict`/`GetAllWithDescriptions` helpers
|
||||||
|
- ✅ Add MagicWallet per-package values to ConfigurationService (Multiplier, MaxDeposit, MaxCredit)
|
||||||
|
- ✅ تأیید: صفر رفرنس فعال به ۹ SystemConstants منسوخ — dead code آماده حذف
|
||||||
|
|
||||||
|
### Phase 7 — UI ✅ (گزارش per-package)
|
||||||
|
- ⬜ FrontOffice: کاشیهای داینامیک پکیج
|
||||||
|
- ⬜ FrontOffice: MyPackages + re-purchase
|
||||||
|
- ✅ FrontOffice: Commission Dashboard per-package — فیلتر dropdown پکیج + ستون پکیج + MudChip (دسکتاپ + موبایل)
|
||||||
|
- ✅ FrontOffice: WeeklyBalance per-package — فیلتر MudSelect پکیج + MudChip اطلاعات هفته
|
||||||
|
- ✅ BackOffice: فیلتر پکیج در گزارشها — PackageSelect component + UserPayouts + BalancesReport
|
||||||
|
|
||||||
|
### Phase 8e — Per-Package Commission Reports ✅ (CMS:`aaaf7fc` FO:`a956cb9` BO:`8be98ae`)
|
||||||
|
- ✅ Proto: اضافه `package_id` فیلتر به ۴ request + `package_id`/`package_title` به ۴ response model
|
||||||
|
- ✅ CMS: اضافه PackageId فیلتر به ۴ query + handler + ۳ DTO + CommissionProfile mapping
|
||||||
|
- ✅ BO: کامپوننت PackageSelect + فیلتر و ستون پکیج در UserPayouts + BalancesReport
|
||||||
|
- ✅ FO: فیلتر و ستون پکیج در CommissionDashboard + WeeklyBalance
|
||||||
|
- ✅ NuGet: `0.0.186` → `0.0.187`
|
||||||
|
|
||||||
|
### Phase 9 — Q24-Q30 Business Decisions + History Infrastructure ✅
|
||||||
|
|
||||||
|
#### 9a: Q24+Q26 — Balance Threshold + SP Worker ✅ (CMS:`a1024a3`)
|
||||||
|
- ✅ Q24: آستانه موجودی `Balance <= 1_000_000` ریال برای ورود Magic و خرید مجدد (بجای `== 0`)
|
||||||
|
- ✅ Q26: `StoredProcedureDeploymentService` (IHostedService) — خواندن فایلهای `.sql` از embedded resource، مقایسه checksum و اعمال خودکار در startup
|
||||||
|
|
||||||
|
#### 9b: Q27 — History Tables Entities ✅ (CMS:`fdbb91d`)
|
||||||
|
- ✅ `PackageHistory` entity — فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive
|
||||||
|
- ✅ `ClubMembershipCycleHistory` entity — فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt
|
||||||
|
- ✅ `PackageAction` و `ClubMembershipCycleAction` enums
|
||||||
|
- ✅ EF Configurations + DbSets + Navigation Properties
|
||||||
|
|
||||||
|
#### 9c: Q28 — UI Guidance ✅ (FO:`474d364` BO:`6939780`)
|
||||||
|
- ✅ FrontOffice: ۷ صفحه با MudAlert (G1-G7) — Packages, Checkout, MyPackages, MagicWallet, Commission, Membership, ActivationSection
|
||||||
|
- ✅ BackOffice: ۶ صفحه با MudAlert (G8-G13) — PackageCRUD, ClubFeatures, ManualPayments, Commission Dashboard, UserPayouts, ClubMembers
|
||||||
|
|
||||||
|
#### 9d: Rename + History Interceptor + Migration ✅ (CMS:`10d2ca2`)
|
||||||
|
- ✅ تغییر نام `UserWalletChangeLog` → `UserWalletHistory` در ۵۴+ فایل (entities, configs, DTOs, commands, queries, protos, services)
|
||||||
|
- ✅ تغییر نام ۳۴ فایل و ۱۱ دایرکتوری
|
||||||
|
- ✅ تغییر نام proto: `userwalletchangelog.proto` → `userwallethistory.proto`
|
||||||
|
- ✅ `IHasHistory<T>` generic interface — متد `CreateHistorySnapshot` برای ثبت خودکار
|
||||||
|
- ✅ `HistoryTrackingSaveChangesInterceptor` — reflection-based، auto-fill Old* از OriginalValues
|
||||||
|
- ✅ `Package` implements `IHasHistory<PackageHistory>`
|
||||||
|
- ✅ EF Migration `Q27_HistoryTables_And_RenameWalletHistory` — **RenameTable** (حفظ داده) + rename PK/FK/Index via sp_rename
|
||||||
|
- ✅ NuGet: `0.0.187` → `0.0.188`
|
||||||
|
|
||||||
|
### Phase 10 — استقرار + DataMigration + UI خرید پکیج ✅
|
||||||
|
|
||||||
|
#### 10a: DataMigration Tool ✅ (Local — بدون remote)
|
||||||
|
- ✅ ابزار مستقل مهاجرت داده — .NET 9 Console app + Dapper (bulk copy) + Polly (retry) + Serilog (logging)
|
||||||
|
- ✅ مهاجرت ۱۸ جدول از DB پروداکشن (`185.252.31.42,2019/Foursat`) به استیجینگ (`KBS`)
|
||||||
|
- ✅ Smart Retry — فقط خطاهای transient (deadlock/timeout/transport)، نه خطاهای منطقی
|
||||||
|
- ✅ FK Disable/Enable — `ALTER TABLE NOCHECK/CHECK CONSTRAINT` حول هر مهاجرت
|
||||||
|
- ✅ TruncateTargetTables — حل duplicate key (`IX_ClubMembership_UserId`) هنگام اجرای مجدد
|
||||||
|
- ✅ Fallback Table Name — جدول مقصد rename شده؟ (`UserWalletChangeLogs` → `UserWalletHistories`)
|
||||||
|
- ✅ PostMigration SQL — همه مراحل با `IF COL_LENGTH`/`OBJECT_ID` guard شده (سازگار با هر دو schema)
|
||||||
|
- ✅ کامیتها: `0e8c6fd` → `8385c90` (MERGE fix) → `31cc464` (FK+truncate+PostMigration)
|
||||||
|
|
||||||
|
#### 10b: EF Migration Staging ✅
|
||||||
|
- ✅ اعمال ۵۵ migration روی DB استیجینگ KBS (`185.252.31.42,2019;Database=KBS`)
|
||||||
|
- ✅ اعمال ۵۵ migration روی DB اپلیکیشن (`194.5.195.53,31433;Database=Foursat`)
|
||||||
|
- ✅ آخرین migration: `20260227024734_Q27_HistoryTables_And_RenameWalletHistory`
|
||||||
|
- ✅ حل خطای لاگین: `Invalid column name 'FirstActivationDate'` — CMS به DB دیگری وصل بود
|
||||||
|
|
||||||
|
#### 10c: PackagePurchaseDialog — دیالوگ داینامیک خرید (FO:`a3681a8`)
|
||||||
|
- ✅ `PackagePurchaseDialog.razor` — دیالوگ ۲ مرحلهای جایگزین دیالوگ hardcoded «پکیج پایه»
|
||||||
|
- ✅ مرحله ۱: نمایش کاشیهای پکیج (responsive grid 2-3 ستونه) با عنوان + قیمت + ویژگیها + badge «پایه»
|
||||||
|
- ✅ مرحله ۲: انتخاب روش پرداخت (مستقیم + اعتبار دایا) با خلاصه پکیج انتخابی
|
||||||
|
- ✅ بارگذاری از `PackageService.GetAllPackagesAsync()` + `PackagePurchaseResult` record
|
||||||
|
- ✅ محدودیت دایا: فقط `SupportsDayaPurchase && PurchaseCycleCount == 0`
|
||||||
|
- ✅ CSS: `.pkg-tile`, `.pkg-tile-badge`, `.pkg-payment-option` در `site.css`
|
||||||
|
- ✅ NuGet: `0.0.188` → `0.0.189`
|
||||||
|
|
||||||
|
#### 10d: ۴ فیکس UI پکیج (FO:`3c1a8ff`)
|
||||||
|
- ✅ **Toman/Rial**: قیمت از سرور به ریال ← `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
|
||||||
|
- ✅ **لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیجها)
|
||||||
|
- ✅ **دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
|
||||||
|
- ✅ **HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۸. Timeline (جدول زمانی)
|
||||||
|
|
||||||
| زمان | رویداد | درصد پروژه |
|
| زمان | رویداد | درصد پروژه |
|
||||||
|------|--------|-----------|
|
|------|--------|-----------|
|
||||||
@@ -232,3 +495,8 @@
|
|||||||
| اسفند ۱۴۰۳ (هفته ۴) | مستندات + نهاییسازی | 95% |
|
| اسفند ۱۴۰۳ (هفته ۴) | مستندات + نهاییسازی | 95% |
|
||||||
| اسفند ۱۴۰۴ (هفته ۱-۲) | 🪄 کیفپول جادویی (فاز 1-6) + اصلاح VAT 10% | 96% |
|
| اسفند ۱۴۰۴ (هفته ۱-۲) | 🪄 کیفپول جادویی (فاز 1-6) + اصلاح VAT 10% | 96% |
|
||||||
| اسفند ۱۴۰۴ (هفته ۳) | 🚀 فعالسازی درگاه + بهبود UI ادمین + مرج پروداکشن | 97% |
|
| اسفند ۱۴۰۴ (هفته ۳) | 🚀 فعالسازی درگاه + بهبود UI ادمین + مرج پروداکشن | 97% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۴) | 📦 تحول پکیجبیس فاز ۰-۶ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation) | 97% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۵) | 📦 فاز 8e: گزارشهای پورسانت per-package (Proto + CMS + BO + FO) | 98% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۶) | 📦 فاز ۹: Q24-Q30 (آستانه + SP Worker + History Tables + UI Guidance + Rename + Interceptor + Migration) | 99% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۷) | 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes (Toman/Rial + لیبل + HTML) | 99.5% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۸) | 💳 فاز ۱۱: فیکس ZarinPal Verify + اصلاح تومان/ریال + صفحه موفقیت + حذف دوبار ×۱۰ + امنیت Callback URL | 99.5% |
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
# 🗺️ نقشه راه، ریسکها و کارهای باقیمانده
|
# 🗺️ نقشه راه، ریسکها و کارهای باقیمانده
|
||||||
|
|
||||||
> **Roadmap + Risk Register + Dependencies + Priorities**
|
> **Roadmap + Risk Register + Dependencies + Priorities**
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فعالسازی درگاه + مرج پروداکشن + بهبود UI)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱ — فیکسهای پرداخت ZarinPal + امنیت Callback URL + تومان/ریال)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۱. وضعیت فعلی پروژه
|
## ۱. وضعیت فعلی پروژه
|
||||||
|
|
||||||
```
|
```
|
||||||
██████████████████████████████████████████████████ 97%
|
██████████████████████████████████████████████████ 95%
|
||||||
|
|
||||||
Core Platform ████████████████████████████████████████████████ 98%
|
Core Platform ████████████████████████████████████████████████ 98%
|
||||||
Club System ████████████████████████████████████████████████ 98%
|
Club System ████████████████████████████████████████████████ 98%
|
||||||
E-Commerce ████████████████████████████████████████████████ 98%
|
E-Commerce ████████████████████████████████████████████████ 98%
|
||||||
Payment ████████████████████████████████████████████████ 97%
|
Payment ████████████████████████████████████████████████ 99%
|
||||||
Magic Wallet ████████████████████████████████████████████████ 100%
|
Magic Wallet ████████████████████████████████████████████████ 100%
|
||||||
UI/UX █████████████████████████████████████████████░░░ 93%
|
Package-Based ███████████████████████████████████████████████░░ 97%
|
||||||
|
UI/UX ██████████████████████████████████████████████░░░ 95%
|
||||||
Deployment ██████████████████████████████████████████████░░ 95%
|
Deployment ██████████████████████████████████████████████░░ 95%
|
||||||
Documentation ████████████████████████████████████████████████ 100%
|
Documentation ████████████████████████████████████████████████ 100%
|
||||||
```
|
```
|
||||||
@@ -207,10 +208,14 @@ DONE (اسفند ۱۴۰۴):
|
|||||||
→ بهبود UI ادمین (UserAutoComplete + نام کاربر در کیفپول) ✅
|
→ بهبود UI ادمین (UserAutoComplete + نام کاربر در کیفپول) ✅
|
||||||
→ مرج پروداکشن هر ۳ ریپو (CMS + FO + BO) ✅
|
→ مرج پروداکشن هر ۳ ریپو (CMS + FO + BO) ✅
|
||||||
→ اجرای Migration روی پروداکشن ✅
|
→ اجرای Migration روی پروداکشن ✅
|
||||||
|
→ 📦 تحول پکیجبیس فاز 0-6 ✅ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation)
|
||||||
|
→ 📦 فاز ۹: Q24-Q30 + History + Rename + Interceptor ✅
|
||||||
|
→ 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes ✅
|
||||||
|
→ 💳 فاز ۱۱: فیکس ZarinPal Verify (amount=0) + تصحیح مدل تومان/ریال + صفحه موفقیت پرداخت + حذف دوبار ×۱۰ + امنیت Callback URL ✅
|
||||||
|
|
||||||
NOW (این ماه):
|
NOW (این ماه):
|
||||||
→ EF Migration نهایی
|
|
||||||
→ تست کامل پروداکشن
|
→ تست کامل پروداکشن
|
||||||
|
→ فیکس باگهای کشفشده در تست
|
||||||
|
|
||||||
NEXT (فروردین):
|
NEXT (فروردین):
|
||||||
→ Mobile Responsive (H2)
|
→ Mobile Responsive (H2)
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# 📋 فیچر بکلاگ — RPCهای آماده (بدون UI)
|
||||||
|
|
||||||
|
> تاریخ: ۱۴۰۴/۱۲/۱۰
|
||||||
|
> منبع: آدیت gRPC (کامیت `3575e48`) → ۱۲ RPC کامل بدون فرانت
|
||||||
|
> اولویتبندی: بر اساس ارزش کسبوکار + نیازمندی پکیجبیس
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 خلاصه
|
||||||
|
|
||||||
|
از ۲۴ RPC مُرده شناساییشده، **۱۲ عدد پیادهسازی کامل** دارند ولی هرگز از فرانتها وصل نشدند. اینها فیچرهای آماده هستند که فقط نیاز به UI دارند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 ماتریس فیچر × اولویت
|
||||||
|
|
||||||
|
### 🔴 اولویت بالا — مرتبط با پکیجبیس کردن
|
||||||
|
|
||||||
|
| # | RPC | تارگت | صفحه | اقدام | تخمین |
|
||||||
|
|---|-----|-------|------|-------|-------|
|
||||||
|
| F1 | `AssignFeatureToMembership` | BackOffice | ClubFeaturesPage.razor | دکمه «اختصاص فیچر به عضو» + ماتریس PackageFeature | ۴ ساعت |
|
||||||
|
| F2 | `ChangeNetworkParent` | BackOffice | UserNetworkInfo.razor | ✅ دکمه «تغییر والد» + مودال ChangeParentDialog | BO:`e020354` |
|
||||||
|
| F3 | `CalculateOrderPV` | FrontOffice | Store/OrderDetail.razor | ✅ نمایش PV سفارش + PV هر محصول | FO:`3bffc13` |
|
||||||
|
|
||||||
|
### 🟡 اولویت متوسط — بهبود UX فروشگاه
|
||||||
|
|
||||||
|
| # | RPC | تارگت | صفحه | اقدام | تخمین |
|
||||||
|
|---|-----|-------|------|-------|-------|
|
||||||
|
| F4 | `CustomerReorderPreviousOrder` | FrontOffice | OrderHistory (Store/Discount) | دکمه «تکرار سفارش» در هر ردیف تاریخچه | ۳ ساعت |
|
||||||
|
| F5 | `CustomerTrackOrder` | FrontOffice | OrderTracking.razor | وصل Tracking API → نمایش TrackingCode + وضعیت ارسال | ۴ ساعت |
|
||||||
|
| F6 | `UpdateCustomerSettings` | FrontOffice | Profile/Settings.razor | فرم تنظیمات اعلان (Email/SMS/Push) + دکمه ذخیره | ۳ ساعت |
|
||||||
|
| F7 | `GetLowStockProducts` | BackOffice | LowStockPage.razor | وصل API → فیلتر threshold + هشدار بصری | ۳ ساعت |
|
||||||
|
|
||||||
|
### 🟢 اولویت پایین — گزارشدهی و عملیات انبوه
|
||||||
|
|
||||||
|
| # | RPC | تارگت | صفحه | اقدام | تخمین |
|
||||||
|
|---|-----|-------|------|-------|-------|
|
||||||
|
| F8 | `GetInventorySummary` | BackOffice | InventoryMainPage.razor | کارت خلاصه بالای صفحه (تعداد کل + ارزش ریالی) | ۳ ساعت |
|
||||||
|
| F9 | `GetStockValueReport` | BackOffice | InventoryMainPage.razor | تب «گزارش ارزش» + دانلود Excel | ۴ ساعت |
|
||||||
|
| F10 | `BulkAddStock` | BackOffice | InventoryMainPage.razor | دکمه «افزودن دستهای» + آپلود CSV/فرم چندتایی | ۶ ساعت |
|
||||||
|
| F11 | `BulkUpdateProductStock` | BackOffice | InventoryMainPage.razor | دکمه «بروزرسانی دستهای» (Set/Add/Subtract) | ۶ ساعت |
|
||||||
|
| F12 | `GetConfigurationByKey` | Internal | — | بدون UI — استفاده داخلی بهینه بجای GetAll | ۰ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 نقشه پیادهسازی
|
||||||
|
|
||||||
|
### فاز A — همراه پکیجبیس (فاز ۴ BIZ-PACKAGE-BASED-SYSTEM)
|
||||||
|
|
||||||
|
```
|
||||||
|
F1 (AssignFeatureToMembership) → با T4.6 (ماتریس PackageFeature) ادغام
|
||||||
|
F2 (ChangeNetworkParent) ✅ تکمیل → BO:`e020354`
|
||||||
|
F3 (CalculateOrderPV) ✅ تکمیل → FO:`3bffc13`
|
||||||
|
```
|
||||||
|
|
||||||
|
### فاز B — بعد از پکیجبیس (Sprint بعدی)
|
||||||
|
|
||||||
|
```
|
||||||
|
F4 → F7: بهبود UX فروشگاه و مشتری
|
||||||
|
تخمین: ۱۳ ساعت = ~۲ روز
|
||||||
|
```
|
||||||
|
|
||||||
|
### فاز C — آینده (بدون فوریت)
|
||||||
|
|
||||||
|
```
|
||||||
|
F8 → F12: گزارشدهی و عملیات انبوه
|
||||||
|
تخمین: ۱۹ ساعت = ~۳ روز
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 ارجاعات
|
||||||
|
|
||||||
|
| مستند | محتوا |
|
||||||
|
|-------|-------|
|
||||||
|
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت کامل ۳۴۲ RPC — ۱۲ نگهداری + ۱۲ آرشیو |
|
||||||
|
| [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی سیستم پکیجبیس — ۳۹ تغییر |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۱۰ — F2+F3 تکمیل | باقیمانده فاز A: F1*
|
||||||
@@ -0,0 +1,864 @@
|
|||||||
|
# 🔄 نقشهراه تحول پکیجبیس — تسکهای گامبهگام
|
||||||
|
|
||||||
|
> **وضعیت:** در حال اجرا — **فاز ۰-۱۰ (تکمیل کد + استقرار staging) ✅** | NuGet v0.0.189 | تست باقیمانده
|
||||||
|
> **تاریخ:** ۱۴۰۴/۱۲/۰۸
|
||||||
|
> **پیشنیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) **v6** (تکمیل Q24-Q30 + History + Rename)
|
||||||
|
> **هدف:** شکستن **۴۸+ تغییر** به تسکهای اتمیک با ترتیب اجرا و وابستگیها
|
||||||
|
> **کامیتها:**
|
||||||
|
> CMS: `8b9c317`→`fe3edd1`→`ae92ab8`→`a9cd2fd`→`8e5c7c5`→`ccb938e`→`0002a5a`→`607f791`→`7176fe4`→`d19c569`→`469d97b`→`161f796`→`8446e0e`→`ce8e248`→`7554d70`→`aaaf7fc`→`dcd1135`→`a1024a3`→`fdbb91d`→`10d2ca2`
|
||||||
|
> FrontOffice: `b82cac4`→`71f391a`→`0bbc11e`→`d71d463`→`40882c8`→`a956cb9`→`3bffc13`→`474d364`
|
||||||
|
> BackOffice: `f1b0085`→`89f5241`→`c96377a`→`8be98ae`→`e020354`→`6939780`
|
||||||
|
|
||||||
|
> ⚠️ **تغییرات v3:** پورسانت per-package، carryover مجزا، SP parameters داینامیک، گزارشدهی FO/BO per-package
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 نمای کلی
|
||||||
|
|
||||||
|
```
|
||||||
|
مرحله ۰: فیکس باگ فوری (۱ روز) ✅ `8b9c317` + `fe3edd1`
|
||||||
|
└─→ مرحله ۱: زیرساخت Domain + DB (۴ روز) ✅ `ae92ab8`
|
||||||
|
└─→ مرحله ۱.۵: Migration + Seed ✅ `a9cd2fd`
|
||||||
|
├─→ مرحله ۲: منطق کسبوکار (۴ روز) ✅ `8e5c7c5`
|
||||||
|
│ └─→ مرحله ۳: بازسازی لایه Package ✅ `ccb938e`
|
||||||
|
│ └─→ مرحله ۴: CRUD + Legacy Fixes ✅ `0002a5a`
|
||||||
|
│ └─→ مرحله UI (۵ روز) ✅
|
||||||
|
└─→ مرحله ۳: پورسانت (۴ روز) ✅ `607f791`+`7176fe4`
|
||||||
|
└─→ مرحله ۶: Deprecation cleanup ✅ `d19c569`
|
||||||
|
└─→ مرحله ۷: Migration + Cleanup ✅
|
||||||
|
├─→ 7a: Cosmetic cleanup ✅ CMS:`469d97b` FO:`b82cac4` BO:`f1b0085`
|
||||||
|
├─→ 7b: FO RPC migration ✅ CMS:`161f796` FO:`71f391a`
|
||||||
|
└─→ 7c: Delete deprecated ✅ CMS:`8446e0e`
|
||||||
|
└─→ مرحله ۸: FO/BO Completion
|
||||||
|
├─→ 8a: Checkout wire-up ✅ FO:`0bbc11e`
|
||||||
|
├─→ 8b: BO CRUD expansion ✅ CMS:`ce8e248` BO:`89f5241`
|
||||||
|
├─→ 8c: FO Package pages ✅ FO:`d71d463`
|
||||||
|
└─→ 8d: Proto cleanup ✅ CMS:`7554d70` FO:`40882c8` BO:`c96377a`
|
||||||
|
└─→ 8e: Per-package reports ✅ CMS:`aaaf7fc` FO:`a956cb9` BO:`8be98ae`
|
||||||
|
└─→ 8f: UI completion ✅ CMS:`dcd1135` FO:`3bffc13` BO:`e020354`
|
||||||
|
└─→ مرحله ۹: Q24-Q30 + History + Rename
|
||||||
|
├─→ 9a: Q24+Q26 (threshold+SP) ✅ CMS:`a1024a3`
|
||||||
|
├─→ 9b: Q27 History entities ✅ CMS:`fdbb91d`
|
||||||
|
├─→ 9c: Q28 UI Guidance ✅ FO:`474d364` BO:`6939780`
|
||||||
|
└─→ 9d: Rename+Interceptor+Mig ✅ CMS:`10d2ca2`
|
||||||
|
└─→ مرحله ۱۰: استقرار + DataMigration + UI
|
||||||
|
├─→ 10a: DataMigration Tool ✅ Local: `0e8c6fd`→`31cc464`
|
||||||
|
├─→ 10b: EF Staging Migrations ✅
|
||||||
|
├─→ 10c: PackagePurchaseDialog ✅ FO:`a3681a8`
|
||||||
|
└─→ 10d: UI Fixes (Rial/Toman+لیبل+HTML) ✅ FO:`3c1a8ff`
|
||||||
|
└─→ مرحله ۵: تست + نهایی ⬜
|
||||||
|
|
||||||
|
مسیر بحرانی: ۰→۱→۱.۵→۲→۳→۴→UI→۹→۱۰→۵ = ~۲۲ روز | انجامشده: ۰→10d (~۲۰ روز)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۰ — فیکس باگهای فوری ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | کامیت: `8b9c317` + `fe3edd1`
|
||||||
|
|
||||||
|
### ✅ وضعیت باگها (بررسی اولیه لازم)
|
||||||
|
|
||||||
|
| # | باگ | Handler | شرح فیکس |
|
||||||
|
|---|------|---------|----------|
|
||||||
|
| B1 | DiscountBalance شارژ نمیشود | `VerifyGoldenPackagePurchaseCommandHandler` | اضافه `DiscountBalance += Amount × 2` + WalletChangeLog |
|
||||||
|
| B2 | UserPackagePurchase ساخته نمیشود | `VerifyGoldenPackagePurchaseCommandHandler` | ساخت record بعد verify موفق |
|
||||||
|
| B3 | UserPackagePurchase ساخته نمیشود | `VerifyPackagePurchaseCommandHandler` | ساخت record بعد verify موفق |
|
||||||
|
| B4 | UserPackagePurchase ساخته نمیشود | `VerifyBasePackagePaymentCommandHandler` | ساخت record بعد verify موفق |
|
||||||
|
|
||||||
|
#### دستور کار B1:
|
||||||
|
```
|
||||||
|
1. باز کردن VerifyGoldenPackagePurchaseCommandHandler.cs
|
||||||
|
2. پیدا کردن جایی که Balance شارژ میشود
|
||||||
|
3. اضافه کردن:
|
||||||
|
wallet.DiscountBalance += command.Amount * 2;
|
||||||
|
// + ساخت WalletChangeLog برای DiscountBalance
|
||||||
|
4. تست: verify → چک DiscountBalance در DB
|
||||||
|
```
|
||||||
|
|
||||||
|
#### دستور کار B2-B4 (الگوی مشترک):
|
||||||
|
```
|
||||||
|
1. بعد از verify موفق و شارژ wallet:
|
||||||
|
var purchase = new UserPackagePurchase
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
PackageId = packageId, // فعلاً BasePackageId = 4
|
||||||
|
PurchaseDate = DateTime.UtcNow,
|
||||||
|
Amount = amount,
|
||||||
|
PurchaseMethod = purchaseMethod, // ZarinPal, BFF, etc.
|
||||||
|
TransactionId = transactionId,
|
||||||
|
IsVerified = true
|
||||||
|
};
|
||||||
|
_context.UserPackagePurchases.Add(purchase);
|
||||||
|
2. تست: verify → چک UserPackagePurchases table
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۱ — زیرساخت (Domain + DB) ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | کامیت: `ae92ab8` (Phase 1) + `a9cd2fd` (Phase 1.5 Migration)
|
||||||
|
|
||||||
|
### T1.1 — بروزرسانی Package Entity (۱۱ فیلد جدید — v3)
|
||||||
|
|
||||||
|
**فایل:** `CMS/src/CMSMicroservice.Domain/Entities/Package.cs`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ 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; }
|
||||||
|
+ public decimal DiscountMultiplier { get; set; } = 2.0m;
|
||||||
|
+ public decimal MagicWalletMultiplier { get; set; } = 2.5m;
|
||||||
|
+ // === v3: تنظیمات پورسانت per-package ===
|
||||||
|
+ public int MaxBalancesPerLeg { get; set; } = 300; // نقرهای=۳۰
|
||||||
|
+ public int MaxNetworkLevel { get; set; } = 15;
|
||||||
|
+ // === v3: سقف کیفپول جادویی per-package ===
|
||||||
|
+ public long MagicWalletMaxDeposit { get; set; } = 1_000_000_000;
|
||||||
|
+ public long MagicWalletMaxCredit { get; set; } = 2_500_000_000;
|
||||||
|
+
|
||||||
|
+ public virtual ICollection<PackageFeature> PackageFeatures { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**EF Config:** `PackageConfiguration.cs`
|
||||||
|
- حداکثر یک `IsBasePackage = true` (Index filter)
|
||||||
|
- Precision for decimal fields
|
||||||
|
|
||||||
|
### T1.2 — ایجاد PackageFeature Entity
|
||||||
|
|
||||||
|
**فایل جدید:** `CMS/src/CMSMicroservice.Domain/Entities/PackageFeature.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### T1.3-T1.6 — اضافه PackageId به entityها
|
||||||
|
|
||||||
|
| Entity | فیلد | Required? | توضیح | v3? |
|
||||||
|
|--------|------|-----------|-------|-----|
|
||||||
|
| ClubMembership | `long? PackageId` | nullable (بعد migration → required) | آخرین پکیج | |
|
||||||
|
| ClubMembershipCycle | `long PackageId` | required | پکیج این چرخه | |
|
||||||
|
| WeeklyCommissionPool | `long PackageId` | required + Unique(WeekDefId, PkgId) | Pool هر پکیج | |
|
||||||
|
| UserCommissionPayout | `long? PackageId` | nullable + **Unique(UserId, WeekId, PkgId)** | ردیابی | 🔄 |
|
||||||
|
| **NetworkWeeklyBalance** | **`long PackageId`** | **required + Unique(UserId, WeekId, PkgId)** | **تعادل per-package** | **🆕** |
|
||||||
|
|
||||||
|
### T1.7 — حذف SystemConstants (v3: ۷ ثابت)
|
||||||
|
|
||||||
|
**فایل:** `CMS/src/CMSMicroservice.Domain/Common/SystemConstants.cs`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- public const long BasePackageAmount = 56_000_000;
|
||||||
|
- public const long DayaLoanAmount = 56_000_000;
|
||||||
|
- public const long ClubActivationFee = 25_200_000;
|
||||||
|
- public const long ClubMembershipGiftValue = 25_200_000;
|
||||||
|
- public const decimal MagicWalletMultiplier = 2.5m;
|
||||||
|
- // === v3: انتقال به Package entity ===
|
||||||
|
- public const int CommissionMaxWeeklyBalancesPerLeg = 300;
|
||||||
|
- public const int CommissionMaxNetworkLevel = 15;
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **قبل از حذف:** grep تمام مصرفکنندهها → جایگزین با `Package.Property`
|
||||||
|
> ⚠️ **v3:** `CommissionMaxWeeklyBalancesPerLeg` و `CommissionMaxNetworkLevel` هم باید per-package شوند
|
||||||
|
|
||||||
|
### T1.8 — Database Migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet ef migrations add AddPackageBasedSystem
|
||||||
|
```
|
||||||
|
|
||||||
|
**شامل:**
|
||||||
|
- ستونهای جدید Package
|
||||||
|
- جدول PackageFeatures
|
||||||
|
- FKها در 4 entity
|
||||||
|
- Unique constraint
|
||||||
|
|
||||||
|
### T1.9 — Data Migration Script
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. بروزرسانی پکیج فعلی (ID=4 → اضافه فیلدهای جدید)
|
||||||
|
UPDATE "CMS"."Packages" SET
|
||||||
|
"SortOrder" = 2,
|
||||||
|
"IsActive" = true,
|
||||||
|
"IsBasePackage" = true,
|
||||||
|
"SupportsDayaPurchase" = true,
|
||||||
|
"SupportsDirectPurchase" = true,
|
||||||
|
"ActivationFee" = 25200000,
|
||||||
|
"DiscountMultiplier" = 2.0,
|
||||||
|
"MagicWalletMultiplier" = 2.5,
|
||||||
|
-- v3: تنظیمات پورسانت
|
||||||
|
"MaxBalancesPerLeg" = 300,
|
||||||
|
"MaxNetworkLevel" = 15,
|
||||||
|
"MagicWalletMaxDeposit" = 1000000000,
|
||||||
|
"MagicWalletMaxCredit" = 2500000000
|
||||||
|
WHERE "Id" = 4;
|
||||||
|
|
||||||
|
-- 2. Link existing data to base package
|
||||||
|
UPDATE "CMS"."ClubMemberships" SET "PackageId" = 4 WHERE "PackageId" IS NULL;
|
||||||
|
UPDATE "CMS"."ClubMembershipCycles" SET "PackageId" = 4 WHERE "PackageId" IS NULL;
|
||||||
|
UPDATE "CMS"."WeeklyCommissionPools" SET "PackageId" = 4 WHERE "PackageId" IS NULL;
|
||||||
|
UPDATE "CMS"."UserCommissionPayouts" SET "PackageId" = 4 WHERE "PackageId" IS NULL;
|
||||||
|
-- v3: NetworkWeeklyBalance هم PackageId میگیره
|
||||||
|
UPDATE "CMS"."NetworkWeeklyBalances" SET "PackageId" = 4 WHERE "PackageId" IS NULL;
|
||||||
|
|
||||||
|
-- 3. Seed Silver package (شامل فیلدهای v3)
|
||||||
|
INSERT INTO "CMS"."Packages" (..., "MaxBalancesPerLeg", "MaxNetworkLevel",
|
||||||
|
"MagicWalletMaxDeposit", "MagicWalletMaxCredit", ...)
|
||||||
|
VALUES ('پکیج نقرهای', 5600000, ..., 30, 15, 100000000, 250000000, ...);
|
||||||
|
```
|
||||||
|
|
||||||
|
### T1.10 — بروزرسانی Protoها
|
||||||
|
|
||||||
|
| Proto File | تغیر | v3? |
|
||||||
|
|-----------|-------|-----|
|
||||||
|
| package.proto | فیلدهای جدید Package message (۱۱ فیلد) | 🔄 |
|
||||||
|
| clubmembership.proto | package_id در request/response | |
|
||||||
|
| commission.proto | **`package_id` + `package_title`** در ۴ message | **🆕** |
|
||||||
|
| commission.proto | **Message جدید: `CustomerCommissionPackageSummary`** | **🆕** |
|
||||||
|
| commission.proto | **فیلتر `package_id` در Requestها** | **🆕** |
|
||||||
|
|
||||||
|
### T1.11 — اضافه PackageId به NetworkWeeklyBalance (🆕 v3)
|
||||||
|
|
||||||
|
**فایل:** `CMS/src/CMSMicroservice.Domain/Entities/NetworkWeeklyBalance.cs`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ public long PackageId { get; set; }
|
||||||
|
+ public virtual Package Package { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**EF Config:** اضافه Unique Index:
|
||||||
|
```csharp
|
||||||
|
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId }).IsUnique();
|
||||||
|
builder.HasOne(e => e.Package).WithMany().HasForeignKey(e => e.PackageId);
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **تاثیر حجم:** رکوردهای تعادل ×N (تعداد پکیج). مثلاً ۱۰۰۰ کاربر × ۲ پکیج = ۲۰۰۰ رکورد هفتگی
|
||||||
|
|
||||||
|
### T1.12 — Data Migration: NetworkWeeklyBalance (🆕 v3)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- رکوردهای موجود → پکیج پایه
|
||||||
|
UPDATE "CMS"."NetworkWeeklyBalances"
|
||||||
|
SET "PackageId" = (SELECT "Id" FROM "CMS"."Packages" WHERE "IsBasePackage" = true LIMIT 1)
|
||||||
|
WHERE "PackageId" IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۲ — منطق کسبوکار ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | کامیت: `8e5c7c5` (Phase 2) + `ccb938e` (Phase 3) + `0002a5a` (Phase 4)
|
||||||
|
|
||||||
|
### T2.1 — Generic Verify Handler
|
||||||
|
|
||||||
|
**هدف:** ادغام VerifyGolden + VerifyBase + VerifyGeneric → یک handler
|
||||||
|
|
||||||
|
**الگوریتم:**
|
||||||
|
```
|
||||||
|
1. دریافت TransactionId از request
|
||||||
|
2. خواندن Transaction → PackageId → Package entity
|
||||||
|
3. verify با درگاه (ZarinPal/BFF/...)
|
||||||
|
4. اگر موفق:
|
||||||
|
a. wallet.Balance += Package.Price
|
||||||
|
b. wallet.DiscountBalance += Package.Price × Package.DiscountMultiplier
|
||||||
|
c. ساخت WalletChangeLog (Balance)
|
||||||
|
d. ساخت WalletChangeLog (DiscountBalance)
|
||||||
|
e. ساخت UserPackagePurchase record
|
||||||
|
f. اگر اولین خرید: JoinNetwork
|
||||||
|
g. بروزرسانی ClubMembershipCycle.PackageId
|
||||||
|
5. return success + receipt
|
||||||
|
```
|
||||||
|
|
||||||
|
### T2.2 — Generic Purchase Handler
|
||||||
|
|
||||||
|
**هدف:** ادغام PurchaseGolden + PurchasePackage + InitiateBase → یک handler
|
||||||
|
|
||||||
|
**تغییرات:**
|
||||||
|
- حذف فیلتر `Title.Contains("طلایی")`
|
||||||
|
- حذف `BasePackageId = 4`
|
||||||
|
- خواندن Package entity از DB بر اساس `request.PackageId`
|
||||||
|
- Gateway URL + Amount از Package.Price
|
||||||
|
|
||||||
|
### T2.3-T2.4 — ActivateClubMembership بهبود
|
||||||
|
|
||||||
|
**تغییرات:**
|
||||||
|
```diff
|
||||||
|
- var features = await GetAllFeatureIds(); // همه فیچرها
|
||||||
|
+ var features = await GetPackageFeatures(packageId); // فیچرهای پکیج
|
||||||
|
|
||||||
|
- membership.PackageAmount = SystemConstants.BasePackageAmount;
|
||||||
|
+ membership.PackageAmount = package.Price;
|
||||||
|
|
||||||
|
- var activationFee = SystemConstants.ClubActivationFee;
|
||||||
|
+ var activationFee = package.ActivationFee;
|
||||||
|
```
|
||||||
|
|
||||||
|
### T2.5-T2.6 — Re-Purchase Logic
|
||||||
|
|
||||||
|
**EXIT Magic Mode — تغییرات:**
|
||||||
|
```diff
|
||||||
|
wallet.WalletMode = WalletMode.Normal;
|
||||||
|
wallet.MagicCompletedAt = DateTime.UtcNow;
|
||||||
|
cycle.MagicCompletedAt = DateTime.UtcNow;
|
||||||
|
+ user.PackagePurchaseMethod = PackagePurchaseMethod.None;
|
||||||
|
+ membership.IsActive = false;
|
||||||
|
+ cycle.IsCurrentCycle = false;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guard تغییرات:**
|
||||||
|
```diff
|
||||||
|
- if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
||||||
|
- throw new RpcException("قبلاً پکیج خریداری شده");
|
||||||
|
+ if (user.PackagePurchaseMethod != PackagePurchaseMethod.None
|
||||||
|
+ && !HasCompletedMagicCycle(membership))
|
||||||
|
+ throw new RpcException("چرخه جاری هنوز تکمیل نشده");
|
||||||
|
```
|
||||||
|
|
||||||
|
### T2.7 — JWT Claims جدید
|
||||||
|
|
||||||
|
```diff
|
||||||
|
claims.Add("HasPurchasedPackage", "true");
|
||||||
|
+ claims.Add("CanRepurchase", HasCompletedMagicCycle(membership).ToString());
|
||||||
|
+ claims.Add("PackageId", membership.PackageId?.ToString() ?? "");
|
||||||
|
+ claims.Add("PackageTitle", package?.Title ?? "");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۳ — محاسبه پورسانت (موازی با مرحله ۲) ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | کامیت: `607f791` + `7176fe4` | ⏱️ **۴ روز** | ریسک: بحرانی (مالی)
|
||||||
|
|
||||||
|
### T3.1-T3.2 — SPs + PackageId + پارامترهای داینامیک (🔄 v3)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- sp_CalculateWeeklyBalances — v3: حذف hardcode
|
||||||
|
ALTER PROCEDURE sp_CalculateWeeklyBalances
|
||||||
|
@WeekDefinitionId BIGINT,
|
||||||
|
@PackageId BIGINT,
|
||||||
|
@MaxBalancesPerLeg INT, -- v3: از Package entity (نه ۳۰۰ hardcode!)
|
||||||
|
@MaxNetworkLevel INT -- v3: از Package entity (نه ۱۵ hardcode!)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
-- فیلتر: فقط کاربرانی که این پکیج را دارند
|
||||||
|
-- carryover: فقط رکوردهای PackageId = @PackageId
|
||||||
|
-- cap: از @MaxBalancesPerLeg (نه ۳۰۰)
|
||||||
|
-- depth: CTE تا @MaxNetworkLevel (نه ۱۵)
|
||||||
|
INSERT INTO "CMS"."NetworkWeeklyBalances" ("PackageId", ...)
|
||||||
|
SELECT @PackageId, ...
|
||||||
|
FROM "CMS"."UserWallets" w
|
||||||
|
INNER JOIN "CMS"."ClubMemberships" m ON m."UserId" = w."UserId"
|
||||||
|
WHERE m."PackageId" = @PackageId
|
||||||
|
AND m."IsActive" = true;
|
||||||
|
END;
|
||||||
|
```
|
||||||
|
|
||||||
|
### T3.3 — Loop Service (🔄 v3: ارسال تنظیمات پکیج)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// WeeklyCommissionCalculationService.cs
|
||||||
|
var activePackages = await _context.Packages
|
||||||
|
.Where(p => p.IsActive && !p.IsDeleted)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var package in activePackages)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Calculating commission for package {Id}: {Title} " +
|
||||||
|
"(MaxBalances={Max}, MaxLevel={Level})",
|
||||||
|
package.Id, package.Title,
|
||||||
|
package.MaxBalancesPerLeg, package.MaxNetworkLevel);
|
||||||
|
|
||||||
|
// v3: پاس دادن تنظیمات پکیج
|
||||||
|
await strategy.CalculateWeeklyBalancesAsync(
|
||||||
|
weekId, package.Id,
|
||||||
|
package.MaxBalancesPerLeg, package.MaxNetworkLevel);
|
||||||
|
|
||||||
|
await strategy.CalculateWeeklyPoolAsync(weekId, package.Id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### T3.4 — OrmCommissionCalculationStrategy (🔄 v3)
|
||||||
|
|
||||||
|
**تغییرات:**
|
||||||
|
```diff
|
||||||
|
- var maxBalances = SystemConstants.CommissionMaxWeeklyBalancesPerLeg; // 300
|
||||||
|
- var maxLevel = SystemConstants.CommissionMaxNetworkLevel; // 15
|
||||||
|
+ // پارامتر از بیرون — per-package
|
||||||
|
+ int maxBalances = maxBalancesPerLeg; // e.g., نقرهای=30, پایه=300
|
||||||
|
+ int maxLevel = maxNetworkLevel;
|
||||||
|
|
||||||
|
- // فیلتر کاربران
|
||||||
|
+ // فیلتر کاربران بر اساس پکیج
|
||||||
|
+ .Where(m => m.PackageId == packageId && m.IsActive)
|
||||||
|
|
||||||
|
- // carryover
|
||||||
|
+ // carryover: فقط رکوردهای همان PackageId
|
||||||
|
+ .Where(b => b.PackageId == packageId && b.WeekDefinitionId == prevWeekId)
|
||||||
|
```
|
||||||
|
|
||||||
|
### T3.5 — SpCommissionCalculationStrategy (🆕 v3)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// قبل: فقط WeekDefinitionId
|
||||||
|
await connection.ExecuteAsync("CMS.sp_CalculateWeeklyBalances",
|
||||||
|
new { WeekDefinitionId = weekId, ForceRecalculate = true });
|
||||||
|
|
||||||
|
// بعد (v3): پکیج + تنظیمات داینامیک
|
||||||
|
await connection.ExecuteAsync("CMS.sp_CalculateWeeklyBalances",
|
||||||
|
new {
|
||||||
|
WeekDefinitionId = weekId,
|
||||||
|
PackageId = package.Id,
|
||||||
|
MaxBalancesPerLeg = package.MaxBalancesPerLeg,
|
||||||
|
MaxNetworkLevel = package.MaxNetworkLevel,
|
||||||
|
ForceRecalculate = true
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### T3.6 — Carryover per-package (🆕 v3)
|
||||||
|
|
||||||
|
> ⚠️ **بحرانی:** week-shifting باید فقط رکوردهای همان PackageId را shift کند
|
||||||
|
|
||||||
|
```
|
||||||
|
هفته ۱۰ → هفته ۱۱:
|
||||||
|
علی: carryover_پایه = {Left: surplus, Right: surplus} ← جداگانه
|
||||||
|
علی: carryover_نقرهای = {Left: 0, Right: 0} ← جداگانه
|
||||||
|
|
||||||
|
✖ اشتباه: قاطی کردن carryover پایه و نقرهای!
|
||||||
|
✔ صحیح: هر PackageId فقط carryover خودش را میبینه
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⚠️ نکته بحرانی
|
||||||
|
|
||||||
|
> پورسانت = پول واقعی. **هر تغییر در SPs باید:**
|
||||||
|
> 1. ابتدا در staging با داده واقعی تست شود
|
||||||
|
> 2. نتایج قبل و بعد مقایسه شوند
|
||||||
|
> 3. Rollback plan آماده باشد
|
||||||
|
> 4. در production ابتدا read-only اجرا شود (بدون commit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۷ — Migration + Cleanup (سهگانه) ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | ⏱️ **۱ روز** | ریسک: پایین
|
||||||
|
|
||||||
|
### فاز 7a — Cosmetic Cleanup ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `469d97b` | FO `b82cac4` | BO `f1b0085`
|
||||||
|
|
||||||
|
**CMS:**
|
||||||
|
- حذف orphaned `PurchasePackage` handler (۳ فایل، بدون caller)
|
||||||
|
- فیکس doc-comments: `طلایی` → `پکیج` در ۶ فایل (enums, entities, handlers)
|
||||||
|
|
||||||
|
**FrontOffice:**
|
||||||
|
- حذف hardcoded `پکیج طلایی` از `MyPackages.razor` و `Packages.razor`
|
||||||
|
- اضافه `PackageTitle` property به `UserPackageStatusDto` record
|
||||||
|
|
||||||
|
**BackOffice:**
|
||||||
|
- تغییر label `پکیج طلایی` → `خرید پکیج` در `UserNetworkInfo.razor`
|
||||||
|
|
||||||
|
### فاز 7b — FrontOffice RPC Migration ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `161f796` | FO `71f391a`
|
||||||
|
|
||||||
|
**CMS:**
|
||||||
|
- `CustomerPurchasePackage`: embed `orderId` در callback URL قبل از ارسال به درگاه
|
||||||
|
- `$"{request.CallbackUrl}{separator}orderId={purchase.Id}"`
|
||||||
|
|
||||||
|
**FrontOffice:**
|
||||||
|
- `Profile/Index.razor.cs`: مهاجرت `InitiateBasePackagePaymentAsync` → `CustomerPurchasePackageAsync`
|
||||||
|
- `Profile/PaymentCallback.razor`: مهاجرت `VerifyBasePackagePaymentAsync` → `CustomerVerifyPackagePurchaseAsync`
|
||||||
|
- پارامترهای جدید: `PackageId`, `CallbackUrl`, `PurchaseMethod`, `OrderId`, `Authority`, `Status`
|
||||||
|
|
||||||
|
### فاز 7c — Delete Deprecated Handlers ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `8446e0e` (14 فایل، 1125 حذف)
|
||||||
|
|
||||||
|
**حذف ۴ handler CQRS (۱۲ فایل):**
|
||||||
|
- `PurchaseGoldenPackage/` (Command, Handler, Validator)
|
||||||
|
- `VerifyGoldenPackagePurchase/` (Command, Handler, Validator)
|
||||||
|
- `InitiateBasePackagePayment/` (Command, Handler, Validator)
|
||||||
|
- `VerifyBasePackagePayment/` (Command, Handler, Validator)
|
||||||
|
|
||||||
|
**Cleanup:**
|
||||||
|
- `PackageService.cs`: حذف ۴ gRPC override method (proto RPCs حالا auto-throw `Unimplemented`)
|
||||||
|
- `PackageProfile.cs`: حذف ۶ Mapster mapping block + ۴ using directive
|
||||||
|
- Build: 0 Error ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۸ — FrontOffice Checkout + NuGet
|
||||||
|
|
||||||
|
> 🔄 در حال اجرا | فاز 8a تکمیل ✅
|
||||||
|
|
||||||
|
### فاز 8a — Checkout Wire-up ✅
|
||||||
|
|
||||||
|
> کامیت: FO `0bbc11e`
|
||||||
|
|
||||||
|
**Checkout.razor.cs:**
|
||||||
|
- حذف dead code: `ProcessPayment()` از flow قدیمی `TransactionsContract + UserOrderContract` استفاده میکرد
|
||||||
|
- Rewrite با `CustomerPurchasePackageAsync` (مثل Profile/Index.razor.cs)
|
||||||
|
- Callback URL → `/profile/payment-callback` (از صفحه verify موجود استفاده مجدد)
|
||||||
|
- حذف DI بلااستفاده: `UserOrderContract`, `TransactionContract`
|
||||||
|
- حذف usings: `Transactions`, `UserOrder`, `WellKnownTypes`
|
||||||
|
|
||||||
|
**Profile/Index.razor.cs (cosmetic):**
|
||||||
|
- Rename `basePackage` → `selectedPackage`, `tempCallbackUrl` → `callbackUrl`
|
||||||
|
|
||||||
|
### فاز 8b — BackOffice Package CRUD Expansion ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `ce8e248` | BO `89f5241` (7 فایل، +138/-23)
|
||||||
|
|
||||||
|
**NuGet Rebuild:**
|
||||||
|
- Proto version bump: `0.0.184` → `0.0.185`
|
||||||
|
- Pack و deploy به local feed (`/nupkg`)
|
||||||
|
- BackOffice NuGet.config: اضافه local feed source
|
||||||
|
|
||||||
|
**CreateDialog.razor (۱۲ فیلد جدید):**
|
||||||
|
- `SortOrder` — MudNumericField<int> ترتیب نمایش
|
||||||
|
- `ActivationFee` — MudNumericField<long> هزینه فعالسازی
|
||||||
|
- `DiscountMultiplier` — MudNumericField<double> ضریب تخفیف
|
||||||
|
- `MagicWalletMultiplier` — MudNumericField<double> ضریب کیف پول جادویی
|
||||||
|
- `MagicWalletMaxDeposit` — MudNumericField<long> سقف واریز جادویی
|
||||||
|
- `MagicWalletMaxCredit` — MudNumericField<long> سقف اعتبار جادویی
|
||||||
|
- `MaxBalancesPerLeg` — MudNumericField<int> حداکثر تعادل هر پا
|
||||||
|
- `MaxNetworkLevel` — MudNumericField<int> حداکثر سطح شبکه
|
||||||
|
- `IsActive` — MudCheckBox فعال/غیرفعال
|
||||||
|
- `IsBasePackage` — MudCheckBox پکیج پایه
|
||||||
|
- `SupportsDirectPurchase` — MudCheckBox پرداخت مستقیم
|
||||||
|
- `SupportsDayaPurchase` — MudCheckBox اعتبار دایا
|
||||||
|
|
||||||
|
**UpdateDialog.razor:** همان ۱۲ فیلد
|
||||||
|
|
||||||
|
**PackageMainPage Grid (۴ ستون جدید):**
|
||||||
|
- `Price` — فرمتشده با N0
|
||||||
|
- `SortOrder` — ترتیب
|
||||||
|
- `IsActive` — MudChip فعال/غیرفعال
|
||||||
|
- `IsBasePackage` — MudChip پایه/عادی
|
||||||
|
|
||||||
|
**سایر:**
|
||||||
|
- Dialog size: `MaxWidth.Small` → `MaxWidth.Medium`
|
||||||
|
- CreateNew defaults: `IsActive=true, DiscountMultiplier=2.0, MagicWalletMultiplier=2.5, ...`
|
||||||
|
- فیکس `HasPurchasedGoldenPackage` → `HasPurchasedPackage` در `UserNetworkInfo.razor`
|
||||||
|
|
||||||
|
### فاز 8c — FrontOffice Package Pages ✅
|
||||||
|
|
||||||
|
> کامیت: FO `d71d463` (5 فایل، +109/-56)
|
||||||
|
|
||||||
|
**NuGet:** `0.0.182` → `0.0.185` + local feed source
|
||||||
|
|
||||||
|
**PackageDetail.razor.cs:**
|
||||||
|
- مهاجرت `GetPackageAsync` (admin RPC) → `GetCustomerPackageDetailsAsync` (customer RPC)
|
||||||
|
- Features: از hardcoded ثابت → از `PackageFeature` API داینامیک
|
||||||
|
- Specifications: از hardcoded → از `PackageFeature.IsHighlighted` API
|
||||||
|
- حذف ۵ hardcoded feature string + ۴ hardcoded specification
|
||||||
|
|
||||||
|
**PackageService.cs:**
|
||||||
|
- `PackageDto`: اضافه ۸ فیلد جدید (ActivationFee, DiscountMultiplier, MagicWalletMultiplier, etc.)
|
||||||
|
- `GetAllPackagesAsync`: مپ فیلدهای جدید از `CustomerPackageModel`
|
||||||
|
- `GetUserPackageStatusAsync`: از stub → اتصال واقعی به `GetUserPackageStatusAsync` RPC
|
||||||
|
|
||||||
|
**Packages.razor:**
|
||||||
|
- Un-exclude از build (حذف `<Content Remove>` + `<Compile Remove>`)
|
||||||
|
- جایگزینی ۳ feature bullet hardcoded → dynamic features:
|
||||||
|
- `SupportsDirectPurchase` → پرداخت مستقیم
|
||||||
|
- `SupportsDayaPurchase` → پرداخت با اعتبار دایا
|
||||||
|
- `DiscountMultiplier` → ضریب تخفیف: X.Xx
|
||||||
|
- `MagicWalletMultiplier` → کیف پول جادویی: X.Xx
|
||||||
|
- `IsBasePackage` → پکیج پایه ⭐
|
||||||
|
|
||||||
|
### فاز 8d — Proto Cleanup ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `7554d70` (2 فایل، -103) | FO `40882c8` | BO `c96377a`
|
||||||
|
|
||||||
|
**حذف ۴ deprecated RPC:**
|
||||||
|
- `PurchaseGoldenPackage` — جایگزین: `CustomerPurchasePackage`
|
||||||
|
- `VerifyGoldenPackagePurchase` — جایگزین: `CustomerVerifyPackagePurchase`
|
||||||
|
- `InitiateBasePackagePayment` — جایگزین: `CustomerPurchasePackage`
|
||||||
|
- `VerifyBasePackagePayment` — جایگزین: `CustomerVerifyPackagePurchase`
|
||||||
|
|
||||||
|
**حذف ۸ deprecated message type:**
|
||||||
|
- `PurchaseGoldenPackageRequest` / `PurchaseGoldenPackageResponse`
|
||||||
|
- `VerifyGoldenPackagePurchaseRequest` / `VerifyGoldenPackagePurchaseResponse`
|
||||||
|
- `InitiateBasePackagePaymentRequest` / `InitiateBasePackagePaymentResponse`
|
||||||
|
- `VerifyBasePackagePaymentRequest` / `VerifyBasePackagePaymentResponse`
|
||||||
|
|
||||||
|
**حفظ شده:** `GetUserPackageStatus` RPC + messages (هنوز در استفاده)
|
||||||
|
|
||||||
|
**NuGet:** `0.0.185` → `0.0.186` (همه ریپوها)
|
||||||
|
|
||||||
|
### فاز 8e — Per-Package Commission Reports ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `aaaf7fc` | FO `a956cb9` | BO `8be98ae`
|
||||||
|
|
||||||
|
**Proto (commission.proto):**
|
||||||
|
- اضافه `package_id` فیلتر به ۴ request message: `GetUserCommissionPayoutsRequest`, `GetUserWeeklyBalancesRequest`, `GetMyCommissionPayoutsRequest`, `GetMyWeeklyBalancesRequest`
|
||||||
|
- اضافه `package_id` + `package_title` به ۴ response model: `UserCommissionPayoutModel`, `UserWeeklyBalanceModel`, `CustomerCommissionPayoutModel`, `CustomerWeeklyBalanceModel`
|
||||||
|
|
||||||
|
**CMS (12 فایل):**
|
||||||
|
- ۴ Query record: اضافه `public long? PackageId { get; init; }`
|
||||||
|
- ۴ Handler: اضافه `.Include(x => x.Package)` + فیلتر `Where(x => x.PackageId == request.PackageId.Value)` + map `PackageId`/`PackageTitle`
|
||||||
|
- ۳ Response DTO: اضافه `PackageId` + `PackageTitle`
|
||||||
|
- `CommissionProfile.cs`: تنظیم mappingهای Mapster برای admin + customer
|
||||||
|
|
||||||
|
**BackOffice (6 فایل):**
|
||||||
|
- کامپوننت جدید `PackageSelect.razor/.cs`: dropdown قابل استفاده مجدد با بارگذاری پکیجها از `PackageContract`
|
||||||
|
- `UserPayouts.razor/.cs`: فیلتر PackageSelect + ستون پکیج با MudChip
|
||||||
|
- `BalancesReport.razor`: فیلتر PackageSelect + ستون پکیج با MudChip + mapping PackageTitle
|
||||||
|
|
||||||
|
**FrontOffice (7 فایل):**
|
||||||
|
- `CommissionDtos.cs`: اضافه `PackageId` + `PackageTitle` به `CommissionPayoutDto` و `WeeklyBalanceDto`
|
||||||
|
- `CommissionService.cs`: اضافه پارامتر `packageId` به `GetMyCommissionPayoutsAsync` و `GetMyWeeklyBalanceAsync`
|
||||||
|
- `CommissionDashboardPage.razor/.cs`: فیلتر dropdown پکیج + ستون «پکیج» با MudChip (دسکتاپ + موبایل)
|
||||||
|
- `WeeklyBalancePage.razor/.cs`: فیلتر MudSelect پکیج + نمایش MudChip پکیج در بخش اطلاعات هفته
|
||||||
|
|
||||||
|
**NuGet:** `0.0.186` → `0.0.187` (همه ریپوها)
|
||||||
|
|
||||||
|
### فاز 8f — UI Completion (T4.2 + T4.3 + T4.13 + F2 + F3) ✅
|
||||||
|
|
||||||
|
> کامیت: CMS `dcd1135` | FO `3bffc13` | BO `e020354`
|
||||||
|
|
||||||
|
**CMS (T4.13 — PackageFeature CRUD):**
|
||||||
|
- Proto: اضافه `repeated int64 feature_ids` به ۴ message (Create/Update Request, Get/GetAll Response)
|
||||||
|
- `CreateNewPackageCommand/Handler`: sync FeatureIds → ساخت `PackageFeature` records
|
||||||
|
- `UpdatePackageCommand/Handler`: sync FeatureIds → حذف قبلیها + ساخت جدید
|
||||||
|
- `GetPackage/GetAllPackageByFilter`: اضافه `.Include(x => x.PackageFeatures)` + map FeatureIds
|
||||||
|
|
||||||
|
**BackOffice (F2 + T4.13):**
|
||||||
|
- **F2:** کامپوننت جدید `ChangeParentDialog.razor/.cs` — مودال جابجایی در شبکه با NewParentId, NewLeg, Reason
|
||||||
|
- **F2:** دکمه «تغییر والد» در `UserNetworkInfo.razor`
|
||||||
|
- **T4.13:** checkbox matrix فیچرها در `CreateDialog` و `UpdateDialog` — بارگذاری از `ConfigurationContractClient`
|
||||||
|
|
||||||
|
**FrontOffice (T4.2 + T4.3 + F3):**
|
||||||
|
- **T4.2:** پرداخت شرطی در `Checkout.razor` بر اساس `SupportsDirectPurchase`/`SupportsDayaPurchase`
|
||||||
|
- **T4.3:** منطق خرید مجدد در `MyPackages.razor` — بارگذاری `MagicWalletStatus` + CTA شرطی + progress bar
|
||||||
|
- **F3:** نمایش PV سفارش در `Store/OrderDetail.razor` — `CalculateOrderPVAsync` + جدول PV هر محصول
|
||||||
|
|
||||||
|
**NuGet:** `0.0.187` → `0.0.188` (همه ریپوها)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۴ — UI (FrontOffice + BackOffice)
|
||||||
|
|
||||||
|
> ⏱️ **۵ روز** (v3: +۲) | وابستگی: مرحله ۲ + ۳ | ریسک: متوسط
|
||||||
|
|
||||||
|
### T4.1 — کاشیهای پکیج داینامیک
|
||||||
|
|
||||||
|
**فایل:** `FrontOffice/src/.../Pages/Package/Packages.razor`
|
||||||
|
|
||||||
|
```razor
|
||||||
|
@* قبل: hardcoded *@
|
||||||
|
@* بعد: *@
|
||||||
|
@foreach (var package in _packages.OrderBy(p => p.SortOrder))
|
||||||
|
{
|
||||||
|
<PackageCard Package="@package"
|
||||||
|
OnPurchase="StartPurchase"
|
||||||
|
ShowFeatures="true"
|
||||||
|
ShowPV="true" />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### T4.2 — مودال پرداخت شرطی ✅ FO:`3bffc13`
|
||||||
|
|
||||||
|
**پیادهسازی:**
|
||||||
|
- `Checkout.razor.cs`: اضافه `PackageService` injection، بارگذاری پکیجها با `GetAllPackagesAsync()`
|
||||||
|
- `Checkout.razor`: دکمههای پرداخت شرطی بر اساس `SupportsDirectPurchase` و `SupportsDayaPurchase`
|
||||||
|
- اضافه `DayaLoanPayment()` method + alert برای عدم وجود روش پرداخت
|
||||||
|
- `Pack` record: اضافه `SupportsDirectPurchase` و `SupportsDayaPurchase`
|
||||||
|
|
||||||
|
### T4.3 — MyPackages + Re-Purchase ✅ FO:`3bffc13`
|
||||||
|
|
||||||
|
**پیادهسازی:**
|
||||||
|
- `MyPackages.razor.cs`: بارگذاری `MagicWalletStatus` از `WalletService.GetMagicWalletStatusAsync()`
|
||||||
|
- فرمول خرید مجدد: `WalletMode == 0 && PurchaseCycleCount >= 1 && MagicRemainingDeposit == 0`
|
||||||
|
- `MyPackages.razor`: CTA شرطی «🎉 چرخه جادویی تکمیل شد!» + دکمه «خرید پکیج جدید»
|
||||||
|
- بخش پیشرفت کیف پول جادویی: مبلغ واریزی، باقیمانده، اعتبار دریافتی + progress bar
|
||||||
|
|
||||||
|
### T4.8 — FrontOffice: CommissionDashboard per-package (🆕 v3) ✅ FO:`a956cb9`
|
||||||
|
|
||||||
|
**پیادهسازی:**
|
||||||
|
- `CommissionDtos.cs`: اضافه `PackageId` + `PackageTitle` به `CommissionPayoutDto` و `WeeklyBalanceDto`
|
||||||
|
- `CommissionService.cs`: اضافه پارامتر `packageId` به `GetMyCommissionPayoutsAsync` و `GetMyWeeklyBalanceAsync`
|
||||||
|
- `CommissionDashboardPage.razor`: اضافه dropdown فیلتر پکیج + ستون «پکیج» با MudChip + نمایش پکیج در card موبایل
|
||||||
|
- `CommissionDashboardPage.razor.cs`: inject `PackageService`، فیلد `_filterPackageId`، بارگذاری لیست پکیجها
|
||||||
|
|
||||||
|
### T4.9 — FrontOffice: WeeklyBalance per-package (🆕 v3) ✅ FO:`a956cb9`
|
||||||
|
|
||||||
|
**پیادهسازی:**
|
||||||
|
- `WeeklyBalancePage.razor`: اضافه MudSelect فیلتر پکیج کنار WeekSelector + نمایش MudChip پکیج در بخش اطلاعات هفته
|
||||||
|
- `WeeklyBalancePage.razor.cs`: inject `PackageService`، فیلد `_filterPackageId`، ارسال به `CommissionService.GetMyWeeklyBalanceAsync`
|
||||||
|
|
||||||
|
### T4.10-T4.12 — BackOffice: گزارشهای پورسانت per-package (🆕 v3) ✅ BO:`8be98ae`
|
||||||
|
|
||||||
|
**پیادهسازی:**
|
||||||
|
- کامپوننت جدید `PackageSelect.razor/.cs`: dropdown قابل استفاده مجدد با بارگذاری پکیجها از `PackageContract`
|
||||||
|
- `UserPayouts.razor/.cs`: فیلتر PackageSelect + ستون پکیج با MudChip
|
||||||
|
- `BalancesReport.razor`: فیلتر PackageSelect + ستون پکیج با MudChip + mapping `PackageTitle`
|
||||||
|
|
||||||
|
### T4.13 — BackOffice: Package CRUD + Quick Access فیچرها (🆕 v3) ✅ CMS:`dcd1135` BO:`e020354`
|
||||||
|
|
||||||
|
**CMS پیادهسازی:**
|
||||||
|
- Proto: اضافه `repeated int64 feature_ids` به ۴ message (Create/Update Request, Get/GetAll Response)
|
||||||
|
- `CreateNewPackageCommand/Handler`: اضافه `FeatureIds` + ساخت `PackageFeature` records
|
||||||
|
- `UpdatePackageCommand/Handler`: اضافه `FeatureIds` + sync (حذف قبلیها + ساخت جدید)
|
||||||
|
- `GetPackageQueryHandler`: اضافه `.Include(x => x.PackageFeatures)` + map `FeatureIds`
|
||||||
|
- `GetAllPackageByFilterQueryHandler`: اضافه `.Include(x => x.PackageFeatures)` قبل از `PaginatedListAsync`
|
||||||
|
- NuGet: `0.0.187` → `0.0.188`
|
||||||
|
|
||||||
|
**BO پیادهسازی:**
|
||||||
|
- `CreateDialog.razor/.cs`: بارگذاری `ClubFeatures` از `ConfigurationContractClient` + checkbox matrix
|
||||||
|
- `UpdateDialog.razor/.cs`: همان pattern + pre-populate از `Model.FeatureIds`
|
||||||
|
- Mapster: `Adapt<UpdatePackageRequest>()` خودکار `FeatureIds` را map میکند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۹ — Q24-Q30 Business Decisions + History Infrastructure ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | وابستگی: مرحله ۸ | کامیتها: CMS:`a1024a3`→`fdbb91d`→`10d2ca2` FO:`474d364` BO:`6939780`
|
||||||
|
|
||||||
|
### 9a: Q24 آستانه موجودی + Q26 SP Worker ✅ (CMS:`a1024a3`)
|
||||||
|
|
||||||
|
**Q24 — آستانه موجودی:**
|
||||||
|
- شرط ورود به Magic و خرید مجدد از `Balance == 0` به `Balance <= 1_000_000` ریال تغییر کرد
|
||||||
|
- چون قیمت محصولات متفاوته، Balance دقیقاً صفر نمیشه
|
||||||
|
- فایلها: `UserOrderService.cs` (شرط EXIT Magic + Re-purchase guard)
|
||||||
|
|
||||||
|
**Q26 — SP Worker:**
|
||||||
|
- `StoredProcedureDeploymentService` (IHostedService) — در startup فایلهای `.sql` از embedded resource خوانده میشوند
|
||||||
|
- مقایسه checksum با جدول `__SPChecksums` — فقط SPهای تغییریافته re-deploy میشوند
|
||||||
|
- فایلها: `StoredProcedureDeploymentService.cs`, embedded `.sql` resources
|
||||||
|
|
||||||
|
### 9b: Q27 History Tables Entities ✅ (CMS:`fdbb91d`)
|
||||||
|
|
||||||
|
**Entityهای جدید:**
|
||||||
|
- `PackageHistory`: فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive + Action + PerformedBy + Reason
|
||||||
|
- `ClubMembershipCycleHistory`: فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt + Action + UserId + CycleNumber
|
||||||
|
|
||||||
|
**Enums جدید:**
|
||||||
|
- `PackageAction`: Created, Updated, Activated, Deactivated, PriceChanged, FeaturesChanged
|
||||||
|
- `ClubMembershipCycleAction`: Created, MagicStarted, MagicCompleted, Closed, AdminModified
|
||||||
|
|
||||||
|
**زیرساخت:**
|
||||||
|
- EF Configurations (indexes, maxLength, precision)
|
||||||
|
- DbSets در `IApplicationDbContext` و `ApplicationDbContext`
|
||||||
|
- Navigation Properties: `Package.Histories`, `ClubMembershipCycle.Histories`
|
||||||
|
|
||||||
|
### 9c: Q28 UI Guidance ✅ (FO:`474d364` BO:`6939780`)
|
||||||
|
|
||||||
|
**FrontOffice — ۷ صفحه با MudAlert آموزشی:**
|
||||||
|
- G1: Packages.razor — توضیح سیستم پکیجبیس
|
||||||
|
- G2: Checkout — هشدار شارژ کیفپول اعتباری
|
||||||
|
- G3: MyPackages — توضیح وضعیت پکیجها
|
||||||
|
- G4: MagicWallet — هشدار شرایط خروج + سقف شارژ
|
||||||
|
- G5: CommissionDashboard — توضیح per-package
|
||||||
|
- G6: ClubMembership — آموزش چرخه عضویت
|
||||||
|
- G7: ActivationSection — هشدار هزینه فعالسازی
|
||||||
|
|
||||||
|
**BackOffice — ۶ صفحه با MudAlert:**
|
||||||
|
- G8: PackageCRUD — هشدار ثبت تغییرات در History
|
||||||
|
- G9: ClubFeatures — توضیح ارتباط فیچر-پکیج
|
||||||
|
- G10: ManualPayments — هشدار مبلغ بر اساس پکیج
|
||||||
|
- G11: Commission Dashboard — توضیح Pool per-package
|
||||||
|
- G12: UserPayouts — توضیح فیلتر پکیج
|
||||||
|
- G13: ClubMembers — اطلاعات چرخه عضویت
|
||||||
|
|
||||||
|
### 9d: Rename + History Interceptor + EF Migration ✅ (CMS:`10d2ca2`)
|
||||||
|
|
||||||
|
**Rename (86 فایل):**
|
||||||
|
- `UserWalletChangeLog` → `UserWalletHistory` در 54+ فایل (entities, configs, DTOs, commands, queries, protos, services)
|
||||||
|
- 34 فایل rename شده + 11 دایرکتوری rename شده
|
||||||
|
- Proto: `userwalletchangelog.proto` → `userwallethistory.proto`
|
||||||
|
|
||||||
|
**History Interceptor:**
|
||||||
|
- `IHasHistory<T>` generic interface در `Domain/Common` — متد `CreateHistorySnapshot(action, performedBy)`
|
||||||
|
- `HistoryTrackingSaveChangesInterceptor` در `Infrastructure/Persistence/Interceptors` — reflection-based
|
||||||
|
- شناسایی entityهای `IHasHistory<>` از ChangeTracker
|
||||||
|
- فراخوانی `CreateHistorySnapshot` برای Modified/Added
|
||||||
|
- Auto-fill فیلدهای `Old*` از `OriginalValues` با naming convention
|
||||||
|
- `Package` implements `IHasHistory<PackageHistory>` — اولین entity
|
||||||
|
|
||||||
|
**EF Migration (`Q27_HistoryTables_And_RenameWalletHistory`):**
|
||||||
|
- ⚠️ EF Core اتوماتیک `DropTable` + `CreateTable` تولید کرد → **دستی اصلاح شد** به `RenameTable` (حفظ دادهها)
|
||||||
|
- `RenameTable` + `RenameIndex` × 2 + `sp_rename` برای PK و FKها
|
||||||
|
- `CreateTable` برای `ClubMembershipCycleHistories` و `PackageHistories` (جداول جدید)
|
||||||
|
- Down method: reverse rename + drop new tables
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرحله ۵ — تست و استقرار
|
||||||
|
|
||||||
|
> ⏱️ **۳ روز** (v3: +۱) | وابستگی: مرحله ۴
|
||||||
|
|
||||||
|
### Checklist تست
|
||||||
|
|
||||||
|
**خرید + فعالسازی:**
|
||||||
|
- [ ] خرید پکیج نقرهای (ZarinPal)
|
||||||
|
- [ ] خرید پکیج پایه (ZarinPal)
|
||||||
|
- [ ] خرید پکیج پایه (Daya Loan)
|
||||||
|
- [ ] خرید پکیج پایه (Manual Payment)
|
||||||
|
- [ ] فعالسازی باشگاه با پکیج نقرهای → فیچرهای محدود
|
||||||
|
- [ ] فعالسازی باشگاه با پکیج پایه → همه فیچرها
|
||||||
|
|
||||||
|
**چرخه Magic + خرید مجدد:**
|
||||||
|
- [ ] تکمیل چرخه Magic → ریست وضعیت
|
||||||
|
- [ ] خرید مجدد بعد تکمیل چرخه (همان پکیج)
|
||||||
|
- [ ] خرید مجدد با پکیج متفاوت (پایه → نقرهای)
|
||||||
|
|
||||||
|
**پورسانت per-package (v3):**
|
||||||
|
- [ ] Commission Pool جداگانه هر پکیج
|
||||||
|
- [ ] تعادل per-package: MaxBalancesPerLeg متفاوت (پایه=۳۰۰, نقرهای=۳۰)
|
||||||
|
- [ ] Carryover مجزا: shift فقط رکوردهای همان PackageId
|
||||||
|
- [ ] SP پارامترها صحیح: @MaxBalancesPerLeg و @MaxNetworkLevel از Package
|
||||||
|
- [ ] NetworkWeeklyBalance رکوردها: ۲ پکیج = ۲× رکورد
|
||||||
|
|
||||||
|
**گزارش per-package (v3):**
|
||||||
|
- [ ] FO: مشتری کارتهای خلاصه per-package را میبیند
|
||||||
|
- [ ] FO: مجموع پاداش = جمع همه پکیجها
|
||||||
|
- [ ] BO: فیلتر dropdown پکیج کار میکند
|
||||||
|
- [ ] BO: CSV export شامل ستون پکیج
|
||||||
|
|
||||||
|
**Migration + سایر:**
|
||||||
|
- [ ] Data Migration — PackageId در رکوردهای قبلی (شامل NetworkWeeklyBalance)
|
||||||
|
- [ ] JWT claims جدید (CanRepurchase, PackageId)
|
||||||
|
- [x] UI: کاشیهای داینامیک FrontOffice
|
||||||
|
- [x] UI: ماتریس فیچر + Quick Access BackOffice
|
||||||
|
- [ ] Rollback: بدون data loss
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 تقویم پیشنهادی (v3)
|
||||||
|
|
||||||
|
| هفته | روز | تسک |
|
||||||
|
|------|-----|------|
|
||||||
|
| هفته ۱ | روز ۱ | مرحله ۰: فیکس ۴ باگ |
|
||||||
|
| | روز ۲-۳ | مرحله ۱: Package entity (۱۱ فیلد) + PackageFeature |
|
||||||
|
| | روز ۴-۵ | مرحله ۱: FKها + NetworkWeeklyBalance + Migration |
|
||||||
|
| هفته ۲ | روز ۶-۷ | مرحله ۲: Generic handlers + re-purchase |
|
||||||
|
| | روز ۶-۸ | مرحله ۳: SP params + carryover per-package (موازی) |
|
||||||
|
| | روز ۸-۱۰ | مرحله ۲: Guards + JWT + Manual |
|
||||||
|
| هفته ۳ | روز ۱۱-۱۲ | مرحله ۴: FrontOffice UI + گزارش per-package |
|
||||||
|
| | روز ۱۳-۱۴ | مرحله ۴: BackOffice UI + گزارش per-package |
|
||||||
|
| | روز ۱۵-۱۷ | مرحله ۵: تست + deploy |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 ارجاعات
|
||||||
|
|
||||||
|
| مستند | محتوا |
|
||||||
|
|-------|-------|
|
||||||
|
| [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی فنی — **۴۸+ تغییر** (v3) + باگها |
|
||||||
|
| [PACKAGE-TRANSFORMATION-UX.md](PACKAGE-TRANSFORMATION-UX.md) | تاثیر UX بر فرانتها |
|
||||||
|
| [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده |
|
||||||
|
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۸ — فاز ۰-۹d تکمیل (۳۰ کامیت: ۲۰ CMS + ۸ FO + ۶ BO) | NuGet v0.0.188 | باقیمانده: تست + deploy*
|
||||||
@@ -0,0 +1,494 @@
|
|||||||
|
# 🏗️ تحلیل تحول پکیجبیس — تاثیر بر تجربه کاربر (UX)
|
||||||
|
|
||||||
|
> **وضعیت:** در حال تحلیل
|
||||||
|
> **تاریخ:** ۱۴۰۴/۱۲/۰۶
|
||||||
|
> **پیشنیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) v2
|
||||||
|
> **هدف:** مستندسازی تاثیر تغییر رویکرد پکیجبیس بر تجربه مشتری و ادمین در فرانتها
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست
|
||||||
|
|
||||||
|
1. [چشمانداز کلی](#۱-چشمانداز-کلی)
|
||||||
|
2. [تجربه مشتری (FrontOffice) — قبل و بعد](#۲-تجربه-مشتری-frontoffice--قبل-و-بعد)
|
||||||
|
3. [تجربه ادمین (BackOffice) — قبل و بعد](#۳-تجربه-ادمین-backoffice--قبل-و-بعد)
|
||||||
|
4. [تسکهای تحول — مرحلهبهمرحله](#۴-تسکهای-تحول--مرحلهبهمرحله)
|
||||||
|
5. [پیشبینی نیازمندیهای آینده](#۵-پیشبینی-نیازمندیهای-آینده)
|
||||||
|
6. [ماتریس تاثیرگذاری بر صفحات](#۶-ماتریس-تاثیرگذاری-بر-صفحات)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. چشمانداز کلی
|
||||||
|
|
||||||
|
### فلسفه تغییر
|
||||||
|
|
||||||
|
| بُعد | **فعلی (تکپکیج)** | **هدف (چندپکیج)** |
|
||||||
|
|------|-------------------|--------------------|
|
||||||
|
| **مدل قیمتی** | فقط ۵۶M تومان — "همه یا هیچ" | سطوح متنوع (نقرهای ۵.۶M, پایه ۵۶M, ...) — "ورود تدریجی" |
|
||||||
|
| **تجربه ورود** | سنگین — کاربر باید ۵۶M بپردازد | سبک — شروع از ۵.۶M و ارتقا بعدی |
|
||||||
|
| **چرخه عمر** | یکبار خرید → برای همیشه | چندبار خرید → هر چرخه Magic Wallet |
|
||||||
|
| **فیچرها** | ثابت — همه فیچرها برای همه | پویا — هر پکیج فیچرهای خودش |
|
||||||
|
| **کمیسیون** | یک Pool مشترک | Pool جداگانه هر پکیج |
|
||||||
|
| **مدیریت** | hardcoded — تغییر = deploy | داینامیک — ادمین از پنل تغییر میدهد |
|
||||||
|
|
||||||
|
### چه کسانی تاثیر میبینند؟
|
||||||
|
|
||||||
|
```
|
||||||
|
👤 مشتری (FrontOffice):
|
||||||
|
├── ثبتنامکننده جدید: گزینههای بیشتر → تصمیمگیری آسانتر
|
||||||
|
├── مشتری فعال: دکمه "ارتقا" + "خرید مجدد"
|
||||||
|
└── مشتری Magic: نمایش پیشرفت چرخه + آمادهسازی خرید بعدی
|
||||||
|
|
||||||
|
👔 ادمین (BackOffice):
|
||||||
|
├── مدیر محصول: CRUD پکیج + ماتریس فیچر
|
||||||
|
├── مدیر مالی: Commission Pool جداگانه + گزارشها
|
||||||
|
└── پشتیبان: فعالسازی دستی با انتخاب پکیج
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. تجربه مشتری (FrontOffice) — قبل و بعد
|
||||||
|
|
||||||
|
### ۲.۱ صفحه لیست پکیجها (`Packages.razor`)
|
||||||
|
|
||||||
|
#### قبل (فعلی):
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ پکیج طلایی │
|
||||||
|
│ ──────────── │
|
||||||
|
│ ✅ دسترسی به باشگاه مشتریان │
|
||||||
|
│ ✅ کیفپول جادویی │
|
||||||
|
│ ✅ فروشگاه تخفیفی │
|
||||||
|
│ │
|
||||||
|
│ 💰 ۵۶,۰۰۰,۰۰۰ تومان │
|
||||||
|
│ │
|
||||||
|
│ [خرید پکیج] │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### بعد (پکیجبیس):
|
||||||
|
```
|
||||||
|
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
|
||||||
|
│ 🥈 پکیج نقرهای │ │ 🏆 پکیج پایه │ │ 💎 پکیج ویژه │
|
||||||
|
│ ──────────── │ │ ──────────── │ │ ──────────── │
|
||||||
|
│ ✅ باشگاه مشتریان │ │ ✅ باشگاه مشتریان │ │ ✅ باشگاه مشتریان │
|
||||||
|
│ ✅ کیفپول جادویی │ │ ✅ کیفپول جادویی │ │ ✅ کیفپول جادویی │
|
||||||
|
│ ❌ فروشگاه تخفیفی │ │ ✅ فروشگاه تخفیفی │ │ ✅ فروشگاه تخفیفی │
|
||||||
|
│ ❌ پشتیبانی اختصاصی │ │ ❌ پشتیبانی اختصاصی │ │ ✅ پشتیبانی اختصاصی │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ 💰 ۵,۶۰۰,۰۰۰ تومان │ │ 💰 ۵۶,۰۰۰,۰۰۰ تومان │ │ 💰 ??? تومان │
|
||||||
|
│ │ │ ⭐ محبوبترین │ │ 🆕 بزودی │
|
||||||
|
│ [خرید] [جزئیات] │ │ [خرید] [جزئیات] │ │ [در انتظار] │
|
||||||
|
│ ────────────────── │ │ ────────────────── │ │ ────────────────── │
|
||||||
|
│ 📊 PV: 5,600,000 │ │ 📊 PV: 56,000,000 │ │ │
|
||||||
|
│ 🎁 هدیه: 11,200,000 │ │ 🎁 هدیه: 112,000,000 │ │ │
|
||||||
|
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**تغییرات کلیدی:**
|
||||||
|
- کاشیها از API میآیند (نه hardcoded)
|
||||||
|
- فیچرهای هر پکیج از `PackageFeature` خوانده میشود
|
||||||
|
- نمایش PV (Point Value) برای هر پکیج
|
||||||
|
- نمایش Gift Value (= `Price × DiscountMultiplier`)
|
||||||
|
- دکمههای شرطی: دایا فقط برای پکیجهای `SupportsDayaPurchase`
|
||||||
|
- Badge «محبوبترین» / «ارزانترین» بر اساس `SortOrder`
|
||||||
|
|
||||||
|
### ۲.۲ صفحه پکیجهای من (`MyPackages.razor`)
|
||||||
|
|
||||||
|
#### قبل:
|
||||||
|
```
|
||||||
|
وضعیت عضویت: فعال ✅
|
||||||
|
پکیج: طلایی
|
||||||
|
تاریخ فعالسازی: ۱۴۰۳/۰۹/۱۵
|
||||||
|
```
|
||||||
|
|
||||||
|
#### بعد:
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 📦 پکیج فعال: پکیج پایه │
|
||||||
|
│ ──────────── │
|
||||||
|
│ وضعیت: فعال ✅ | چرخه: ۲ | مدت: ۱۸۰ روز │
|
||||||
|
│ │
|
||||||
|
│ ┌──── کیفپول جادویی ────┐ │
|
||||||
|
│ │ موجودی: ۱۲,۳۰۰,۰۰۰ │ │
|
||||||
|
│ │ شارژ: ۴۳۵,۰۰۰,۰۰۰ │ │
|
||||||
|
│ │ سقف: ۱,۰۰۰,۰۰۰,۰۰۰ │ │
|
||||||
|
│ │ ████████░░░░ ۴۳.۵% │ │
|
||||||
|
│ └─────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──── PV انباشته ────┐ │
|
||||||
|
│ │ PV کل: ۸۹,۶۰۰,۰۰۰ │ │
|
||||||
|
│ │ آخرین سفارش: ۴.۲M │ │
|
||||||
|
│ └─────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ❌ چرخه جادویی تکمیل نشده — هنوز امکان خرید مجدد نیست │
|
||||||
|
│ ───── یا ───── │
|
||||||
|
│ ✅ چرخه جادویی تکمیل شد! [خرید پکیج جدید] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─── تاریخچه چرخهها ───┐
|
||||||
|
│ چرخه ۱: پایه — ۱۴۰۳/۰۹ تا ۱۴۰۴/۰۳ — ✅ تکمیل │
|
||||||
|
│ چرخه ۲: پایه — ۱۴۰۴/۰۳ تا ادامه دارد — 🔄 فعال │
|
||||||
|
└────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**تغییرات کلیدی:**
|
||||||
|
- نمایش شماره چرخه و نوع پکیج
|
||||||
|
- نوار پیشرفت Magic Wallet (چقدر تا تکمیل چرخه)
|
||||||
|
- PV انباشته (از `CalculateOrderPV`)
|
||||||
|
- دکمه شرطی «خرید مجدد» (فقط بعد تکمیل چرخه)
|
||||||
|
- تاریخچه چرخهها (از `ClubMembershipCycle`)
|
||||||
|
|
||||||
|
### ۲.۳ صفحه چکاوت (`Checkout.razor`)
|
||||||
|
|
||||||
|
#### قبل:
|
||||||
|
```
|
||||||
|
سبد خرید:
|
||||||
|
محصول A × 2 = ۲,۰۰۰,۰۰۰ تومان
|
||||||
|
مالیات (۹%): ۱۸۰,۰۰۰ تومان
|
||||||
|
────────────────
|
||||||
|
جمع: ۲,۱۸۰,۰۰۰ تومان
|
||||||
|
```
|
||||||
|
|
||||||
|
#### بعد:
|
||||||
|
```
|
||||||
|
سبد خرید:
|
||||||
|
محصول A × 2 = ۲,۰۰۰,۰۰۰ تومان
|
||||||
|
مالیات (۹%): ۱۸۰,۰۰۰ تومان
|
||||||
|
────────────────
|
||||||
|
جمع: ۲,۱۸۰,۰۰۰ تومان
|
||||||
|
|
||||||
|
📊 PV این سفارش: ۲,۰۰۰,۰۰۰ ← جدید
|
||||||
|
💎 PV انباشته: ۹۱,۶۰۰,۰۰۰ ← جدید
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۲.۴ پرداخت پکیج — مودال خرید
|
||||||
|
|
||||||
|
#### قبل:
|
||||||
|
```
|
||||||
|
┌─── خرید پکیج طلایی ───┐
|
||||||
|
│ │
|
||||||
|
│ مبلغ: ۵۶,۰۰۰,۰۰۰ تومان │
|
||||||
|
│ │
|
||||||
|
│ [پرداخت آنلاین] │
|
||||||
|
│ [اقساط دایا] │
|
||||||
|
│ [پرداخت دستی] │
|
||||||
|
└──────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### بعد:
|
||||||
|
```
|
||||||
|
┌─── خرید پکیج نقرهای ───┐ ┌─── خرید پکیج پایه ───┐
|
||||||
|
│ │ │ │
|
||||||
|
│ مبلغ: ۵,۶۰۰,۰۰۰ تومان │ │ مبلغ: ۵۶,۰۰۰,۰۰۰ تومان│
|
||||||
|
│ │ │ │
|
||||||
|
│ سهم باشگاه: ۲,۵۲۰,۰۰۰ │ │ سهم باشگاه: ۲۵,۲۰۰,۰۰│
|
||||||
|
│ شارژ کیفپول: ۵,۶۰۰,۰۰۰ │ │ شارژ کیفپول: ۵۶,۰۰۰,۰│
|
||||||
|
│ هدیه تخفیفی: ۱۱,۲۰۰,۰۰۰ │ │ هدیه تخفیفی: ۱۱۲,۰۰۰,۰│
|
||||||
|
│ │ │ │
|
||||||
|
│ [پرداخت آنلاین] ✅ │ │ [پرداخت آنلاین] ✅ │
|
||||||
|
│ [اقساط دایا] ❌ ندارد │ │ [اقساط دایا] ✅ │
|
||||||
|
│ [پرداخت دستی] ✅ │ │ [پرداخت دستی] ✅ │
|
||||||
|
└────────────────────────────┘ └────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**تغییرات کلیدی:**
|
||||||
|
- نمایش breakdown مالی: سهم باشگاه + شارژ کیفپول + هدیه تخفیفی
|
||||||
|
- دکمههای پرداخت شرطی بر اساس `SupportsDayaPurchase` / `SupportsDirectPurchase`
|
||||||
|
- متن قرارداد داینامیک بر اساس پکیج انتخابشده
|
||||||
|
|
||||||
|
### ۲.۵ صفحه تنظیمات مشتری (`Settings.razor`) — جدید
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─── تنظیمات اعلانها ───┐
|
||||||
|
│ │
|
||||||
|
│ 📧 اعلان ایمیل: [✅] │
|
||||||
|
│ 📱 اعلان SMS: [✅] │
|
||||||
|
│ 🔔 اعلان Push: [❌] │
|
||||||
|
│ │
|
||||||
|
│ [ذخیره تغییرات] │
|
||||||
|
└──────────────────────────┘
|
||||||
|
```
|
||||||
|
> وصل به RPC: `UpdateCustomerSettings`
|
||||||
|
|
||||||
|
### ۲.۶ تاریخچه سفارشات — دکمه تکرار
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─── تاریخچه سفارشات ────────────────────────────────────────┐
|
||||||
|
│ # │ تاریخ │ مبلغ │ وضعیت │ PV │ عملیات │
|
||||||
|
│───┼────────────┼────────────┼───────────┼───────────┼────────│
|
||||||
|
│ 1 │ ۱۴۰۴/۱۱/۰۲│ ۴,۲۰۰,۰۰۰ │ تحویل ✅ │ ۴,۲۰۰,۰۰ │ [🔄] [📍]│
|
||||||
|
│ 2 │ ۱۴۰۴/۱۰/۱۵│ ۱,۸۰۰,۰۰۰ │ ارسال 📦 │ ۱,۸۰۰,۰۰ │ [📍]│
|
||||||
|
│ 3 │ ۱۴۰۴/۰۹/۲۰│ ۳,۵۰۰,۰۰۰ │ تحویل ✅ │ ۳,۵۰۰,۰۰ │ [🔄] [📍]│
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
🔄 = تکرار سفارش (CustomerReorderPreviousOrder)
|
||||||
|
📍 = ردیابی سفارش (CustomerTrackOrder)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. تجربه ادمین (BackOffice) — قبل و بعد
|
||||||
|
|
||||||
|
### ۳.۱ مدیریت پکیجها (`PackageMainPage.razor`)
|
||||||
|
|
||||||
|
#### قبل:
|
||||||
|
```
|
||||||
|
┌─── مدیریت پکیجها ────────────────────────────────┐
|
||||||
|
│ # │ عنوان │ قیمت │ وضعیت │ عملیات │
|
||||||
|
│───┼──────────┼─────────────┼───────┼──────────────│
|
||||||
|
│ 1 │ طلایی │ ۵۶,۰۰۰,۰۰۰ │ فعال │ [ویرایش] │
|
||||||
|
└────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### بعد:
|
||||||
|
```
|
||||||
|
┌─── مدیریت پکیجها ──────────────────────────────────────────────────────┐
|
||||||
|
│ # │ عنوان │ قیمت │ سهم باشگاه │ ضریب │ دایا │ پایه │ ترتیب│ عملیات │
|
||||||
|
│───┼─────────┼─────────────┼────────────┼────────┼──────┼──────┼──────┼───────────────│
|
||||||
|
│ 1 │ نقرهای │ ۵,۶۰۰,۰۰۰ │ ۲,۵۲۰,۰۰۰ │ ×2.0 │ ❌ │ ❌ │ 1 │ [✏️] [📋] [❌] │
|
||||||
|
│ 2 │ پایه │ ۵۶,۰۰۰,۰۰۰ │ ۲۵,۲۰۰,۰۰│ ×2.0 │ ✅ │ ✅ │ 2 │ [✏️] [📋] [❌] │
|
||||||
|
└──────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
✏️ = ویرایش 📋 = مدیریت فیچرها ❌ = حذف
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳.۲ ماتریس فیچر × پکیج (`PackageFeatureMatrixPage.razor`) — صفحه جدید
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─── ماتریس فیچر × پکیج ─────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ فیچر │ نقرهای │ پایه │ ویژه │
|
||||||
|
│ ─────────────────────────┼─────────┼────────┼──────────────────│
|
||||||
|
│ دسترسی به باشگاه │ ✅ │ ✅ │ ✅ │
|
||||||
|
│ کیفپول جادویی │ ✅ │ ✅ │ ✅ │
|
||||||
|
│ فروشگاه عادی │ ✅ │ ✅ │ ✅ │
|
||||||
|
│ فروشگاه تخفیفی │ ❌ │ ✅ │ ✅ │
|
||||||
|
│ محصولات ClubExclusive │ ❌ │ ✅ │ ✅ │
|
||||||
|
│ پشتیبانی اختصاصی │ ❌ │ ❌ │ ✅ │
|
||||||
|
│ کمیسیون شبکه │ ✅ │ ✅ │ ✅ │
|
||||||
|
│ ─────────────────────────┼─────────┼────────┼──────────────────│
|
||||||
|
│ │ [ذخیره] │ [ذخیره]│ [ذخیره] │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
ادمین با checkbox فیچرها را به هر پکیج اختصاص میدهد.
|
||||||
|
وصل به RPC: AssignFeatureToMembership
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳.۳ فعالسازی باشگاه (`ActivateClubDialog.razor`)
|
||||||
|
|
||||||
|
#### قبل:
|
||||||
|
```
|
||||||
|
فعالسازی باشگاه مشتریان
|
||||||
|
کاربر: علی محمدی
|
||||||
|
[فعالسازی] ← hardcoded 56M + همه فیچرها
|
||||||
|
```
|
||||||
|
|
||||||
|
#### بعد:
|
||||||
|
```
|
||||||
|
فعالسازی باشگاه مشتریان
|
||||||
|
کاربر: علی محمدی
|
||||||
|
پکیج: [▼ انتخاب پکیج ▼] ← dropdown از API
|
||||||
|
├── نقرهای (۵,۶۰۰,۰۰۰)
|
||||||
|
└── پایه (۵۶,۰۰۰,۰۰۰)
|
||||||
|
|
||||||
|
جزئیات:
|
||||||
|
سهم باشگاه: _________ (خودکار)
|
||||||
|
فیچرها: _________ (از ماتریس پکیج)
|
||||||
|
|
||||||
|
[فعالسازی]
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳.۴ داشبورد (`Index.razor`) — بهبود
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─── آمار باشگاه ────────────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 👥 کل اعضا: ۱,۲۴۰ │
|
||||||
|
│ 📦 پکیج نقرهای: ۸۲۰ | پکیج پایه: ۴۲۰ │
|
||||||
|
│ 💰 Pool نقرهای: ۲,۰۶۶,۴۰۰,۰۰۰ | Pool پایه: ۱۰,۵۸۴,۰۰۰,۰۰│
|
||||||
|
│ ⚠️ هشدار: ۱۲ محصول موجودی کم │
|
||||||
|
│ │
|
||||||
|
│ ┌── موجودی انبار ──┐ ┌── ارزش کل انبار ──┐ │
|
||||||
|
│ │ ۳,۴۵۰ آیتم │ │ ۸۹,۲۰۰,۰۰۰,۰۰۰ │ │
|
||||||
|
│ │ ۱۲ نوع محصول │ │ ریال │ │
|
||||||
|
│ └───────────────────┘ └───────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
وصل به: GetInventorySummary, GetStockValueReport, GetLowStockProducts
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳.۵ مدیریت شبکه (`UserNetworkInfo.razor`) — بهبود
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─── مدیریت شبکه ─────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ کاربر: سارا احمدی (ID: 1045) │
|
||||||
|
│ پکیج: نقرهای │ چرخه: ۱ │ PV: ۵,۶۰۰,۰۰۰ │
|
||||||
|
│ Parent: علی محمدی (ID: 1001) │
|
||||||
|
│ شاخه: چپ │ عمق: ۳ │
|
||||||
|
│ │
|
||||||
|
│ [جابجایی در شبکه] ← مودال: انتخاب parent جدید │
|
||||||
|
│ │
|
||||||
|
└───────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
وصل به: ChangeNetworkParent
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. تسکهای تحول — مرحلهبهمرحله
|
||||||
|
|
||||||
|
### مرحله ۱: زیرساخت Domain + DB (پایه)
|
||||||
|
|
||||||
|
> ⚠️ **بدون این مرحله هیچکدام از تغییرات UI ممکن نیست**
|
||||||
|
|
||||||
|
| # | تسک | لایه | فایلها | شرح |
|
||||||
|
|---|------|------|--------|------|
|
||||||
|
| T1.1 | اضافه ۷ فیلد به Package entity | Domain | Package.cs, PackageConfiguration.cs | SortOrder, IsActive, IsBasePackage, SupportsDayaPurchase, SupportsDirectPurchase, ActivationFee, DiscountMultiplier, MagicWalletMultiplier |
|
||||||
|
| T1.2 | ایجاد PackageFeature entity | Domain | PackageFeature.cs, PackageFeatureConfiguration.cs | join table: Package ↔ ClubFeature |
|
||||||
|
| T1.3 | اضافه PackageId به ClubMembership | Domain | ClubMembership.cs | FK nullable → بعد migration → required |
|
||||||
|
| T1.4 | اضافه PackageId به ClubMembershipCycle | Domain | ClubMembershipCycle.cs | FK nullable → بعد migration → required |
|
||||||
|
| T1.5 | اضافه PackageId به WeeklyCommissionPool | Domain | WeeklyCommissionPool.cs | FK + Unique(WeekDefinitionId, PackageId) |
|
||||||
|
| T1.6 | اضافه PackageId به UserCommissionPayout | Domain | UserCommissionPayout.cs | FK nullable |
|
||||||
|
| T1.7 | حذف ۵ SystemConstants | Domain | SystemConstants.cs | BasePackageAmount, DayaLoanAmount, ClubActivationFee, ClubMembershipGiftValue, MagicWalletMultiplier |
|
||||||
|
| T1.8 | EF Migration + Seed | Infrastructure | Migration file | ۲ پکیج + فیچرها + FKها |
|
||||||
|
| T1.9 | Data Migration Script | Infrastructure | SQL script | کاربران فعلی → PackageId = پکیج پایه |
|
||||||
|
| T1.10 | بروزرسانی Protoها | Proto | package.proto, clubmembership.proto, commission.proto | فیلدهای جدید |
|
||||||
|
|
||||||
|
### مرحله ۲: منطق کسبوکار (Handlers)
|
||||||
|
|
||||||
|
> **هر handler باید از Package entity مقادیر مالی بخواند**
|
||||||
|
|
||||||
|
| # | تسک | فایل | شرح |
|
||||||
|
|---|------|------|------|
|
||||||
|
| T2.1 | Generic Verify Handler | VerifyPackagePurchaseCommandHandler.cs | DiscountMultiplier از Package + ساخت UserPackagePurchase |
|
||||||
|
| T2.2 | Generic Purchase Handler | PurchasePackageCommandHandler.cs | حذف "طلایی" و ID=4 |
|
||||||
|
| T2.3 | ActivateClubMembership بهبود | ActivateClubMembershipCommandHandler.cs | فیچر از PackageFeature + ActivationFee از Package |
|
||||||
|
| T2.4 | EXIT Magic Mode ریست | UserOrderService.cs | PackagePurchaseMethod=None, membership.IsActive=false |
|
||||||
|
| T2.5 | Guards re-purchase | G1-G3 handlers | اجازه خرید اگر MagicCompletedAt پر |
|
||||||
|
| T2.6 | Re-contract | AcceptClubMembershipContractCommandHandler.cs | اجازه قرارداد مجدد |
|
||||||
|
| T2.7 | Manual Payment بهبود | CreateManualPaymentCommandHandler.cs | DiscountMultiplier از Package |
|
||||||
|
| T2.8 | Daya Loan بهبود | CheckAndProcessDayaLoansCommandHandler.cs | حذف ID=4 |
|
||||||
|
| T2.9 | PackageFeature CRUD | جدید | ادمین بتواند فیچر ↔ پکیج مدیریت کند |
|
||||||
|
| T2.10 | JWT claims جدید | JWT builder | اضافه CanRepurchase + PackageType |
|
||||||
|
|
||||||
|
### مرحله ۳: محاسبه پورسانت (Commission)
|
||||||
|
|
||||||
|
| # | تسک | فایل | شرح |
|
||||||
|
|---|------|------|------|
|
||||||
|
| T3.1 | SP WeeklyBalances + PackageId | sp_CalculateWeeklyBalances.sql | فیلتر بر اساس PackageId |
|
||||||
|
| T3.2 | SP CommissionPool + PackageId | sp_CalculateWeeklyCommissionPool.sql | Pool جداگانه هر پکیج |
|
||||||
|
| T3.3 | Loop روی پکیجها | WeeklyCommissionCalculationService.cs | هر پکیج فعال → محاسبه جداگانه |
|
||||||
|
| T3.4 | ORM Strategy بهبود | OrmCommissionCalculationStrategy.cs | فیلتر PackageId |
|
||||||
|
| T3.5 | تست محاسبات | — | با داده واقعی staging |
|
||||||
|
|
||||||
|
### مرحله ۴: FrontOffice UI
|
||||||
|
|
||||||
|
| # | تسک | صفحه | شرح |
|
||||||
|
|---|------|------|------|
|
||||||
|
| T4.1 | کاشیهای داینامیک | Packages.razor | لود از API + فیچر مقایسه |
|
||||||
|
| T4.2 | مودال پرداخت شرطی | PackageDetail.razor | دکمه دایا فقط اگر SupportsDayaPurchase |
|
||||||
|
| T4.3 | MyPackages re-purchase | MyPackages.razor | نوار پیشرفت + دکمه خرید مجدد |
|
||||||
|
| T4.4 | ActivationSection داینامیک | ActivationSection.razor | قیمت از پکیج |
|
||||||
|
| T4.5 | قرارداد داینامیک | ClubMembershipContractDialog.razor | متن متناسب با پکیج |
|
||||||
|
| T4.6 | PV در Checkout | Checkout.razor | نمایش PV سفارش |
|
||||||
|
| T4.7 | تکرار سفارش | Store Orders pages | دکمه 🔄 |
|
||||||
|
| T4.8 | ردیابی سفارش | OrderTracking.razor | وصل به API |
|
||||||
|
| T4.9 | تنظیمات اعلان | Settings.razor | فرم Email/SMS/Push |
|
||||||
|
|
||||||
|
### مرحله ۵: BackOffice UI
|
||||||
|
|
||||||
|
| # | تسک | صفحه | شرح |
|
||||||
|
|---|------|------|------|
|
||||||
|
| T5.1 | CRUD پکیج بهبود | PackageMainPage.razor | فیلدهای جدید + ستونهای اضافه |
|
||||||
|
| T5.2 | ماتریس فیچر | PackageFeatureMatrixPage.razor (جدید) | checkbox grid |
|
||||||
|
| T5.3 | ActivateClub dropdown | ActivateClubDialog.razor | انتخاب پکیج |
|
||||||
|
| T5.4 | داشبورد بهبود | Index.razor | آمار Pool جداگانه + موجودی |
|
||||||
|
| T5.5 | شبکه بهبود | UserNetworkInfo.razor | جابجایی parent |
|
||||||
|
| T5.6 | موجودی کم | LowStockPage.razor | وصل API |
|
||||||
|
| T5.7 | گزارش ارزش انبار | InventoryMainPage.razor | تب گزارش |
|
||||||
|
| T5.8 | عملیات دستهای | InventoryMainPage.razor | Bulk Add/Update |
|
||||||
|
|
||||||
|
### مرحله ۶: تست و استقرار
|
||||||
|
|
||||||
|
| # | تسک | شرح |
|
||||||
|
|---|------|------|
|
||||||
|
| T6.1 | تست خرید هر پکیج | ZarinPal + Manual |
|
||||||
|
| T6.2 | تست re-purchase | تکمیل چرخه → خرید مجدد |
|
||||||
|
| T6.3 | تست Commission Pool | جداگانه بودن هر پکیج |
|
||||||
|
| T6.4 | تست Migration | rollback plan |
|
||||||
|
| T6.5 | Deploy staging → production | blue-green |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. پیشبینی نیازمندیهای آینده
|
||||||
|
|
||||||
|
### ۵.۱ نیازمندیهای مشتری (که فعلاً اولویت پایین هستند)
|
||||||
|
|
||||||
|
| # | نیاز | RPC آماده? | توضیح |
|
||||||
|
|---|------|-----------|-------|
|
||||||
|
| N1 | ارتقای پکیج (نقرهای → پایه) | ❌ جدید | پرداخت تفاضل + فعالسازی فیچرهای جدید |
|
||||||
|
| N2 | مقایسه پکیجها side-by-side | ❌ جدید | جدول فیچر مقایسهای (client-side) |
|
||||||
|
| N3 | اعلان قبل از اتمام چرخه | ❌ جدید | Background service: 5 روز قبل → push/SMS |
|
||||||
|
| N4 | گزارش PV ماهانه | CalculateOrderPV ✅ | جدول PV هر ماه + نمودار |
|
||||||
|
| N5 | پروفایل شبکه | ❌ جدید | مشتری درخت خودش را ببیند |
|
||||||
|
|
||||||
|
### ۵.۲ نیازمندیهای ادمین (که فعلاً اولویت پایین هستند)
|
||||||
|
|
||||||
|
| # | نیاز | RPC آماده? | توضیح |
|
||||||
|
|---|------|-----------|-------|
|
||||||
|
| N6 | پکیج تخفیفی زماندار | ❌ جدید | پکیج با قیمت ویژه برای مدت محدود |
|
||||||
|
| N7 | گزارش تبدیل (conversion) | ❌ جدید | چند نفر از نقرهای به پایه ارتقا دادند |
|
||||||
|
| N8 | هشدار Pool خالی | ❌ جدید | اگر Pool یک پکیج خالی شد → هشدار |
|
||||||
|
| N9 | export گزارش مالی | GetStockValueReport ✅ | دانلود Excel |
|
||||||
|
| N10 | تخصیص فیچر bulk | AssignFeatureToMembership ✅ | فیچر به همه اعضای یک پکیج |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۶. ماتریس تاثیرگذاری بر صفحات
|
||||||
|
|
||||||
|
### FrontOffice
|
||||||
|
|
||||||
|
| صفحه | تغییر | شدت | مرحله |
|
||||||
|
|------|-------|------|-------|
|
||||||
|
| Packages.razor | بازنویسی کامل — کاشیهای داینامیک | 🔴 | مرحله ۴ |
|
||||||
|
| PackageDetail.razor | فیچرها از API + دکمه شرطی | 🟡 | مرحله ۴ |
|
||||||
|
| MyPackages.razor | چرخه + پیشرفت + re-purchase | 🔴 | مرحله ۴ |
|
||||||
|
| Checkout.razor | PV display | 🟢 | مرحله ۴ |
|
||||||
|
| ActivationSection.razor | قیمت داینامیک | 🟡 | مرحله ۴ |
|
||||||
|
| ClubMembershipContractDialog.razor | متن داینامیک | 🟡 | مرحله ۴ |
|
||||||
|
| PaymentCallback.razor | تغییر JWT claims | 🟡 | مرحله ۴ |
|
||||||
|
| MembershipPage.razor | نمایش نوع پکیج | 🟢 | مرحله ۴ |
|
||||||
|
| Settings.razor | فرم اعلان جدید | 🟡 | مرحله ۴ |
|
||||||
|
| Store Orders | دکمه تکرار + ردیابی | 🟡 | مرحله ۴ |
|
||||||
|
|
||||||
|
### BackOffice
|
||||||
|
|
||||||
|
| صفحه | تغییر | شدت | مرحله |
|
||||||
|
|------|-------|------|-------|
|
||||||
|
| PackageMainPage.razor | ستونهای جدید + CRUD بهبود | 🟡 | مرحله ۵ |
|
||||||
|
| PackageFeatureMatrixPage.razor | **صفحه کاملاً جدید** | 🔴 | مرحله ۵ |
|
||||||
|
| ActivateClubDialog.razor | dropdown پکیج | 🟡 | مرحله ۵ |
|
||||||
|
| Index.razor (Dashboard) | آمار Pool جداگانه + موجودی | 🟡 | مرحله ۵ |
|
||||||
|
| UserNetworkInfo.razor | جابجایی + نمایش پکیج | 🟡 | مرحله ۵ |
|
||||||
|
| ClubMembers.razor | ستون پکیج | 🟢 | مرحله ۵ |
|
||||||
|
| Statistics.razor | چارت توزیع پکیج | 🟡 | مرحله ۵ |
|
||||||
|
| InventoryMainPage.razor | خلاصه + گزارش + bulk | 🟡 | مرحله ۵ |
|
||||||
|
| LowStockPage.razor | وصل API | 🟢 | مرحله ۵ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 ارجاعات
|
||||||
|
|
||||||
|
| مستند | ربط |
|
||||||
|
|-------|-----|
|
||||||
|
| [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی فنی ۳۹ تغییر |
|
||||||
|
| [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده |
|
||||||
|
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC |
|
||||||
|
| [BUSINESS-01-CLUB-COMMISSION.md](../business/BUSINESS-01-CLUB-COMMISSION.md) | مستند باشگاه و کمیسیون |
|
||||||
|
| [BUSINESS-04-USER-MEMBERSHIP.md](../business/BUSINESS-04-USER-MEMBERSHIP.md) | مستند عضویت کاربر |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶*
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# ⚙️ معماری CMS و زیرساخت فنی
|
# ⚙️ معماری CMS و زیرساخت فنی
|
||||||
|
|
||||||
> **منابع ادغامشده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
|
> **منابع ادغامشده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet entities + gRPC)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس ZarinPal Verify + Callback URL امنیت + appsettings.Development.json)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -140,6 +140,7 @@ public class CreateProductCommandHandler
|
|||||||
| `CategoryService` | category.proto | GetCategories, Create, Update |
|
| `CategoryService` | category.proto | GetCategories, Create, Update |
|
||||||
| `SystemConfigService` | config.proto | GetConfig, UpdateConfig |
|
| `SystemConfigService` | config.proto | GetConfig, UpdateConfig |
|
||||||
| `UserWalletService` | userwallet.proto | GetCustomerWallet, InitiateMagicCharge, GetMagicWalletStatus |
|
| `UserWalletService` | userwallet.proto | GetCustomerWallet, InitiateMagicCharge, GetMagicWalletStatus |
|
||||||
|
| `UserWalletHistoryService` | userwallethistory.proto | *(renamed from UserWalletChangeLogService)* |
|
||||||
|
|
||||||
### ۴.۲ PaginationState (مشترک)
|
### ۴.۲ PaginationState (مشترک)
|
||||||
|
|
||||||
@@ -195,6 +196,45 @@ Engine: MSSQL 2022-CU16, Collation=Arabic_CI_AS
|
|||||||
| `sp_CalculateWeeklyBalances` | محاسبه بالانس هفتگی هر عضو |
|
| `sp_CalculateWeeklyBalances` | محاسبه بالانس هفتگی هر عضو |
|
||||||
| `sp_CalculateWeeklyCommissionPool` | توزیع Pool هفتگی |
|
| `sp_CalculateWeeklyCommissionPool` | توزیع Pool هفتگی |
|
||||||
|
|
||||||
|
### ۵.۴ SP Auto-Deploy Worker (Q26)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// StoredProcedureDeploymentService : IHostedService
|
||||||
|
// در startup:
|
||||||
|
// 1. خواندن فایلهای .sql از embedded resource
|
||||||
|
// 2. مقایسه checksum با جدول __SPChecksums
|
||||||
|
// 3. فقط SPهای تغییریافته re-deploy میشوند
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵.۵ History Tracking System (Q27)
|
||||||
|
|
||||||
|
### IHasHistory<T> Interface
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IHasHistory<THistory> where THistory : BaseAuditableEntity, new()
|
||||||
|
{
|
||||||
|
THistory CreateHistorySnapshot(string action, string? performedBy);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HistoryTrackingSaveChangesInterceptor
|
||||||
|
|
||||||
|
- **مکان:** `Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs`
|
||||||
|
- **مکانیسم:** `SaveChangesInterceptor` — قبل از `SaveChanges` اجرا میشود
|
||||||
|
- **شناسایی:** از `ChangeTracker` entityهایی که `IHasHistory<>` پیادهسازی کردن (Modified/Added)
|
||||||
|
- **Auto-fill:** فیلدهای `Old*` از `entry.OriginalValues` با naming convention (مثلاً `OldPrice` ← `OriginalValues["Price"]`)
|
||||||
|
- **Entityهای فعال:** `Package` → `PackageHistory`
|
||||||
|
|
||||||
|
### History Tables
|
||||||
|
|
||||||
|
| جدول | Entity مرتبط | فیلدهای Old/New |
|
||||||
|
|------|-------------|----------------|
|
||||||
|
| `PackageHistories` | Package | Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive |
|
||||||
|
| `ClubMembershipCycleHistories` | ClubMembershipCycle | IsCurrentCycle, MagicStartedAt, MagicCompletedAt |
|
||||||
|
| `UserWalletHistories` | UserWallet | *(renamed from UserWalletChangeLogs — RenameTable migration)* |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۶. حذف BFF / Gateway
|
## ۶. حذف BFF / Gateway
|
||||||
@@ -265,7 +305,15 @@ FrontOffice Service Layer:
|
|||||||
"WorkerCount": 4
|
"WorkerCount": 4
|
||||||
},
|
},
|
||||||
"Kavenegar": { "ApiKey": "***" },
|
"Kavenegar": { "ApiKey": "***" },
|
||||||
"ZarinPal": { "MerchantId": "***" },
|
"ZarinPal": { "MerchantId": "***", "UseSandbox": true },
|
||||||
"DayaLoan": { "UseMock": true }
|
"DayaLoan": { "UseMock": true },
|
||||||
|
"CmsBaseUrl": "https://cms.se.kbs1.ir",
|
||||||
|
"FrontOfficeBaseUrl": "http://localhost:5268"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **⚠️ نکات مهم appsettings:**
|
||||||
|
> - `CmsBaseUrl` — برای callback URLهای درگاه (شارژ کیفپول جادویی/اعتباری)
|
||||||
|
> - `FrontOfficeBaseUrl` — برای redirect بعد پرداخت (خرید پکیج/تراکنش عمومی)
|
||||||
|
> - `appsettings.Development.json` — URLهای localhost برای توسعه محلی
|
||||||
|
> - همه callback URLها از config خوانده میشوند — هیچ URL از ورودی کاربر نمیآید (امنیت Open Redirect)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 🖥️ BackOffice و FrontOffice — معماری UI
|
# 🖥️ BackOffice و FrontOffice — معماری UI
|
||||||
|
|
||||||
> **منابع ادغامشده:** `BACKOFFICE-ARCHITECTURE.md`, `BACKOFFICE-STORE-UNIFICATION.md`, `UI-MODERNIZATION-PLAN.md`, `UI-UNIFICATION-PLAN.md`, `PHASE-1-COMPLETE.md`, `PHASE-3-COMPLETE.md`, `PRODUCT-IMAGES-SQUARE.md`
|
> **منابع ادغامشده:** `BACKOFFICE-ARCHITECTURE.md`, `BACKOFFICE-STORE-UNIFICATION.md`, `UI-MODERNIZATION-PLAN.md`, `UI-UNIFICATION-PLAN.md`, `PHASE-1-COMPLETE.md`, `PHASE-3-COMPLETE.md`, `PRODUCT-IMAGES-SQUARE.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: UserAutoComplete + فعالسازی درگاه)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکسهای پرداخت Phase 11 + صفحه موفقیت + تومان/ریال + امنیت Callback)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -119,7 +119,8 @@ FrontOffice/src/FrontOffice/
|
|||||||
│ │ └── Contract.razor ← امضای قرارداد
|
│ │ └── Contract.razor ← امضای قرارداد
|
||||||
│ ├── Profile/
|
│ ├── Profile/
|
||||||
│ │ ├── Index.razor ← داشبورد پروفایل + تایل Magic
|
│ │ ├── Index.razor ← داشبورد پروفایل + تایل Magic
|
||||||
│ │ └── MagicWallet.razor ← 🪄 کیفپول جادویی (NEW)
|
│ │ ├── MagicWallet.razor ← 🪄 کیفپول جادویی
|
||||||
|
│ │ └── PaymentCallback.razor ← 💳 صفحه نتیجه پرداخت (TransactionId + موجودی واقعی)
|
||||||
│ ├── Blog/
|
│ ├── Blog/
|
||||||
│ ├── Auth/
|
│ ├── Auth/
|
||||||
│ │ ├── Login.razor
|
│ │ ├── Login.razor
|
||||||
@@ -134,6 +135,7 @@ FrontOffice/src/FrontOffice/
|
|||||||
└── Shared/
|
└── Shared/
|
||||||
├── AppImage.razor
|
├── AppImage.razor
|
||||||
├── ProductCard.razor ← مشترک بین Store و DiscountStore
|
├── ProductCard.razor ← مشترک بین Store و DiscountStore
|
||||||
|
├── PackagePurchaseDialog.razor ← دیالوگ ۲-مرحلهای خرید پکیج (NEW)
|
||||||
└── LoadMoreButton.razor
|
└── LoadMoreButton.razor
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -263,6 +265,10 @@ if (user.Identity?.IsAuthenticated == true) {
|
|||||||
| UserAutoComplete کامپوننت | ✅ | 100% |
|
| UserAutoComplete کامپوننت | ✅ | 100% |
|
||||||
| نمایش نام کاربر در Wallet | ✅ | 100% |
|
| نمایش نام کاربر در Wallet | ✅ | 100% |
|
||||||
| فعالسازی دکمههای درگاه | ✅ | 100% |
|
| فعالسازی دکمههای درگاه | ✅ | 100% |
|
||||||
|
| PackagePurchaseDialog | ✅ | 100% |
|
||||||
|
| Toman/Rial فیکس نمایش قیمت | ✅ | 100% |
|
||||||
|
| صفحه نتیجه پرداخت (PaymentCallback) | ✅ | 100% |
|
||||||
|
| امنیت Callback URL | ✅ | 100% |
|
||||||
| Mobile Responsive (Phase 7) | ⬜ | 0% |
|
| Mobile Responsive (Phase 7) | ⬜ | 0% |
|
||||||
| Dark Mode | ⬜ | 0% |
|
| Dark Mode | ⬜ | 0% |
|
||||||
| PWA | ⬜ | 0% |
|
| PWA | ⬜ | 0% |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 🔄 مهاجرت داده، BFF و Gateway
|
# 🔄 مهاجرت داده، BFF و Gateway
|
||||||
|
|
||||||
> **منابع ادغامشده:** `BACKOFFICE-BFF-MIGRATION.md`, `customer-facing-capabilities-codex.md`, `DATA-TABLE-MAPPINGS.md`, `DATAMIGRATION-README.md`, `FRONTOFFICE-TO-CMS-MIGRATION.md`, `GATEWAY-REMOVAL-MIGRATION-PLAN.md`, `MIGRATION-PROGRESS.md`
|
> **منابع ادغامشده:** `BACKOFFICE-BFF-MIGRATION.md`, `customer-facing-capabilities-codex.md`, `DATA-TABLE-MAPPINGS.md`, `DATAMIGRATION-README.md`, `FRONTOFFICE-TO-CMS-MIGRATION.md`, `GATEWAY-REMOVAL-MIGRATION-PLAN.md`, `MIGRATION-PROGRESS.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Migration پروداکشن)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: DataMigration Tool + EF Staging Migrations)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -136,15 +136,15 @@ Proto-generated classes مستقیم در UI استفاده میشوند
|
|||||||
|
|
||||||
```
|
```
|
||||||
DataMigration/
|
DataMigration/
|
||||||
├── FourSat.DataMigration/ ← Console app
|
├── FourSat.DataMigration/ ← Console app (.NET 9 + Dapper + Polly + Serilog)
|
||||||
│ ├── Program.cs
|
│ ├── Program.cs ← Entry point
|
||||||
│ ├── Migrators/
|
│ ├── appsettings.json ← Source/Target connection strings + TruncateTargetTables
|
||||||
│ │ ├── UserMigrator.cs
|
│ ├── Services/
|
||||||
│ │ ├── ProductMigrator.cs
|
│ │ └── MigrationService.cs ← Smart retry, FK disable/enable, fallback table names
|
||||||
│ │ ├── OrderMigrator.cs
|
│ ├── Scripts/
|
||||||
│ │ └── ClubMigrator.cs
|
│ │ └── PostMigration_DataTransformation.sql ← Guardشده با IF COL_LENGTH/OBJECT_ID
|
||||||
│ └── Mappings/
|
│ └── Mappings/
|
||||||
│ └── TableMappings.cs
|
│ └── TableMappings.cs ← Source → Target table/column mappings
|
||||||
└── FourSat.GeographySeeder/ ← Seed geography data
|
└── FourSat.GeographySeeder/ ← Seed geography data
|
||||||
├── Program.cs
|
├── Program.cs
|
||||||
└── Data/
|
└── Data/
|
||||||
@@ -152,6 +152,21 @@ DataMigration/
|
|||||||
└── cities.json
|
└── cities.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ۵.۱.۱ ویژگیهای DataMigration Tool (اسفند ۱۴۰۴)
|
||||||
|
|
||||||
|
| ویژگی | توضیح |
|
||||||
|
|--------|--------|
|
||||||
|
| **Smart Retry** | فقط خطاهای transient SQL (deadlock, timeout, transport) — نه خطاهای منطقی |
|
||||||
|
| **FK Disable/Enable** | `ALTER TABLE NOCHECK/CHECK CONSTRAINT ALL` حول هر مهاجرت |
|
||||||
|
| **TruncateTargetTables** | حل duplicate key (`IX_ClubMembership_UserId`) هنگام re-run |
|
||||||
|
| **Fallback Table Name** | اگر جدول rename شده (`UserWalletChangeLogs` → `UserWalletHistories`) |
|
||||||
|
| **PostMigration Guards** | همه مراحل با `IF COL_LENGTH`/`OBJECT_ID` برای سازگاری با هر دو schema |
|
||||||
|
| **Polly Retry** | exponential backoff (2s, 8s, 32s) + لاگ structured |
|
||||||
|
| **Serilog** | لاگ فایل + کنسول با جزئیات هر جدول |
|
||||||
|
|
||||||
|
> **کامیتها:** `0e8c6fd` → `8385c90` (MERGE fix) → `31cc464` (FK+truncate+PostMigration)
|
||||||
|
> **وضعیت:** Local only — بدون remote (در workspace `DataMigration/` قرار دارد)
|
||||||
|
|
||||||
### ۵.۲ Data Table Mappings
|
### ۵.۲ Data Table Mappings
|
||||||
|
|
||||||
| جدول مبدأ (قدیم) | جدول مقصد (CMS) | نکات |
|
| جدول مبدأ (قدیم) | جدول مقصد (CMS) | نکات |
|
||||||
@@ -178,14 +193,17 @@ DataMigration/
|
|||||||
| `populate-weekly-commission-pools.sql` | داده تاریخی Pool |
|
| `populate-weekly-commission-pools.sql` | داده تاریخی Pool |
|
||||||
| `update_products_price_10_percent.sql` | افزایش قیمت ۱۰% |
|
| `update_products_price_10_percent.sql` | افزایش قیمت ۱۰% |
|
||||||
|
|
||||||
### ۵.۴ Migrationهای EF Core اجراشده روی Production (اسفند ۱۴۰۴)
|
### ۵.۴ Migrationهای EF Core اجراشده روی Production/Staging (اسفند ۱۴۰۴)
|
||||||
|
|
||||||
| Migration | توضیح | DB |
|
| Migration | توضیح | DB |
|
||||||
|-----------|--------|----|
|
|-----------|--------|----||
|
||||||
| `ExpandDiscountProductFullInformation` | گسترش فیلدهای محصول تخفیفی | KBS (Production `45.149.79.127`) |
|
| `ExpandDiscountProductFullInformation` | گسترش فیلدهای محصول تخفیفی | KBS (Production `45.149.79.127`) |
|
||||||
| `AddMagicWalletFields` (u21) | فیلدهای کیفپول جادویی + ClubMembershipCycle | KBS (Production) |
|
| `AddMagicWalletFields` (u21) | فیلدهای کیفپول جادویی + ClubMembershipCycle | KBS (Production) |
|
||||||
|
| ۵۵ migration کامل | از Initial تا `Q27_HistoryTables_And_RenameWalletHistory` | KBS Staging (`185.252.31.42,2019/KBS`) |
|
||||||
|
| ۵۵ migration کامل | از Initial تا `Q27_HistoryTables_And_RenameWalletHistory` | App DB (`194.5.195.53,31433/Foursat`) |
|
||||||
|
|
||||||
> ⚠️ نکته: Migration `u21` در زمان merge تکراری بود (timestampهای `20260222155925` و `20260222160755`). فایل تکراری حذف شد.
|
> ✅ **نکته:** CMS به ۲ DB مختلف وصل میشود — هر دو باید migrate شوند.
|
||||||
|
> ✅ Migration `u21` در زمان merge تکراری بود — فایل تکراری حذف شد.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -236,3 +254,5 @@ FourSat.GeographySeeder:
|
|||||||
| Geography Seeder | ✅ | 100% |
|
| Geography Seeder | ✅ | 100% |
|
||||||
| Proto package unification | ✅ | 100% |
|
| Proto package unification | ✅ | 100% |
|
||||||
| EF Migration پروداکشن | ✅ | 100% |
|
| EF Migration پروداکشن | ✅ | 100% |
|
||||||
|
| DataMigration Tool (Prod→Staging) | ✅ | 100% |
|
||||||
|
| EF Migration استیجینگ (KBS + Foursat) | ✅ | 100% |
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# TECH-06 — جریان شارژ Pool کمیسیون هفتگی
|
||||||
|
|
||||||
|
> تاریخ: ۱۴۰۵/۰۲/۱۰
|
||||||
|
> وضعیت: **باگ شناساییشده — منتظر Fix**
|
||||||
|
> مرتبط با: `ActivateClubMembershipCommandHandler.cs` · `AcceptClubMembershipContractCommandHandler.cs` · `CreateManualPaymentCommandHandler.cs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. مسیر مشتری جدید (اولین خرید پکیج)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor U as کاربر (FrontOffice)
|
||||||
|
participant CB as PaymentCallback.razor
|
||||||
|
participant PI as Profile/Index.razor
|
||||||
|
participant CD as ClubMembershipContractDialog
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
|
||||||
|
U->>CB: بازگشت از درگاه<br/>?type=package&orderId=X&Authority=Y
|
||||||
|
CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
|
||||||
|
|
||||||
|
Note over CMS: PackageService.VerifyPackagePurchase()
|
||||||
|
CMS->>CMS: تأیید با درگاه ✓
|
||||||
|
CMS->>CMS: ActivateClubMembership(ForceActivation=false)
|
||||||
|
|
||||||
|
Note over CMS: isNewMembership = true
|
||||||
|
CMS->>CMS: ClubMembership(IsActive=false) ایجاد
|
||||||
|
CMS->>CMS: ClubMembershipCycle #1 ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
|
||||||
|
|
||||||
|
CMS-->>CB: Success=true
|
||||||
|
CB->>CB: RefreshToken<br/>HasPurchasedPackage=true<br/>IsClubMemberActive=false
|
||||||
|
|
||||||
|
U->>PI: کلیک "بازگشت به پروفایل"
|
||||||
|
PI->>PI: OnAfterRenderAsync<br/>CheckAndShowClubContractModal()
|
||||||
|
|
||||||
|
Note over PI: HasPurchasedPackage=true<br/>AND IsClubMemberActive=false → نمایش مودال
|
||||||
|
|
||||||
|
PI->>CD: DialogService.ShowAsync (غیرقابل بستن)
|
||||||
|
U->>CD: مطالعه قرارداد + درخواست OTP
|
||||||
|
CD->>CMS: CreateNewOtpToken(purpose=signClubContract)
|
||||||
|
CMS-->>CD: OTP ارسال شد
|
||||||
|
U->>CD: وارد کردن OTP ۶ رقمی
|
||||||
|
CD->>CMS: AcceptClubMembershipContract(otp, signGuid)
|
||||||
|
|
||||||
|
Note over CMS: AcceptClubMembershipContractCommandHandler
|
||||||
|
CMS->>CMS: IsActive == false → guard رد میشه ✓
|
||||||
|
CMS->>CMS: IsActive = true
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ دوم ❌
|
||||||
|
|
||||||
|
CMS-->>CD: Success=true
|
||||||
|
CD->>PI: dialog.Close(Ok)
|
||||||
|
PI->>PI: LoadUserAuthInfo → IsClubMemberActive=true
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**: Pool برای عضو جدید **۲ برابر** شارژ میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. مسیر خرید مجدد (بعد از تکمیل چرخه Magic)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor U as کاربر (FrontOffice)
|
||||||
|
participant CB as PaymentCallback.razor
|
||||||
|
participant PI as Profile/Index.razor
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
|
||||||
|
Note over CMS: وضعیت: IsActive=true<br/>IsCurrentCycle=true (باگ B6 — ریست نشده)
|
||||||
|
|
||||||
|
U->>CB: بازگشت از درگاه (خرید مجدد)
|
||||||
|
CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
|
||||||
|
|
||||||
|
CMS->>CMS: ActivateClubMembership(ForceActivation=false)
|
||||||
|
Note over CMS: isNewMembership = false<br/>existingMembership.IsActive=true<br/>hasCurrentCycle=true
|
||||||
|
|
||||||
|
CMS->>CMS: return true زودهنگام ❌
|
||||||
|
|
||||||
|
Note over CMS: Cycle جدید ساخته نمیشه ❌<br/>Pool شارژ نمیشه ❌
|
||||||
|
|
||||||
|
CMS-->>CB: Success=true
|
||||||
|
CB->>CB: RefreshToken → IsClubMemberActive=true
|
||||||
|
|
||||||
|
U->>PI: بازگشت به پروفایل
|
||||||
|
PI->>PI: IsClubMemberActive=true<br/>→ مودال نمایش داده نمیشه ✓
|
||||||
|
|
||||||
|
Note over PI,CMS: Pool هرگز شارژ نشد ❌<br/>Cycle جدید وجود ندارد ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**: Pool برای خرید مجدد **هرگز** شارژ نمیشود. ریشه مشکل: باگ B6 — `IsCurrentCycle` هنگام خروج از Magic ریست نمیشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. مسیر ادمین (BackOffice — فعالسازی دستی)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor A as ادمین (BackOffice)
|
||||||
|
participant DL as ActivateClubDialog.razor
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
|
||||||
|
A->>DL: باز کردن دیالوگ فعالسازی برای کاربر X
|
||||||
|
DL->>DL: انتخاب UserId و PackageId
|
||||||
|
A->>DL: کلیک "تایید و فعالسازی"
|
||||||
|
|
||||||
|
DL->>CMS: ActivateClubMembership(UserId=X, ForceActivation=true)
|
||||||
|
Note over CMS: ActivateClubMembershipCommandHandler<br/>skip همه validationهای مالی
|
||||||
|
|
||||||
|
alt کاربر جدید (isNewMembership=true)
|
||||||
|
CMS->>CMS: ClubMembership(IsActive=false) ایجاد
|
||||||
|
CMS->>CMS: Cycle #1 ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
|
||||||
|
Note over CMS: کاربر هنوز عضو فعال نیست!<br/>IsActive=false
|
||||||
|
|
||||||
|
Note over A,CMS: کاربر باید به FO رود و قرارداد امضا کند
|
||||||
|
Note over A,CMS: AcceptContract → Pool += fee ← شارژ دوم ❌
|
||||||
|
else خرید مجدد (isNewMembership=false، IsCurrentCycle ریست شده)
|
||||||
|
CMS->>CMS: IsActive=true, hasCurrentCycle=false → ادامه میدهد
|
||||||
|
CMS->>CMS: Cycle جدید ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ یک بار ✅
|
||||||
|
end
|
||||||
|
|
||||||
|
CMS-->>DL: Empty (success)
|
||||||
|
DL->>A: "عضویت با موفقیت فعال شد"
|
||||||
|
Note over A,CMS: AcceptContract از BO هرگز فراخوانی نمیشود
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**: ادمین برای کاربر جدید نیز باعث double-charge میشود (چون کاربر بعداً از FO قرارداد امضا میکند). برای خرید مجدد رفتار درست است.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. خلاصه باگها
|
||||||
|
|
||||||
|
| سناریو | Pool شارژ واقعی | Pool شارژ انتظاری | Cycle ساخته میشود | وضعیت |
|
||||||
|
|--------|----------------|-------------------|--------------------|--------|
|
||||||
|
| مشتری جدید (IPG) | **2×fee** | 1×fee | ✅ بله | ❌ Double-charge |
|
||||||
|
| خرید مجدد مشتری | **0×fee** | 1×fee | ❌ خیر (B6) | ❌ هرگز شارژ نمیشود |
|
||||||
|
| ادمین — ForceActivate کاربر جدید | **2×fee** | 1×fee | ✅ بله | ❌ Double-charge |
|
||||||
|
| ادمین — ForceActivate خرید مجدد | **1×fee** | 1×fee | ✅ بله | ✅ درست |
|
||||||
|
| ادمین — ManualPayment (پرداخت دستی) | **1×fee** | 1×fee | ❌ خیر | ⚠️ Pool درست، ولی والدین امتیاز نمیگیرند |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. ریشه مشکلات
|
||||||
|
|
||||||
|
### باگ A — Double-charge در عضو جدید
|
||||||
|
**فایل**: `ActivateClubMembershipCommandHandler.cs` — بخش Pool (خط ~۳۱۱)
|
||||||
|
**علت**: هنگامی که `isNewMembership=true`، Pool شارژ میشود؛ بعداً `AcceptContract` هم Pool را شارژ میکند.
|
||||||
|
**Fix**: شارژ Pool در `ActivateClubMembership` را فقط برای `!isNewMembership` انجام بده:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
|
||||||
|
// عضو جدید: Pool توسط AcceptClubMembershipContract شارژ میشه (هنگام امضای قرارداد)
|
||||||
|
// خرید مجدد: قرارداد مجدد امضا نمیشه — Pool همینجا شارژ میشه
|
||||||
|
if (!isNewMembership)
|
||||||
|
{
|
||||||
|
// ... کد موجود شارژ Pool ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### باگ B6 — خرید مجدد کار نمیکند
|
||||||
|
**فایل**: `UserOrderService.cs` — بخش خروج از Magic
|
||||||
|
**علت**: هنگام خروج از Magic، `cycle.IsCurrentCycle` به `false` ریست نمیشود → `ActivateClubMembership` با `hasCurrentCycle=true` زودهنگام برمیگردد.
|
||||||
|
**Fix**: در `ExitMagicMode`:
|
||||||
|
```csharp
|
||||||
|
cycle.IsCurrentCycle = false; // ← اضافه شود
|
||||||
|
```
|
||||||
|
|
||||||
|
### باگ C — پرداخت دستی: Cycle هرگز ساخته نمیشود
|
||||||
|
**فایل**: `CreateManualPaymentCommandHandler.cs`
|
||||||
|
**علت**: پرداخت دستی `ActivateClubMembership` را صدا نمیزند → هیچ `ClubMembershipCycle` ساخته نمیشود → SP این کاربر را به عنوان "عضو جدید" برای والدینش حساب نمیکند.
|
||||||
|
**تأثیر**: Pool یکبار شارژ میشود (توسط AcceptContract ✓) ولی balance والدین در sp_CalculateWeeklyBalances افزایش نمییابد (چون Cycle ندارد ❌).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. مسیر پرداخت دستی (BackOffice — ManualPayment)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor A as ادمین (BackOffice)
|
||||||
|
participant DL as ManualPaymentDialog.razor
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
actor U as کاربر (FrontOffice)
|
||||||
|
participant PI as Profile/Index.razor
|
||||||
|
participant CD as ClubMembershipContractDialog
|
||||||
|
|
||||||
|
A->>DL: باز کردن دیالوگ پرداخت دستی
|
||||||
|
DL->>DL: انتخاب کاربر + پکیج + نوع پرداخت + تصویر رسید
|
||||||
|
A->>DL: کلیک "ثبت پرداخت"
|
||||||
|
|
||||||
|
DL->>CMS: CreateManualPayment(userId, packageId, type, referenceNumber)
|
||||||
|
Note over CMS: CreateManualPaymentCommandHandler
|
||||||
|
|
||||||
|
CMS->>CMS: Transaction(DepositExternal1) ایجاد
|
||||||
|
CMS->>CMS: ManualPayment(Status=Approved) ایجاد ← بدون نیاز به تایید دو مرحله
|
||||||
|
CMS->>CMS: wallet.Balance += package.Price
|
||||||
|
CMS->>CMS: wallet.DiscountBalance += package.Price × DiscountMultiplier
|
||||||
|
CMS->>CMS: user.PackagePurchaseMethod = DirectPurchase
|
||||||
|
|
||||||
|
Note over CMS: ❌ ActivateClubMembership صدا زده نمیشود<br/>❌ ClubMembershipCycle ساخته نمیشود<br/>❌ Pool شارژ نمیشود
|
||||||
|
|
||||||
|
CMS-->>DL: ManualPaymentId
|
||||||
|
DL->>A: "پرداخت دستی با موفقیت ثبت شد"
|
||||||
|
|
||||||
|
Note over A,U: کاربر باید به FO مراجعه کند
|
||||||
|
U->>PI: ورود به پروفایل
|
||||||
|
PI->>PI: OnAfterRenderAsync → CheckAndShowClubContractModal()
|
||||||
|
Note over PI: HasPurchasedPackage=true (PackagePurchaseMethod=DirectPurchase)<br/>IsClubMemberActive=false → نمایش مودال
|
||||||
|
|
||||||
|
PI->>CD: DialogService.ShowAsync (غیرقابل بستن)
|
||||||
|
U->>CD: امضای قرارداد + OTP
|
||||||
|
CD->>CMS: AcceptClubMembershipContract(otp, signGuid)
|
||||||
|
|
||||||
|
Note over CMS: AcceptClubMembershipContractCommandHandler
|
||||||
|
CMS->>CMS: user.ClubMembership == null → isNewMembership = true
|
||||||
|
CMS->>CMS: ClubMembership(IsActive=true) ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ یکبار ✅
|
||||||
|
|
||||||
|
Note over CMS: ❌ ClubMembershipCycle هرگز ساخته نمیشود<br/>(AcceptContract از Cycle خبری ندارد)
|
||||||
|
|
||||||
|
CMS-->>CD: Success=true
|
||||||
|
CD->>PI: dialog.Close(Ok)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**:
|
||||||
|
> - Pool: **1×** شارژ میشود ✅ (درست)
|
||||||
|
> - `ClubMembershipCycle`: **هرگز ساخته نمیشود** ❌
|
||||||
|
> - در `sp_CalculateWeeklyBalances`: کاربر `IsActive=true` دارد → خودش میتواند کمیسیون دریافت کند ✅
|
||||||
|
> - ولی والدین این کاربر **هیچ "عضو جدید" برای این هفته دریافت نمیکنند** ❌ (چون SP از `ClubMembershipCycles.PackagePurchasedAt` میخواند)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۶. validation داشبورد کمیسیون
|
||||||
|
|
||||||
|
پس از رفع باگ A، validation باید از **فقط یک منبع** استفاده کند:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// درست: فقط ClubMembershipCycles.PackagePurchasedAt
|
||||||
|
// این جدول برای هر خرید (چه جدید چه مجدد) یک رکورد دارد
|
||||||
|
var activations = await _context.ClubMembershipCycles
|
||||||
|
.CountAsync(c => c.PackageId == packageId
|
||||||
|
&& c.PackagePurchasedAt >= weekDef.StartDate
|
||||||
|
&& c.PackagePurchasedAt < weekDef.EndDate);
|
||||||
|
```
|
||||||
|
|
||||||
|
> قبل از رفع باگ A، validation فعلی (firstActivations + cycleActivations) تصادفاً با double-charge جبران میشد.
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# TECH-07 — لاگ کامل Session 1404/02/10 (2026-04-30)
|
||||||
|
|
||||||
|
> نوع سند: **گزارش کار**
|
||||||
|
> تاریخ: ۱۴۰۵/۰۲/۱۰
|
||||||
|
> مرتبط با: CMS · BackOffice · FrontOffice · Database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [بخش اول — رفع باگ Double-Charge Pool](#۱-رفع-باگ-double-charge-pool)
|
||||||
|
2. [بخش دوم — بررسی دادههای هفتههای ۲۲ و ۲۳](#۲-بررسی-دادههای-هفتههای-۲۲-و-۲۳)
|
||||||
|
3. [بخش سوم — ویژگی Network Tree (اطلاعات هفتگی)](#۳-ویژگی-network-tree-نوع-فعالسازی--پکیج)
|
||||||
|
4. [بخش چهارم — بهبود UI نمودار درختی](#۴-بهبود-ui-نمودار-درختی)
|
||||||
|
5. [بخش پنجم — رفع باگ Pagination فروشگاه تخفیف](#۵-رفع-باگ-pagination-فروشگاه-تخفیف)
|
||||||
|
6. [خلاصه فایلهای تغییریافته](#خلاصه-فایلهای-تغییریافته)
|
||||||
|
7. [وظایف باقیمانده (Pending)](#وظایف-باقیمانده)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. رفع باگ Double-Charge Pool
|
||||||
|
|
||||||
|
### مشکل
|
||||||
|
در جریان فعالسازی عضویت باشگاه، Pool کمیسیون هفتگی **دوبار** شارژ میشد:
|
||||||
|
- بار اول: در `ActivateClubMembership` (از طریق `VerifyPackagePurchase`)
|
||||||
|
- بار دوم: در `AcceptClubMembershipContract` (تأیید قرارداد توسط کاربر)
|
||||||
|
|
||||||
|
همچنین `CreateManualPayment` هم یک مسیر مستقل داشت که بدون Check هفته، Pool اشتباه را شارژ میکرد.
|
||||||
|
|
||||||
|
### ریشه مشکل
|
||||||
|
تابع `GetOrCreateCurrentWeeklyPool` بدون در نظر گرفتن هفته واقعی `PackagePurchasedAt`، Pool هفته جاری را انتخاب میکرد.
|
||||||
|
|
||||||
|
### فایلهای اصلاحشده
|
||||||
|
|
||||||
|
#### `ActivateClubMembershipCommandHandler.cs`
|
||||||
|
```csharp
|
||||||
|
// قبل: همیشه Pool هفته جاری را شارژ میکرد
|
||||||
|
// بعد: فقط یکبار در محل صحیح (AcceptContract) شارژ میشود
|
||||||
|
// حذف: شارژ Pool از داخل ActivateClubMembership (for isNewMembership scenario)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `AcceptClubMembershipContractCommandHandler.cs`
|
||||||
|
```csharp
|
||||||
|
// اضافه: بررسی هفته قرارداد — اگر هفته PackagePurchasedAt با هفته جاری فرق دارد
|
||||||
|
// از Pool هفته مناسب استفاده میکند نه Pool هفته جاری
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CreateManualPaymentCommandHandler.cs`
|
||||||
|
```csharp
|
||||||
|
// اصلاح: Cross-week fix — Pool هفته صحیح بر اساس تاریخ پرداخت دستی
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `sp_CalculateWeeklyCommissionPool.sql` (SP در Infrastructure)
|
||||||
|
```sql
|
||||||
|
-- اصلاح: IsCurrentCycle check برای جلوگیری از Double-Count
|
||||||
|
-- هر کاربر فقط یکبار در محاسبه Pool شمرده میشود
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. بررسی دادههای هفتههای ۲۲ و ۲۳
|
||||||
|
|
||||||
|
### تشخیص
|
||||||
|
|
||||||
|
با اجرای diagnostic SQL روی DB، دو anomaly کشف شد:
|
||||||
|
|
||||||
|
#### هفته ۲۲ — Pool Ghost (PoolId=10056)
|
||||||
|
| فیلد | مقدار |
|
||||||
|
|------|-------|
|
||||||
|
| TotalPoolAmount | 2,520,000 |
|
||||||
|
| AllCycles | 0 |
|
||||||
|
| ریشه | هانیه سادات عشاقی (UserId=189) — خرید 1404/01/12 (هفته ۲۱) ولی AcceptContract در 17:02 دقیقه بعد Pool هفته ۲۲ را شارژ کرد |
|
||||||
|
|
||||||
|
**دلیل:** CreatedAt و ModifiedAt timestamp مغایرت داشت — Pool در هفته ۲۲ ایجاد شد اما Cycle در هفته ۲۱ بود.
|
||||||
|
|
||||||
|
**اصلاح دستی DB (Pending):**
|
||||||
|
```sql
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 0, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10056 AND WeekDefinitionId = 22;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### هفته ۲۳ — Pool ناقص (PoolId=10054)
|
||||||
|
| فیلد | مقدار |
|
||||||
|
|------|-------|
|
||||||
|
| TotalPoolAmount | 0 |
|
||||||
|
| IsCalculated | False |
|
||||||
|
| ریشه | محمدصادق عسلی (UserId=190) — خرید هفته ۲۳، Cycle وجود دارد ولی Pool=0 (قبل از fix) |
|
||||||
|
|
||||||
|
**اصلاح دستی DB (Pending):**
|
||||||
|
```sql
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 2520000, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10054 AND WeekDefinitionId = 23;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. ویژگی Network Tree (نوع فعالسازی + پکیج)
|
||||||
|
|
||||||
|
### هدف
|
||||||
|
صفحه `/network/tree` در BackOffice باید در هر node نشان دهد:
|
||||||
|
- آیا این کاربر در هفته انتخابی **عضو جدید** بوده یا **تمدید کرده**؟
|
||||||
|
- نام پکیجی که خریداری کرده؟
|
||||||
|
|
||||||
|
### پیادهسازی Full-Stack
|
||||||
|
|
||||||
|
#### الف) SP_GetNetworkTree (dbbkup/SP_GetNetworkTree.sql)
|
||||||
|
```sql
|
||||||
|
-- اضافه شد:
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 cc.*
|
||||||
|
FROM CMS.ClubMembershipCycles cc
|
||||||
|
WHERE cc.ClubMembershipId = cm.Id
|
||||||
|
AND cc.PackagePurchasedAt >= @WeekStartDate
|
||||||
|
AND cc.PackagePurchasedAt < @WeekEndDate
|
||||||
|
AND (@ActivationWeekDefinitionId IS NULL OR @WeekStartDate IS NOT NULL)
|
||||||
|
) AS cc_target
|
||||||
|
|
||||||
|
-- ستونهای جدید در output:
|
||||||
|
IsActivatedInTargetWeek -- آیا در هفته انتخابی فعال شده؟
|
||||||
|
IsNewActivation -- 1=اولین فعالسازی (CycleNumber=1), 0=تمدید, NULL=بدون Cycle
|
||||||
|
PackageName -- نام پکیج اون هفته
|
||||||
|
PackageId -- شناسه پکیج
|
||||||
|
```
|
||||||
|
|
||||||
|
**نکته:** منطق هفتهبندی از `cm.ActivatedAt` به `Cycle.PackagePurchasedAt` تغییر کرد.
|
||||||
|
|
||||||
|
**Deploy SP:**
|
||||||
|
```
|
||||||
|
SP مستقیم روی DB اجرا شد (نه EmbeddedResource Infrastructure)
|
||||||
|
اجرا شد در: /tmp/DbDiag با C# script
|
||||||
|
تأیید شد: SELECT OBJECT_ID('[CMS].[GetNetworkTree]') → موجود
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ب) Application Layer
|
||||||
|
**`NetworkTreeNodeDto.cs`** — فیلدهای جدید:
|
||||||
|
```csharp
|
||||||
|
bool? IsNewActivation
|
||||||
|
string? PackageName
|
||||||
|
long? PackageId
|
||||||
|
```
|
||||||
|
|
||||||
|
**`NetworkTreeDto.cs`** — همین فیلدها
|
||||||
|
|
||||||
|
**`GetNetworkTreeQueryHandler.cs`**:
|
||||||
|
```csharp
|
||||||
|
// خواندن از DataReader:
|
||||||
|
IsNewActivation = reader.IsDBNull(reader.GetOrdinal("IsNewActivation"))
|
||||||
|
? null
|
||||||
|
: reader.GetInt32(reader.GetOrdinal("IsNewActivation")) == 1,
|
||||||
|
PackageName = reader["PackageName"] as string,
|
||||||
|
PackageId = reader.IsDBNull(reader.GetOrdinal("PackageId"))
|
||||||
|
? null
|
||||||
|
: reader.GetInt64(reader.GetOrdinal("PackageId"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ج) Proto (networkmembership.proto)
|
||||||
|
```protobuf
|
||||||
|
// NetworkTreeNodeModel — فیلدهای جدید:
|
||||||
|
google.protobuf.BoolValue is_new_activation = 22;
|
||||||
|
string package_name = 23;
|
||||||
|
google.protobuf.Int64Value package_id = 24;
|
||||||
|
```
|
||||||
|
|
||||||
|
**NuGet Package:** `Foursat.CMSMicroservice.Protobuf` → از `0.0.194` به **`0.0.195`** bump و push شد.
|
||||||
|
|
||||||
|
#### د) Mapping (NetworkMembershipProfile.cs)
|
||||||
|
```csharp
|
||||||
|
PackageName = node.PackageName ?? string.Empty,
|
||||||
|
PackageId = node.PackageId.HasValue ? node.PackageId.Value : null,
|
||||||
|
IsNewActivation = node.IsNewActivation.HasValue ? node.IsNewActivation.Value : null
|
||||||
|
```
|
||||||
|
|
||||||
|
#### هـ) BackOffice — NetworkTreeViewer.razor
|
||||||
|
**DataGrid — دو ستون جدید:**
|
||||||
|
```razor
|
||||||
|
<!-- ستون نوع فعالسازی -->
|
||||||
|
<PropertyColumn Property="x => x.IsNewActivation" Title="نوع فعالسازی">
|
||||||
|
@if (context.Item.IsNewActivation == true)
|
||||||
|
{
|
||||||
|
<MudChip Color="Color.Success">🆕 عضو جدید</MudChip>
|
||||||
|
}
|
||||||
|
else if (context.Item.IsNewActivation == false)
|
||||||
|
{
|
||||||
|
<MudChip Color="Color.Secondary">🔄 خرید مجدد</MudChip>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
|
<!-- ستون پکیج -->
|
||||||
|
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**JS (jsNodes):**
|
||||||
|
```js
|
||||||
|
isNewActivation: n.IsNewActivation,
|
||||||
|
packageName: n.PackageName ?? ""
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. بهبود UI نمودار درختی
|
||||||
|
|
||||||
|
### مشکل اولیه
|
||||||
|
بج «🆕 جدید» با `position: absolute` از گوشه کارت بیرون میزد و با محتوای دیگر برخورد میکرد.
|
||||||
|
|
||||||
|
### فایلهای تغییریافته
|
||||||
|
|
||||||
|
#### `admin-org-chart.js` (wwwroot/js)
|
||||||
|
|
||||||
|
**ساختار کارت بازنویسی شد:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ [Avatar] نام کاربر │
|
||||||
|
│ پکیج نقره... │ ← inline زیر اسم
|
||||||
|
│ L13 چپ عضو جدید │ ← pill در meta row
|
||||||
|
├─────────────────────────────┤
|
||||||
|
│ ✓ فعال 1404/12/24 │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**تغییرات:**
|
||||||
|
- `activationTypeBadge` (absolute positioning) → `activationTypePill` (inline span)
|
||||||
|
- `highlightBadge` (✨ floating) → حذف شد
|
||||||
|
- `packageBadge` به زیر اسم کاربر منتقل شد (نه footer)
|
||||||
|
- ابعاد کارت: `160×80` → `178×92` px
|
||||||
|
|
||||||
|
#### `admin-org-chart.css` (wwwroot/css)
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* جدید: activation pill به جای badge */
|
||||||
|
.admin-node-card .activation-pill { /* inline flex */ }
|
||||||
|
.admin-node-card .new-member-pill { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
|
||||||
|
.admin-node-card .renewal-pill { background: #ede7f6; color: #5e35b1; border: 1px solid #b39ddb; }
|
||||||
|
|
||||||
|
/* بهبود: پکیج روشنتر */
|
||||||
|
.admin-node-card .package-name-badge { background: #eceff1; color: #546e7a; border: 1px solid #b0bec5; }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. رفع باگ Pagination فروشگاه تخفیف
|
||||||
|
|
||||||
|
### مشکل
|
||||||
|
در صفحه `/discount-store/products`، دکمه «نمایش محصولات بیشتر» کار نمیکرد — همیشه صفحه اول برمیگشت.
|
||||||
|
|
||||||
|
### ریشه مشکل
|
||||||
|
|
||||||
|
**فایل غایب:** `DiscountProductProfile.cs` (Mapster) وجود نداشت.
|
||||||
|
|
||||||
|
**جریان mapping:**
|
||||||
|
```
|
||||||
|
GetDiscountProductsRequest (proto)
|
||||||
|
↓ request.Adapt<GetDiscountProductsQuery>()
|
||||||
|
GetDiscountProductsQuery
|
||||||
|
```
|
||||||
|
|
||||||
|
بدون profile، auto-mapping دو مشکل داشت:
|
||||||
|
1. `request.SearchQuery (string)` → `query.SearchTerm (string?)` → نامتطابق نام، NULL میشد
|
||||||
|
2. `request.PageNumber (int)` → `query.PaginationQuery.PageNumber` — **Mapster نمیتوانست به nested object مپ کند** → `PaginationQuery = null` → default: `PageNumber=1` همیشه!
|
||||||
|
|
||||||
|
### راهحل
|
||||||
|
|
||||||
|
**فایل جدید:** `CMS/src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
config.NewConfig<GetDiscountProductsRequest, GetDiscountProductsQuery>()
|
||||||
|
.Map(dest => dest.SearchTerm,
|
||||||
|
src => string.IsNullOrEmpty(src.SearchQuery) ? null : src.SearchQuery)
|
||||||
|
.Map(dest => dest.CategoryId,
|
||||||
|
src => src.CategoryId != null ? src.CategoryId.Value : (long?)null)
|
||||||
|
.Map(dest => dest.IsActive,
|
||||||
|
src => src.IsActive != null ? src.IsActive.Value : (bool?)null)
|
||||||
|
.Map(dest => dest.PaginationQuery, src => new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||||
|
PageSize = src.PageSize > 0 ? src.PageSize : 12
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
همچنین `GetDiscountProductsResponseDto → GetDiscountProductsResponse` هم به صورت صریح مپ شد تا `MetaData` و `Models` درست انتقال یابند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه فایلهای تغییریافته
|
||||||
|
|
||||||
|
| فایل | نوع تغییر | پروژه |
|
||||||
|
|------|-----------|-------|
|
||||||
|
| `ActivateClubMembershipCommandHandler.cs` | Fix — حذف Double-Charge | CMS Application |
|
||||||
|
| `AcceptClubMembershipContractCommandHandler.cs` | Fix — Cross-week Pool | CMS Application |
|
||||||
|
| `CreateManualPaymentCommandHandler.cs` | Fix — Cross-week Pool | CMS Application |
|
||||||
|
| `sp_CalculateWeeklyCommissionPool.sql` | Fix — IsCurrentCycle | CMS Infrastructure |
|
||||||
|
| `SP_GetNetworkTree.sql` | Feature — IsNewActivation, PackageName, PackageId | DB/dbbkup |
|
||||||
|
| `NetworkTreeNodeDto.cs` | Feature — فیلدهای جدید | CMS Application |
|
||||||
|
| `NetworkTreeDto.cs` | Feature — فیلدهای جدید | CMS Application |
|
||||||
|
| `GetNetworkTreeQueryHandler.cs` | Feature — خواندن فیلدهای جدید | CMS Application |
|
||||||
|
| `networkmembership.proto` | Feature — ۳ فیلد جدید در NetworkTreeNodeModel | Protobuf |
|
||||||
|
| `NetworkMembershipProfile.cs` | Feature — mapping فیلدهای جدید | CMS WebApi |
|
||||||
|
| `NetworkTreeViewer.razor` | Feature — DataGrid ستونهای جدید | BackOffice |
|
||||||
|
| `admin-org-chart.js` | Feature+Fix — inline pill، پکیج زیر اسم | BackOffice wwwroot |
|
||||||
|
| `admin-org-chart.css` | Feature+Fix — استایل pillهای مرتب | BackOffice wwwroot |
|
||||||
|
| `DiscountProductProfile.cs` | Fix — Pagination mapping صحیح | CMS WebApi (جدید) |
|
||||||
|
|
||||||
|
### NuGet Package
|
||||||
|
| پکیج | نسخه قبل | نسخه جدید |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| `Foursat.CMSMicroservice.Protobuf` | 0.0.194 | **0.0.195** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## وظایف باقیمانده
|
||||||
|
|
||||||
|
### ضروری — اصلاح دادههای DB
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- هفته ۲۲: Pool Ghost (هانیه سادات عشاقی ← AcceptContract هفته اشتباه)
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 0, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10056 AND WeekDefinitionId = 22;
|
||||||
|
|
||||||
|
-- هفته ۲۳: Pool ناقص (محمدصادق عسلی ← Pool قبل از Fix ایجاد شده بود)
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 2520000, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10054 AND WeekDefinitionId = 23;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### بهبود آینده
|
||||||
|
- [ ] `SP_GetNetworkTree.sql` به Infrastructure EmbeddedResource اضافه شود (auto-deploy)
|
||||||
|
- [ ] `DiscountProductDto` در Application — اضافه کردن فیلد `Created` از DB
|
||||||
|
- [ ] تست pagination فروشگاه پس از restart CMS
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
# TECH-08 — لاگ Session 1404/02/23 (2026-05-13)
|
||||||
|
|
||||||
|
> نوع سند: **گزارش کار**
|
||||||
|
> تاریخ: ۱۴۰۵/۰۲/۲۳
|
||||||
|
> مرتبط با: CMS · FrontOffice
|
||||||
|
> کامیت CMS: `683ed37` (branch: `kub-stage`)
|
||||||
|
> کامیت FrontOffice: `231da2c` (branch: `kub-stage`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [هدف و خلاصه](#هدف-و-خلاصه)
|
||||||
|
2. [تغییرات CMS (Backend)](#تغییرات-cms-backend)
|
||||||
|
3. [تغییرات FrontOffice](#تغییرات-frontoffice)
|
||||||
|
4. [معماری GuestActionGate](#معماری-guestactiongate)
|
||||||
|
5. [فلوچارت تجربه کاربر](#فلوچارت-تجربه-کاربر)
|
||||||
|
6. [فایلهای تغییر یافته](#فایلهای-تغییر-یافته)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## هدف و خلاصه
|
||||||
|
|
||||||
|
هدف این session:
|
||||||
|
|
||||||
|
1. **نمایش ۶ محصول پرفروش معمولی + ۶ محصول پرفروش فروشگاه اعتباری** در لندینگ پیج FrontOffice، زیر هدر اصلی (۳ محصول در هر ردیف، دو section مجزا).
|
||||||
|
2. **دسترسی guest** (کاربر بدون لاگین) به مرور محصولات برای پرزنت به مشتریان بالقوه.
|
||||||
|
3. **Hybrid auth flow**: کاربر guest محصولات را میبیند؛ اگر روی "افزودن به سبد" کلیک کرد، مودال لاگین باز میشود و پس از ورود موفق، عمل به صورت خودکار انجام میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## تغییرات CMS (Backend)
|
||||||
|
|
||||||
|
### ۱. `discountproduct.proto`
|
||||||
|
|
||||||
|
```proto
|
||||||
|
// اضافه شده به GetDiscountProductsRequest
|
||||||
|
google.protobuf.StringValue sort_by = 9;
|
||||||
|
|
||||||
|
// اضافه شده به DiscountProductDto
|
||||||
|
int32 sale_count = 12;
|
||||||
|
```
|
||||||
|
|
||||||
|
**چرا:** برای واکشی پرفروشترین محصولات فروشگاه اعتباری باید امکان sort بر اساس `sale_count` وجود داشته باشد. قبلاً این فیلد در DTO برگردانده نمیشد.
|
||||||
|
|
||||||
|
### ۲. `CMSMicroservice.Protobuf.csproj`
|
||||||
|
|
||||||
|
نسخه از `0.0.195` به `0.0.196` بالا رفت تا پکیج NuGet جدید publish شود.
|
||||||
|
|
||||||
|
### ۳. `GetDiscountProductsQuery.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public string? SortBy { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۴. `GetDiscountProductsQueryHandler.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// قبل: همیشه OrderByDescending(p => p.Created)
|
||||||
|
// بعد: dynamic sort با fallback
|
||||||
|
if (!string.IsNullOrEmpty(request.SortBy))
|
||||||
|
query = query.ApplyOrder(request.SortBy);
|
||||||
|
else
|
||||||
|
query = query.OrderByDescending(p => p.Created);
|
||||||
|
|
||||||
|
// و در SELECT:
|
||||||
|
SaleCount = p.SaleCount,
|
||||||
|
```
|
||||||
|
|
||||||
|
از extension method موجود `ApplyOrder` (کتابخانه `System.Linq.Dynamic.Core`) استفاده شد تا نیازی به تغییر جداگانه نباشد.
|
||||||
|
|
||||||
|
### ۵. `DiscountProductProfile.cs` (Mapster)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Request mapping
|
||||||
|
.Map(dest => dest.SortBy, src => string.IsNullOrEmpty(src.SortBy) ? null : src.SortBy)
|
||||||
|
|
||||||
|
// Response mapping
|
||||||
|
SaleCount = p.SaleCount,
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## تغییرات FrontOffice
|
||||||
|
|
||||||
|
### ۱. `GuestActionGate.cs` (فایل جدید)
|
||||||
|
|
||||||
|
```
|
||||||
|
FrontOffice.Main/Utilities/GuestActionGate.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
سرویس utility جدید که هر action نیازمند لاگین را wrap میکند:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task<bool> RunAsync(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (await _authService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
await _authDialogService.ShowAuthDialogAsync();
|
||||||
|
if (await _authService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
در `ConfigureServices.cs` به صورت Scoped ثبت شد:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
services.AddScoped<GuestActionGate>();
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۲. `ProductService.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public Task<ProductListResult> GetTopSellingAsync(int count = 6)
|
||||||
|
=> GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count);
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳. `DiscountProductService.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// پارامتر جدید به GetProductsAsync اضافه شد
|
||||||
|
public async Task<DiscountProductListResult> GetProductsAsync(
|
||||||
|
..., string? sortBy = null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(sortBy))
|
||||||
|
request.SortBy = sortBy;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DiscountProductListResult> GetTopSellingAsync(int count = 6)
|
||||||
|
=> GetProductsAsync(page: 1, pageSize: count, sortBy: "SaleCount desc");
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۴. `Index.razor` و `Index.razor.cs`
|
||||||
|
|
||||||
|
دو section جدید در لندینگ پیج زیر hero اضافه شد:
|
||||||
|
|
||||||
|
**Section 1 — محصولات پرفروش معمولی:**
|
||||||
|
- عنوان: "محصولات پرفروش"
|
||||||
|
- ۶ کارت (۳ در هر ردیف با MudGrid)
|
||||||
|
- هر کارت: تصویر، نام، قیمت با VAT، دکمه "افزودن به سبد"
|
||||||
|
- دکمه "بیشتر" → `/products`
|
||||||
|
|
||||||
|
**Section 2 — محصولات پرفروش فروشگاه اعتباری:**
|
||||||
|
- عنوان: "فروشگاه اعتباری"
|
||||||
|
- ۶ کارت (۳ در هر ردیف)
|
||||||
|
- هر کارت: تصویر، نام، قیمت، درصد تخفیف
|
||||||
|
- دکمه "بیشتر" → `/discount-store`
|
||||||
|
|
||||||
|
**Loading state:** در حین بارگذاری یک spinner نشان داده میشود و سپس sectionها fade-in میشوند.
|
||||||
|
|
||||||
|
**Data loading (parallel):**
|
||||||
|
```csharp
|
||||||
|
var topRegTask = ProductService.GetTopSellingAsync(6);
|
||||||
|
var topDiscTask = DiscountProductService.GetTopSellingAsync(6);
|
||||||
|
var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2);
|
||||||
|
await Task.WhenAll(topRegTask, topDiscTask, featuredPostsTask);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cart actions با GuestActionGate:**
|
||||||
|
```csharp
|
||||||
|
private async Task AddRegularToCart(Product p)
|
||||||
|
=> await GuestGate.RunAsync(() => Cart.Add(p, 1));
|
||||||
|
|
||||||
|
private async Task AddDiscountToCart(DiscountProductCard p)
|
||||||
|
=> await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۵. Hybridize کردن صفحات موجود
|
||||||
|
|
||||||
|
#### صفحات لیست و جزئیات محصول (GuestActionGate):
|
||||||
|
|
||||||
|
| فایل | تغییر |
|
||||||
|
|------|-------|
|
||||||
|
| `Store/Products.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
| `Store/ProductDetail.razor.cs` | `AddToCart` و `RemoveFromCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
| `DiscountStore/Products.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
| `DiscountStore/ProductDetail.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
|
||||||
|
#### صفحات Cart و Checkout (Soft Auth Gate):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (!await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
|
}
|
||||||
|
// ادامه بارگذاری...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
این pattern روی:
|
||||||
|
- `Store/Cart.razor.cs`
|
||||||
|
- `Store/CheckoutSummary.razor.cs`
|
||||||
|
- `DiscountStore/Cart.razor.cs`
|
||||||
|
- `DiscountStore/Checkout.razor.cs`
|
||||||
|
|
||||||
|
اعمال شد. اگر guest مستقیماً وارد سبد خرید شود، مودال لاگین نشان داده میشود.
|
||||||
|
|
||||||
|
### ۶. `MembershipPage.razor` (fix متنی)
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- شارژ ۵۶ میلیون تومان کیف پول فروشگاه اعتباری
|
||||||
|
+ شارژ برابر ارزش پکیج فعال در کیف پول فروشگاه اعتباری
|
||||||
|
```
|
||||||
|
|
||||||
|
متن hardcodeشده با مقدار دینامیک جایگزین شد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## معماری GuestActionGate
|
||||||
|
|
||||||
|
```
|
||||||
|
کاربر کلیک میکند
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
GuestActionGate.RunAsync(action)
|
||||||
|
│
|
||||||
|
├─► آیا لاگین است؟ ──YES──► action() اجرا میشود ✅
|
||||||
|
│
|
||||||
|
NO
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
AuthDialogService.ShowAuthDialogAsync()
|
||||||
|
(مودال OTP باز میشود)
|
||||||
|
│
|
||||||
|
├─► آیا لاگین شد؟ ──YES──► action() اجرا میشود ✅
|
||||||
|
│
|
||||||
|
NO (بستن مودال)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
return false (هیچ اتفاقی نمیافتد) ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
این pattern **defense-in-depth** است: `CartService.Add` هم به تنهایی چک `IsAuthenticatedAsync` دارد؛ `GuestActionGate` لایه UX روی آن اضافه میکند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فلوچارت تجربه کاربر
|
||||||
|
|
||||||
|
```
|
||||||
|
کاربر وارد لندینگ پیج میشود (بدون لاگین)
|
||||||
|
│
|
||||||
|
├─► ۶ محصول پرفروش معمولی نمایش داده میشود
|
||||||
|
├─► ۶ محصول پرفروش اعتباری نمایش داده میشود
|
||||||
|
│
|
||||||
|
├─► "بیشتر" کلیک → /products یا /discount-store
|
||||||
|
│ (صفحات لیست کامل، بدون لاگین قابل مرور)
|
||||||
|
│
|
||||||
|
├─► روی محصول کلیک → صفحه جزئیات
|
||||||
|
│ (بدون لاگین قابل مشاهده)
|
||||||
|
│
|
||||||
|
└─► "افزودن به سبد" کلیک
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
مودال لاگین (OTP)
|
||||||
|
│
|
||||||
|
├─► ورود موفق → محصول به سبد اضافه میشود ✅
|
||||||
|
└─► بستن مودال → هیچ اتفاقی نمیافتد
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فایلهای تغییر یافته
|
||||||
|
|
||||||
|
### CMS — کامیت `683ed37`
|
||||||
|
|
||||||
|
```
|
||||||
|
src/CMSMicroservice.Protobuf/Protos/discountproduct.proto (+2)
|
||||||
|
src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj (~2)
|
||||||
|
src/CMSMicroservice.Application/DiscountShopCQ/Queries/
|
||||||
|
GetDiscountProducts/GetDiscountProductsQuery.cs (+1)
|
||||||
|
GetDiscountProducts/GetDiscountProductsQueryHandler.cs (+7 -3)
|
||||||
|
src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs (+3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### FrontOffice — کامیت `231da2c`
|
||||||
|
|
||||||
|
```
|
||||||
|
src/FrontOffice.Main/Utilities/GuestActionGate.cs (NEW +42)
|
||||||
|
src/FrontOffice.Main/ConfigureServices.cs (+1)
|
||||||
|
src/FrontOffice.Main/Utilities/ProductService.cs (+3)
|
||||||
|
src/FrontOffice.Main/Utilities/DiscountProductService.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/Index.razor (+~180)
|
||||||
|
src/FrontOffice.Main/Pages/Index.razor.cs (+45)
|
||||||
|
src/FrontOffice.Main/Pages/Store/Products.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/Store/Cart.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/Club/MembershipPage.razor (~1)
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user