dd5a2617cf
- 6 bugs found (DiscountBalance, UserPackagePurchase, re-purchase blocked) - 15 hardcodes identified for removal - 7 guards blocking re-purchase analyzed - 5 payment path inconsistencies documented - 39 changes across 6 layers planned - 5 phases: bugfix → infra → logic → commission → UI → test - v1 draft preserved as BIZ-PACKAGE-BASED-SYSTEM-v1-draft.md
25 KiB
25 KiB
📦 سیستم مبتنی بر پکیج (Package-Based System)
وضعیت: تحلیل و بررسی — منتظر تایید
تاریخ: اسفند ۱۴۰۴
تاثیرگذاری: زیاد — بخشهای متعدد سیستم تحت تاثیر قرار میگیرد
۱. خلاصه فیچر
وضعیت فعلی: سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن میچرخد.
وضعیت هدف: سیستم چندین پکیج با قیمتها و ویژگیهای متفاوت پشتیبانی میکند. هر پکیج روشهای پرداخت، محاسبه پورسانت، شارژ کیف پول و فیچرهای مختص خود را دارد.
مثال پکیجها:
┌──────────────┬──────────────┬──────────────┬──────────────┐
│ 🥈 نقرهای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │
│ ۵.۶M تومان │ ۵۶M تومان │ ؟؟ تومان │ ؟؟ تومان │
│ │ (پکیج پایه) │ │ │
│ فقط مستقیم │ دایا+مستقیم │ فقط مستقیم │ فقط مستقیم │
│ فیچر محدود │ همه فیچرها │ همه فیچرها │ همه+اختصاصی │
└──────────────┴──────────────┴──────────────┴──────────────┘
۲. وضعیت فعلی سیستم (AS-IS)
۲.۱ فلوی فعلی فعالسازی
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)
۳.۱ فلوی جدید فعالسازی
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
فعلی:
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 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 (پل بین پکیج و فیچر)
/// <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
public class ClubMembership : BaseAuditableEntity
{
// ... فیلدهای فعلی حفظ میشوند ...
// === فیلد جدید ===
public long PackageId { get; set; } // کدام پکیج خریداری شده
public virtual Package Package { get; set; }
}
۳.۵ تغییرات Entity — ClubMembershipCycle
public class ClubMembershipCycle : BaseAuditableEntity
{
// ... فیلدهای فعلی حفظ میشوند ...
// === فیلد جدید ===
public long PackageId { get; set; } // پکیج این سایکل
public virtual Package Package { get; set; }
// PackageAmount قبلاً وجود دارد — از Package.Price پر میشود
}
۳.۶ تغییرات Entity — WeeklyCommissionPool
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 میشوند:
// 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 — پکیجهای اولیه
-- 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 | ۴-۵ روز | فاز ۲ |
| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ |
| مجموع | ~۱۶-۲۱ روز کاری |
فازهای ۲ و ۳ قابل موازیسازی هستند.