Files
docs/roadmap/PACKAGE-TRANSFORMATION-TASKS.md
T
masoodafar-web 01244f426e docs: package-based transformation — complete roadmap + UX impact + feature backlog
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
2026-02-24 23:18:50 +03:30

14 KiB
Raw Blame History

🔄 نقشه‌راه تحول پکیج‌بیس — تسک‌های گام‌به‌گام

وضعیت: تایید‌شده — آماده شروع
تاریخ: ۱۴۰۴/۱۲/۰۶
پیش‌نیاز: 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 باید:

  1. ابتدا در staging با داده واقعی تست شود
  2. نتایج قبل و بعد مقایسه شوند
  3. Rollback plan آماده باشد
  4. در 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

آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶