39590d2cbe
Updated 8 docs: - CHANGELOG: Phase 10a-d (DataMigration tool, EF staging migrations, PackagePurchaseDialog, 4 UI fixes) - PAYMENT-FINANCE: Rial→Toman conversion chain documented, PackagePurchaseDialog status - TECH-02: Added PackagePurchaseDialog to folder structure + status table - TECH-04: DataMigration tool features (smart retry, FK handling, fallback tables), EF staging - PACKAGE-TASKS: Phase 10 graph, NuGet v0.0.189, T4.1+T4.2 marked ✅ - BIZ-PACKAGE: v6→v7, commits updated, T4.1+T4.2 marked ✅ - INDEX: Updated last-update + R3 description - ROADMAP: Progress bars updated, DONE section + NOW section refreshed
865 lines
40 KiB
Markdown
865 lines
40 KiB
Markdown
# 🔄 نقشهراه تحول پکیجبیس — تسکهای گامبهگام
|
||
|
||
> **وضعیت:** در حال اجرا — **فاز ۰-۱۰ (تکمیل کد + استقرار 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*
|