diff --git a/business/BIZ-PACKAGE-BASED-SYSTEM.md b/business/BIZ-PACKAGE-BASED-SYSTEM.md new file mode 100644 index 0000000..549dd4a --- /dev/null +++ b/business/BIZ-PACKAGE-BASED-SYSTEM.md @@ -0,0 +1,554 @@ +# 📦 سیستم مبتنی بر پکیج (Package-Based System) + +> **وضعیت:** تحلیل و بررسی — منتظر تایید +> **تاریخ:** اسفند ۱۴۰۴ +> **تاثیرگذاری:** زیاد — بخش‌های متعدد سیستم تحت تاثیر قرار می‌گیرد + +--- + +## ۱. خلاصه فیچر + +**وضعیت فعلی:** سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن می‌چرخد. + +**وضعیت هدف:** سیستم چندین پکیج با قیمت‌ها و ویژگی‌های متفاوت پشتیبانی می‌کند. هر پکیج روش‌های پرداخت، محاسبه پورسانت، شارژ کیف پول و فیچرهای مختص خود را دارد. + +``` +مثال پکیج‌ها: +┌──────────────┬──────────────┬──────────────┬──────────────┐ +│ 🥈 نقره‌ای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │ +│ ۵.۶M تومان │ ۵۶M تومان │ ؟؟ تومان │ ؟؟ تومان │ +│ │ (پکیج پایه) │ │ │ +│ فقط مستقیم │ دایا+مستقیم │ فقط مستقیم │ فقط مستقیم │ +│ فیچر محدود │ همه فیچرها │ همه فیچرها │ همه+اختصاصی │ +└──────────────┴──────────────┴──────────────┴──────────────┘ +``` + +--- + +## ۲. وضعیت فعلی سیستم (AS-IS) + +### ۲.۱ فلوی فعلی فعالسازی + +```mermaid +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) + +### ۳.۱ فلوی جدید فعالسازی + +```mermaid +flowchart TD + A["کاربر وارد سیستم"] --> B["صفحه پکیج‌ها
(کاشی‌های نقره‌ای/طلایی/الماسی/...)"] + + B -->|"کلیک روی پکیج"| C{"نوع پکیج"} + + C -->|"پکیج پایه (طلایی)"| D["مدال با دو گزینه:
۱. خرید الماس دایا
۲. پرداخت مستقیم"] + C -->|"پکیج‌های دیگر"| E["مدال با یک گزینه:
فقط پرداخت مستقیم
+ توضیحات + قیمت"] + + D -->|"دایا"| F["فلوی دایا"] + D -->|"مستقیم"| G["درگاه پرداخت"] + E --> G + + F --> H["پرداخت موفق"] + G --> H + + H --> I["شارژ کیف پول
(متناسب با قیمت پکیج)"] + I --> J["مدال قرارداد باشگاه"] + J --> K["OTP + امضا"] + K --> L["فعال‌سازی
+ اختصاص فیچرهای پکیج"] + L --> M["✅ کاربر فعال"] +``` + +### ۳.۲ تغییرات Entity — Package + +**فعلی:** +```csharp +public class Package : BaseAuditableEntity +{ + public string Title { get; set; } + public string Description { get; set; } + public string ImagePath { get; set; } + public long Price { get; set; } +} +``` + +**پیشنهادی:** +```csharp +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 PackageFeatures { get; set; } + public virtual ICollection Purchases { get; set; } +} +``` + +### ۳.۳ Entity جدید — PackageFeature (پل بین پکیج و فیچر) + +```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; // آیا این فیچر در پکیج هست؟ +} +``` + +### ۳.۴ تغییرات Entity — ClubMembership + +```csharp +public class ClubMembership : BaseAuditableEntity +{ + // ... فیلدهای فعلی حفظ می‌شوند ... + + // === فیلد جدید === + public long PackageId { get; set; } // کدام پکیج خریداری شده + public virtual Package Package { get; set; } +} +``` + +### ۳.۵ تغییرات Entity — ClubMembershipCycle + +```csharp +public class ClubMembershipCycle : BaseAuditableEntity +{ + // ... فیلدهای فعلی حفظ می‌شوند ... + + // === فیلد جدید === + public long PackageId { get; set; } // پکیج این سایکل + public virtual Package Package { get; set; } + // PackageAmount قبلاً وجود دارد — از Package.Price پر می‌شود +} +``` + +### ۳.۶ تغییرات Entity — WeeklyCommissionPool + +```csharp +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 می‌شوند: +```csharp +// 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 — پکیج‌های اولیه + +```sql +-- 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 | ۴-۵ روز | فاز ۲ | +| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ | +| **مجموع** | **~۱۶-۲۱ روز کاری** | | + +> فازهای ۲ و ۳ قابل موازی‌سازی هستند. diff --git a/business/BUSINESS-02-PAYMENT-FINANCE.md b/business/BUSINESS-02-PAYMENT-FINANCE.md index cbfd905..12ee42f 100644 --- a/business/BUSINESS-02-PAYMENT-FINANCE.md +++ b/business/BUSINESS-02-PAYMENT-FINANCE.md @@ -1,7 +1,7 @@ # 💰 سیستم مالی، پرداخت و درگاه‌ها > **منابع ادغام‌شده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md` -> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فعال‌سازی درگاه ZarinPal + تنظیمات محیطی) +> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس WalletChangeLog + validation کیف‌پول اعتباری + نام‌گذاری جدید کیف‌پول‌ها) --- @@ -24,9 +24,9 @@ flowchart TD PYMS --> WALLETS subgraph WALLETS["3 Wallet System"] - W1["💰 Balance\nنقدی"] - W2["🌟 NetworkBalance\nشبکه"] - W3["🏷️ DiscountBalance\nاعتباری"] + W1["💰 Balance\nکیف پول اصلی"] + W2["🌟 NetworkBalance\nپاداش تیمی"] + W3["🏷️ DiscountBalance\nکیف پول اعتباری"] end ``` @@ -137,7 +137,11 @@ flowchart TD قیمت محصول = 1,000,000 ریال MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخصوص خود را دارد) -سهم تخفیف = MIN(1,000,000 × 40%, DiscountBalanceکاربر) = 400,000 +سهم تخفیف = قیمت × MaxDiscountPercent% = 400,000 + +⚠️ اگر DiscountBalance < سهم تخفیف → خطا: «موجودی کیف پول اعتباری کافی نیست» + (دیگر MIN استفاده نمی‌شود — کاربر باید موجودی کافی داشته باشد) + باقیمانده → ZarinPal IPG = 1,000,000 - 400,000 = 600,000 ───────── مجموع = 1,000,000 @@ -151,11 +155,15 @@ MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخص flowchart TD A["کاربر عضو باشگاه\nمشاهده محصول"] --> B["قیمت تخفیف‌خورده نمایش داده می‌شود"] B --> C["افزودن به سبد\nمحاسبه MaxDiscount% هر محصول"] - C --> D["سهم تخفیف = MIN(قیمت×MaxDiscount%, DiscountBalance)"] - D --> E["باقیمانده = مجموع - سهم تخفیف"] + C --> D["سهم تخفیف = قیمت × MaxDiscount%"] + D --> V{"DiscountBalance >= سهم تخفیف?"} + V -->|خیر| X["❌ خطا: موجودی کیف پول اعتباری کافی نیست"] + V -->|بله| E["باقیمانده = مجموع - سهم تخفیف"] E --> F{"باقیمانده > 0?"} F -->|بله| G["کسر DiscountBalance\n+ Redirect → ZarinPal IPG\nباقیمانده + 10% VAT"] F -->|خیر| H["فقط کسر از DiscountBalance\nبدون درگاه → ثبت مستقیم"] + G --> LOG["ثبت UserWalletChangeLog"] + H --> LOG ``` ### ۵.۳ دسترسی فروشگاه اعتباری @@ -164,8 +172,21 @@ flowchart TD |------|--------| | `IsClubMember = true` | دسترسی به Discount Store | | `IsClubMember = false` | فقط Regular Store | -| `DiscountBalance > 0` | می‌تواند از تخفیف استفاده کند | -| `DiscountBalance = 0` | پرداخت ۱۰۰% از طریق ZarinPal IPG | +| `DiscountBalance >= سهم تخفیف` | خرید مجاز | +| `DiscountBalance < سهم تخفیف` | ❌ خطا: موجودی کیف پول اعتباری کافی نیست | + +### ۵.۴ UserWalletChangeLog (اسفند ۱۴۰۴ — فیکس) + +> **باگ:** هنگام خرید از فروشگاه اعتباری، `DiscountBalance` در دیتابیس کم می‌شد ولی هیچ +> `UserWalletChangeLog` ثبت نمی‌شد → کاربر در تاریخچه کیف‌پول چیزی نمی‌دید. + +فیکس در ۳ هندلر: + +| هندلر | سناریو | فیکس | +|--------|---------|------| +| `PlaceOrderCommandHandler` | پرداخت کامل با DiscountBalance (بدون درگاه) | ✅ ثبت log با `ChangeDiscountValue = -amount` | +| `CompleteOrderPaymentCommandHandler` | پرداخت ترکیبی (درگاه + DiscountBalance) | ✅ ثبت log بعد از verify موفق درگاه | +| `VerifyDiscountWalletChargeCommandHandler` | شارژ کیف‌پول اعتباری | ✅ ثبت log با `ChangeDiscountValue = +amount` | --- @@ -221,6 +242,18 @@ service PaymentService { | وام دایا | ✅ کامل | Mock mode فعال در staging | | پرداخت ترکیبی | ✅ کامل | Discount + IPG | | Pool هفتگی | ✅ کامل | SP + Hangfire | +| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیف‌پول در ۳ هندلر اضافه شد | +| Validation کیف‌پول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد | | پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت | | Refund | ⬜ طراحی | فقط در PYMS تعریف‌شده | | کیف‌پول جادویی (Magic) | ✅ کامل | فاز 1-6 پیاده‌سازی شده — Production فعال | + +### ۸.۱ نام‌گذاری استاندارد کیف‌پول‌ها (اسفند ۱۴۰۴) + +| فیلد دیتابیس | نام قدیم (UI) | نام جدید (UI) | +|-------------|--------------|---------------| +| `Balance` | عادی / اعتباری / نقدی | **کیف پول اصلی** | +| `DiscountBalance` | تخفیفی / تخفیف | **کیف پول اعتباری** | +| `NetworkBalance` | شبکه / طلایی / پورسانت | **پاداش تیمی** | + +> تغییرات UI در ۱۲ فایل (FrontOffice: 5, BackOffice: 7) اعمال شد. diff --git a/business/BUSINESS-03-ECOMMERCE-STORES.md b/business/BUSINESS-03-ECOMMERCE-STORES.md index 1b4a7d0..31ba937 100644 --- a/business/BUSINESS-03-ECOMMERCE-STORES.md +++ b/business/BUSINESS-03-ECOMMERCE-STORES.md @@ -1,7 +1,7 @@ # 🛒 فروشگاه، موجودی و محصولات > **منابع ادغام‌شده:** `discount-shop-business.md`, `DISCOUNT-STORE-STATUS.md`, `package-purchase-system.md`, `INVENTORY-IMPROVEMENTS.md`, `INVENTORY-REFACTORING-STATUS.md`, `PRODUCT-BUNDLE-FEATURE.md`, `SHOP-UNIFICATION.md` -> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: VAT 10%) +> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: ExpirePendingOrders ۱۵ دقیقه + فروشگاه اعتباری نام‌گذاری) --- @@ -226,5 +226,44 @@ graph TD | Lazy Loading | ✅ کامل | 100% | | موجودی خودکار | ✅ کامل | 100% | | تصاویر مربعی | ✅ کامل | 100% | +| انقضای سفارشات Pending | ✅ کامل | 100% | | باندل محصولات | ⬜ طراحی | 30% | | مقایسه محصول | ⬜ ایده | 0% | + +--- + +## ۹. انقضای خودکار سفارشات Pending (ExpirePendingOrdersService) + +> سرویس پس‌زمینه‌ای که سفارشات فروشگاه اعتباری را بعد از ۱۵ دقیقه منقضی می‌کند. + +### ۹.۱ پارامترها + +| پارامتر | مقدار | توضیح | +|---------|-------|-------| +| `ExpirationTime` | **۱۵ دقیقه** | مدت زمان مجاز برای پرداخت | +| `CheckInterval` | ۵ دقیقه | فاصله بررسی | + +### ۹.۲ عملکرد + +```mermaid +flowchart TD + A["هر ۵ دقیقه\nExpirePendingOrdersService"] --> B["جستجوی DiscountOrders\nPaymentStatus=Pending\nCreated < (now - 15 min)"] + B --> C{"سفارشی یافت شد?"} + C -->|خیر| A + C -->|بله| D["آزادسازی رزرو موجودی\nReleaseReservationAsync"] + D --> E["PaymentStatus → Reject\nDeliveryStatus → Cancelled"] + E --> F["Transaction.PaymentStatus → Reject"] + F --> G["Log: Expired order #X"] + G --> A +``` + +### ۹.۳ فایل + +``` +CMS/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs +``` + +رجیستر شده در `ConfigureServices.cs`: +```csharp +services.AddHostedService(); +``` diff --git a/technical/TECH-03-DEPLOYMENT-INFRA.md b/technical/TECH-03-DEPLOYMENT-INFRA.md index 887fea8..5ad1aa8 100644 --- a/technical/TECH-03-DEPLOYMENT-INFRA.md +++ b/technical/TECH-03-DEPLOYMENT-INFRA.md @@ -1,7 +1,7 @@ # 🚀 استقرار، CI/CD و زیرساخت > **منابع ادغام‌شده:** `CICD-PIPELINE-GUIDE.md`, `DEPLOYMENT-README.md`, `INFRASTRUCTURE-GUIDE.md`, `INGRESS-NGINX-WARNING.md`, `OFFLINE-DEPLOYMENT-GUIDE.md`, `SERVER-MIRRORS-CONFIG.md` -> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: PVC آپلود + K8s Secret برای config دائمی + جدا کردن appsettings هر برنچ) +> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس URL پروداکشن + چری‌پیک فیکس‌های WalletChangeLog/Validation/Expiry) --- @@ -473,21 +473,55 @@ flowchart TD | `9288d06` | feat: externalize appsettings to K8s Secret — config persists independently | | `e72673c` | chore(staging): remove appsettings.Production.json (فقط kub-stage) | | `3ebe0f9` | chore(production): remove appsettings.Staging.json (فقط production) | +| `f3ac5ad` | fix: add missing UserWalletChangeLog for discount shop purchases | +| `0457ef6` | fix: validate discount wallet balance before applying discount | +| `e206b71` | fix: reduce discount order expiry from 30 to 15 minutes | +| `2620a24` | fix: correct production URLs from kbs1 to kbs2 in cms-config | > کامیت‌های PVC و Secret به هر دو شاخه push شده‌اند. > ⚠️ کامیت‌های حذف appsettings فقط به برنچ مربوطه push شده — cherry-pick نکنید! +### ۹.۳ فیکس URL پروداکشن (اسفند ۱۴۰۴) + +> **مشکل:** در `cms-config.yaml` پروداکشن، URL‌ها به اشتباه `kbs1.ir` (استیج) بودند. +> زرین‌پال callback را به سرور استیج می‌فرستاد → خطای 401 → `Code=-1` (خطای ناشناخته). + +| فیلد | مقدار اشتباه | مقدار صحیح | +|------|-------------|------------| +| `CmsBaseUrl` | `https://cms.kbs1.ir` | `https://cms.kbs2.ir` | +| `FrontOfficeBaseUrl` | `https://kbs1.ir` | `https://kbs2.ir` | + +```bash +# فیکس مستقیم روی سرور (بدون نیاز به rebuild) +kubectl apply -f cms-config.yaml +kubectl rollout restart deployment/cms +``` + +### ۹.۴ نام‌گذاری کیف‌پول‌ها (اسفند ۱۴۰۴) + +> تغییر عنوان کیف‌پول‌ها در تمام UI (FrontOffice: 5 فایل، BackOffice: 7 فایل): + +| فیلد | نام قدیم | نام جدید | +|------|---------|----------| +| `Balance` | عادی / نقدی | **کیف پول اصلی** | +| `DiscountBalance` | تخفیفی / تخفیف | **کیف پول اعتباری** | +| `NetworkBalance` | شبکه / طلایی | **پاداش تیمی** | + **تنظیمات محیطی Production (`appsettings.Production.json`):** | تنظیم | مقدار | |--------|-------| | `ZarinPal.MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` | | `ZarinPal.UseSandbox` | `false` | +| `CmsBaseUrl` | `https://cms.kbs2.ir` | +| `FrontOfficeBaseUrl` | `https://kbs2.ir` | | `SeedWorkers.MagicWalletCycleSeed.Enabled` | `true` | | `Kestrel.Endpoints.Grpc.Protocols` | `Http2` | | `Seq.ServerUrl` | `http://seq-svc:5341` | | `ConnectionStrings.Default` | `Server=mssql-svc;Database=KBS` | +> ⚠️ **مهم:** URL‌ها باید `kbs2.ir` باشند نه `kbs1.ir` — اشتباه در URL باعث خطای 401 زرین‌پال می‌شود. + ```bash # k8s-health-check.sh (namespace = default) kubectl get pods