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
40 KiB
🔄 نقشهراه تحول پکیجبیس — تسکهای گامبهگام
وضعیت: در حال اجرا — فاز ۰-۱۰ (تکمیل کد + استقرار staging) ✅ | NuGet v0.0.189 | تست باقیمانده
تاریخ: ۱۴۰۴/۱۲/۰۸
پیشنیاز: 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
+ 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
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
- 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
dotnet ef migrations add AddPackageBasedSystem
شامل:
- ستونهای جدید Package
- جدول PackageFeatures
- FKها در 4 entity
- Unique constraint
T1.9 — Data Migration Script
-- 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
+ public long PackageId { get; set; }
+ public virtual Package Package { get; set; }
EF Config: اضافه Unique Index:
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)
-- رکوردهای موجود → پکیج پایه
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 بهبود
تغییرات:
- 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 — تغییرات:
wallet.WalletMode = WalletMode.Normal;
wallet.MagicCompletedAt = DateTime.UtcNow;
cycle.MagicCompletedAt = DateTime.UtcNow;
+ user.PackagePurchaseMethod = PackagePurchaseMethod.None;
+ membership.IsActive = false;
+ cycle.IsCurrentCycle = false;
Guard تغییرات:
- if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
- throw new RpcException("قبلاً پکیج خریداری شده");
+ if (user.PackagePurchaseMethod != PackagePurchaseMethod.None
+ && !HasCompletedMagicCycle(membership))
+ throw new RpcException("چرخه جاری هنوز تکمیل نشده");
T2.7 — JWT Claims جدید
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)
-- 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: ارسال تنظیمات پکیج)
// 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)
تغییرات:
- 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)
// قبل: فقط 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 باید:
- ابتدا در staging با داده واقعی تست شود
- نتایج قبل و بعد مقایسه شوند
- Rollback plan آماده باشد
- در production ابتدا read-only اجرا شود (بدون commit)
مرحله ۷ — Migration + Cleanup (سهگانه) ✅
✅ تکمیلشده | ⏱️ ۱ روز | ریسک: پایین
فاز 7a — Cosmetic Cleanup ✅
کامیت: CMS
469d97b| FOb82cac4| BOf1b0085
CMS:
- حذف orphaned
PurchasePackagehandler (۳ فایل، بدون caller) - فیکس doc-comments:
طلایی→پکیجدر ۶ فایل (enums, entities, handlers)
FrontOffice:
- حذف hardcoded
پکیج طلاییازMyPackages.razorوPackages.razor - اضافه
PackageTitleproperty بهUserPackageStatusDtorecord
BackOffice:
- تغییر label
پکیج طلایی→خرید پکیجدرUserNetworkInfo.razor
فاز 7b — FrontOffice RPC Migration ✅
کامیت: CMS
161f796| FO71f391a
CMS:
CustomerPurchasePackage: embedorderIdدر callback URL قبل از ارسال به درگاه$"{request.CallbackUrl}{separator}orderId={purchase.Id}"
FrontOffice:
Profile/Index.razor.cs: مهاجرتInitiateBasePackagePaymentAsync→CustomerPurchasePackageAsyncProfile/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-throwUnimplemented)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| BO89f5241(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 ترتیب نمایشActivationFee— MudNumericField هزینه فعالسازیDiscountMultiplier— MudNumericField ضریب تخفیفMagicWalletMultiplier— MudNumericField ضریب کیف پول جادوییMagicWalletMaxDeposit— MudNumericField سقف واریز جادوییMagicWalletMaxCredit— MudNumericField سقف اعتبار جادوییMaxBalancesPerLeg— MudNumericField حداکثر تعادل هر پاMaxNetworkLevel— MudNumericField حداکثر سطح شبکهIsActive— MudCheckBox فعال/غیرفعالIsBasePackage— MudCheckBox پکیج پایهSupportsDirectPurchase— MudCheckBox پرداخت مستقیمSupportsDayaPurchase— MudCheckBox اعتبار دایا
UpdateDialog.razor: همان ۱۲ فیلد
PackageMainPage Grid (۴ ستون جدید):
Price— فرمتشده با N0SortOrder— ترتیب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 ثابت → از
PackageFeatureAPI داینامیک - Specifications: از hardcoded → از
PackageFeature.IsHighlightedAPI - حذف ۵ hardcoded feature string + ۴ hardcoded specification
PackageService.cs:
PackageDto: اضافه ۸ فیلد جدید (ActivationFee, DiscountMultiplier, MagicWalletMultiplier, etc.)GetAllPackagesAsync: مپ فیلدهای جدید ازCustomerPackageModelGetUserPackageStatusAsync: از stub → اتصال واقعی بهGetUserPackageStatusAsyncRPC
Packages.razor:
- Un-exclude از build (حذف
<Content Remove>+<Compile Remove>) - جایگزینی ۳ feature bullet hardcoded → dynamic features:
SupportsDirectPurchase→ پرداخت مستقیمSupportsDayaPurchase→ پرداخت با اعتبار دایاDiscountMultiplier→ ضریب تخفیف: X.XxMagicWalletMultiplier→ کیف پول جادویی: X.XxIsBasePackage→ پکیج پایه ⭐
فاز 8d — Proto Cleanup ✅
کامیت: CMS
7554d70(2 فایل، -103) | FO40882c8| BOc96377a
حذف ۴ deprecated RPC:
PurchaseGoldenPackage— جایگزین:CustomerPurchasePackageVerifyGoldenPackagePurchase— جایگزین:CustomerVerifyPackagePurchaseInitiateBasePackagePayment— جایگزین:CustomerPurchasePackageVerifyBasePackagePayment— جایگزین:CustomerVerifyPackagePurchase
حذف ۸ deprecated message type:
PurchaseGoldenPackageRequest/PurchaseGoldenPackageResponseVerifyGoldenPackagePurchaseRequest/VerifyGoldenPackagePurchaseResponseInitiateBasePackagePaymentRequest/InitiateBasePackagePaymentResponseVerifyBasePackagePaymentRequest/VerifyBasePackagePaymentResponse
حفظ شده: GetUserPackageStatus RPC + messages (هنوز در استفاده)
NuGet: 0.0.185 → 0.0.186 (همه ریپوها)
فاز 8e — Per-Package Commission Reports ✅
کامیت: CMS
aaaf7fc| FOa956cb9| BO8be98ae
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)+ mapPackageId/PackageTitle - ۳ Response DTO: اضافه
PackageId+PackageTitle CommissionProfile.cs: تنظیم mappingهای Mapster برای admin + customer
BackOffice (6 فایل):
- کامپوننت جدید
PackageSelect.razor/.cs: dropdown قابل استفاده مجدد با بارگذاری پکیجها ازPackageContract UserPayouts.razor/.cs: فیلتر PackageSelect + ستون پکیج با MudChipBalancesReport.razor: فیلتر PackageSelect + ستون پکیج با MudChip + mapping PackageTitle
FrontOffice (7 فایل):
CommissionDtos.cs: اضافهPackageId+PackageTitleبهCommissionPayoutDtoوWeeklyBalanceDtoCommissionService.cs: اضافه پارامترpackageIdبهGetMyCommissionPayoutsAsyncوGetMyWeeklyBalanceAsyncCommissionDashboardPage.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| FO3bffc13| BOe020354
CMS (T4.13 — PackageFeature CRUD):
- Proto: اضافه
repeated int64 feature_idsبه ۴ message (Create/Update Request, Get/GetAll Response) CreateNewPackageCommand/Handler: sync FeatureIds → ساختPackageFeaturerecordsUpdatePackageCommand/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
@* قبل: 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: اضافهPackageServiceinjection، بارگذاری پکیجها باGetAllPackagesAsync()Checkout.razor: دکمههای پرداخت شرطی بر اساسSupportsDirectPurchaseوSupportsDayaPurchase- اضافه
DayaLoanPayment()method + alert برای عدم وجود روش پرداخت Packrecord: اضافه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وWeeklyBalanceDtoCommissionService.cs: اضافه پارامترpackageIdبهGetMyCommissionPayoutsAsyncوGetMyWeeklyBalanceAsyncCommissionDashboardPage.razor: اضافه dropdown فیلتر پکیج + ستون «پکیج» با MudChip + نمایش پکیج در card موبایلCommissionDashboardPage.razor.cs: injectPackageService، فیلد_filterPackageId، بارگذاری لیست پکیجها
T4.9 — FrontOffice: WeeklyBalance per-package (🆕 v3) ✅ FO:a956cb9
پیادهسازی:
WeeklyBalancePage.razor: اضافه MudSelect فیلتر پکیج کنار WeekSelector + نمایش MudChip پکیج در بخش اطلاعات هفتهWeeklyBalancePage.razor.cs: injectPackageService، فیلد_filterPackageId، ارسال بهCommissionService.GetMyWeeklyBalanceAsync
T4.10-T4.12 — BackOffice: گزارشهای پورسانت per-package (🆕 v3) ✅ BO:8be98ae
پیادهسازی:
- کامپوننت جدید
PackageSelect.razor/.cs: dropdown قابل استفاده مجدد با بارگذاری پکیجها ازPackageContract UserPayouts.razor/.cs: فیلتر PackageSelect + ستون پکیج با MudChipBalancesReport.razor: فیلتر PackageSelect + ستون پکیج با MudChip + mappingPackageTitle
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+ ساختPackageFeaturerecordsUpdatePackageCommand/Handler: اضافهFeatureIds+ sync (حذف قبلیها + ساخت جدید)GetPackageQueryHandler: اضافه.Include(x => x.PackageFeatures)+ mapFeatureIdsGetAllPackageByFilterQueryHandler: اضافه.Include(x => x.PackageFeatures)قبل ازPaginatedListAsync- NuGet:
0.0.187→0.0.188
BO پیادهسازی:
CreateDialog.razor/.cs: بارگذاریClubFeaturesازConfigurationContractClient+ checkbox matrixUpdateDialog.razor/.cs: همان pattern + pre-populate ازModel.FeatureIds- Mapster:
Adapt<UpdatePackageRequest>()خودکارFeatureIdsرا map میکند
مرحله ۹ — Q24-Q30 Business Decisions + History Infrastructure ✅
✅ تکمیلشده | وابستگی: مرحله ۸ | کامیتها: CMS:
a1024a3→fdbb91d→10d2ca2FO:474d364BO: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.sqlresources
9b: Q27 History Tables Entities ✅ (CMS:fdbb91d)
Entityهای جدید:
PackageHistory: فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive + Action + PerformedBy + ReasonClubMembershipCycleHistory: فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt + Action + UserId + CycleNumber
Enums جدید:
PackageAction: Created, Updated, Activated, Deactivated, PriceChanged, FeaturesChangedClubMembershipCycleAction: 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
- شناسایی entityهای
PackageimplementsIHasHistory<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)
- UI: کاشیهای داینامیک FrontOffice
- 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 | طراحی فنی — ۴۸+ تغییر (v3) + باگها |
| PACKAGE-TRANSFORMATION-UX.md | تاثیر UX بر فرانتها |
| FEATURE-BACKLOG.md | بکلاگ ۱۲ RPC آماده |
| GRPC-SERVICES-AUDIT.md | آدیت ۳۴۲ RPC |
آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۸ — فاز ۰-۹d تکمیل (۳۰ کامیت: ۲۰ CMS + ۸ FO + ۶ BO) | NuGet v0.0.188 | باقیمانده: تست + deploy