New documents: - roadmap/FEATURE-BACKLOG.md: 12 kept RPCs → feature tasks with priority, target pages, and time estimates (F1-F12) - roadmap/PACKAGE-TRANSFORMATION-UX.md: UX impact analysis — before/after wireframes for 19 pages (10 FO + 9 BO), customer + admin experience changes, future needs prediction - roadmap/PACKAGE-TRANSFORMATION-TASKS.md: step-by-step implementation plan (6 phases, ~13 day critical path), atomic tasks with code diffs, dependency graph, test checklist Updated: - cms/GRPC-SERVICES-AUDIT.md: cross-references to new docs Total: 998 lines of documentation covering: - 12 RPC feature tasks prioritized by package-based relevance - 19 page wireframes (before/after comparison) - 39 transformation tasks broken into 6 phases - 10 predicted future requirements (N1-N10) - Risk analysis + rollback plan + calendar
14 KiB
🔄 نقشهراه تحول پکیجبیس — تسکهای گامبهگام
وضعیت: تاییدشده — آماده شروع
تاریخ: ۱۴۰۴/۱۲/۰۶
پیشنیاز: BIZ-PACKAGE-BASED-SYSTEM.md v2
هدف: شکستن ۳۹ تغییر به تسکهای اتمیک با ترتیب اجرا و وابستگیها
📊 نمای کلی
مرحله ۰: فیکس باگ فوری (۱ روز)
└─→ مرحله ۱: زیرساخت Domain + DB (۳ روز)
├─→ مرحله ۲: منطق کسبوکار (۴ روز) ← موازی
│ └─→ مرحله ۴: FrontOffice UI (۳ روز)
└─→ مرحله ۳: پورسانت (۳ روز) ← موازی
└─→ مرحله ۵: BackOffice UI (۳ روز)
└─→ مرحله ۶: تست + استقرار (۲ روز)
مسیر بحرانی: ۰→۱→۲→۴→۶ = ~۱۳ روز
مرحله ۰ — فیکس باگهای فوری
⏱️ ۱ روز | وابستگی: ندارد | ریسک: کم
✅ وضعیت باگها (بررسی اولیه لازم)
| # | باگ | 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)
⏱️ ۳ روز | وابستگی: مرحله ۰ | ریسک: متوسط (migration)
T1.1 — بروزرسانی Package Entity
فایل: 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;
+ 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? | توضیح |
|---|---|---|---|
| ClubMembership | long? PackageId |
nullable (بعد migration → required) | آخرین پکیج |
| ClubMembershipCycle | long PackageId |
required | پکیج این چرخه |
| WeeklyCommissionPool | long PackageId |
required + Unique(WeekDefId, PkgId) | Pool هر پکیج |
| UserCommissionPayout | long? PackageId |
nullable | ردیابی |
T1.7 — حذف SystemConstants
فایل: 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; // اگر وجود داشت
⚠️ قبل از حذف: grep تمام مصرفکنندهها → جایگزین با
Package.Property
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
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;
-- 3. Seed Silver package
INSERT INTO "CMS"."Packages" (...) VALUES ('پکیج نقرهای', 5600000, ...);
T1.10 — بروزرسانی Protoها
| Proto File | تغییر |
|---|---|
| package.proto | فیلدهای جدید Package message |
| clubmembership.proto | package_id در request/response |
| commission.proto | package_id در pool/payout messages |
مرحله ۲ — منطق کسبوکار
⏱️ ۴ روز | وابستگی: مرحله ۱ | ریسک: بالا (رگرسیون)
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 ?? "");
مرحله ۳ — محاسبه پورسانت (موازی با مرحله ۲)
⏱️ ۳ روز | وابستگی: مرحله ۱ | ریسک: بحرانی (مالی)
T3.1-T3.2 — SPs + PackageId
-- sp_CalculateWeeklyBalances:
ALTER PROCEDURE sp_CalculateWeeklyBalances
@WeekDefinitionId BIGINT,
@PackageId BIGINT -- ← جدید
AS
BEGIN
-- فیلتر بالانسها فقط کاربرانی که این پکیج را دارند
INSERT INTO "CMS"."WeeklyBalances" (...)
SELECT ...
FROM "CMS"."UserWallets" w
INNER JOIN "CMS"."ClubMemberships" m ON m."UserId" = w."UserId"
WHERE m."PackageId" = @PackageId -- ← فیلتر
AND m."IsActive" = true
AND w."WalletMode" = 0; -- Normal only
END;
T3.3 — Loop Service
// 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}",
package.Id, package.Title);
await strategy.CalculateWeeklyBalancesAsync(weekId, package.Id);
await strategy.CalculateWeeklyPoolAsync(weekId, package.Id);
}
⚠️ نکته بحرانی
پورسانت = پول واقعی. هر تغییر در SPs باید:
- ابتدا در staging با داده واقعی تست شود
- نتایج قبل و بعد مقایسه شوند
- Rollback plan آماده باشد
- در production ابتدا read-only اجرا شود (بدون commit)
مرحله ۴ — FrontOffice UI
⏱️ ۳ روز | وابستگی: مرحله ۲ | ریسک: متوسط
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 — مودال پرداخت شرطی
@if (_selectedPackage.SupportsDirectPurchase)
{
<MudButton OnClick="PayOnline">پرداخت آنلاین</MudButton>
}
@if (_selectedPackage.SupportsDayaPurchase)
{
<MudButton OnClick="PayDaya">اقساط دایا</MudButton>
}
T4.3 — MyPackages + Re-Purchase
@if (_canRepurchase)
{
<MudAlert Severity="Severity.Success">
🎉 چرخه جادویی تکمیل شد! میتوانید پکیج جدید بخرید.
</MudAlert>
<MudButton Href="/packages">خرید پکیج جدید</MudButton>
}
else
{
<MagicWalletProgress Wallet="@_wallet" Cycle="@_currentCycle" />
}
مرحله ۵ — BackOffice UI
⏱️ ۳ روز | وابستگی: مرحله ۳ | ریسک: کم
(تسکها در PACKAGE-TRANSFORMATION-UX.md بخش ۳ مستند شده)
مرحله ۶ — تست و استقرار
⏱️ ۲ روز | وابستگی: مرحله ۴ و ۵
Checklist تست
- خرید پکیج نقرهای (ZarinPal)
- خرید پکیج پایه (ZarinPal)
- خرید پکیج پایه (Daya Loan)
- خرید پکیج پایه (Manual Payment)
- فعالسازی باشگاه با پکیج نقرهای → فیچرهای محدود
- فعالسازی باشگاه با پکیج پایه → همه فیچرها
- تکمیل چرخه Magic → ریست وضعیت
- خرید مجدد بعد تکمیل چرخه
- Commission Pool جداگانه هر پکیج
- Data Migration — PackageId در رکوردهای قبلی
- JWT claims جدید (CanRepurchase, PackageId)
- UI: کاشیهای داینامیک FrontOffice
- UI: ماتریس فیچر BackOffice
- Rollback: بدون data loss
📅 تقویم پیشنهادی
| هفته | روز | تسک |
|---|---|---|
| هفته ۱ | روز ۱ | مرحله ۰: فیکس ۴ باگ |
| روز ۲-۳ | مرحله ۱: Package entity + PackageFeature | |
| روز ۴ | مرحله ۱: FKها + Migration | |
| هفته ۲ | روز ۵-۶ | مرحله ۲: Generic handlers + re-purchase |
| روز ۵-۶ | مرحله ۳: SP + Loop (موازی) | |
| روز ۷-۸ | مرحله ۲: Guards + JWT + Manual | |
| هفته ۳ | روز ۹-۱۰ | مرحله ۴: FrontOffice UI |
| روز ۱۱ | مرحله ۵: BackOffice UI | |
| روز ۱۲-۱۳ | مرحله ۶: تست + deploy |
🔗 ارجاعات
| مستند | محتوا |
|---|---|
| BIZ-PACKAGE-BASED-SYSTEM.md | طراحی فنی — ۳۹ تغییر + باگها |
| PACKAGE-TRANSFORMATION-UX.md | تاثیر UX بر فرانتها |
| FEATURE-BACKLOG.md | بکلاگ ۱۲ RPC آماده |
| GRPC-SERVICES-AUDIT.md | آدیت ۳۴۲ RPC |
آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶