Compare commits

..

31 Commits

Author SHA1 Message Date
masoodafar-web 4218d08597 docs: add session log for 2026-05-13 (guest browsing + top-seller landing sections)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 19:50:15 +03:30
masoodafar-web 1db77b1a1b Add comprehensive database integrity audit report and fix critical bugs in commission pool charging flow
- Introduced a detailed audit report for the CMS database integrity, highlighting issues related to data entry, code bugs, and stored procedures.
- Fixed double-charge issue in the commission pool during club membership activation.
- Updated stored procedures to ensure correct pool calculations across different weeks.
- Enhanced the network tree feature to include activation type and package details.
- Improved UI for the network tree display and resolved pagination issues in the discount store.
2026-05-01 00:07:25 +03:30
masoodafar-web e3850f9dd8 docs: فاز ۱۱ — فیکس‌های پرداخت ZarinPal + تصحیح تومان/ریال + امنیت Callback URL
- CHANGELOG: Phase 11 (11a-11f) — ZarinPal verify fix, تومان/ریال مدل, صفحه موفقیت, حذف ×۱۰ دوبار, callback URL امنیت
- BUSINESS-02: تصحیح مدل ارزی (DB=تومان نه ریال), ZarinPal verify fix, جدول callback URL امنیت
- TECH-01: اضافه CmsBaseUrl/FrontOfficeBaseUrl به appsettings, توضیح امنیت Open Redirect
- TECH-02: اضافه PaymentCallback.razor, وضعیت‌های جدید
- ROADMAP: بروزرسانی Payment 97→99%, اضافه فاز ۱۱ به DONE list
2026-02-27 22:33:35 +03:30
masoodafar-web 39590d2cbe docs: Phase 10 — DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes
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
2026-02-27 20:56:00 +03:30
masoodafar-web de69bf862c docs: F1-F7 همه تکمیل — آپدیت PACKAGE-MIGRATION-GUIDE
- F1-F7 از 🟡 به  تغییر کردند
- NuGet: v0.0.188 → v0.0.189
- R8 (validator hardcoded 1B): فیکس شد
- کامیت هش‌های جدید اضافه شد
- آمار کامیت‌ها و تاریخ بروز شد
2026-02-27 09:05:50 +03:30
masoodafar-web 6fc15b474e docs: update PACKAGE-MIGRATION-GUIDE — Q24-Q30 all done, F8-F11 completed
- Mark Q24, Q26, Q27, Q28 as  with commit refs
- Mark F8-F11 as completed (were marked as future/pending)
- Add 3 new CMS commits (a1024a3, fdbb91d, 10d2ca2)
- Add FO commit (474d364) and BO commit (6939780)
- Add Migration step 2.5b: Q27_HistoryTables_And_RenameWalletHistory
- Update stats: 48 commits, 227+ files, 30 business decisions
- Update CMS commit table header to 20 commits
2026-02-27 06:50:07 +03:30
masoodafar-web e9f1fb9911 docs: Phase 9 — Q24-Q30 + History Interceptor + Rename + Migration
- CHANGELOG: add Phase 9 (9a-9d) with all new commits (CMS:a1024a3→fdbb91d→10d2ca2, FO:474d364, BO:6939780)
- CHANGELOG: update summary table (279 items, 99%)
- TASKS: add Phase 9 section with detailed descriptions + update commit list (30 total)
- BIZ: update header to 'code complete — Phase 0-9 ' + add Phase 9 commits
- TECH-01: add SP Worker (Q26), History Tracking System (Q27), IHasHistory, Interceptor docs
- TECH-01: add UserWalletHistoryService (renamed from ChangeLog)
- INDEX: update R3/R6 descriptions, update timestamp
2026-02-27 06:35:09 +03:30
masoodafar-web 085583c274 docs(biz): v6 fix — carryover is per-DOWNLINE-package (not per-user-package), user's own package change has NO effect on carryover 2026-02-27 04:01:25 +03:30
masoodafar-web f8908d8e2b docs(biz): v6 — Q24-Q30: balance threshold, SP worker, history tables, UI guidance, DayaLoans+EXIT+carryover confirmations 2026-02-27 03:42:47 +03:30
masoodafar-web ba10b6485b docs: add comprehensive Package Migration Guide — business impact + step-by-step deployment plan 2026-02-27 03:00:23 +03:30
masoodafar-web df1affa46e docs: mark T4.2, T4.3, T4.13, F2, F3 as completed — phase 8f
- PACKAGE-TRANSFORMATION-TASKS.md: add phase 8f section, mark T4.2/T4.3/T4.13 complete
- FEATURE-BACKLOG.md: mark F2 (ChangeNetworkParent) + F3 (CalculateOrderPV) complete
- NuGet v0.0.188 | CMS:dcd1135 FO:3bffc13 BO:e020354
2026-02-26 22:10:45 +03:30
masoodafar-web 38aababc1a docs: update for Phase 8e — per-package commission reports
- PACKAGE-TRANSFORMATION-TASKS: mark T4.8-T4.12 completed, add Phase 8e section, update tree diagram, bump to NuGet v0.0.187
- OVERVIEW-03-CHANGELOG: add Phase 8e entry, mark commission per-package items done, update timeline & summary table
2026-02-26 21:13:16 +03:30
masoodafar-web e6d086b559 Docs: Phase 8b-8d — BO CRUD expansion + FO Customer RPCs + Proto cleanup
- PACKAGE-TRANSFORMATION-TASKS.md: Add phases 8b, 8c, 8d with full details
  Updated overview diagram, commit hashes, status to 0-8d complete
- OVERVIEW-03-CHANGELOG.md: Add Phase 8b (BO CRUD) + Phase 8a+8c (FO)
  Updated summary table to 99% (240/241 complete)
2026-02-26 18:04:04 +03:30
masoodafar-web a61a987b57 docs: Add Phase 8a — Checkout wire-up + NuGet plan 2026-02-26 03:36:33 +03:30
masoodafar-web d94b09878a docs: Update TASKS for Phase 7a-c completion (16 commits across 3 repos)
- Phase 7a: Cosmetic cleanup (CMS+FO+BO)
- Phase 7b: FrontOffice RPC migration to Customer* RPCs
- Phase 7c: Delete 4 deprecated CQRS handlers (1125 lines removed)
- Updated overview diagram, commit list, and footer
2026-02-26 03:06:22 +03:30
masoodafar-web 0aa126af1c Docs: Phase 5-6 completion — per-package commission + deprecation cleanup
- CHANGELOG: Phase 5 (per-pkg commission, golden cleanup) + Phase 6 (deprecation, ConfigService MagicWallet)
- ROADMAP: Progress 83%→93%, timeline updated through Phase 6
- TASKS: Mark مرحله ۳ (پورسانت) , add commits 607f791→7176fe4→d19c569
2026-02-26 01:14:51 +03:30
masoodafar-web 37330a3e45 docs: update all documentation for Package-Based Transformation Phase 0-4
- OVERVIEW-03-CHANGELOG: Add complete Package-Based section (Phase 0-4) with
  commit references, add summary table row, update timeline
- OVERVIEW-05-ROADMAP: Add Package-Based progress bar (83%), update DONE/NOW
  sections with Phase 0-4 complete and Phase 5-6 pending
- OVERVIEW-02-INDEX: Add 4 new roadmap files to index, update file counts
- BIZ-PACKAGE-BASED-SYSTEM: Status → 'در حال پیاده‌سازی — فاز 0-4 تکمیل'
  with all 7 commit hashes
- PACKAGE-TRANSFORMATION-TASKS: Mark Phase 0/1/2 as complete with commit refs,
  update overview diagram with completion markers
2026-02-26 00:32:33 +03:30
masoodafar-web 977ef69e26 docs(biz): v5 — قرارداد یک‌بار (Q19) + فیچر DIFF (Q20) + First/Last Activation (Q21-Q22) + carryover تغییر پکیج (Q23)
تغییرات بنیادی v5:
- Q19: قرارداد باشگاه فقط یک بار امضا — حذف re-contract از G5, A10, T2.7
- Q20: فیچرها DIFF/تفاضل — FeatureDiffService جدید (مقایسه + اعمال اختلاف)
- Q21: ClubMembership: ActivatedAt → FirstActivationDate + LastActivationDate + FirstPackageId + LastPackageId
- Q22: تشخیص فعال‌شدگان هفته از LastActivationDate
- Q23: carryover strictly per-package — تغییر پکیج = carryover قبلی شمرده نمی‌شود

بخش‌های جدید:
- 5.4: قرارداد یک‌بار + فلوچارت خرید مجدد بدون قرارداد
- 5.5: الگوریتم DIFF فیچرها + مثال عملی + کد پیشنهادی
- 5.6: تشخیص فعال‌شدگان هفته (SQL)
- ClubMembership entity v5 با ۴ فیلد جدید

اصلاحات:
- EXIT Magic Mode: حذف membership.IsActive=false
- State diagram: re-purchase بدون قرارداد
- Migration: ActivatedAt → First/LastActivationDate
- Impact Analysis: 95+ تغییر (51 اصلی + 44 سایدافکت)
- Timeline: ~28 روز مجموع، ~24 روز critical path
2026-02-25 21:50:02 +03:30
masoodafar-web 1885fcbd3b docs: BIZ-PACKAGE-BASED-SYSTEM v4 — comprehensive side-effect discovery
44 NEW side effects discovered across 6 layers (total: 92 changes):

Side Effects — CMS Domain (3):

Side Effects — CMS Application (10):
- ChargeMagicWalletCommandHandler: global MagicWalletMaxDeposit (1B)
- VerifyMagicWalletChargeCommandHandler: global multiplier ×2.5
- UserOrderService EXIT/ENTRY: global caps → user trapped/ejected wrong
- 3 FluentValidation validators: hardcoded 1B ceiling
- GetAllFeatureIds(): ALL features granted regardless of package
- JWT: no PackageId/CanRepurchase, just boolean HasPurchased
- WalletGrpcService.GetMagicWalletStatus: global caps to frontend
- 4 Notifications: no PackageId in interface

Side Effects — Background (3):
- ClubMembershipCycleSeedService: seeds with hardcoded amounts
- DayaLoanStatusCheckWorker: global DayaLoanAmount
- ChatikaAccountActivationWorker: no package filter

Side Effects — FrontOffice (9):
- Contract text '56M toman' = LEGAL LIABILITY
- Magic wallet ×2.5 and deposit cap hardcoded (6 places + C# code)
- 'پکیج طلایی' hardcoded (5+ places) — wrong name
- PackageId=1 hardcoded in activation flow

Side Effects — BackOffice (8):
- ManualActivationDialog: 56M hardcoded + disabled + no package selector
- SystemConfigurationPage: global settings need per-package
- CSV exports (3 places): no package column

Impact: 48 core changes + 44 side effects = 92 total
Timeline: v3 17 days → v4 22 days critical path (+5 days)
2026-02-25 00:27:14 +03:30
masoodafar-web 33d5ae9305 docs: BIZ-PACKAGE-BASED-SYSTEM v3 — per-package commission deep analysis
Major v3 changes:
- Q12-Q18: MaxWeeklyBalancesPerLeg, MaxNetworkLevel, MagicWalletMaxDeposit,
  MagicWalletMaxCredit all become per-package (not global SystemConstants)
- NetworkWeeklyBalance gets PackageId + Unique(UserId,WeekId,PackageId)
- SP changes: @MaxBalancesPerLeg and @MaxNetworkLevel as dynamic params
  (removing hardcoded 300/15)
- SpCommissionCalculationStrategy: pass package settings to SPs
- Carryover per-package: week-shifting only for same PackageId records
- commission.proto: package_id+package_title in 4 message types,
  new CustomerCommissionPackageSummary message, package filter in requests
- FrontOffice: commission dashboard with per-package summary cards
- BackOffice: package filter dropdown in all commission reports + CSV
- Package Create/Edit: Quick Access checkboxes for features inline
- Seed data: silver MaxBalancesPerLeg=30, MagicWalletMax=100M/250M
- Migration: NetworkWeeklyBalances existing records get base PackageId
- Impact Analysis: 39 -> 48+ changes across 6 layers
- Timeline: 14 -> 17 days critical path (+3 days for per-package work)

Updated docs:
- business/BIZ-PACKAGE-BASED-SYSTEM.md (v2 -> v3)
- roadmap/PACKAGE-TRANSFORMATION-TASKS.md (synced with v3)
2026-02-24 23:48:09 +03:30
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
masoodafar-web 3575e483b9 docs: deep analysis of 24 dead gRPC RPCs — keep 12, archive 12
Analyzed all 24 dead RPC implementations line-by-line:
- 12 KEEP (future-proof): CustomerReorderPreviousOrder, CustomerTrackOrder,
  CalculateOrderPV, GetInventorySummary, GetStockValueReport, BulkAddStock,
  BulkUpdateProductStock, GetConfigurationByKey, UpdateCustomerSettings,
  ChangeNetworkParent, AssignFeatureToMembership, GetLowStockProducts
- 12 ARCHIVE: fms.proto (2), BulkAdjustStock, 2 empty order stubs,
  2 non-functional configs, 2 redundant city ops, 3 duplicate RPCs

Key findings:
- 17/24 were actually FULLY IMPLEMENTED, just never wired to frontend
- fms.proto is the only completely dead proto file (no service class)
- Archive != Delete — marked for exclusion only
2026-02-24 22:40:44 +03:30
masoodafar-web 09b8b804d4 docs: audit all CMS gRPC services — 342 RPCs, 125 unused from frontends, 24 dead code 2026-02-24 22:25:53 +03:30
masoodafar-web dd5a2617cf docs: BIZ-PACKAGE-BASED-SYSTEM v2 — deep analysis + approved decisions
- 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
2026-02-24 21:34:59 +03:30
masoodafar-web c78850f86e docs: update BUSINESS-02, BUSINESS-03, TECH-03
BUSINESS-02:
- فرمول هایبرید: حذف MIN، اضافه validation کیف‌پول اعتباری
- فلوی خرید: اضافه مرحله بررسی موجودی + UserWalletChangeLog
- نام‌گذاری جدید کیف‌پول‌ها: اصلی، اعتباری، پاداش تیمی
- جدول وضعیت: اضافه WalletChangeLog + Validation

BUSINESS-03:
- بخش ۹ جدید: ExpirePendingOrdersService (۱۵ دقیقه)
- دیاگرام Mermaid فلوی انقضا

TECH-03:
- فیکس URL پروداکشن (kbs1→kbs2) + هشدار
- ۴ کامیت جدید در بخش ۹.۲
- بخش ۹.۳ فیکس URL پروداکشن
- بخش ۹.۴ نام‌گذاری کیف‌پول‌ها
2026-02-24 00:29:03 +03:30
masoodafar-web 0115142faf docs: update TECH-03 — K8s Secret for persistent config, branch/appsettings separation, updated CI/CD flow 2026-02-23 22:13:15 +03:30
masoodafar-web 52e6e1530c docs: update TECH-03 — CI/CD pipeline details, fix namespace default, add PVC health check commands, add K8s commit history 2026-02-23 21:40:55 +03:30
masoodafar-web f5173a4def docs: فعال‌سازی درگاه ZarinPal + مرج پروداکشن + بهبود UI ادمین
- CHANGELOG: اضافه Phase 18 BackOffice + فعال‌سازی درگاه FO + تنظیمات محیطی CMS + مرج پروداکشن
- PAYMENT-FINANCE: MerchantId واقعی + تنظیمات محیطی Staging/Production + Magic فاز 6 کامل
- ROADMAP: بروزرسانی درصدها + Magic 100% + Payment 97% + DONE section
- TECH-02: ساختار فولدر + UserAutoComplete + WalletManagement + دکمه‌های درگاه
- BUSINESS-01: Magic Wallet فاز 1-6 کامل
- TECH-04: Migration پروداکشن ExpandDiscountProductFullInformation + حذف u21 تکراری
- TECH-03: جزئیات مرج پروداکشن + تنظیمات appsettings.Production.json
- MAGIC-WALLET-PLAN: وضعیت کامل
- INDEX: بروزرسانی تاریخ
2026-02-22 23:26:36 +03:30
masoodafar-web 6b6173e2be docs: rename 'تخفیفی' to 'اعتباری' across all documentation
- 9 files updated: BUSINESS-01/02/03/04, TECH-02, OVERVIEW-01/03/04, MAGIC-WALLET-SPEC
- فروشگاه تخفیفی → فروشگاه اعتباری
- کیف‌پول تخفیفی → کیف‌پول اعتباری
- Consistent naming with FrontOffice UI
2026-02-22 20:57:05 +03:30
masoodafar-web c14bea6a06 docs: update MAGIC-WALLET-PLAN checklist - all items complete
- Mark migrations as completed (u21 applied + 74 rows seeded)
- Mark ChargeDiscountWallet as fully implemented
- Update status from pending to done with details
2026-02-22 20:41:29 +03:30
masoodafar-web 421a651975 docs: Magic Wallet + VAT 10% documentation update
- All 14 totalDoc files updated with Magic Wallet additions
- MAGIC-WALLET-PLAN.md: Phase 1-6 checklist fully marked complete
- Business docs: Magic Wallet section, commission filter, new entities
- Payment docs: VAT 9%→10%, TransactionType 14+15, ZarinPal 4th usage
- Technical docs: UserWallet fields, ClubMembershipCycle, gRPC RPCs
- Overview docs: Magic flowchart, ER diagram, changelog, glossary, roadmap
2026-02-22 20:09:01 +03:30
28 changed files with 7039 additions and 237 deletions
@@ -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["صفحه پکیج‌ها<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
**فعلی:**
```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<PackageFeature> PackageFeatures { get; set; }
public virtual ICollection<UserPackagePurchase> Purchases { get; set; }
}
```
### ۳.۳ Entity جدید — PackageFeature (پل بین پکیج و فیچر)
```csharp
/// <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
```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 | ۴-۵ روز | فاز ۲ |
| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ |
| **مجموع** | **~۱۶-۲۱ روز کاری** | |
> فازهای ۲ و ۳ قابل موازی‌سازی هستند.
File diff suppressed because it is too large Load Diff
+78 -2
View File
@@ -1,7 +1,7 @@
# 🏆 سیستم باشگاه، کمیسیون و درخت شبکه‌ای
> **منابع ادغام‌شده:** `club-commission-system-complete.md`, `balance-calculation-rules.md`, `club-membership-contract-system.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet کامل + بهبود مدیریت اعضا)
---
@@ -12,7 +12,9 @@
| **عضویت باشگاه** | خرید پکیج طلایی (۵۶M) → فعالسازی (۲۵.۲M) → عضو فعال باشگاه |
| **درخت باینری** | هر کاربر حداکثر ۲ فرزند مستقیم (چپ/راست) — بدون محدودیت عمق |
| **کمیسیون هفتگی** | محاسبه بر اساس تعادل چپ/راست — یکشنبه ۰۰:۰۵ (Hangfire cron) |
| **۳ کیف پول** | `Balance` (نقدی) + `NetworkBalance` (طلایی/کمیسیون) + `DiscountBalance` (تخفیفی) |
| **۳ کیف پول** | `Balance` (نقدی) + `NetworkBalance` (طلایی/کمیسیون) + `DiscountBalance` (اعتباری) |
| **کیف‌پول جادویی** | وقتی Balance=0 → حالت Magic فعال → شارژ ×2.5 → سقف 100M/دور |
| **چرخه عضویت** | `ClubMembershipCycle` — هر خرید پکیج = یک دور جدید (برای تاریخ کمیسیون) |
---
@@ -105,6 +107,21 @@ flowchart LR
> فرمت هفته: `YYYY-Www` (شمسی، شنبه‌پایه)
### ۴.۴ فیلتر کاربران Magic از کمیسیون
> ⚠️ **کاربرانی که در حالت Magic هستند (`WalletMode = 1`) از محاسبات کمیسیون هفتگی خارج می‌شوند.**
```
فیلتر در ۳ نقطه:
✅ CalculateWeeklyBalancesCommandHandler.cs → WHERE wallet.WalletMode != Magic
✅ OrmCommissionCalculationStrategy.cs → فیلتر LINQ
✅ sp_CalculateWeeklyBalances.sql → NOT EXISTS (WalletMode=1)
تاریخ محاسبه:
قبل: ClubMembership.ActivatedAt (مشکل: بعد از خرید مجدد overwrite می‌شد)
بعد: ClubMembershipCycle.PackagePurchasedAt (هر دور تاریخ مستقل)
```
---
## ۵. تنظیمات سیستمی (SystemConstants)
@@ -120,6 +137,9 @@ flowchart LR
| `MaxDirectChildrenPerLeg` | 1 | حداکثر فرزند مستقیم هر پا |
| `MinimumWithdrawAmount` | 1,000,000 | حداقل مبلغ برداشت (ریال) |
| `ShopVAT` | 0.1 (10%) | مالیات ارزش افزوده |
| `MagicWalletMultiplier` | 2.5 | ضریب شارژ جادویی (واریز × 2.5) |
| `MagicWalletMaxDeposit` | 1,000,000,000 | سقف واریز هر دور (100M تومان = 1B ریال) |
| `MagicWalletMaxCredit` | 2,500,000,000 | سقف اعتبار هر دور (250M تومان) |
| `CommissionCalculationMethod` | "SP" | روش محاسبه = Stored Procedure |
---
@@ -158,3 +178,59 @@ flowchart TD
| **Retry** | Polly — ۳ تلاش، backoff نمایی |
| **فعال‌سازی** | فقط برای اعضای فعال باشگاه |
| **وضعیت** | ✅ Production ready |
---
## ۹. کیف‌پول جادویی (Magic Wallet) ✅
> **وضعیت: فاز ۱ تا ۵ پیاده‌سازی شده — فاز ۶ باقیمانده**
> **مرجع کامل:** [MAGIC-WALLET-SPEC](../roadmap/MAGIC-WALLET-SPEC.md)
### ۹.۱ چرخه کامل
```mermaid
flowchart TD
A["خرید پکیج 56M\nBalance=56M, Discount=112M"] --> B["خرید از فروشگاه\nBalance کم می‌شود"]
B --> C{"Balance = 0?"}
C -->|خیر| B
C -->|بله| D{"عضو باشگاه فعال؟"}
D -->|خیر| E["حالت عادی باقی بمان"]
D -->|بله| F["🪄 ورود به حالت جادویی\nWalletMode = Magic"]
F --> G["شارژ از درگاه\nواریز × 2.5 = اعتبار Balance"]
G --> H{"Balance=0 AND\nTotalDeposited≥100M?"}
H -->|خیر| G
H -->|بله| I["خروج از جادویی\nWalletMode = Normal"]
I --> J["خرید مجدد پکیج\nفقط IPG — بدون دایا"]
J --> A
```
### ۹.۲ قوانین کلیدی
| قانون | مقدار |
|-------|-------|
| ضریب شارژ | واریز × 2.5 = اعتبار Balance |
| سقف واریز/دور | 100M تومان (1B ریال) |
| سقف اعتبار/دور | 250M تومان (2.5B ریال) |
| کمیسیون در Magic | ❌ غیرفعال |
| شرط خروج | Balance=0 **و** TotalDeposited≥100M (هر دو همزمان) |
| ریست سقف | هر خرید مجدد پکیج → سقف از صفر |
### ۹.۳ Entity‌های جدید
```csharp
// فیلدهای جدید UserWallet
public WalletMode WalletMode { get; set; } // Normal=0, Magic=1
public long MagicTotalDeposited { get; set; } // مجموع واریزی دور فعلی
public long MagicTotalCredited { get; set; } // مجموع اعتبار دریافتی
public DateTime? MagicActivatedAt { get; set; }
public DateTime? MagicCompletedAt { get; set; }
// Entity جدید — حل مشکل تاریخ کمیسیون
public class ClubMembershipCycle {
public long Id { get; set; }
public long ClubMembershipId { get; set; }
public int CycleNumber { get; set; } // شماره دور (1, 2, 3, ...)
public DateTime PackagePurchasedAt { get; set; } // تاریخ خرید این دور
public bool IsCurrentCycle { get; set; } // دور فعلی
}
```
+97 -31
View File
@@ -1,7 +1,7 @@
# 💰 سیستم مالی، پرداخت و درگاه‌ها
> **منابع ادغام‌شده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: تصحیح مدل تومان/ریال + فیکس ZarinPal Verify + امنیت Callback URL)
---
@@ -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
```
@@ -34,10 +34,11 @@ flowchart TD
## ۲. درگاه ZarinPal (IPG)
> **⚠️ محل استفاده:** ZarinPal فقط در سه جا استفاده می‌شود:
> 1. **فروشگاه تخفیفی** — باقیمانده بعد از کسر DiscountBalance (اگر > 0)
> 2. **شارژ کیف‌پول** — واریز مستقیم از پروفایل کاربر
> 3. **خرید پکیج** — (فعلاً غیرفعال: "درگاه پرداخت متصل نیست")
> ** محل استفاده:** ZarinPal در چهار جا فعال است:
> 1. **فروشگاه اعتباری** — باقیمانده بعد از کسر DiscountBalance (اگر > 0)
> 2. **شارژ کیف‌پول اعتباری** — واریز مستقیم از پروفایل کاربر
> 3. **خرید پکیج** — پرداخت مستقیم با کارت بانکی (هر دو شاخه فعال)
> 4. **شارژ کیف‌پول جادویی** — واریز با ضریب ×2.5 (فقط در حالت Magic)
>
> ❌ **فروشگاه عادی (Regular Store) از ZarinPal استفاده نمی‌کند** — فقط کسر از Balance کیف‌پول
@@ -46,21 +47,46 @@ flowchart TD
```mermaid
flowchart TD
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
B --> C["PYMS → ZarinPal API\nدریافت Authority"]
B --> C["PYMS → ZarinPal API\nمبلغ ×۱۰ (تومان→ریال)\nدریافت Authority"]
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
D --> E["بازگشت با Authority\nCMS VerifyPayment"]
D --> E["بازگشت با Authority\nCMS VerifyPayment (مبلغ ×۱۰)"]
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیف‌پول"]
E -->|ناموفق| G["❌ نمایش پیام خطا"]
```
> **✅ فیکس ZarinPal Verify (اسفند ۱۴۰۴ — `721661a`):**
> - **باگ:** `VerifyPaymentAsync(authority)` با ۲ آرگومان → amount=0 → ZarinPal Code=-1
> - **فیکس:** lookup `PaymentTransaction.Amount` از DB + استفاده از overload ۳ آرگومانه `VerifyPaymentAsync(authority, orderId, amount)`
> - `IPaymentGatewayService` — default impl ۳ آرگومانه اضافه شد
> - ۷ فایل تغییر: PackageService, TransactionsService, VerifyDiscountWalletChargeCommandHandler, VerifyPackagePurchaseCommandHandler, IPaymentGatewayService, MockPaymentGatewayService, DayaPaymentService
### ۲.۲ تنظیمات ZarinPal
| پارامتر | مقدار |
|----------|-------|
| `MerchantId` | از appsettings |
| `CallbackUrl` | `/payment/callback` |
| `Sandbox` | true (staging) / false (production) |
| `Currency` | IRR (ریال → تبدیل به تومان در UI) |
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
| `CallbackUrl` | از `appsettings.json` خوانده می‌شود (نه از ورودی کاربر) |
| `Sandbox` | `true` (staging) / `false` (production) |
| `Currency` | DB: تومان — ZarinPal: ریال (×۱۰ هنگام ارسال) |
> **✅ مدل ارزی (تصحیح اسفند ۱۴۰۴):**
> - **DB:** `Package.Price` و همه مبالغ مالی به **تومان** ذخیره می‌شوند
> - **CMS → ZarinPal:** `ZarinPalPaymentService` مبلغ را ×۱۰ می‌کند (`amountInRials = amount * 10`)
> - **FrontOffice UI:** مبالغ مستقیم به تومان نمایش داده می‌شوند (بدون تبدیل)
> - **FrontOffice → CMS:** مبالغ به تومان ارسال می‌شوند (FO هیچ تبدیلی انجام نمی‌دهد)
> - **باگ قبلی ۱:** FO مبلغ تومان را ×۱۰ تبدیل می‌کرد + CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر (فیکس: `2f9ef15`)
> - **باگ قبلی ۲:** `FormattedPrice = Price / 10` اشتباه بود — Price از قبل تومان است (فیکس: `3c1a8ff` اصلاح شد)
> **✅ امنیت Callback URL (اسفند ۱۴۰۴):**
> - هیچ callback URL از ورودی کاربر خوانده نمی‌شود — همه از `appsettings.json` خوانده می‌شوند
> - `PackageService` و `TransactionsService`: از `FrontOfficeBaseUrl` config
> - `MagicWallet` و `DiscountWallet`: از `CmsBaseUrl` config
> - جلوگیری از حمله Open Redirect
> **تنظیمات محیطی:**
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
> - `appsettings.Production.json`: `UseSandbox: false` (واقعی)
> - Production URL: `cms.kbs1.ir` | FrontOffice GwUrl: `cms.kbs2.ir`
---
@@ -123,7 +149,7 @@ flowchart TD
---
## ۵. پرداخت ترکیبی فروشگاه تخفیفی (Hybrid Payment)
## ۵. پرداخت ترکیبی فروشگاه اعتباری (Hybrid Payment)
### ۵.۱ فرمول
@@ -131,7 +157,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
@@ -139,27 +169,44 @@ MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخص
ℹ️ تخفیف ثابت ۳۰% نیست — فیلد Product.MaxDiscountPercent (0-100) تعیین‌کننده است.
```
### ۵.۲ فلوی خرید فروشگاه تخفیفی
### ۵.۲ فلوی خرید فروشگاه اعتباری
```mermaid
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باقیمانده + 9% VAT"]
F -->|بله| G["کسر DiscountBalance\n+ Redirect → ZarinPal IPG\nباقیمانده + 10% VAT"]
F -->|خیر| H["فقط کسر از DiscountBalance\nبدون درگاه → ثبت مستقیم"]
G --> LOG["ثبت UserWalletChangeLog"]
H --> LOG
```
### ۵.۳ دسترسی فروشگاه تخفیفی
### ۵.۳ دسترسی فروشگاه اعتباری
| شرط | نتیجه |
|------|--------|
| `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` |
---
@@ -182,25 +229,26 @@ service PaymentService {
|-----|-----|--------|
| PackagePurchase | 1 | خرید پکیج طلایی |
| StorePurchase | 2 | خرید از فروشگاه |
| DiscountStorePurchase | 3 | خرید از فروشگاه تخفیفی |
| DiscountStorePurchase | 3 | خرید از فروشگاه اعتباری |
| CommissionPayout | 4 | واریز کمیسیون هفتگی |
| WalletCharge | 5 | شارژ مستقیم کیف‌پول |
| ActivationFee | 6 | هزینه فعالسازی |
| DayaLoanCharge | 7 | شارژ از وام دایا |
| MagicWalletDeposit | 14 | واریز به کیف‌پول جادویی |
| MagicWalletBonus | 15 | بونوس ضریب ×2.5 کیف‌پول جادویی |
---
## ۷. مالیات و VAT
```
هر دو فروشگاه از نرخ 9% استفاده می‌کنند:
هر دو فروشگاه از نرخ 10% استفاده می‌کنند:
Regular Store → const vatRate = 0.09m (hardcoded در SubmitShopBuyOrderCommandHandler)
Discount Store → VatCalculator.VAT_RATE = 0.09m
Regular Store → const vatRate = 0.10m (hardcoded در SubmitShopBuyOrderCommandHandler)
Discount Store → VatCalculator.VAT_RATE = 0.10m
SystemConstants.ShopVAT = 0.1 (10%)
⚠️ SystemConstants.ShopVAT = 0.1 (10%) — تعریف‌شده ولی استفاده نمی‌شود (stale constant)
قیمت نمایشی = قیمت پایه × (1 + 0.09)
قیمت نمایشی = قیمت پایه × (1 + 0.10)
در صورتحساب: قیمت پایه + مالیات جداگانه نمایش داده می‌شود
```
@@ -210,9 +258,27 @@ service PaymentService {
| ماژول | وضعیت | یادداشت |
|-------|--------|---------|
| ZarinPal IPG | ✅ کامل | Production ready |
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
| ZarinPal Verify | ✅ فیکس شده | رفع amount=0 با overload ۳ آرگومانه (`721661a`) |
| Callback URL امنیت | ✅ فیکس شده | همه از config خوانده می‌شوند — جلوگیری از Open Redirect |
| وام دایا | ✅ کامل | Mock mode فعال در staging |
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
| Pool هفتگی | ✅ کامل | SP + Hangfire |
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیف‌پول در ۳ هندلر اضافه شد |
| Validation کیف‌پول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
| Toman/Rial مدل | ✅ تصحیح شده | DB=تومان، فقط ZarinPal ریال (×۱۰) — FO بدون تبدیل |
| صفحه موفقیت پرداخت | ✅ بهبود | TransactionId + موجودی واقعی + دکمه بازگشت |
| PackagePurchaseDialog | ✅ کامل | دیالوگ داینامیک کاشی‌ای با انتخاب روش پرداخت |
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
| Refund | ⬜ طراحی | فقط در PYMS تعریف‌شده |
| کیف‌پول جادویی (Magic) | ✅ کامل | فاز 1-6 پیاده‌سازی شده — Production فعال |
### ۸.۱ نام‌گذاری استاندارد کیف‌پول‌ها (اسفند ۱۴۰۴)
| فیلد دیتابیس | نام قدیم (UI) | نام جدید (UI) |
|-------------|--------------|---------------|
| `Balance` | عادی / اعتباری / نقدی | **کیف پول اصلی** |
| `DiscountBalance` | تخفیفی / تخفیف | **کیف پول اعتباری** |
| `NetworkBalance` | شبکه / طلایی / پورسانت | **پاداش تیمی** |
> تغییرات UI در ۱۲ فایل (FrontOffice: 5, BackOffice: 7) اعمال شد.
+45 -6
View File
@@ -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`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: ExpirePendingOrders ۱۵ دقیقه + فروشگاه اعتباری نام‌گذاری)
---
@@ -13,14 +13,14 @@ flowchart LR
R1["همه کاربران"]
R2["پرداخت از Balance کیف‌پول"]
R3["قیمت عادی"]
R4["VAT = 9%"]
R4["VAT = 10%"]
end
subgraph DS["Discount Store — /discount-store"]
D1["فقط اعضای باشگاه"]
D2["پرداخت ترکیبی تخفیف+نقد"]
D3["تخفیف بر اساس MaxDiscountPercent"]
D4["VAT = 9%"]
D4["VAT = 10%"]
end
subgraph SHARED["مشترک"]
@@ -96,7 +96,7 @@ public class Inventory {
}
// فیلد کلیدی در Product:
public int MaxDiscountPercent { get; set; } // 0 تا 100 — درصد تخفیف در فروشگاه تخفیفی
public int MaxDiscountPercent { get; set; } // 0 تا 100 — درصد تخفیف در فروشگاه اعتباری
```
### ۳.۳ فلوی سفارش و موجودی
@@ -188,7 +188,7 @@ flowchart TD
/store/cart → سبد خرید
/store/checkout → پرداخت
فروشگاه تخفیفی:
فروشگاه اعتباری:
/discount-store → لیست محصولات
/discount-store/product/{id} → جزئیات
/discount-store/cart → سبد (ترکیبی)
@@ -222,9 +222,48 @@ graph TD
| ماژول | وضعیت | درصد |
|-------|--------|------|
| فروشگاه عادی | ✅ کامل | 100% |
| فروشگاه تخفیفی | ✅ کامل | 100% |
| فروشگاه اعتباری | ✅ کامل | 100% |
| 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<ExpirePendingOrdersService>();
```
+8 -4
View File
@@ -1,7 +1,7 @@
# 👤 سفر کاربر، ثبت‌نام و چرخه عضویت
> **منابع ادغام‌شده:** `club-membership-contract-system.md`, `REGISTRATION-FLOW-FIXES.md`, `chatika-integration.md`, `club-feature-management-services.md`, `ADMIN-CUSTOMER-SEPARATION-FIX.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet cycle)
---
@@ -19,7 +19,11 @@ flowchart TD
F --> G["امضای قرارداد OTP"]
G --> H["فعالسازی 25.2M"]
H --> I["عضویت درخت باینری"]
I --> J["دسترسی فروشگاه تخفیفی\nفیچرهای باشگاه\nکمیسیون هفتگی"]
I --> J["دسترسی فروشگاه اعتباری\nفیچرهای باشگاه\nکمیسیون هفتگی"]
J --> K{"Balance = 0?"}
K -->|بله| L["🪄 کیف‌پول جادویی\nشارژ ×2.5 از درگاه"]
L --> M["خروج از Magic\nخرید مجدد پکیج"]
M --> F
```
---
@@ -135,7 +139,7 @@ public interface IClubFeatureService {
| کد فیچر | نام | توضیح | وضعیت |
|----------|------|--------|--------|
| `DISCOUNT_STORE` | فروشگاه تخفیفی | دسترسی به فروشگاه ۳۰% تخفیف | ✅ فعال |
| `DISCOUNT_STORE` | فروشگاه اعتباری | دسترسی به فروشگاه اعتباری | ✅ فعال |
| `CHATIKA_AI` | چاتیکا | مشاوره هوش مصنوعی | ✅ فعال |
| `COMMISSION` | کمیسیون | دریافت کمیسیون هفتگی | ✅ فعال |
| `NETWORK_VIEW` | نمای شبکه | مشاهده درخت باینری | ✅ فعال |
@@ -161,7 +165,7 @@ public class UserClubFeature {
```csharp
// صفحه اصلی — مسیردهی هوشمند
if (IsAuthenticated && IsClubMember)
نمایش داشبورد باشگاه + فروشگاه تخفیفی
نمایش داشبورد باشگاه + فروشگاه اعتباری
else if (IsAuthenticated)
نمایش فروشگاه عادی + پروفایل
else
+5 -2
View File
@@ -1,7 +1,7 @@
# 📄 محتوا، صفحات، بلاگ و ایمیل/SMS
> **منابع ادغام‌شده:** `SITE-PAGES-SIMPLIFICATION.md`, `system-constants.md`, `email-sms-configuration.md`, `chatika-integration.md`, `CMS-README.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet + VAT 10%)
---
@@ -178,7 +178,10 @@ CREATE TABLE SystemConfigurations (
| Club | `BasePackageAmount` | 56000000 | قیمت پکیج طلایی (ریال) |
| Club | `CommissionMaxNetworkLevel` | 15 | عمق محاسبه کمیسیون |
| Club | `CommissionMaxWeeklyBalancesPerLeg` | 300 | سقف هفتگی |
| Payment | `ShopVAT` | 0.1 (10%) | مالیات (فروشگاه تخفیفی: 9%) |
| Payment | `ShopVAT` | 0.1 (10%) | مالیات ارزش افزوده (هر دو فروشگاه) |
| Magic | `MagicWalletMultiplier` | 2.5 | ضریب شارژ جادویی |
| Magic | `MagicWalletMaxDeposit` | 1,000,000,000 | سقف واریز/دور (100M تومان) |
| Magic | `MagicWalletMaxCredit` | 2,500,000,000 | سقف اعتبار/دور (250M تومان) |
| Payment | `DayaLoanAmount` | 56000000 | مبلغ وام (ریال) |
| Payment | `MinimumWithdrawAmount` | 1000000 | حداقل برداشت (ریال) |
| Store | `MaxDiscountPercent` | per-product | 0-100، هر محصول جداگانه |
+219
View File
@@ -0,0 +1,219 @@
# آدیت سرویس‌های gRPC — CMS
> تاریخ: ۱۴۰۴/۰۴
> آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶
> هدف: شناسایی RPCهایی که از هیچ‌کدام از فرانت‌ها (FrontOffice مشتری + BackOffice ادمین) فراخوانی نمی‌شوند + تصمیم‌گیری نگهداری vs آرشیو
---
## 📊 خلاصه آمار
| متریک | تعداد |
|--------|-------|
| کل فایل‌های proto | 43 (بدون google/) |
| کل سرویس‌های gRPC | 42 |
| **کل RPC متدها** | **342** |
| استفاده‌شده در FrontOffice | 92 |
| استفاده‌شده در BackOffice | 159 |
| **استفاده‌شده (مجموع یکتا)** | **217** |
| **استفاده‌نشده از فرانت‌ها** | **125** |
| ↳ استفاده‌شده داخلی CMS | 101 |
| ↳ **کد مُرده واقعی** | **24** |
---
## 🔴 بخش ۱ — تحلیل ۲۴ RPC مُرده: نگهداری vs آرشیو
### ✅ نگهداری (آینده‌نگرانه — ۱۲ عدد)
> این RPCها پیاده‌سازی کامل دارند و در نقشه‌راه آینده محصول کاربرد دارند.
| # | RPC | فایل Proto | کیفیت کد | دلیل نگهداری |
|---|-----|-----------|---------|-------------|
| 1 | `CustomerReorderPreviousOrder` | userorder.proto | ✅ **کامل** — آیتم‌های سفارش قبلی به سبد اضافه می‌شود | UX حیاتی: «تکرار سفارش قبلی» — فیچر رایج فروشگاهی، فقط نیاز به دکمه در FrontOffice |
| 2 | `CustomerTrackOrder` | userorder.proto | ✅ **کامل** — وضعیت + TrackingCode + DeliveryInfo | UX حیاتی: «ردیابی سفارش» — وقتی ارسال پستی فعال شود ضروری است |
| 3 | `CalculateOrderPV` | userorder.proto | ✅ **کامل** — PV هر آیتم + جمع کل | سیستم MLM: محاسبه PV (Point Value) سفارش — برای فاز بعدی کمیسیون بر اساس خرید |
| 4 | `GetInventorySummary` | inventory.proto | ✅ **کامل** — آمار تعداد + ارزش کل | داشبورد ادمین: خلاصه موجودی انبار — نیاز به کارت در BackOffice Dashboard |
| 5 | `GetStockValueReport` | inventory.proto | ✅ **کامل** — گزارش ارزش ریالی موجودی | گزارش مالی: ارزش دارایی انبار — برای حسابداری ضروری |
| 6 | `BulkAddStock` | inventory.proto | ✅ **کامل** — loop با error handling | عملیات انبوه: افزودن موجودی دسته‌ای — بعد از ورود کالای فیزیکی |
| 7 | `BulkUpdateProductStock` | products.proto | ✅ **کامل** — Set/Add/Subtract با error handling | عملیات انبوه: بروزرسانی دسته‌ای موجودی محصول |
| 8 | `GetConfigurationByKey` | configuration.proto | ✅ **کامل** — خواندن از SystemConstants | API مفید: دریافت یک تنظیم خاص بدون بارگذاری همه — performance بهتر |
| 9 | `UpdateCustomerSettings` | user.proto | ✅ **کامل** — Email/SMS/Push notifications | تنظیمات اعلان‌ها: وقتی پنل تنظیمات مشتری ساخته شود |
| 10 | `ChangeNetworkParent` | networkmembership.proto | ✅ **CQRS کامل** → MoveInNetworkCommand | مدیریت شبکه: جابجایی کاربر در درخت — ابزار ادمین ضروری |
| 11 | `AssignFeatureToMembership` | clubmembership.proto | ✅ **CQRS کامل** → AssignClubFeatureCommand | مدیریت عضویت: اختصاص فیچر به عضویت — برای فاز بسته‌بندی پویا |
| 12 | `GetLowStockProducts` | products.proto | ✅ **کامل** — فیلتر threshold + pagination | هشدار موجودی: مکمل GetLowStockItems — فیلتر ClubExclusive اضافه دارد |
### 🗑️ آرشیو (حذف امن — ۱۲ عدد)
> این RPCها یا stub خالی هستند، یا جایگزین بهتری دارند، یا هرگز ساخته نشدند.
| # | RPC | فایل Proto | وضعیت کد | دلیل آرشیو |
|---|-----|-----------|---------|-----------|
| 1 | `CreateNewFileInfo` | fms.proto | ❌ **۰ رفرنس** — هیچ Service/Handler ندارد | سرویس FMS هرگز طراحی نشد — فایل‌ها از imageresolver استفاده می‌کنند |
| 2 | `DeleteFileInfo` | fms.proto | ❌ **۰ رفرنس** — هیچ Service/Handler ندارد | همان — کل fms.proto حذف‌شدنی |
| 3 | `BulkAdjustStock` | inventory.proto | ❌ **۰ رفرنس** — حتی Service method ندارد | هرگز پیاده‌سازی نشد — از AdjustStock تکی استفاده می‌شود |
| 4 | `CreateNewOrderForCustomer` | userorder.proto | ❌ **Stub خالی**`return new()` | مسیر سفارش مشتری از SubmitShopBuyOrder می‌گذرد — تکراری |
| 5 | `SubmitOrderForCustomer` | userorder.proto | ❌ **Stub خالی**`return new()` | مسیر سفارش مشتری از SubmitShopBuyOrder می‌گذرد — تکراری |
| 6 | `DeactivateConfiguration` | configuration.proto | ❌ **throw میکند** — «تنظیمات فقط خواندنی هستند» | عمداً غیرفعال شده — SystemConstants ثابت هستند |
| 7 | `GetConfigurationHistory` | configuration.proto | ❌ **خالی برمی‌گرداند**`return new()` | SystemConstants تاریخچه ندارند — بی‌معنی |
| 8 | `DeleteCity` | City.proto | ✅ کامل ولی **بی‌نیاز** | شهرها seed دیتا هستند — حذف شهر باعث خرابی آدرس‌ها می‌شود |
| 9 | `UpdateCity` | City.proto | ✅ کامل ولی **بی‌نیاز** | شهرها از سرویس خارجی seed شده‌اند — ویرایش دستی نیاز نیست |
| 10 | `GetOrdersByDateRange` | userorder.proto | ✅ کامل ولی **تکراری** | `GetAllUserOrderByFilter` همین قابلیت + فیلترهای بیشتر دارد |
| 11 | `GetServiceHealth` | health.proto | ✅ کامل ولی **تکراری** | `GetSystemHealth` کل سیستم را برمی‌گرداند — فیلتر client-side کافی است |
| 12 | `GetCategoryByIdForCustomer` | category.proto | ✅ کامل ولی **تکراری** | `GetCategory` (admin) + `GetAllCategoriesForCustomer` کافی است |
---
## 🟡 بخش ۲ — استفاده داخلی CMS (Internal Only — ۱۰۱ عدد)
> این RPCها از فرانت‌ها فراخوانی نمی‌شوند ولی **در کد بکند CMS فعال هستند** (background services, handlers, internal flows). **حذف نشوند!**
### B1. احراز هویت و کاربر (user.proto)
| RPC | رفرنس CMS | علت |
|-----|-----------|-----|
| `CreateNewUser` | 175 | ثبت‌نام کاربر — اصلی‌ترین فلو |
| `GetJwtToken` | 38 | صدور توکن JWT |
| `AdminGetJwtToken` | 19 | لاگین ادمین |
| `SetPasswordForUser` | 24 | تنظیم رمز عبور |
| `ChangeCustomerPassword` | 5 | تغییر رمز مشتری |
| `UploadCustomerAvatar` | 6 | آپلود آواتار |
| `GetCustomerProfile` | 13 | پروفایل مشتری |
| `GetCustomerReferrals` | 13 | لیست معرفی‌شدگان |
| `GetCustomerSettings` | 13 | تنظیمات مشتری |
### B2. بسته‌ها و پرداخت (package.proto / manualpayment.proto)
| RPC | رفرنس CMS | علت |
|-----|-----------|-----|
| `PurchaseGoldenPackage` | 21 | خرید بسته طلایی — فلو فعال |
| `VerifyGoldenPackagePurchase` | 22 | تأیید خرید بسته طلایی |
| `CustomerPurchasePackage` | 3 | خرید مشتری (proto-generated + service) |
| `CustomerVerifyPackagePurchase` | 3 | تأیید خرید مشتری |
| `GetCustomerPurchaseHistory` | 13 | تاریخچه خرید |
| `ProcessManualMembershipPayment` | 20 | پرداخت دستی عضویت |
### B3. شبکه و عضویت (networkmembership.proto / clubmembership.proto)
| RPC | رفرنس CMS | علت |
|-----|-----------|-----|
| `JoinNetwork` | 14 | پیوستن به شبکه — فراخوانی خودکار |
| `RemoveFromNetwork` | 14 | حذف از شبکه |
### B4. کمیسیون (commission.proto)
| RPC | رفرنس CMS | علت |
|-----|-----------|-----|
| `CalculateWeeklyBalances` | 26 | سرویس پس‌زمینه هفتگی |
| `CalculateWeeklyCommissionPool` | 22 | سرویس پس‌زمینه هفتگی |
| `ProcessUserPayouts` | 15 | پردازش پرداخت‌ها |
| `GetCommissionPayoutHistory` | 20 | تاریخچه پرداخت کمیسیون |
### B5. انبارداری (inventory.proto)
| RPC | رفرنس CMS | علت |
|-----|-----------|-----|
| `ConfirmSale` | 10 | تأیید فروش — فلو سفارش |
| `ReserveStock` | 12 | رزرو موجودی — فلو سفارش |
| `ReleaseReservation` | 10 | آزادسازی رزرو |
| `ProcessReturn` | 6 | پردازش مرجوعی |
| `DeleteWarehouse` | 18 | حذف انبار |
| `GetInventoryByProduct` | 26 | موجودی بر اساس محصول |
| `GetInventoryItem` | 19 | آیتم انبار |
| `GetWarehouse` | 18 | دریافت انبار |
| `SetDefaultWarehouse` | 18 | تنظیم انبار پیش‌فرض |
| `UpdateWarehouse` | 18 | بروزرسانی انبار |
| `GetStockMovementsByInventoryItem` | 17 | حرکات موجودی |
### B6. تراکنش‌ها (transactions.proto)
| RPC | رفرنس CMS | علت |
|-----|-----------|-----|
| `CreateNewTransactions` | 26 | ایجاد تراکنش — فلو پرداخت |
| `DeleteTransactions` | 23 | حذف تراکنش |
| `GetAllTransactionsByFilter` | 25 | لیست تراکنش‌ها |
| `CustomerPaymentVerification` | 3 | تأیید پرداخت مشتری |
| `GetCustomerTransaction` | 27 | تراکنش مشتری |
| `GetCustomerTransactionsByFilter` | 14 | فیلتر تراکنش‌ها |
| `RefundTransaction` | 31 | استرداد تراکنش |
| `UpdateTransactions` | 23 | بروزرسانی تراکنش |
| `VerifyTransaction` | 24 | تأیید تراکنش |
### B7. سایر CRUD داخلی (خلاصه)
> ۵۸ RPC در فایل‌های contract, usercontract, factordetails, productcategory, productgalleries, productimages, userwallet, userwalletchangelog, otptoken, usercarts, discountproduct, public_messages, category, products, producttag, tag, City, useraddress, userorder — همه CRUD داخلی با ≥3 رفرنس در CMS.
---
## 🟢 بخش ۳ — سرویس‌های کاملاً مورد استفاده
### فایل‌های proto که تمام RPCهایشان استفاده می‌شود:
| فایل Proto | کل RPC | استفاده FO | استفاده BO |
|-----------|--------|-----------|-----------|
| appversion.proto | 3 | ✅ 1 | ✅ 3 |
| blogcategory.proto | 6 | ✅ 2 | ✅ 6 |
| blogpost.proto | 11 | ✅ 5 | ✅ 9 |
| blogpostimage.proto | 4 | ✅ 0 | ✅ 4 |
| discountcategory.proto | 4 | ✅ 1 | ✅ 4 |
| discountorder.proto | 7 | ✅ 3 | ✅ 4 |
| discountshoppingcart.proto | 5 | ✅ 5 | ✅ 0 |
| manualpayment.proto | 5 | ✅ 0 | ✅ 4 |
| public_messages.proto | 8 | ✅ 0 | ✅ 7 |
| role.proto | 5 | ✅ 0 | ✅ 5 |
| sitepage.proto | 10 | ✅ 2 | ✅ 10 |
| sitepagesettings.proto | 5 | ✅ 1 | ✅ 5 |
| tag.proto | 6 | ✅ 0 | ✅ 5 |
| userrole.proto | 5 | ✅ 0 | ✅ 5 |
---
## 📋 بخش ۴ — خلاصه تصمیمات
### ماتریکس نهایی ۲۴ RPC مُرده
```
✅ نگهداری (12): CustomerReorderPreviousOrder, CustomerTrackOrder,
CalculateOrderPV, GetInventorySummary, GetStockValueReport,
BulkAddStock, BulkUpdateProductStock, GetConfigurationByKey,
UpdateCustomerSettings, ChangeNetworkParent,
AssignFeatureToMembership, GetLowStockProducts
🗑️ آرشیو (12): CreateNewFileInfo, DeleteFileInfo, BulkAdjustStock,
CreateNewOrderForCustomer, SubmitOrderForCustomer,
DeactivateConfiguration, GetConfigurationHistory,
DeleteCity, UpdateCity, GetOrdersByDateRange,
GetServiceHealth, GetCategoryByIdForCustomer
```
### فایل‌های proto آرشیو‌شدنی (کامل)
| فایل | وضعیت | اقدام |
|------|-------|-------|
| **fms.proto** | کل فایل مُرده (2 RPC) | حذف از csproj — ساخته نشود |
### RPCهای آرشیو‌شدنی (جزئی — داخل فایل‌های فعال)
| فایل Proto | RPCهای آرشیو | RPCهای فعال |
|-----------|-------------|-------------|
| inventory.proto | `BulkAdjustStock` (1) | 23 فعال |
| userorder.proto | `CreateNewOrderForCustomer`, `SubmitOrderForCustomer` (2) | 18 فعال |
| configuration.proto | `DeactivateConfiguration`, `GetConfigurationHistory` (2) | 5 فعال |
| City.proto | `DeleteCity`, `UpdateCity` (2) | 6 فعال |
| health.proto | `GetServiceHealth` (1) | 1 فعال |
| category.proto | `GetCategoryByIdForCustomer` (1) | 7 فعال |
---
## ⚠️ نکات مهم
1. **آرشیو ≠ حذف!** — RPCهای آرشیو‌شده با `[Obsolete]` + `#region [ARCHIVED]` علامت‌گذاری شدند (کامیت `13dd0f5`)
2. RPCهای دسته B (Internal — ۱۰۱ عدد) **حیاتی** هستند — بدون آنها سیستم از کار می‌افتد
3. RPCهای «نگهداری» (۱۲ عدد) کد **کامل و آماده** دارند — بکلاگ فیچر: [FEATURE-BACKLOG.md](../roadmap/FEATURE-BACKLOG.md)
4. **fms.proto** از csproj اکسکلود شد (کامیت `13dd0f5`)
5. نقشه‌راه تحول پکیج‌بیس: [PACKAGE-TRANSFORMATION-TASKS.md](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md)
6. قبل از هر تغییر، حتماً `grep -rn "RpcName" CMS/src/` بزنید تا مطمئن شوید
---
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶*
+700
View File
@@ -0,0 +1,700 @@
# 📦 راهنمای مهاجرت سیستم پکیج‌بیس — خلاصه تغییرات و پلن استقرار
> **وضعیت:** آماده تست و استقرار — **Q1-Q30 تکمیل‌شده ✅ | F1-F11 تکمیل‌شده ✅**
> **تاریخ:** ۸ اسفند ۱۴۰۴ (27 Feb 2026) — آپدیت ۱۰ اسفند
> **نسخه NuGet:** v0.0.189
> **تعداد کامیت‌ها:** ۵۱+ کامیت در ۴ ریپازیتوری (۲۱ CMS + ۹ FO + ۷ BO + ۱۴+ docs)
> **مدت پیاده‌سازی:** ۶ روز (۲۴ فوریه – ۱ مارس ۲۰۲۶)
> **ریپوها:** CMS (`gitea`/`kub-stage`) · FrontOffice (`kub-stage`) · BackOffice (`kub-stage`) · totalDoc (`foursatDocs`/`main`)
---
## فهرست مطالب
1. [خلاصه اجرایی](#1-خلاصه-اجرایی)
2. [چه چیزی تغییر کرده؟ — نمای بیزینسی](#2-چه-چیزی-تغییر-کرده--نمای-بیزینسی)
3. [بخش‌های تحت تاثیر سیستم](#3-بخشهای-تحت-تاثیر-سیستم)
4. [جزئیات تغییرات هر ریپو](#4-جزئیات-تغییرات-هر-ریپو)
5. [پلن مهاجرت مرحله‌به‌مرحله](#5-پلن-مهاجرت-مرحلهبهمرحله)
6. [Rollback Plan](#6-rollback-plan)
7. [چک‌لیست تست قبل از Production](#7-چکلیست-تست-قبل-از-production)
8. [ریسک‌ها و نکات بحرانی](#8-ریسکها-و-نکات-بحرانی)
---
## 1. خلاصه اجرایی
### قبل (سیستم تک‌پکیج):
- فقط **یک پکیج پایه** (۵۶ میلیون تومان) وجود داشت
- تمام مقادیر مالی (قیمت، هزینه فعال‌سازی، ضرایب، سقف‌ها) **hardcoded** در کد بودند
- خرید مجدد پکیج **غیرممکن** بود (حتی بعد تکمیل چرخه)
- پورسانت فقط از **یک Pool واحد** محاسبه می‌شد
- همه کاربران **همه فیچرها** را دریافت می‌کردند
### بعد (سیستم چندپکیجی):
- سیستم **N پکیج** با قیمت و ویژگی‌های متفاوت پشتیبانی می‌کند
- تمام مقادیر مالی از **دیتابیس (Package entity)** خوانده می‌شوند
- خرید مجدد بعد تکمیل چرخه Magic Wallet **فعال** شده
- هر پکیج **Commission Pool مستقل** خود را دارد
- فیچرها **per-package** هستند و با الگوریتم **DIFF** مدیریت می‌شوند
- قرارداد باشگاه **فقط یک بار** (اولین خرید) امضا می‌شود
### آمار تغییرات:
| شاخص | مقدار |
|-------|-------|
| فایل‌های تغییریافته | **۲۲۷+ فایل** |
| خطوط اضافه‌شده | **+۱۷,۰۰۰+** |
| خطوط حذف‌شده | **−۲,۶۶۰+** |
| تصمیمات بیزینسی پیاده‌شده | **۳۰ تصمیم** (Q1Q30) |
| باگ‌های فیکس‌شده | **۶ باگ بحرانی** |
| مقادیر hardcoded حذف‌شده | **۱۵+ مورد** |
| Handlerهای deprecated حذف‌شده | **۴ handler** (۱۲ فایل) |
| RPCهای deprecated حذف‌شده | **۴ RPC** + ۸ message type |
| فایل‌های rename شده | **۳۴ فایل** + ۱۱ دایرکتوری (UserWalletChangeLog → UserWalletHistory) |
| History Tables جدید | **۳ جدول** (PackageHistories, ClubMembershipCycleHistories, UserWalletHistories) |
---
## 2. چه چیزی تغییر کرده؟ — نمای بیزینسی
### 2.1 🏪 مدل فروش پکیج
| قابلیت | قبل | بعد |
|--------|-----|------|
| تعداد پکیج | ۱ (پایه ۵۶M) | **N پکیج** (پایه ۵۶M + نقره‌ای ۵.۶M + ...) |
| قیمت‌گذاری | hardcoded `56_000_000` | از `Package.Price` در دیتابیس |
| هزینه فعال‌سازی | hardcoded `25_200_000` | از `Package.ActivationFee` |
| ضریب تخفیف | hardcoded `× 2` | از `Package.DiscountMultiplier` |
| پشتیبانی دایا | فقط پکیج پایه | بر اساس `Package.SupportsDayaPurchase` |
| پرداخت مستقیم | همه | بر اساس `Package.SupportsDirectPurchase` |
### 2.2 🔄 چرخه خرید مجدد (Re-Purchase)
| مرحله | قبل | بعد |
|-------|-----|------|
| تکمیل چرخه Magic | کاربر در بن‌بست | `PackagePurchaseMethod = None` ریست می‌شود |
| خرید مجدد | **مسدود** (guard G1-G3) | **مجاز** — بعد تکمیل چرخه Magic |
| قرارداد باشگاه | هر بار | **فقط یک بار** — خرید مجدد Skip (Q19) |
| فیچرها | همه فیچرها بدون توجه به پکیج | **DIFF/تفاضل** — فقط اختلاف اعمال می‌شود (Q20) |
| تاریخچه | فقط `ActivatedAt` | `FirstActivationDate` + `LastActivationDate` (Q21) |
### 2.3 💰 پورسانت و تعادل‌ها
| ویژگی | قبل | بعد |
|-------|-----|------|
| Commission Pool | ۱ Pool واحد | **Pool جداگانه هر پکیج** |
| تعادل هفتگی | ۱ رکورد per user/week | **N رکورد** per user/week/package |
| MaxBalancesPerLeg | hardcoded `300` | per-package (پایه=۳۰۰, نقره‌ای=۳۰) |
| MaxNetworkLevel | hardcoded `15` | per-package از دیتابیس |
| Carryover | یک‌پارچه | **per-downline-package** — بر اساس پکیج زیرمجموعه‌ها (تغییر پکیج خود کاربر تاثیری ندارد) |
| Stored Procedure | پارامترهای ثابت | پارامترهای داینامیک از Package entity |
| گزارش مشتری | بدون تفکیک | **breakdown per-package** |
| گزارش ادمین | بدون فیلتر | **فیلتر بر اساس پکیج** |
### 2.4 🪄 کیف پول جادویی (Magic Wallet)
| ویژگی | قبل | بعد |
|-------|-----|------|
| ضریب جادویی | hardcoded `× 2.5` | از `Package.MagicWalletMultiplier` |
| سقف واریز | hardcoded `1,000,000,000` | از `Package.MagicWalletMaxDeposit` |
| سقف اعتبار | hardcoded `2,500,000,000` | از `Package.MagicWalletMaxCredit` |
| شرط EXIT | بررسی سقف global | بررسی سقف **per-package** |
### 2.5 📋 فیچرهای باشگاه
| ویژگی | قبل | بعد |
|-------|-----|------|
| تخصیص فیچر | `GetAllFeatureIds()` — همه فیچرها | از `Package.PackageFeatures` — per-package |
| خرید مجدد | — | الگوریتم **DIFF**: مقایسه فیچرهای فعلی با پکیج جدید |
| مدیریت ادمین | — | ماتریس checkbox پکیج × فیچر در BackOffice |
---
## 3. بخش‌های تحت تاثیر سیستم
### 3.1 نقشه تاثیرگذاری
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 🏗️ سیستم پکیج‌بیس — Impact Map │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─── CMS (Backend) ──────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 📦 Domain Layer (Entity تغییرات) │ │
│ │ ├── Package.cs ← +۱۱ فیلد جدید │ │
│ │ ├── PackageFeature.cs ← Entity کاملاً جدید │ │
│ │ ├── ClubMembership.cs ← ActivatedAt → ۴ فیلد First/Last │ │
│ │ ├── ClubMembershipCycle.cs ← +PackageId │ │
│ │ ├── WeeklyCommissionPool.cs ← +PackageId │ │
│ │ ├── UserCommissionPayout.cs ← +PackageId │ │
│ │ ├── NetworkWeeklyBalance.cs ← +PackageId │ │
│ │ └── SystemConstants.cs ← حذف ۹ ثابت منسوخ │ │
│ │ │ │
│ │ ⚙️ Application Layer (Handler تغییرات) │ │
│ │ ├── ActivateClubMembershipCommandHandler ← فیچر DIFF + re-activate│ │
│ │ ├── AcceptClubMembershipContractCommandHandler ← فیچر DIFF │ │
│ │ ├── VerifyPackagePurchaseCommandHandler ← حذف fallback 2.0m │ │
│ │ ├── CustomerPurchasePackage/Verify ← Generic purchase flow │ │
│ │ ├── ChargeMagicWalletCommandHandler ← سقف per-package │ │
│ │ ├── VerifyMagicWalletChargeCommandHandler ← ضریب per-package │ │
│ │ ├── UserOrderService (EXIT Magic) ← ریست + سقف per-package │ │
│ │ ├── CreateManualPaymentCommandHandler ← ضریب از Package │ │
│ │ └── CheckAndProcessDayaLoansCommandHandler ← حذف ID=4 │ │
│ │ │ │
│ │ 🔌 Infrastructure Layer │ │
│ │ ├── sp_CalculateWeeklyBalances ← @PackageId + @Max params │ │
│ │ ├── sp_CalculateWeeklyCommissionPool ← @PackageId │ │
│ │ ├── WeeklyCommissionCalculationService ← Loop per-package │ │
│ │ ├── OrmCommissionCalculationStrategy ← فیلتر PackageId │ │
│ │ └── SpCommissionCalculationStrategy ← پارامترهای داینامیک │ │
│ │ │ │
│ │ 📡 Proto/gRPC Layer │ │
│ │ ├── package.proto ← ۱۱ فیلد + PackageFeature CRUD │ │
│ │ ├── commission.proto ← package_id/title در ۴ model + فیلتر │ │
│ │ ├── حذف ۴ RPC deprecated (Golden/Base) │ │
│ │ └── حذف ۸ message type deprecated │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── FrontOffice (مشتری) ─────────────────────────────────────────────┐│
│ │ ├── Packages.razor ← کاشی‌های داینامیک (نه hardcoded) ││
│ │ ├── PackageDetail.razor ← فیچرها از API (نه ثابت) ││
│ │ ├── Checkout.razor ← پرداخت شرطی (دایا/مستقیم) ││
│ │ ├── MyPackages.razor ← خرید مجدد + پیشرفت Magic ││
│ │ ├── ActivationSection.razor ← قیمت داینامیک (نه ۵۶M hardcoded) ││
│ │ ├── ClubMembershipContractDialog ← متن قرارداد داینامیک ││
│ │ ├── CommissionDashboard ← فیلتر + ستون پکیج ││
│ │ ├── WeeklyBalancePage ← فیلتر per-package ││
│ │ ├── PaymentCallback ← مهاجرت به Customer* RPCs ││
│ │ └── حذف "پکیج طلایی" hardcoded (۵+ جا) ││
│ └─────────────────────────────────────────────────────────────────────┘│
│ │
│ ┌─── BackOffice (ادمین) ──────────────────────────────────────────────┐│
│ │ ├── Package CRUD ← +۱۲ فیلد جدید در Create/Update ││
│ │ ├── PackageFeature Matrix ← checkbox فیچرها ││
│ │ ├── ManualPaymentDialog ← حذف ۵۶M hardcoded + Amount editable ││
│ │ ├── ChangeParentDialog ← جابجایی در شبکه (جدید) ││
│ │ ├── UserPayouts ← فیلتر + ستون پکیج ││
│ │ ├── BalancesReport ← فیلتر + ستون پکیج ││
│ │ ├── PackageSelect Component ← dropdown قابل استفاده مجدد ││
│ │ └── حذف "پکیج طلایی" → "خرید پکیج" ││
│ └─────────────────────────────────────────────────────────────────────┘│
│ │
│ ┌─── Database ────────────────────────────────────────────────────────┐│
│ │ ├── Packages ← ۱۱ ستون جدید + Seed نقره‌ای ││
│ │ ├── PackageFeatures ← جدول جدید ││
│ │ ├── ClubMemberships ← ۴ ستون First/Last + حذف ActivatedAt ││
│ │ ├── ClubMembershipCycles ← +PackageId ││
│ │ ├── WeeklyCommissionPools ← +PackageId + Unique ││
│ │ ├── UserCommissionPayouts ← +PackageId + Unique ││
│ │ ├── NetworkWeeklyBalances ← +PackageId + Unique ││
│ │ └── EF Migration + Data Backfill ││
│ └─────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.2 خلاصه آماری per-repo
| ریپو | کامیت | فایل | اضافه | حذف | شرح اصلی |
|-------|-------|------|-------|-----|-----------|
| **CMS** | ۲۰ | ۱۷۹+ | +۱۳,۵۸۶ | −۲,۳۲۷ | Domain + Business + Commission + Proto + History + Rename + Interceptor |
| **FrontOffice** | ۸ | ۲۹ | +۵۵۰ | −۱۳۶ | Dynamic UI + Customer RPCs + Per-package Reports + UI Guidance |
| **BackOffice** | ۶ | ۲۴ | +۵۸۰ | −۳۱ | Package CRUD + Feature Matrix + Per-package Reports + UI Guidance |
| **totalDoc** | ۱۴ | ۱۳ | +۲,۷۰۰ | −۱۹۴ | مستندات بیزینسی + تکنیکال + Phase 9 |
---
## 4. جزئیات تغییرات هر ریپو
### 4.1 CMS — ۲۰ کامیت
| فاز | کامیت | شرح |
|-----|--------|------|
| **Phase 0** | `8b9c317` | فیکس ۴ باگ بحرانی: DiscountBalance + UserPackagePurchase |
| **Phase 0** | `fe3edd1` | فیکس EXIT Magic Mode — ریست PackagePurchaseMethod + بستن چرخه |
| **Phase 1** | `ae92ab8` | زیرساخت Domain: Package +۱۱ فیلد، PackageFeature entity، FKهای جدید |
| **Phase 1.5** | `a9cd2fd` | EF Migration + Seed Data + Data Backfill |
| **Phase 2** | `8e5c7c5` | جایگزینی همه SystemConstants با Package entity reads |
| **Phase 3** | `ccb938e` | بازسازی لایه Package + Proto enhancement + باگ‌فیکس |
| **Phase 4** | `0002a5a` | CRUD DTOs + Legacy fixes |
| **Phase 5** | `607f791` | پورسانت per-package + حذف ref طلایی |
| **SP Fix** | `7176fe4` | فیکس SP: `cm.PackageId``cm.LastPackageId` |
| **Phase 6** | `d19c569` | Deprecation cleanup + ConfigurationService MagicWallet |
| **Phase 7a** | `469d97b` | Cosmetic cleanup + حذف orphan handler |
| **Phase 7b** | `161f796` | Embed orderId در callback URL |
| **Phase 7c** | `8446e0e` | حذف ۴ handler deprecated (۱۴ فایل، −۱,۱۲۵ خط) |
| **Phase 8b** | `ce8e248` | NuGet bump → 0.0.185 |
| **Phase 8d** | `7554d70` | حذف ۴ RPC + ۸ message deprecated از Proto |
| **Phase 8e** | `aaaf7fc` | Per-package filtering در Commission queries |
| **Phase 8f** | `dcd1135` | PackageFeature CRUD support |
| **Audit** | `1ac2366` | Compliance audit — Feature DIFF + حذف fallbackهای hardcoded |
| **Phase 9a** | `a1024a3` | Q24: آستانه موجودی `≤1M` ریال + Q26: SP Worker auto-deploy (IHostedService + checksum) |
| **Phase 9b** | `fdbb91d` | Q27: PackageHistory + ClubMembershipCycleHistory entities + enums + EF configs |
| **Phase 9d** | `10d2ca2` | Rename UserWalletChangeLog→UserWalletHistory (86 فایل) + IHasHistory + Interceptor + Migration |
### 4.2 FrontOffice — ۸ کامیت
| فاز | کامیت | شرح |
|-----|--------|------|
| **Phase 7a** | `b82cac4` | حذف "پکیج طلایی" + PackageTitle در DTO |
| **Phase 7b** | `71f391a` | مهاجرت به Customer* RPCs |
| **Phase 8a** | `0bbc11e` | Checkout wire-up به Customer RPCs |
| **Phase 8c** | `d71d463` | صفحات پکیج — فیچرهای داینامیک |
| **Phase 8d** | `40882c8` | NuGet bump Proto cleanup |
| **Phase 8e** | `a956cb9` | Per-package filtering در Commission pages |
| **Phase 8f** | `3bffc13` | T4.2+T4.3+F3: پرداخت شرطی + خرید مجدد + PV |
| **Audit** | `816dcb7` | حذف ۵۶M hardcoded — قیمت‌گذاری داینامیک |
| **Phase 9c** | `474d364` | Q28: UI Guidance alerts (G1-G7) — ۷ صفحه MudAlert آموزشی |
### 4.3 BackOffice — ۶ کامیت
| فاز | کامیت | شرح |
|-----|--------|------|
| **Phase 7a** | `f1b0085` | تغییر label "پکیج طلایی" → "خرید پکیج" |
| **Phase 8b** | `89f5241` | Package CRUD expansion — ۱۲ فیلد جدید |
| **Phase 8d** | `c96377a` | NuGet bump Proto cleanup |
| **Phase 8e** | `8be98ae` | Per-package commission filtering + PackageSelect component |
| **Phase 8f** | `e020354` | ChangeParentDialog + PackageFeature checkbox matrix |
| **Audit** | `e6cf90e` | ManualPaymentDialog — حذف ۵۶M + Amount editable |
| **Phase 9c** | `6939780` | Q28: UI Guidance alerts (G8-G13) — ۶ صفحه MudAlert |
---
## 5. پلن مهاجرت مرحله‌به‌مرحله
### 📋 پیش‌نیازها
- [ ] بکاپ کامل از دیتابیس Production
- [ ] بکاپ از stateهای Kubernetes (Deployments, ConfigMaps)
- [ ] اطمینان از دسترسی به Container Registry (تصاویر فعلی)
- [ ] زمان‌بندی Maintenance Window (ترجیحاً شب یا آخر هفته)
- [ ] اطلاع‌رسانی به کاربران (در صورت نیاز به downtime)
---
### مرحله ۱ از ۶: بکاپ و آماده‌سازی محیط 🛡️
> ⏱️ تخمین: ۳۰ دقیقه
```
1.1 بکاپ کامل دیتابیس
└── pg_dump -Fc cms_db > cms_backup_pre_package_migration.dump
1.2 بکاپ دیتابیس BO (اگر جداست)
└── pg_dump -Fc bo_db > bo_backup_pre_package_migration.dump
1.3 ثبت وضعیت فعلی
└── تعداد رکوردها:
• ClubMemberships: SELECT COUNT(*) ...
• ClubMembershipCycles: SELECT COUNT(*) ...
• WeeklyCommissionPools: SELECT COUNT(*) ...
• UserCommissionPayouts: SELECT COUNT(*) ...
• NetworkWeeklyBalances: SELECT COUNT(*) ...
• Packages: SELECT COUNT(*) ...
1.4 ذخیره نسخه فعلی Docker images
└── docker tag <current-cms> cms:rollback-point
└── docker tag <current-fo> fo:rollback-point
└── docker tag <current-bo> bo:rollback-point
```
**✅ Checkpoint:** بکاپ‌ها ذخیره شده‌اند و قابل restore هستند.
---
### مرحله ۲ از ۶: استقرار CMS (Backend) 🏗️
> ⏱️ تخمین: ۴۵ دقیقه
> ⚠️ **ترتیب بحرانی:** CMS باید **اول** deploy شود چون FO و BO به آن وابسته‌اند.
```
2.1 Build CMS Docker image
└── cd CMS/src
└── docker build -t cms:package-based .
2.2 اجرای EF Migration
└── این migration شامل:
• ۱۱ ستون جدید به جدول Packages
• جدول جدید PackageFeatures
• ستون PackageId به ۵ جدول (ClubMemberships, Cycles, Pools, Payouts, Balances)
• ۴ ستون First/Last به ClubMemberships
• Unique Indexها
⚠️ Migration خودکار اجرا می‌شود در startup اگر EF auto-migration فعال باشد.
✅ اگر دستی: dotnet ef database update
2.3 Data Backfill — مقداردهی پکیج پایه
└── اسکریپت SQL:
┌──────────────────────────────────────────────────────────┐
│ -- مشخص کردن ID پکیج پایه │
│ DO $$ │
│ DECLARE base_pkg_id BIGINT; │
│ BEGIN │
│ SELECT "Id" INTO base_pkg_id │
│ FROM "CMS"."Packages" │
│ WHERE "IsBasePackage" = true LIMIT 1; │
│ │
│ -- ClubMemberships │
│ UPDATE "CMS"."ClubMemberships" │
│ SET "FirstActivationDate" = "ActivatedAt", │
│ "LastActivationDate" = "ActivatedAt", │
│ "FirstPackageId" = base_pkg_id, │
│ "LastPackageId" = base_pkg_id │
│ WHERE "FirstActivationDate" IS NULL; │
│ │
│ -- ClubMembershipCycles │
│ UPDATE "CMS"."ClubMembershipCycles" │
│ SET "PackageId" = base_pkg_id │
│ WHERE "PackageId" IS NULL; │
│ │
│ -- WeeklyCommissionPools │
│ UPDATE "CMS"."WeeklyCommissionPools" │
│ SET "PackageId" = base_pkg_id │
│ WHERE "PackageId" IS NULL; │
│ │
│ -- UserCommissionPayouts │
│ UPDATE "CMS"."UserCommissionPayouts" │
│ SET "PackageId" = base_pkg_id │
│ WHERE "PackageId" IS NULL; │
│ │
│ -- NetworkWeeklyBalances │
│ UPDATE "CMS"."NetworkWeeklyBalances" │
│ SET "PackageId" = base_pkg_id │
│ WHERE "PackageId" IS NULL; │
│ │
│ RAISE NOTICE 'Migration done: PackageId=%', │
│ base_pkg_id; │
│ END $$; │
└──────────────────────────────────────────────────────────┘
2.4 Verification — بررسی migration
┌──────────────────────────────────────────────────────────┐
│ SELECT 'ClubMemberships' AS tbl, COUNT(*) │
│ FROM "CMS"."ClubMemberships" │
│ WHERE "LastPackageId" IS NULL │
│ UNION ALL │
│ SELECT 'Cycles', COUNT(*) │
│ FROM "CMS"."ClubMembershipCycles" │
│ WHERE "PackageId" IS NULL │
│ UNION ALL │
│ SELECT 'Pools', COUNT(*) │
│ FROM "CMS"."WeeklyCommissionPools" │
│ WHERE "PackageId" IS NULL │
│ UNION ALL │
│ SELECT 'Payouts', COUNT(*) │
│ FROM "CMS"."UserCommissionPayouts" │
│ WHERE "PackageId" IS NULL │
│ UNION ALL │
│ SELECT 'Balances', COUNT(*) │
│ FROM "CMS"."NetworkWeeklyBalances" │
│ WHERE "PackageId" IS NULL; │
│ │
│ -- ✅ همه باید 0 باشند! │
└──────────────────────────────────────────────────────────┘
2.5 Seed پکیج نقره‌ای (اگر توسط EF Seed انجام نشده)
└── INSERT پکیج نقره‌ای + PackageFeatures
2.5b اجرای Migration دوم: Q27_HistoryTables_And_RenameWalletHistory
└── این migration شامل:
• RenameTable: UserWalletChangeLogs → UserWalletHistories (حفظ داده‌ها!)
• RenameIndex × 2 + sp_rename PK + FK × 2
• CreateTable: PackageHistories (فیلدهای Old*/New*)
• CreateTable: ClubMembershipCycleHistories (فیلدهای Old*/New*)
⚠️ داده‌های قبلی UserWalletChangeLogs حفظ می‌شوند (RenameTable نه DropTable)
2.6 Deploy CMS به Kubernetes
└── kubectl set image deployment/cms cms=cms:package-based
└── kubectl rollout status deployment/cms
2.7 Health Check
└── curl http://cms-service/health
└── بررسی لاگ‌ها: kubectl logs deployment/cms --tail=100
```
**✅ Checkpoint:** CMS جدید بالا آمده، migration اجرا شده، همه رکوردها PackageId دارند.
---
### مرحله ۳ از ۶: استقرار FrontOffice 🖥️
> ⏱️ تخمین: ۲۰ دقیقه
> پیش‌نیاز: CMS باید بالا و سالم باشد
```
3.1 Build FrontOffice Docker image
└── cd FrontOffice/src
└── docker build -t fo:package-based .
3.2 Deploy به Kubernetes
└── kubectl set image deployment/frontoffice fo=fo:package-based
└── kubectl rollout status deployment/frontoffice
3.3 Smoke Test
└── ✅ صفحه پکیج‌ها باز می‌شود (کاشی‌های داینامیک)
└── ✅ جزئیات پکیج — فیچرها نمایش داده می‌شود
└── ✅ صفحه پاداش‌ها — فیلتر پکیج کار می‌کند
└── ✅ صفحه تعادل‌ها — per-package نمایش داده می‌شود
└── ✅ متن قرارداد — مبلغ داینامیک (نه ۵۶M hardcoded)
```
**✅ Checkpoint:** FrontOffice جدید بالا آمده و صفحات اصلی کار می‌کنند.
---
### مرحله ۴ از ۶: استقرار BackOffice 🛠️
> ⏱️ تخمین: ۲۰ دقیقه
> پیش‌نیاز: CMS باید بالا و سالم باشد
```
4.1 Build BackOffice Docker image
└── cd BackOffice/src
└── docker build -t bo:package-based .
4.2 Deploy به Kubernetes
└── kubectl set image deployment/backoffice bo=bo:package-based
└── kubectl rollout status deployment/backoffice
4.3 Smoke Test
└── ✅ CRUD پکیج — ۱۲ فیلد جدید نمایش داده می‌شود
└── ✅ ماتریس فیچر — checkboxها load می‌شوند
└── ✅ گزارش تعادل‌ها — فیلتر پکیج کار می‌کند
└── ✅ گزارش پرداخت‌ها — ستون پکیج نمایش داده می‌شود
└── ✅ ManualPayment — مبلغ editable (نه ۵۶M disabled)
```
**✅ Checkpoint:** BackOffice جدید بالا آمده و CRUD + گزارشات کار می‌کنند.
---
### مرحله ۵ از ۶: بررسی پورسانت (بحرانی!) 💰
> ⏱️ تخمین: ۳۰ دقیقه
> ⚠️ پورسانت = پول واقعی — دقت مضاعف لازم است
```
5.1 بررسی SP پارامترها
└── محاسبه پورسانت هفته تستی (staging)
└── بررسی: هر پکیج Pool جداگانه دارد
└── بررسی: MaxBalancesPerLeg صحیح (پایه=۳۰۰, نقره‌ای=۳۰)
└── بررسی: MaxNetworkLevel صحیح
5.2 مقایسه نتایج
└── اجرای محاسبه در staging
└── مقایسه Pool مبلغ با محاسبه دستی
└── ✅ تفاوت < ۱% قابل قبول
5.3 بررسی carryover
└── ✅ carryover فقط per-package
└── ✅ تغییر پکیج → ریست carryover
```
**✅ Checkpoint:** محاسبات پورسانت per-package صحیح هستند.
---
### مرحله ۶ از ۶: تنظیمات نهایی و بررسی سلامت ✅
> ⏱️ تخمین: ۱۵ دقیقه
```
6.1 بررسی PackageFeatures seed شده‌اند
└── SELECT * FROM "CMS"."PackageFeatures";
└── پکیج پایه: همه فیچرها ✅
└── پکیج نقره‌ای: فیچرهای تعیین‌شده ✅
6.2 بررسی JWT Claims (اختیاری)
└── لاگین یک کاربر تست → decode JWT
└── ✅ PackageId وجود دارد
└── ✅ CanRepurchase صحیح
6.3 غیرفعال کردن Maintenance Mode (اگر فعال بود)
6.4 مانیتورینگ ۲۴ ساعته
└── بررسی لاگ خطاها
└── بررسی response timeها
└── بررسی پرداخت‌های جدید
```
**✅ مهاجرت تکمیل شد!**
---
## 6. Rollback Plan
### سناریو ۱: مشکل در Migration دیتابیس
```bash
# Restore از بکاپ
pg_restore -d cms_db cms_backup_pre_package_migration.dump
# Rollback CMS image
kubectl set image deployment/cms cms=cms:rollback-point
```
### سناریو ۲: مشکل در CMS (بعد Migration موفق)
```bash
# ⚠️ نکته: migration undo ممکن نیست (ستون‌های جدید اضافه شده‌اند)
# اما کد قدیمی با ستون‌های nullable مشکلی ندارد
# Rollback فقط CMS image
kubectl set image deployment/cms cms=cms:rollback-point
```
### سناریو ۳: مشکل در FO/BO
```bash
# FO و BO مستقل از هم هستند — هرکدام جداگانه rollback
kubectl set image deployment/frontoffice fo=fo:rollback-point
kubectl set image deployment/backoffice bo=bo:rollback-point
```
### نکته مهم Rollback:
- ستون‌های جدید **nullable** هستند → کد قدیمی بدون مشکل کار می‌کند
- جدول `PackageFeatures` جدید است → کد قدیمی آن را ignore می‌کند
- **فقط Data Backfill** غیرقابل‌برگشت است (ولی ضرری ندارد — فقط NULL → مقدار)
---
## 7. چک‌لیست تست قبل از Production
### 🛒 خرید و فعال‌سازی
| # | تست | روش | نتیجه مورد انتظار |
|---|------|------|-------------------|
| 1 | خرید پکیج نقره‌ای (ZarinPal) | از FO → پکیج‌ها → نقره‌ای → پرداخت | Balance = ۵.۶M, Discount = ۱۱.۲M |
| 2 | خرید پکیج پایه (ZarinPal) | از FO → پکیج‌ها → پایه → پرداخت | Balance = ۵۶M, Discount = ۱۱۲M |
| 3 | خرید پکیج پایه (Daya Loan) | از FO → پکیج‌ها → پایه → دایا | Balance = ۵۶M + loan created |
| 4 | پرداخت دستی (BO) | از BO → ManualPayment → مبلغ دلخواه | Amount editable, not hardcoded |
| 5 | فعال‌سازی با نقره‌ای | فعال‌سازی باشگاه بعد خرید نقره‌ای | فقط فیچرهای نقره‌ای فعال (نه همه) |
| 6 | فعال‌سازی با پایه | فعال‌سازی باشگاه بعد خرید پایه | همه فیچرها فعال |
### 🔄 چرخه Magic + خرید مجدد
| # | تست | نتیجه مورد انتظار |
|---|------|-------------------|
| 7 | تکمیل چرخه Magic → ریست | PackagePurchaseMethod = None |
| 8 | خرید مجدد همان پکیج | بدون قرارداد مجدد، فقط شارژ wallet |
| 9 | خرید مجدد پکیج متفاوت (پایه → نقره‌ای) | DIFF اجرا: فیچرهای اضافی غیرفعال |
### 💰 پورسانت per-package
| # | تست | نتیجه مورد انتظار |
|---|------|-------------------|
| 10 | Pool جداگانه هر پکیج | WeeklyCommissionPool با PackageId متفاوت |
| 11 | MaxBalancesPerLeg متفاوت | پایه=۳۰۰, نقره‌ای=۳۰ |
| 12 | Carryover per-downline-package | تغییر پکیج خود کاربر → carryover حفظ (بر اساس زیرمجموعه‌ها) |
| 13 | SP پارامترها از Package | بدون hardcoded ۳۰۰/۱۵ |
### 📊 گزارشات per-package
| # | تست | نتیجه مورد انتظار |
|---|------|-------------------|
| 14 | FO — فیلتر dropdown پکیج | فیلتر عملکرد صحیح |
| 15 | FO — breakdown پاداش per-package | مبالغ صحیح به تفکیک |
| 16 | BO — فیلتر پکیج در تعادل‌ها | فیلتر عملکرد صحیح |
| 17 | BO — ستون پکیج در پرداخت‌ها | نام پکیج نمایش داده می‌شود |
### 📋 UI / قرارداد
| # | تست | نتیجه مورد انتظار |
|---|------|-------------------|
| 18 | متن قرارداد — مبلغ داینامیک | مبلغ و نام پکیج صحیح (نه ۵۶M hardcoded) |
| 19 | ActivationSection — قیمت | از API خوانده می‌شود |
| 20 | BO — ManualPayment editable | مبلغ قابل ویرایش با validation |
| 21 | BO — Package CRUD ۱۲ فیلد | همه فیلدهای جدید ذخیره/بارگذاری |
| 22 | BO — Feature Matrix | checkboxها sync با DB |
---
## 8. ریسک‌ها و نکات بحرانی
### 🔴 ریسک‌های بحرانی
| # | ریسک | احتمال | تاثیر | کاهش‌دهنده |
|---|-------|--------|-------|------------|
| R1 | Migration دیتابیس — PackageId اشتباه | کم | **فاجعه** | Verification query (مرحله 2.4) + بکاپ |
| R2 | SP تغییریافته → محاسبات مالی اشتباه | متوسط | **فاجعه** | تست staging + مقایسه دستی |
| R3 | Magic Wallet EXIT — سقف global به‌جای per-package | متوسط | **بالا** | بررسی MW1-MW3 در CMS handlers |
| R4 | قرارداد حقوقی — مبلغ اشتباه | کم | **حقوقی** | متن قرارداد داینامیک ✅ فیکس شده |
### 🟡 ریسک‌های متوسط
| # | ریسک | کاهش‌دهنده |
|---|-------|------------|
| R5 | Proto breaking change | Field numberها backward compatible (فقط اضافه) |
| R6 | NuGet version mismatch بین repos | همه روی v0.0.189 ✅ |
| R7 | JWT claims — cache invalidation | کاربران باید re-login کنند |
| R8 | ~~Validator hardcoded 1B~~ | ✅ فیکس شد — `SystemConstants.WalletMaxSafeAmount` (10B) حصار ایمنی |
### ⚠️ تغییرات آینده (هنوز پیاده‌نشده — Phase بعدی)
این موارد در BIZ spec شناسایی شده‌اند ولی **هنوز پیاده نشده‌اند**:
| # | مورد | شدت | شرح |
|---|------|------|------|
| ~~F1~~ | ~~WalletChangeLog + PackageId~~ | ✅ انجام‌شده | CMS:`e5bc3a9` — PackageId در UserWalletHistory |
| ~~F2~~ | ~~Notification + PackageId~~ | ✅ انجام‌شده | CMS:`61b7e4f` — SmsTemplates+IUserNotificationService+UserNotificationService با packageName |
| ~~F3~~ | ~~Background Services + PackageId~~ | ✅ بررسی‌شده | بدون تغییر — هر ۳ worker از قبل per-package صحیح کار می‌کنند |
| ~~F4~~ | ~~CSV exports + ستون پکیج~~ | ✅ انجام‌شده | CMS:`61b7e4f` BO:`92c9922` — proto+handler+CSV برای ManualPayments/WithdrawalRequests |
| ~~F5~~ | ~~SystemConfiguration per-package~~ | ✅ بررسی‌شده | بدون تغییر — مقادیر per-package قبلاً به Package entity منتقل شده‌اند |
| ~~F6~~ | ~~MagicWalletChargePage hardcoded~~ | ✅ انجام‌شده | CMS:`61b7e4f` FO:`ecc4f44` — magic_multiplier+magic_max_credit از API، داشبورد "شارژ چند‌برابری" |
| ~~F7~~ | ~~Validators async per-package~~ | ✅ انجام‌شده | CMS:`61b7e4f` FO:`ecc4f44` — SystemConstants.WalletMaxSafeAmount (10B) حصار ایمنی، سقف واقعی per-package در هندلر |
| ~~F8~~ | ~~آستانه موجودی ورود به Magic (Q24)~~ | ✅ انجام‌شده | CMS:`a1024a3``Balance <= 1_000_000` |
| ~~F9~~ | ~~SP Worker — مدیریت خودکار SP (Q26)~~ | ✅ انجام‌شده | CMS:`a1024a3``StoredProcedureDeploymentService` |
| ~~F10~~ | ~~History Tables — یکسان‌سازی + خودکار (Q27)~~ | ✅ انجام‌شده | CMS:`fdbb91d`+`10d2ca2` — IHasHistory + Interceptor + RenameTable migration |
| ~~F11~~ | ~~UI Guidance — آموزش و هشدار (Q28)~~ | ✅ انجام‌شده | FO:`474d364` BO:`6939780` — ۱۳ صفحه MudAlert |
> ✅ **F1-F11 همه پیاده‌سازی شدند.**
---
## ضمیمه: ۳۰ تصمیم بیزینسی (Q1–Q30)
### پیاده‌شده (Q1Q23):
| # | تصمیم | وضعیت |
|---|-------|-------|
| Q1 | باگ DiscountBalance → فیکس | ✅ `8b9c317` |
| Q2 | ادغام ۳ مسیر پرداخت → Generic | ✅ `ccb938e` + `8446e0e` |
| Q3 | پکیج نقره‌ای + پایه — داینامیک | ✅ `ae92ab8` + `a9cd2fd` |
| Q4 | ActivationFee یک فیلد (حذف GiftValue) | ✅ `ae92ab8` |
| Q5 | DiscountMultiplier داینامیک | ✅ `8e5c7c5` |
| Q6 | Migration کاربران فعلی → پکیج پایه | ✅ `a9cd2fd` |
| Q7 | خرید N بار بعد تکمیل چرخه | ✅ `fe3edd1` + `8e5c7c5` |
| Q8 | Commission Pool جدا per-package | ✅ `607f791` |
| Q9 | MagicWallet Multiplier داینامیک | ✅ `8e5c7c5` |
| Q10 | دایا = پکیج پایه (نه طلایی) | ✅ `ccb938e` |
| Q11 | فیچرها داینامیک per-package | ✅ `dcd1135` |
| Q12 | MaxBalancesPerLeg per-package | ✅ `607f791` |
| Q13 | MaxNetworkLevel per-package | ✅ `607f791` |
| Q14 | MagicWalletMaxDeposit per-package | ✅ `ae92ab8` |
| Q15 | MagicWalletMaxCredit per-package | ✅ `ae92ab8` |
| Q16 | NetworkWeeklyBalance + PackageId | ✅ `ae92ab8` |
| Q17 | گزارش FO breakdown per-package | ✅ `a956cb9` |
| Q18 | گزارش BO فیلتر per-package | ✅ `8be98ae` |
| Q19 | قرارداد فقط یک بار | ✅ `1ac2366` |
| Q20 | فیچر DIFF/تفاضل | ✅ `1ac2366` |
| Q21 | First/Last ActivationDate | ✅ `ae92ab8` |
| Q22 | تشخیص هفته از LastActivationDate | ✅ `607f791` |
| Q23 | Carryover strictly per-package | ✅ `607f791` |
### تصمیمات v6 (Q24–Q30) — ✅ تکمیل‌شده:
| # | تصمیم | وضعیت | کامیت |
|---|-------|-------|-------|
| Q24 | آستانه موجودی ≤ ۱,۰۰۰,۰۰۰ ریال (ورود Magic + خرید مجدد) | ✅ | CMS:`a1024a3` |
| Q25 | DayaLoans فقط پکیج پایه — تایید (بدون تغییر کد) | ✅ تایید | — |
| Q26 | SP Worker — auto-deploy با checksum (IHostedService) | ✅ | CMS:`a1024a3` |
| Q27 | History Tables — PackageHistory + CycleHistory + IHasHistory + Interceptor + Rename UserWalletChangeLog→UserWalletHistory | ✅ | CMS:`fdbb91d`+`10d2ca2` |
| Q28 | UI Guidance — ۱۳ صفحه MudAlert آموزشی/هشداری در FO/BO | ✅ | FO:`474d364` BO:`6939780` |
| Q29 | شرط EXIT Magic — تایید: آخرین پکیج فعال (بدون تغییر کد) | ✅ تایید | — |
| Q30 | Carryover — تایید: توضیح مستند شد (بدون تغییر کد) | ✅ تایید | — |
---
*آخرین بروزرسانی: ۱۰ اسفند ۱۴۰۴ — v7: F1-F7 همه تکمیل‌شده ✅ | ۵۱+ کامیت (۲۱ CMS + ۹ FO + ۷ BO + ۱۴+ docs) | NuGet v0.0.189 | Notifications+PackageName, CSV ستون پکیج, Dynamic MagicWallet, SystemConstants validators*
+356
View File
@@ -0,0 +1,356 @@
# گزارش ممیزی صحت داده‌های دیتابیس CMS
**تاریخ بررسی:** 1405/01/28 (2026-04-17)
**فایل بکاپ:** `dbbkup/CMS-20260417.sql` (4.9MB, 21,397 خط)
**تعداد جداول:** ~47 جدول | **تعداد کاربران:** 115 | **تعداد سفارشات:** 72
---
## فهرست مطالب
1. [خلاصه اجرایی](#خلاصه-اجرایی)
2. [اصلاحیه مهم — PaymentStatus](#اصلاحیه-مهم)
3. [دسته ۱ — ورود دستی / مهاجرت دیتا](#دسته-۱--ورود-دستی--مهاجرت-دیتا)
4. [دسته ۲ — باگ‌های کد](#دسته-۲--باگهای-کد)
5. [دسته ۳ — وضعیت Stored Procedure‌ها](#دسته-۳--وضعیت-stored-procedureها)
6. [آمار کلی جداول](#آمار-کلی-جداول)
7. [خلاصه مالی](#خلاصه-مالی)
8. [اقدامات پیشنهادی](#اقدامات-پیشنهادی)
---
## خلاصه اجرایی
بکاپ دیتابیس CMS در تاریخ 17 آوریل 2026 تحلیل شد. تحلیل شامل بررسی صحت داده‌ها، ارجاعات خارجی (FK)، زنجیره مالی، و تطبیق با کد سورس C# و Stored Procedure‌ها بود.
**وضعیت کلی:** سیستم در حال مهاجرت از ساختار استاتیک به پکیج‌محور بوده. بخش عمده مشکلات ناشی از ورود دستی داده و مهاجرت سیستم دایا است. چند باگ کد نیز در SP کمیسیون و Worker دایا شناسایی شد.
---
## اصلاحیه مهم
> **`PaymentStatus=0` در enum کد یعنی `Success` نه `Pending`!**
>
> ```csharp
> // PaymentStatus.cs
> Success = 0,
> Reject = 1,
> Pending = 2
> ```
>
> بنابراین تمام 72 سفارش واقعاً **موفق** هستند. این مشکل نیست.
---
## دسته ۱ — ورود دستی / مهاجرت دیتا
### 1A) موجودی 56M بدون فلگ `HasReceivedDayaCredit`
**شدت:** 🟠 متوسط
**علت:** ورود دستی / مهاجرت
- **23 کاربر** دقیقاً 56,000,000 ریال در `Balance` دارند ولی `HasReceivedDayaCredit = 0`
- فقط **16 کاربر** از طریق Daya Worker صحیح اعتبار گرفتند (`HasReceivedDayaCredit = 1`)
- بقیه احتمالاً دستی شارژ شدند بدون ثبت تاریخچه
**کاربران آسیب‌پذیر:**
| UserId | نام | Balance | HasReceivedDayaCredit |
|--------|-----|---------|----------------------|
| 51 | مرتضی اینالو | 56,000,000 | 0 |
| 58 | وحید حق‌گو | 56,000,000 | 0 |
| 87 | امیررضا محمدی | 56,000,000 | 0 |
| 43 | کریم خادمی | 56,000,000 | 0 |
| 52 | حمیدرضا اسمعیلی | 56,000,000 | 0 |
| 88 | کریم رعیت‌پیشه | 56,000,000 | 0 |
| 91 | رحیم رعیت‌پیشه | 56,000,000 | 0 |
| 93 | ریحانه سادات هاشمی‌نصر | 56,000,000 | 0 |
| 99 | هستی خادمی | 56,000,000 | 0 |
| 110 | علی وفائی | 56,000,000 | 0 |
| 113 | کاوس بیگ‌اینالو | 56,000,000 | 0 |
| 119 | علیرضا کریمی‌پیروز | 56,000,000 | 0 |
| 122 | ابوالقاسم عابدی | 56,000,000 | 0 |
| 123 | ناصر کریمی‌پیروز | 56,000,000 | 0 |
| 124 | محمدرضا باغجری | 56,000,000 | 0 |
| 126 | امیرعباس میرزایی | 56,000,000 | 0 |
| 138 | سیما اکبرزاده | 56,000,000 | 0 |
| 139 | مسعود توسلیان | 56,000,000 | 0 |
| 142 | صغری شبانکاره | 56,000,000 | 0 |
| 170 | لیلا خدارحمی | 56,000,000 | 0 |
| 172 | مهرافشان زاهدنیا | 56,000,000 | 0 |
| 175 | ناهید حسن‌زاده | 56,000,000 | 0 |
| 176 | مهریدخت میکانیکی | 56,000,000 | 0 |
---
### 1B) کد ملی تکراری — اکانت‌های تستی
**شدت:** 🟡 پایین
**علت:** ورود دستی / تست
| کد ملی | تعداد اکانت | UserIdها | نام |
|--------|------------|----------|-----|
| مشترک #1 | 6 | 9, 11, 12, 13, 40, 41 | مهدی مرجانی |
| مشترک #2 | 3 | 7, 8, 10 | مهدی صیفی |
| مشترک #3 | 2 | 42, 43 | کریم خادمی |
| مشترک #4 | 2 | 50, 51 | مرتضی اینالو |
| مشترک #5 | 2 | 120, 190 | عسلی |
- شماره موبایل تکراری: `09038888074` بین کاربران 120 و 190
---
### 1C) `NetworkInfos` خالی — مهاجرت صحیح انجام شده
**شدت:** ✅ مشکل نیست
جدول `NetworkInfos` خالی است چون داده‌های شبکه به فیلدهای مستقیم `Users` مهاجرت شدند:
- `Users.NetworkParentId` ← FK به والد شبکه
- `Users.LegPosition` ← Left(0) / Right(1)
مهاجرت در `20250601_MigrateParentIdToNetworkParentId.sql` انجام شده. entity `NetworkInfo` در C# وجود ندارد. SP‌ها هم از `Users.NetworkParentId` استفاده می‌کنند.
---
### 1D) `PasswordHash = NULL` برای تمام 115 کاربر
**شدت:** 🟡 نیاز به بررسی
**علت:** احتمالاً بکاپ شامل فیلد پسورد نشده، یا سیستم OTP/موبایل استفاده می‌کند
---
### 1E) دوره‌های عضویت باشگاه — `PaidAmount = 0`
**شدت:** 🟡 نیاز به بررسی
- 87 دوره `ClubMembershipCycles` همه `PaidAmount = 0`
- ممکن است عضویت باشگاه خودکار با خرید پکیج فعال شود (نه پرداخت جداگانه)
---
## دسته ۲ — باگ‌های کد
### 2A) تراکنش‌های تکراری دایا — Race Condition در `CheckAndProcessDayaLoansCommandHandler`
**شدت:** 🔴 بحرانی
**فایل:** `CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs`
**یافته‌ها:**
- **109 رکورد `DayaLoanContracts`** ولی فقط **16 کاربر** `HasReceivedDayaCredit=1`
- **64 تراکنش** با «دریافت اعتبار دایا» ساخته شده ولی فقط **11 رکورد `UserPackagePurchases`**
- تراکنش‌ها با `RefId` منحصربه‌فرد ساخته شدند (مثل `C4_T8579002`) — همه در `2025-11-19 01:24:24` ایجاد شدند
**تحلیل ریشه‌ای:**
- Daya Worker (Hangfire هر 15 دقیقه) احتمالاً برای بعضی کاربران **چند بار** اجرا شده
- `CreateTransaction` و `DayaLoanContract` ساخته شده ولی `HasReceivedDayaCredit=true` ست نشده (exception بعد از SaveChanges اول ولی قبل از SaveChanges دوم)
- یا: چون همه در یک لحظه ساخته شدند (`2025-11-19 01:24:24`)، ممکن است **یک بار bulk import دستی** بوده
**ریسک:** کاربرانی که `HasReceivedDayaCredit=0` دارند ممکن است **دوباره** از Worker اعتبار بگیرند.
---
### 2B) SP `sp_CalculateWeeklyCommissionPool` — `DistributedAmount` آپدیت نمی‌شود
**شدت:** 🔴 بحرانی
**فایل:** `dbbkup/CMS-20260417.sql` خط ~18830
در Step 10 (آپدیت نهایی Pool):
```sql
-- کد فعلی (باگ‌دار):
UPDATE CMS.WeeklyCommissionPools
SET
IsCalculated = 1,
CalculatedAt = @CalculatedAt,
TotalBalances = @TotalBalances,
ValuePerBalance = @ValuePerBalance,
LastModified = @CalculatedAt,
LastModifiedBy = 'SP'
WHERE Id = @PoolId;
```
**مشکل:** فیلد `DistributedAmount` **هرگز مقداردهی نمی‌شود** و 0 باقی می‌ماند.
**نتیجه در دیتا:**
- 15 استخر، مجموع `TotalPoolAmount = 2,016,000,000` ریال
- همه `DistributedAmount = 0`
- ولی 46 پرداخت واقعاً ثبت و به `NetworkBalance` اضافه شدند
---
### 2C) `UserWalletChangeLogs` خالی
**شدت:** 🟠 متوسط
**فایل:** SP Step 9 + `CalculateWeeklyCommissionPoolCommandHandler.cs`
- SP باید در Step 9 لاگ تغییرات کیف‌پول را در `UserWalletChangeLogs` ذخیره کند
- جدول **صفر رکورد** دارد
- **احتمال 1:** SP هرگز Step 9 را درست اجرا نکرده
- **احتمال 2:** ORM Strategy (نه SP) استفاده شده و آن `UserWalletChangeLogs` نمی‌نویسد
- **احتمال 3:** لاگ‌ها در حین ForceRecalculate حذف شدند
---
### 2D) `WalletHistory.ChangeType = NULL` در تمام 239 رکورد
**شدت:** 🟠 متوسط
**فایل:** `UserOrderService.cs` و `PackageService.cs` — هرجا `UserWalletHistory` ساخته می‌شود
- فیلد `ChangeType` هرگز ست نمی‌شود
- کد از `IsIncrease` (bool) برای تفکیک واریز/برداشت استفاده می‌کند
- `ChangeType` احتمالاً فیلد قدیمی deprecated شده
---
### 2E) `NetworkWeeklyBalances.WeeklyCommissionPoolId = NULL` (805 رکورد)
**شدت:** 🟡 پایین
- SP مقدار `WeeklyPoolContribution = 0` ثبت می‌کند و PoolId ست نمی‌شود
- ارتباط بین `NetworkWeeklyBalances` و `WeeklyCommissionPools` از طریق `WeekDefinitionId` برقرار است، نه FK مستقیم
- **عملاً مشکل عملکردی ایجاد نمی‌کند** ولی tracking سخت‌تر می‌شود
---
### 2F) 30 سفارش کیف‌پولی — بررسی WalletHistory
**شدت:** 🟠 نیاز به تأیید
- 30 سفارش با `PaymentMethod=1` (Wallet) ثبت شدند
- اولین بررسی نشان داد «هیچ برداشتی ثبت نشده» — **اما** این بررسی بر اساس `ChangeType` بود که همه NULL هستند
- **باید بر اساس `IsIncrease=0` (false = decrease)** دوباره بررسی شود
- `SubmitShopBuyOrder()` در کد `UserWalletHistory` می‌سازد — احتمالاً رکوردها وجود دارند ولی `ChangeType` NULL است
---
## دسته ۳ — وضعیت Stored Procedure‌ها
### `GetNetworkTree`
| آیتم | وضعیت |
|------|--------|
| از `Users.NetworkParentId` استفاده می‌کند | ✅ صحیح (بعد از مهاجرت) |
| JOIN با `ClubMemberships` | ✅ صحیح |
| `MAXRECURSION 0` | ✅ صحیح |
| فیلتر `IsDeleted = 0` | ✅ صحیح |
### `sp_CalculateWeeklyBalances`
| آیتم | وضعیت |
|------|--------|
| پارامتر `@PackageId` از `Packages.IsBasePackage` | ✅ صحیح |
| `MaxBalancesPerLeg` و `MaxNetworkLevel` از Package | ✅ صحیح |
| Carryover از هفته قبل | ✅ صحیح |
| CTE recursive برای چپ/راست | ✅ صحیح |
| `TotalBalances = MIN(left, right)` | ✅ صحیح |
| `SubordinateBalances` محاسبه | ✅ صحیح |
| 805 رکورد تولید شده | ✅ کار می‌کند |
### `sp_CalculateWeeklyCommissionPool`
| آیتم | وضعیت |
|------|--------|
| `ValuePerBalance = TotalPoolAmount / TotalBalances` | ✅ صحیح |
| ایجاد `UserCommissionPayouts` | ✅ صحیح (46 رکورد) |
| ثبت `CommissionPayoutHistories` | ✅ صحیح |
| شارژ `NetworkBalance` کیف‌پول | ✅ صحیح |
| آپدیت `DistributedAmount` در Pool | ❌ **انجام نمی‌شود** |
| ثبت `UserWalletChangeLogs` | ⚠️ نامشخص |
| ForceRecalculate — Revert | ✅ منطق صحیح |
---
## آمار کلی جداول
| جدول | تعداد | وضعیت |
|------|--------|--------|
| Users | 115 | |
| UserWallets | 115 | |
| UserWalletHistories | 239 | ChangeType همه NULL |
| UserWalletChangeLogs | 0 | ⚠️ خالی |
| UserOrders | 72 | همه PaymentStatus=0 (Success) |
| FactorDetails | 177 | |
| Transactions | 175 | 64 تراکنش دایا |
| PaymentTransactions | 22 | فقط DiscountOrders + شارژ |
| Products | 163 | |
| Categories | 14 | |
| InventoryItems | 175 | |
| StockMovements | 242 | 11 chain issue |
| ClubMemberships | 88 | همه IsActive=1 |
| ClubMembershipCycles | 87 | همه PaidAmount=0 |
| UserClubFeatures | 249 | |
| NetworkInfos | 0 | ✅ deprecated — مهاجرت شده |
| NetworkWeeklyBalances | 805 | PoolId همه NULL |
| WeeklyCommissionPools | 15 | DistributedAmount همه 0 |
| UserCommissionPayouts | 46 | Status=3, مبالغ کلان |
| WeekDefinitions | 59 | |
| Packages | 2 | Base=56M, Secondary=5.6M |
| DayaLoanContracts | 109 | |
| UserPackagePurchases | 11 | |
| DiscountOrders | 13 | |
| DiscountOrderDetails | 14 | |
| DiscountCategories | 8 | |
| OrderVATs | 44 | ✅ محاسبات صحیح |
| UserAddresses | 127 | |
| Roles | 3 | user, admin, Administrator |
| UserRoles | 119 | 115 user + 2 admin + 2 Administrator |
| ProductImages | 4 | |
| ShippingMethods | 0 | ⚠️ خالی |
| SitePages | 0 | ⚠️ خالی |
| SystemConfigurations | 0 | ⚠️ خالی |
| Coupons | 0 | ⚠️ خالی |
| ProductProperties | 0 | ⚠️ خالی |
---
## خلاصه مالی
### موجودی‌های کل سیستم
| فیلد | مبلغ (ریال) |
|------|-------------|
| مجموع `Balance` کل کیف‌پول‌ها | 2,548,684,394 |
| مجموع `NetworkBalance` (کمیسیون) | 453,599,993 |
| مجموع `DiscountBalance` (تخفیف) | 7,326,400,000 |
| مجموع سفارشات (UserOrders) | 1,039,410,812 |
| مجموع استخر کمیسیون (WeeklyPools) | 2,016,000,000 |
### پکیج‌ها
| Package | قیمت | IsBase | MaxBalancesPerLeg | MaxNetworkLevel | DiscountMultiplier |
|---------|-------|--------|-------------------|-----------------|-------------------|
| Package 1 | 56,000,000 | ✅ | 300 | 1,000,000 | 2.0 |
| Package 4 | 5,600,000 | ❌ | 30 | 1,000,000 | 2.0 |
### ارجاعات شکسته (FK)
| ارجاع | تعداد |
|-------|--------|
| FactorDetails → OrderId ناموجود | 2 (DetailId=22,23 → OrderId=21) |
| InventoryItems ≠ آخرین StockMovement | 3 |
| StockMovement chain breaks | 11 |
---
## اقدامات پیشنهادی
### اولویت بالا (انجام ندهید تا بررسی بیشتر)
1. **فیکس SP `sp_CalculateWeeklyCommissionPool`:** اضافه کردن `DistributedAmount` به UPDATE نهایی
2. **بررسی Daya Worker:** race condition در `CheckAndProcessDayaLoansCommandHandler` — ممکن است تراکنش تکراری بسازد
3. **23 کاربر با 56M بدون فلگ دایا:** تعیین اینکه آیا دستی شارژ شدند یا از Worker — سپس اصلاح `HasReceivedDayaCredit`
### اولویت متوسط
4. **WalletHistory ChangeType:** تأیید اینکه deprecated شده و `IsIncrease` جایگزین است
5. **UserWalletChangeLogs خالی:** بررسی اینکه ORM Strategy استفاده شده یا SP Strategy
6. **اکانت‌های تکراری:** تصمیم‌گیری درباره 13 اکانت تکراری (حذف/ادغام)
### اولویت پایین
7. **جداول خالی:** SystemConfigurations, ShippingMethods, SitePages — آیا باید از seed پر شوند؟
8. **StockMovement chain issues:** 11 ناسازگاری — آیا از ورود دستی موجودی بوده؟
---
*این گزارش فقط مستندات یافته‌ها است. هیچ تغییری در کد یا دیتابیس اعمال نشده است.*
+33 -5
View File
@@ -1,7 +1,7 @@
# 📊 فلوچارت‌ها و دیاگرام‌های کلان
> **دید بالا (Big Picture): فلوی کاربر، مالی و داده**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **دید بالا (Big Picture): فلوی کاربر، مالی، داده و کیف‌پول جادویی**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet + VAT 10%)
---
@@ -20,10 +20,28 @@ flowchart TD
H -->|خیر| F["🛒 Regular Store\nخرید عادی — پرداخت از کیف‌پول"]
H -->|بله| I["🏆 Club Member Dashboard"]
I --> J["فروشگاه تخفیفی\nper-product MaxDiscount%"]
I --> J["فروشگاه اعتباری\nper-product MaxDiscount%"]
I --> K["درخت شبکه\nباینری"]
I --> L["کمیسیون\nهفتگی"]
I --> M["Chatika AI"]
I --> N["🪄 کیف‌پول جادویی\nشارژ ×2.5"]
```
---
## ۱.۱ فلوی کیف‌پول جادویی (Magic Wallet) ✅
```mermaid
flowchart TD
A["خرید پکیج 56M\nBalance=56M"] --> B["خرید از فروشگاه\nBalance کم می‌شود"]
B --> C{"Balance = 0 +\nعضو فعال باشگاه?"}
C -->|خیر| B
C -->|بله| D["🪄 Magic Mode\nWalletMode = 1"]
D --> E["شارژ از درگاه\nواریز × 2.5"]
E --> F{"Balance=0 AND\nDeposited≥100M?"}
F -->|خیر| G["خرید یا شارژ ادامه"]
G --> F
F -->|بله| H["خروج از Magic\nخرید مجدد پکیج"]
```
---
@@ -48,7 +66,7 @@ flowchart TD
subgraph WALLETS["═══ توزیع به کیف‌پول‌ها ═══"]
W1["💰 Balance — نقدی\n• IPG: +56M\n• Daya: +56M\n• فعالسازی: −25.2M\n• خرید فروشگاه"]
W2["🌟 NetworkBalance — طلایی\n• شارژ نمی‌شود\n• فقط محاسبه کمیسیون\n• سقف 300/هفته"]
W3["🏷️ DiscountBalance — تخفیفی\n• IPG: +112M\n• Daya: +112M\n• per-product MaxDiscount%"]
W3["🏷️ DiscountBalance — اعتباری\n• IPG: +112M\n• Daya: +112M\n• per-product MaxDiscount%"]
end
W1 & W2 --> POOL
@@ -144,7 +162,7 @@ flowchart TD
D2 --> E2["محاسبه سهم تخفیف\nMaxDiscount% هر محصول"]
E2 --> F2["محاسبه باقیمانده\ngatewayAmount = total - discountUsed"]
F2 --> G2{"gatewayAmount > 0?"}
G2 -->|بله| H2["کسر DiscountBalance\n+ ZarinPal IPG برای باقیمانده + 9% VAT"]
G2 -->|بله| H2["کسر DiscountBalance\n+ ZarinPal IPG برای باقیمانده + 10% VAT"]
H2 --> I2["Redirect → ZarinPal\nCallback → ثبت سفارش"]
G2 -->|خیر| J2["فقط کسر از DiscountBalance\nبدون درگاه"]
J2 --> K2["ثبت سفارش"]
@@ -203,12 +221,22 @@ erDiagram
UserWallet ||--o{ UserWalletChangeLog : logs
ClubMembership ||--o{ ClubMembershipCycle : has
BlogPost }o--|| Category : belongs_to
UserWallet {
long Balance
long NetworkBalance
long DiscountBalance
int WalletMode
long MagicTotalDeposited
long MagicTotalCredited
}
ClubMembershipCycle {
int CycleNumber
datetime PackagePurchasedAt
bool IsCurrentCycle
}
User {
Guid NetworkParentId
+13 -5
View File
@@ -3,7 +3,8 @@
> **فهرست کامل ۱۵ فایل مستند پروژه FourSat (کارا بازار سلامت)**
> **تاریخ تجمیع:** اسفند ۱۴۰۴
> **تعداد فایل‌های مبدأ:** ۵۳ فایل (~۳۲,۰۰۰ خط)
> **تعداد فایل‌های نهایی:** ۱۷ فایل (۱۵ اصلی + ۲ roadmap)
> **تعداد فایل‌های نهایی:** ۲۲ فایل (۱۵ اصلی + ۷ roadmap/business)
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (فاز ۱۰: DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes | NuGet v0.0.189)
---
@@ -34,7 +35,10 @@ totalDoc/
└── 📁 roadmap/ (فیچرهای جدید — در حال توسعه)
├── MAGIC-WALLET-SPEC.md مشخصات کیف‌پول جادویی
── MAGIC-WALLET-PLAN.md پلن پیاده‌سازی + checklist
── MAGIC-WALLET-PLAN.md پلن پیاده‌سازی + checklist
├── PACKAGE-TRANSFORMATION-TASKS.md تسک‌های تحول پکیج‌بیس (فاز 0-5)
├── PACKAGE-TRANSFORMATION-UX.md تاثیر بر UX فرانت‌ها
└── FEATURE-BACKLOG.md بکلاگ ۱۲ RPC آماده
```
---
@@ -45,8 +49,8 @@ totalDoc/
| # | فایل | موضوع | خلاصه |
|---|------|--------|--------|
| B1 | [BUSINESS-01-CLUB-COMMISSION](../business/BUSINESS-01-CLUB-COMMISSION.md) | باشگاه و کمیسیون | درخت باینری، فرمول ۴مرحله‌ای، Pool هفتگی، وام دایا، Chatika |
| B2 | [BUSINESS-02-PAYMENT-FINANCE](../business/BUSINESS-02-PAYMENT-FINANCE.md) | مالی و پرداخت | ZarinPal IPG، ۳ کیف‌پول، PYMS، پرداخت ترکیبی، VAT |
| B1 | [BUSINESS-01-CLUB-COMMISSION](../business/BUSINESS-01-CLUB-COMMISSION.md) | باشگاه و کمیسیون | درخت باینری، فرمول ۴مرحله‌ای، Pool هفتگی، وام دایا، Chatika، **🪄 کیف‌پول جادویی** |
| B2 | [BUSINESS-02-PAYMENT-FINANCE](../business/BUSINESS-02-PAYMENT-FINANCE.md) | مالی و پرداخت | ZarinPal IPG، ۳ کیف‌پول، PYMS، پرداخت ترکیبی، VAT 10%، **Magic Charge** |
| B3 | [BUSINESS-03-ECOMMERCE-STORES](../business/BUSINESS-03-ECOMMERCE-STORES.md) | فروشگاه | Regular + Discount Store، Lazy Load، موجودی خودکار، باندل |
| B4 | [BUSINESS-04-USER-MEMBERSHIP](../business/BUSINESS-04-USER-MEMBERSHIP.md) | کاربر و عضویت | ثبت‌نام OTP، قرارداد، فیچرهای باشگاه، Auth-Aware، Referral |
| B5 | [BUSINESS-05-CONTENT-MANAGEMENT](../business/BUSINESS-05-CONTENT-MANAGEMENT.md) | محتوا | Site Pages (Shopify)، بلاگ، مدیریت فایل، SMS/Email، Landing |
@@ -76,7 +80,11 @@ totalDoc/
| # | فایل | موضوع | خلاصه |
|---|------|--------|--------|
| R1 | [MAGIC-WALLET-SPEC](../roadmap/MAGIC-WALLET-SPEC.md) | کیف‌پول جادویی — مشخصات | State Machine، ضریب ×2.5، سقف 100M، قوانین، API، مدل داده |
| R2 | [MAGIC-WALLET-PLAN](../roadmap/MAGIC-WALLET-PLAN.md) | کیف‌پول جادویی — پلن | ۶ فاز، checklist، تخمین زمان، وابستگی ChargeDiscountWallet |
| R2 | [MAGIC-WALLET-PLAN](../roadmap/MAGIC-WALLET-PLAN.md) | کیف‌پول جادویی — پلن | ۶ فاز، **فاز 1-6 تکمیل ✅** |
| R3 | [PACKAGE-TRANSFORMATION-TASKS](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md) | تحول پکیج‌بیس — تسک‌ها | ۱۰ فاز، **فاز 0-10 تکمیل ✅**، تست + deploy در انتظار |
| R4 | [PACKAGE-TRANSFORMATION-UX](../roadmap/PACKAGE-TRANSFORMATION-UX.md) | تاثیر بر UX | تحلیل تاثیر بر FrontOffice + BackOffice |
| R5 | [FEATURE-BACKLOG](../roadmap/FEATURE-BACKLOG.md) | بکلاگ فیچرها | ۱۲ RPC آماده بدون UI، اولویت‌بندی‌شده |
| R6 | [BIZ-PACKAGE-BASED-SYSTEM](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی سیستم پکیج‌بیس | v6، ۳۰ تصمیم (Q1-Q30) + ۵۱ تغییر + ۴۴ سایدافکت |
---
+319 -13
View File
@@ -1,7 +1,7 @@
# 📜 تاریخچه کارهای انجام‌شده
> **همه فعالیت‌های پروژه به صورت بولت با توضیح یک‌خطی و درصد تکمیل**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱ — فیکس‌های پرداخت ZarinPal + امنیت Callback URL + اصلاح تومان/ریال)
---
@@ -9,12 +9,13 @@
| حوزه | تعداد آیتم | تکمیل‌شده | درصد کل |
|------|-----------|----------|---------|
| **BackOffice** | 58 | 57 | **98%** |
| **FrontOffice** | 35 | 32 | **91%** |
| **CMS Core** | 45 | 42 | **93%** |
| **Deployment** | 20 | 18 | **90%** |
| **Migration** | 15 | 15 | **100%** |
| **مجموع** | **173** | **164** | **95%** |
| **BackOffice** | 67 | 67 | **100%** |
| **FrontOffice** | 60 | 60 | **100%** |
| **CMS Core** | 89 | 89 | **100%** |
| **Package-Based System** | 52 | 52 | **100%** |
| **Deployment** | 22 | 21 | **95%** |
| **Migration** | 21 | 21 | **100%** |
| **مجموع** | **311** | **310** | **99.5%** |
---
@@ -61,9 +62,21 @@
- ✅ Hangfire InventorySync — ایجاد رکوردهای گمشده
- ⬜ Dark Mode — طراحی نشده (Phase آینده)
### Phase 18: بهبود UI ادمین (اسفند ۱۴۰۴)
- ✅ UserAutoComplete در ActivateClubDialog — جایگزین MudNumericField برای انتخاب کاربر
- ✅ فیلتر کاربر در صفحه ClubMembers — UserAutoComplete در تولبار جستجو
- ✅ نمایش نام کاربر در WalletManagementPage — TemplateColumn با UserName + ID
### Phase 8b: CRUD پکیج کامل (اسفند ۱۴۰۴)
- ✅ CreateDialog: اضافه ۱۲ فیلد جدید — SortOrder, ActivationFee, DiscountMultiplier, MagicWallet*, MaxBalancesPerLeg, MaxNetworkLevel, IsActive, IsBasePackage, SupportsDirectPurchase, SupportsDayaPurchase
- ✅ UpdateDialog: همان ۱۲ فیلد جدید — ایجاد فرم کامل ادمین
- ✅ PackageMainPage Grid: ۴ ستون جدید — قیمت (N0), ترتیب, وضعیت (فعال/غیرفعال chip), نوع (پایه/عادی chip)
- ✅ فیکس `HasPurchasedGoldenPackage``HasPurchasedPackage` — UserNetworkInfo.razor
- ✅ NuGet bump 0.0.184 → 0.0.186 + local feed source
---
## ۲. FrontOffice — فازها (91% کامل)
## ۲. FrontOffice — فازها (95% کامل)
### UI Modernization Phase 1-3
- ✅ ارتقا به MudBlazor v8 — همه کامپوننت‌ها
@@ -86,6 +99,95 @@
- ✅ نمای درخت شبکه — باینری بصری
- ✅ امضای قرارداد — OTP + scroll-to-bottom
- ✅ صفحه Chatika — چت AI
- ✅ کیف‌پول جادویی — MagicWallet.razor + فرم شارژ + پروگرس‌بار سقف
### فعال‌سازی درگاه پرداخت (اسفند ۱۴۰۴)
- ✅ دکمه پرداخت شارژ کیف‌پول اعتباری — ChargeDiscountWallet.razor فعال شد (حذف «بزودی»)
- ✅ دکمه پرداخت شارژ کیف‌پول جادویی — MagicWallet.razor فعال شد (حذف «بزودی»)
- ✅ دکمه‌های پرداخت مستقیم خرید پکیج — Index.razor هر دو شاخه فعال شدند (حذف «بزودی»)
### فیکس‌های پرداخت و UX (اسفند ۱۴۰۴ — Phase 11)
-**صفحه موفقیت پرداخت**`TransactionId` بجای `RefId` + موجودی واقعی + `Href="/profile"` (FO:`5ded91a`)
-**حذف دوبار ×۱۰** — FO مستقیم تومان ارسال می‌کند، CMS/ZarinPal ×۱۰ می‌کند (FO:`2f9ef15`)
-**حذف CallbackUrl از درخواست**`Index.razor.cs` و `Checkout.razor.cs` دیگر URL ارسال نمی‌کنند (FO:`2b1dc47`)
- ✅ ۳ کامیت، ۷ فایل تغییر
### Phase 8a+8c: Checkout + Package Pages (اسفند ۱۴۰۴)
- ✅ Checkout wire-up — مهاجرت به `CustomerPurchasePackageAsync` (حذف dead code قدیمی)
- ✅ PackageDetail: `GetPackageAsync``GetCustomerPackageDetailsAsync` — features/specs از API (نه hardcoded)
- ✅ Packages.razor: un-exclude از build + dynamic feature bullets از `CustomerPackageModel`
- ✅ PackageService: `PackageDto` غنی‌شده با ۸ فیلد جدید + `GetUserPackageStatusAsync` متصل به RPC واقعی
- ✅ NuGet bump 0.0.182 → 0.0.186 + local feed source
- ✅ فیکس GwUrl پروداکشن — تصحیح از cms.kbs1.ir به cms.kbs2.ir
### Phase 10a: PackagePurchaseDialog — دیالوگ داینامیک خرید پکیج (اسفند ۱۴۰۴)
-`PackagePurchaseDialog.razor` — دیالوگ ۲ مرحله‌ای: مرحله ۱ = کاشی‌های پکیج (responsive grid)، مرحله ۲ = انتخاب روش پرداخت
- ✅ حذف دیالوگ inline خرید «پکیج پایه» از `Index.razor` — جایگزین با دیالوگ داینامیک
- ✅ بارگذاری پکیج‌ها از `PackageService.GetAllPackagesAsync()` — نمایش عنوان + قیمت + ویژگی‌ها
- ✅ پشتیبانی از ۲ روش پرداخت: مستقیم (درگاه بانکی) + اعتبار دایا (فقط پکیج پایه + دور اول)
- ✅ CSS کلاس‌های جدید: `.pkg-tile`, `.pkg-tile-badge`, `.pkg-payment-option`
- ✅ کامیت: `a3681a8` (FO)
### Phase 10b: ۴ فیکس UI پکیج (اسفند ۱۴۰۴)
-**Toman/Rial**: قیمت از سرور به ریال ← `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
-**لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیج‌ها)
-**دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
-**HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
- ✅ کامیت: `3c1a8ff` (FO)
### Phase 11: فیکس‌های پرداخت + تومان/ریال + امنیت Callback URL (اسفند ۱۴۰۴)
#### 11a: اصلاح مدل تومان/ریال (CMS+FO)
> **تصحیح مهم:** دیتابیس به **تومان** ذخیره می‌کند نه ریال. فقط درگاه ZarinPal ریال نیاز دارد (×۱۰).
-`ZarinPalPaymentService.InitiatePaymentAsync` — مبلغ ×۱۰ تبدیل به ریال فقط هنگام ارسال به ZarinPal
-`ZarinPalPaymentService.VerifyPaymentWithAmountAsync` — مبلغ ×۱۰ هنگام verify
- ✅ FrontOffice نمایش مستقیم مبلغ تومان (بدون `/10`) — فیکس `MagicWallet.razor`, `ChargeDiscountWallet.razor.cs`
- ✅ حذف `Price / 10` اضافی در `ClubMembershipContractDialog.razor`
#### 11b: فیکس ZarinPal Verify — رفع خطای Code=-1 (CMS:`721661a`)
> **باگ:** `VerifyPaymentAsync` با ۲ آرگومان مبلغ صفر (0) ارسال می‌کرد → ZarinPal Code=-1 برمی‌گرداند
-`PackageService` — lookup `PaymentTransaction.Amount` + استفاده از overload ۳ آرگومانه
-`TransactionsService` — همان فیکس
-`VerifyDiscountWalletChargeCommandHandler` — مبلغ از `PaymentTransaction` + رفع کپی‌پیست باگ
-`VerifyPackagePurchaseCommandHandler` — مبلغ از `PaymentTransaction`
-`IPaymentGatewayService` — default impl ۳ آرگومانه با `NotImplementedException`
-`MockPaymentGatewayService` + `DayaPaymentService` — اضافه overload ۳ آرگومانه
-۷ فایل تغییر
#### 11c: بهبود صفحه موفقیت پرداخت (FO:`5ded91a`)
-`PaymentCallback.razor` — نمایش `TransactionId` بجای `RefId` برای کد رهگیری
- ✅ نمایش موجودی واقعی کیف‌پول از `WalletService.GetBalancesAsync()` (نه مقدار ثابت)
- ✅ دکمه بازگشت: `Href="/profile"` بجای `history.back()` (جلوگیری از حلقه بازگشت به درگاه)
#### 11d: حذف دوبار ×۱۰ شارژ کیف‌پول (FO:`2f9ef15`)
> **باگ:** FO مبلغ تومان را ×۱۰ تبدیل به ریال می‌کرد، سپس CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر
-`MagicWallet.razor.cs` — حذف تبدیل ×۱۰ (ارسال مستقیم تومان)
-`MagicWallet.razor` — فیکس Max و فیلتر preset مبالغ
-`ChargeDiscountWallet.razor.cs` — حذف تبدیل ×۱۰
-`ClubMembershipContractDialog.razor` — حذف `Price/10` اضافی
- ✅ ۴ فایل تغییر
#### 11e: فیکس مسیر Callback کیف‌پول (CMS:`ed2b20a`)
-`PaymentCallbackController` — مسیر redirect از `/magic-wallet` به `/profile/magic-wallet`
- ✅ ایجاد `appsettings.Development.json` — URL‌های محلی (`localhost:32846` و `localhost:5268`)
- ✅ تصحیح کامنت‌های proto: «ریال» → «تومان»
#### 11f: امنیت Callback URL — حذف از ورودی کاربر (CMS:`0107308`, FO:`2b1dc47`)
> **اصلاح امنیتی:** هیچ callback URL نباید از ورودی کاربر بیاید — همه از `appsettings.json` خوانده شوند
-`PackageService` — خواندن `FrontOfficeBaseUrl` از `IConfiguration` بجای `request.CallbackUrl`
-`TransactionsService` — همان فیکس، خواندن از config
- ✅ تأیید: `MagicWallet` و `DiscountWallet` از قبل از `CmsBaseUrl` config می‌خوانند ✅
- ✅ تأیید: `DiscountShop PlaceOrder` از قبل از `CmsBaseUrl` config می‌خواند ✅
- ✅ FO: حذف `CallbackUrl` از `Index.razor.cs` و `Checkout.razor.cs`
- ✅ جدول Callback URL‌ها:
| فلو | Callback URL | منبع |
|-----|-------------|------|
| خرید پکیج | `FrontOfficeBaseUrl/profile/payment-callback?orderId=X` | config |
| کیف‌پول جادویی | `CmsBaseUrl/api/wallet/verify-magic-charge` | config |
| کیف‌پول اعتباری | `CmsBaseUrl/api/wallet/verify-discount-charge` | config |
| فروشگاه اعتباری | `CmsBaseUrl/api/payment/discount-order/callback?orderId=X` | config |
| تراکنش عمومی | `FrontOfficeBaseUrl/profile/payment-callback` | config |
### محتوا و ناوبری
- ✅ بلاگ — لیست + جزئیات + pagination
@@ -98,7 +200,7 @@
---
## ۳. CMS Core (93% کامل)
## ۳. CMS Core (97% کامل)
### ساختار و معماری
- ✅ CQRS با MediatR — Commands + Queries + Handlers
@@ -129,6 +231,34 @@
- ✅ Club Features — activate/deactivate
- ✅ DayaLoan Integration — Hangfire + Polly
### 🪄 کیف‌پول جادویی (Magic Wallet) — فاز 1-6 ✅
- ✅ فاز ۱: WalletMode enum + UserWallet fields + ClubMembershipCycle entity + TransactionType (14,15)
- ✅ فاز ۲: Trigger ورود/خروج Magic در SubmitShopBuyOrder + ActivateClubMembership Cycle
- ✅ فاز ۳: ChargeMagicWallet + VerifyMagicWalletCharge + HTTP callback + gRPC RPCs
- ✅ فاز ۴: فیلتر Magic از کمیسیون (C# + SP) + تاریخ Cycle
- ✅ فاز ۵: MagicWallet.razor UI + WalletService + تایل داشبورد
- ✅ فاز ۶: محدودیت دایا بعد از دور اول + محدودیت فعالسازی در حالت Magic
### VAT
- ✅ اصلاح VAT از 9% به 10% در همه فایل‌ها (VatCalculator, UserOrderService, Checkout, VATService)
### فعال‌سازی درگاه و تنظیمات محیطی (اسفند ۱۴۰۴)
- ✅ تنظیم MerchantId جدید ZarinPal — `4225d555-5fa9-4df0-9b61-1ce152cbbba8`
- ✅ تنظیمات محیطی — Staging: UseSandbox=true / Production: UseSandbox=false
- ✅ فعال‌سازی MagicWalletCycleSeed در Production
- ✅ تنظیم Kestrel Http2 + Seq logging برای Production
- ✅ بهبود user_name در proto — فیلد جدید در GetAllUserWalletByFilterResponseModel
- ✅ اغنای پاسخ UserWalletService — join با جدول Users برای نمایش نام کاربر
- ✅ ارتقای Proto NuGet به نسخه 0.0.183
### فیکس‌های پرداخت و امنیت (اسفند ۱۴۰۴ — Phase 11)
-**ZarinPal Verify fix** — رفع باگ amount=0 در VerifyPaymentAsync (Code=-1) — ۳ آرگومانه overload
-**تصحیح مدل تومان/ریال** — DB به تومان ذخیره می‌کند، فقط ZarinPal ریال (×۱۰) نیاز دارد
-**Callback URL از config**`PackageService` و `TransactionsService` از `FrontOfficeBaseUrl` config می‌خوانند (نه از ورودی)
-**فیکس مسیر redirect**`/magic-wallet``/profile/magic-wallet` در PaymentCallbackController
-**appsettings.Development.json** — URL‌های محلی برای توسعه (CmsBaseUrl + FrontOfficeBaseUrl)
- ✅ کامیت‌ها: `721661a``ed2b20a``0107308`
### محتوا
- ✅ Blog CRUD — با pagination
- ✅ SitePage Settings — JSON typed
@@ -139,7 +269,7 @@
---
## ۴. Deployment و زیرساخت (90% کامل)
## ۴. Deployment و زیرساخت (95% کامل)
- ✅ Docker Compose — تمام سرویس‌ها
- ✅ Dockerfile (CMS) — multi-stage build
@@ -151,8 +281,12 @@
- ✅ Health Check scripts — K8s + service
- ✅ Mirror Configuration — Docker + NuGet
- ✅ Base Image Caching — pull + save + load
- ✅ مرج پروداکشن CMS — حل conflict در appsettings.Production.json + حذف migration تکراری u21
- ✅ مرج پروداکشن FrontOffice — ۲۱ فایل، ۴۰۰ insertion + فیکس GwUrl
- ✅ مرج پروداکشن BackOffice — ۳۶ فایل، بدون conflict
- ✅ اجرای Migration روی پروداکشن — ExpandDiscountProductFullInformation روی DB کی‌بی‌اس
- ⬜ Monitoring (Prometheus/Grafana) — برنامه‌ریزی‌شده
- ⬜ Log Aggregation (ELK/Seq) — برنامه‌ریزی‌شده
- ⬜ Log Aggregation (ELK/Seq) — Seq تنظیم شده در Production (http://seq-svc:5341)
---
@@ -168,6 +302,21 @@
- ✅ Proto Package Unification — یک package مشترک
- ✅ Binary Tree Reconstruction — از سیستم قدیم
### DataMigration Tool (اسفند ۱۴۰۴)
- ✅ ابزار مستقل مهاجرت داده — .NET 9 Console + Dapper + Polly + Serilog
- ✅ Smart Retry Policy — فقط خطاهای transient SQL (deadlock, timeout, transport) — نه خطاهای منطقی
- ✅ FK Disable/Enable — `NOCHECK`/`CHECK` حول مهاجرت برای حل FK violation
- ✅ TruncateTargetTables — حل مشکل duplicate key (IX_ClubMembership_UserId)
- ✅ Fallback Table Name — اگر جدول مقصد rename شده (`UserWalletChangeLogs``UserWalletHistories`)
- ✅ PostMigration SQL — همه مراحل با `IF COL_LENGTH` / `OBJECT_ID` guard شده
- ✅ کامیت‌ها: `0e8c6fd``8385c90``31cc464`
### EF Migration — Staging (اسفند ۱۴۰۴)
- ✅ اعمال migrations روی DB استیجینگ KBS (`185.252.31.42,2019/KBS`) — موفق
- ✅ اعمال migrations روی DB اپلیکیشن (`194.5.195.53,31433/Foursat`) — موفق
- ✅ آخرین migration: `20260227024734_Q27_HistoryTables_And_RenameWalletHistory` (۵۵ migration مجموع)
- ✅ حل خطای لاگین `Invalid column name 'FirstActivationDate'` — دو DB مختلف بودند
---
## ۶. مستندات (100% کامل)
@@ -181,7 +330,157 @@
---
## ۷. Timeline (جدول زمانی)
## ۷. تحول سیستم پکیج‌بیس (Package-Based Transformation) — 90%
> 📦 تبدیل سیستم تک‌پکیجی hardcoded به معماری چند‌پکیجی داینامیک
> مرجع: [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | [PACKAGE-TRANSFORMATION-TASKS.md](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md)
### Phase 0 — فیکس باگ‌های فوری ✅ (`8b9c317`, `fe3edd1`)
- ✅ B1: DiscountBalance شارژ نمی‌شد در VerifyGoldenPackagePurchase — اضافه `DiscountBalance += Amount × 2` + WalletChangeLog
- ✅ B2: UserPackagePurchase ساخته نمی‌شد در VerifyGoldenPackagePurchase — ساخت record بعد verify
- ✅ B3: UserPackagePurchase ساخته نمی‌شد در VerifyPackagePurchase — ساخت record
- ✅ B4: UserPackagePurchase ساخته نمی‌شد در VerifyBasePackagePayment — ساخت record
- ✅ B6: EXIT Magic Mode — ریست PackagePurchaseMethod + بستن چرخه فعلی
### Phase 1 — زیرساخت Domain ✅ (`ae92ab8`)
- ✅ T1.1: Package Entity — ۱۱ فیلد جدید (SortOrder, IsActive, IsBasePackage, DiscountMultiplier, MagicWalletMultiplier, ...)
- ✅ T1.2: PackageFeature Entity — رابطه M:N بین Package و ClubFeature
- ✅ T1.3: ClubMembership — ۴ فیلد First/Last Activation + PackageId
- ✅ T1.4-T1.6: اضافه PackageId به ClubMembershipCycle, WeeklyCommissionPool, UserCommissionPayout, NetworkWeeklyBalance, UserWalletChangeLog
- ✅ T1.7: علامت‌گذاری ۹ SystemConstants به‌عنوان [Obsolete]
- ✅ T1.8: EF Configurations — Index, Precision, FK relations
### Phase 1.5 — Migration + Data Seed ✅ (`a9cd2fd`)
- ✅ EF Migration `AddPackageBasedSystem` — ستون‌ها + جداول + ایندکس‌ها
- ✅ Golden Package Seed (Id=1) — Price=56M, ActivationFee=25.2M, DiscountMultiplier=2.0, MagicWalletMultiplier=2.5
- ✅ Data Backfill — تمام رکوردهای موجود → PackageId=1
- ✅ فیکس nullable DateTime/long در ClubMembership
- ✅ فیکس Shadow FK PackageId1
### Phase 2 — منطق کسب‌وکار ✅ (`8e5c7c5`)
- ✅ T2.1: ActivateClubMembership — ActivationFee از Package entity
- ✅ T2.2: AcceptClubMembershipContract — Package features از DB
- ✅ T2.3: CalculateWeeklyBalances — MaxBalancesPerLeg per-package
- ✅ T2.4: ProcessUserPayouts — PackageId tracking
- ✅ T2.5: DayaLoans — Package.Price بجای hardcoded
- ✅ T2.6: ManualPayment — Package.Price بجای hardcoded
- ✅ T2.7: InitiateBasePackage/VerifyBasePackage — Package-based
- ✅ T2.8: ChargeMagicWallet/VerifyMagicWalletCharge — MagicWalletMultiplier per-package
- ✅ T2.9: OrmCommissionCalculationStrategy — MaxBalancesPerLeg/MaxNetworkLevel per-package
- ✅ T2.10: ConfigurationService/UserOrderService/UserWalletService — Package reads
-**نتیجه:** صفر مصرف SystemConstants deprecated باقی مانده
### Phase 3 — بازسازی لایه Package ✅ (`ccb938e`)
- ✅ Proto: ۱۱ فیلد جدید در ۵ message (CreateNewPackageRequest, UpdatePackageRequest, GetPackageResponse, ...)
- ✅ GetUserPackageStatus — پیاده‌سازی (قبلاً NotImplementedException بود!)
- ✅ CustomerVerifyPackagePurchase — شارژ کیف‌پول اضافه شد (قبلاً missing بود!)
- ✅ VerifyGoldenPackagePurchase — `package.DiscountMultiplier` بجای hardcoded ×2
- ✅ GetAllPackageByFilter — فیلتر IsDeleted
- ✅ GetCustomerPackages — فیلتر IsDeleted + IncludeInactive + SortOrder
- ✅ GetCustomerPackageDetails — PackageFeatures از DB
- ✅ GetCustomerPurchaseHistory — Include Transaction
- ✅ UpdatePackageCommand — ۱۲ فیلد جدید
### Phase 4 — تکمیل CRUD + Legacy Fixes ✅ (`0002a5a`)
- ✅ CreateNewPackageCommand — ۱۲ فیلد جدید با default‌های مناسب
- ✅ GetPackageResponseDto — ۱۲ فیلد جدید (Mapster auto-map)
- ✅ GetAllPackageByFilterResponseModel — ۱۲ فیلد جدید
- ✅ PurchaseGoldenPackage — حذف Title string match شکننده (`"طلایی"/"golden"`) → `IsDeleted/IsActive/SupportsDirectPurchase`
- ✅ VerifyPackagePurchase — حذف hardcoded `order.Amount × 2``package.DiscountMultiplier` از DB
### Phase 5 — پورسانت per-package + پاکسازی golden ✅ (`607f791`, `7176fe4`)
- ✅ ORM Commission: per-user-package calculation via `ClubMembership.LastPackageId`
- userPackageMap، per-user maxBalancesPerLeg/maxNetworkLevel
- Carryover keyed by (UserId, PackageId) tuple
- ✅ SP Commission: loop over packages، pass `@PackageId/@InputMaxBalancesPerLeg/@InputMaxNetworkLevel`
- ✅ sp_CalculateWeeklyBalances: ۳ پارامتر جدید، فیلتر `cm.LastPackageId = @PackageId`، ستون PackageId در INSERT
- ✅ Fix: `cm.PackageId``cm.LastPackageId` — match actual DB column name
- ✅ Fix golden/طلایی string refs in user-facing messages (ActivateClubMembership)
- ✅ Rename `HasPurchasedGoldenPackage``HasPurchasedPackage` (DTO + Handler + Proto + Mapping)
### Phase 6 — Deprecation cleanup + ConfigurationService ✅ (`d19c569`)
- ✅ Mark `PurchaseGoldenPackage`/`VerifyGoldenPackagePurchase` RPCs as `deprecated = true`
- ✅ Mark `InitiateBasePackagePayment`/`VerifyBasePackagePayment` RPCs as `deprecated = true`
- ✅ Remove deprecated SystemConstants from `GetAllAsDict`/`GetAllWithDescriptions` helpers
- ✅ Add MagicWallet per-package values to ConfigurationService (Multiplier, MaxDeposit, MaxCredit)
- ✅ تأیید: صفر رفرنس فعال به ۹ SystemConstants منسوخ — dead code آماده حذف
### Phase 7 — UI ✅ (گزارش per-package)
- ⬜ FrontOffice: کاشی‌های داینامیک پکیج
- ⬜ FrontOffice: MyPackages + re-purchase
- ✅ FrontOffice: Commission Dashboard per-package — فیلتر dropdown پکیج + ستون پکیج + MudChip (دسکتاپ + موبایل)
- ✅ FrontOffice: WeeklyBalance per-package — فیلتر MudSelect پکیج + MudChip اطلاعات هفته
- ✅ BackOffice: فیلتر پکیج در گزارش‌ها — PackageSelect component + UserPayouts + BalancesReport
### Phase 8e — Per-Package Commission Reports ✅ (CMS:`aaaf7fc` FO:`a956cb9` BO:`8be98ae`)
- ✅ Proto: اضافه `package_id` فیلتر به ۴ request + `package_id`/`package_title` به ۴ response model
- ✅ CMS: اضافه PackageId فیلتر به ۴ query + handler + ۳ DTO + CommissionProfile mapping
- ✅ BO: کامپوننت PackageSelect + فیلتر و ستون پکیج در UserPayouts + BalancesReport
- ✅ FO: فیلتر و ستون پکیج در CommissionDashboard + WeeklyBalance
- ✅ NuGet: `0.0.186``0.0.187`
### Phase 9 — Q24-Q30 Business Decisions + History Infrastructure ✅
#### 9a: Q24+Q26 — Balance Threshold + SP Worker ✅ (CMS:`a1024a3`)
- ✅ Q24: آستانه موجودی `Balance <= 1_000_000` ریال برای ورود Magic و خرید مجدد (بجای `== 0`)
- ✅ Q26: `StoredProcedureDeploymentService` (IHostedService) — خواندن فایل‌های `.sql` از embedded resource، مقایسه checksum و اعمال خودکار در startup
#### 9b: Q27 — History Tables Entities ✅ (CMS:`fdbb91d`)
-`PackageHistory` entity — فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive
-`ClubMembershipCycleHistory` entity — فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt
-`PackageAction` و `ClubMembershipCycleAction` enums
- ✅ EF Configurations + DbSets + Navigation Properties
#### 9c: Q28 — UI Guidance ✅ (FO:`474d364` BO:`6939780`)
- ✅ FrontOffice: ۷ صفحه با MudAlert (G1-G7) — Packages, Checkout, MyPackages, MagicWallet, Commission, Membership, ActivationSection
- ✅ BackOffice: ۶ صفحه با MudAlert (G8-G13) — PackageCRUD, ClubFeatures, ManualPayments, Commission Dashboard, UserPayouts, ClubMembers
#### 9d: Rename + History Interceptor + Migration ✅ (CMS:`10d2ca2`)
- ✅ تغییر نام `UserWalletChangeLog``UserWalletHistory` در ۵۴+ فایل (entities, configs, DTOs, commands, queries, protos, services)
- ✅ تغییر نام ۳۴ فایل و ۱۱ دایرکتوری
- ✅ تغییر نام proto: `userwalletchangelog.proto``userwallethistory.proto`
-`IHasHistory<T>` generic interface — متد `CreateHistorySnapshot` برای ثبت خودکار
-`HistoryTrackingSaveChangesInterceptor` — reflection-based، auto-fill Old* از OriginalValues
-`Package` implements `IHasHistory<PackageHistory>`
- ✅ EF Migration `Q27_HistoryTables_And_RenameWalletHistory`**RenameTable** (حفظ داده) + rename PK/FK/Index via sp_rename
- ✅ NuGet: `0.0.187``0.0.188`
### Phase 10 — استقرار + DataMigration + UI خرید پکیج ✅
#### 10a: DataMigration Tool ✅ (Local — بدون remote)
- ✅ ابزار مستقل مهاجرت داده — .NET 9 Console app + Dapper (bulk copy) + Polly (retry) + Serilog (logging)
- ✅ مهاجرت ۱۸ جدول از DB پروداکشن (`185.252.31.42,2019/Foursat`) به استیجینگ (`KBS`)
- ✅ Smart Retry — فقط خطاهای transient (deadlock/timeout/transport)، نه خطاهای منطقی
- ✅ FK Disable/Enable — `ALTER TABLE NOCHECK/CHECK CONSTRAINT` حول هر مهاجرت
- ✅ TruncateTargetTables — حل duplicate key (`IX_ClubMembership_UserId`) هنگام اجرای مجدد
- ✅ Fallback Table Name — جدول مقصد rename شده؟ (`UserWalletChangeLogs``UserWalletHistories`)
- ✅ PostMigration SQL — همه مراحل با `IF COL_LENGTH`/`OBJECT_ID` guard شده (سازگار با هر دو schema)
- ✅ کامیت‌ها: `0e8c6fd``8385c90` (MERGE fix) → `31cc464` (FK+truncate+PostMigration)
#### 10b: EF Migration Staging ✅
- ✅ اعمال ۵۵ migration روی DB استیجینگ KBS (`185.252.31.42,2019;Database=KBS`)
- ✅ اعمال ۵۵ migration روی DB اپلیکیشن (`194.5.195.53,31433;Database=Foursat`)
- ✅ آخرین migration: `20260227024734_Q27_HistoryTables_And_RenameWalletHistory`
- ✅ حل خطای لاگین: `Invalid column name 'FirstActivationDate'` — CMS به DB دیگری وصل بود
#### 10c: PackagePurchaseDialog — دیالوگ داینامیک خرید (FO:`a3681a8`)
-`PackagePurchaseDialog.razor` — دیالوگ ۲ مرحله‌ای جایگزین دیالوگ hardcoded «پکیج پایه»
- ✅ مرحله ۱: نمایش کاشی‌های پکیج (responsive grid 2-3 ستونه) با عنوان + قیمت + ویژگی‌ها + badge «پایه»
- ✅ مرحله ۲: انتخاب روش پرداخت (مستقیم + اعتبار دایا) با خلاصه پکیج انتخابی
- ✅ بارگذاری از `PackageService.GetAllPackagesAsync()` + `PackagePurchaseResult` record
- ✅ محدودیت دایا: فقط `SupportsDayaPurchase && PurchaseCycleCount == 0`
- ✅ CSS: `.pkg-tile`, `.pkg-tile-badge`, `.pkg-payment-option` در `site.css`
- ✅ NuGet: `0.0.188``0.0.189`
#### 10d: ۴ فیکس UI پکیج (FO:`3c1a8ff`)
-**Toman/Rial**: قیمت از سرور به ریال ← `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
-**لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیج‌ها)
-**دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
-**HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
---
## ۸. Timeline (جدول زمانی)
| زمان | رویداد | درصد پروژه |
|------|--------|-----------|
@@ -189,8 +488,15 @@
| آبان ۱۴۰۳ | CQRS + gRPC + EF Core | 20% |
| آذر ۱۴۰۳ | باشگاه + درخت باینری + کمیسیون | 35% |
| دی ۱۴۰۳ | فروشگاه عادی + پرداخت از کیف‌پول | 45% |
| بهمن ۱۴۰۳ | فروشگاه تخفیفی + وام دایا | 55% |
| بهمن ۱۴۰۳ | فروشگاه اعتباری + وام دایا | 55% |
| اسفند ۱۴۰۳ (هفته ۱) | UI Modernization Phase 1-3 | 65% |
| اسفند ۱۴۰۳ (هفته ۲) | Site Pages + BackOffice audit | 75% |
| اسفند ۱۴۰۳ (هفته ۳) | Inventory + Lazy Load + Images | 85% |
| اسفند ۱۴۰۳ (هفته ۴) | مستندات + نهایی‌سازی | 95% |
| اسفند ۱۴۰۴ (هفته ۱-۲) | 🪄 کیف‌پول جادویی (فاز 1-6) + اصلاح VAT 10% | 96% |
| اسفند ۱۴۰۴ (هفته ۳) | 🚀 فعال‌سازی درگاه + بهبود UI ادمین + مرج پروداکشن | 97% |
| اسفند ۱۴۰۴ (هفته ۴) | 📦 تحول پکیج‌بیس فاز ۰-۶ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation) | 97% |
| اسفند ۱۴۰۴ (هفته ۵) | 📦 فاز 8e: گزارش‌های پورسانت per-package (Proto + CMS + BO + FO) | 98% |
| اسفند ۱۴۰۴ (هفته ۶) | 📦 فاز ۹: Q24-Q30 (آستانه + SP Worker + History Tables + UI Guidance + Rename + Interceptor + Migration) | 99% |
| اسفند ۱۴۰۴ (هفته ۷) | 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes (Toman/Rial + لیبل + HTML) | 99.5% |
| اسفند ۱۴۰۴ (هفته ۸) | 💳 فاز ۱۱: فیکس ZarinPal Verify + اصلاح تومان/ریال + صفحه موفقیت + حذف دوبار ×۱۰ + امنیت Callback URL | 99.5% |
+8 -3
View File
@@ -1,7 +1,7 @@
# 📖 واژه‌نامه، استانداردها و قراردادهای کد
> **اصطلاحات فارسی/انگلیسی، الگوهای نام‌گذاری و استانداردهای حرفه‌ای**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet)
---
@@ -23,11 +23,16 @@
| Pool هفتگی | Weekly Commission Pool | مخزن کمیسیون قابل‌توزیع |
| هزینه فعالسازی | Activation Fee | ۲۵M تومان از Balance |
| واریز هدیه | Gift Value | ۲۵.۲M واریز به Pool |
| فروشگاه تخفیفی | Discount Store | فروشگاه با تخفیف per-product برای اعضا |
| فروشگاه اعتباری | Discount Store | فروشگاه اعتباری per-product برای اعضا |
| پرداخت ترکیبی | Hybrid Payment | DiscountBalance + IPG |
| وام دایا | Daya Loan | وام آنلاین برای خرید پکیج |
| کد معرف | Referral Code | کد یکتا هر عضو برای دعوت |
| موجودی | Inventory | تعداد محصول در انبار |
| کیف‌پول جادویی | Magic Wallet | حالت ویژه: Balance=0 → شارژ ×2.5 از درگاه |
| حالت جادویی | Magic Mode | WalletMode=1 — کمیسیون غیرفعال |
| ضریب شارژ | Magic Multiplier | واریز × 2.5 = اعتبار Balance |
| سقف دور | Per-Cycle Cap | 100M تومان واریز → 250M اعتبار |
| دوره عضویت | Membership Cycle | ClubMembershipCycle — هر خرید پکیج = یک دور |
### ۱.۲ مفاهیم فنی
@@ -40,7 +45,7 @@
| سرویس پرداخت | PYMS | Payment Management Service |
| کیف‌پول نقدی | Balance Wallet | موجودی قابل‌خرج |
| کیف‌پول طلایی | Network Balance | برای محاسبه کمیسیون (شارژ نمی‌شود) |
| کیف‌پول تخفیفی | Discount Balance | برای فروشگاه تخفیفی (IPG و Daya: 112M — دو برابر BasePackageAmount) |
| کیف‌پول اعتباری | Discount Balance | برای فروشگاه اعتباری (IPG و Daya: 112M — دو برابر BasePackageAmount) |
| بارگذاری تنبل | Lazy Loading | لود محصولات 12تایی (FO) / 10تایی (CMS default) |
| صفحات سایت | Site Pages | صفحات قابل‌ویرایش (Shopify-style) |
| ثوابت سیستمی | System Constants | تنظیمات key-value |
+24 -7
View File
@@ -1,7 +1,7 @@
# 🗺️ نقشه راه، ریسک‌ها و کارهای باقیمانده
> **Roadmap + Risk Register + Dependencies + Priorities**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱ — فیکس‌های پرداخت ZarinPal + امنیت Callback URL + تومان/ریال)
---
@@ -11,11 +11,13 @@
██████████████████████████████████████████████████ 95%
Core Platform ████████████████████████████████████████████████ 98%
Club System █████████████████████████████████████████████░░░ 95%
Club System ████████████████████████████████████████████████ 98%
E-Commerce ████████████████████████████████████████████████ 98%
Payment ██████████████████████████████████████░░░░░░░░░ 80%
UI/UX ████████████████████████████████████████░░░░░░░ 85%
Deployment ████████████████████████████████████████████░░ 90%
Payment ████████████████████████████████████████████████ 99%
Magic Wallet ████████████████████████████████████████████████ 100%
Package-Based ███████████████████████████████████████████████░░ 97%
UI/UX ██████████████████████████████████████████████░░░ 95%
Deployment ██████████████████████████████████████████████░░ 95%
Documentation ████████████████████████████████████████████████ 100%
```
@@ -197,8 +199,23 @@ gantt
## ۸. خلاصه اولویت‌بندی
```
NOW (این ماه):
→ Documentation consolidation ✅ DONE
DONE (اسفند ۱۴۰۴):
→ Documentation consolidation ✅
→ 🪄 Magic Wallet فاز 1-6 ✅ (کامل)
→ اصلاح VAT 9% → 10% ✅
→ فعال‌سازی درگاه ZarinPal (پروداکشن) ✅
→ تنظیمات محیطی Staging/Production ✅
→ بهبود UI ادمین (UserAutoComplete + نام کاربر در کیف‌پول) ✅
→ مرج پروداکشن هر ۳ ریپو (CMS + FO + BO) ✅
→ اجرای Migration روی پروداکشن ✅
→ 📦 تحول پکیج‌بیس فاز 0-6 ✅ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation)
→ 📦 فاز ۹: Q24-Q30 + History + Rename + Interceptor ✅
→ 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes ✅
→ 💳 فاز ۱۱: فیکس ZarinPal Verify (amount=0) + تصحیح مدل تومان/ریال + صفحه موفقیت پرداخت + حذف دوبار ×۱۰ + امنیت Callback URL ✅
NOW (این ماه):
→ تست کامل پروداکشن
→ فیکس باگ‌های کشف‌شده در تست
NEXT (فروردین):
→ Mobile Responsive (H2)
+81
View File
@@ -0,0 +1,81 @@
# 📋 فیچر بکلاگ — RPCهای آماده (بدون UI)
> تاریخ: ۱۴۰۴/۱۲/۱۰
> منبع: آدیت gRPC (کامیت `3575e48`) → ۱۲ RPC کامل بدون فرانت
> اولویت‌بندی: بر اساس ارزش کسب‌وکار + نیازمندی پکیج‌بیس
---
## 🎯 خلاصه
از ۲۴ RPC مُرده شناسایی‌شده، **۱۲ عدد پیاده‌سازی کامل** دارند ولی هرگز از فرانت‌ها وصل نشدند. این‌ها فیچرهای آماده هستند که فقط نیاز به UI دارند.
---
## 📊 ماتریس فیچر × اولویت
### 🔴 اولویت بالا — مرتبط با پکیج‌بیس کردن
| # | RPC | تارگت | صفحه | اقدام | تخمین |
|---|-----|-------|------|-------|-------|
| F1 | `AssignFeatureToMembership` | BackOffice | ClubFeaturesPage.razor | دکمه «اختصاص فیچر به عضو» + ماتریس PackageFeature | ۴ ساعت |
| F2 | `ChangeNetworkParent` | BackOffice | UserNetworkInfo.razor | ✅ دکمه «تغییر والد» + مودال ChangeParentDialog | BO:`e020354` |
| F3 | `CalculateOrderPV` | FrontOffice | Store/OrderDetail.razor | ✅ نمایش PV سفارش + PV هر محصول | FO:`3bffc13` |
### 🟡 اولویت متوسط — بهبود UX فروشگاه
| # | RPC | تارگت | صفحه | اقدام | تخمین |
|---|-----|-------|------|-------|-------|
| F4 | `CustomerReorderPreviousOrder` | FrontOffice | OrderHistory (Store/Discount) | دکمه «تکرار سفارش» در هر ردیف تاریخچه | ۳ ساعت |
| F5 | `CustomerTrackOrder` | FrontOffice | OrderTracking.razor | وصل Tracking API → نمایش TrackingCode + وضعیت ارسال | ۴ ساعت |
| F6 | `UpdateCustomerSettings` | FrontOffice | Profile/Settings.razor | فرم تنظیمات اعلان (Email/SMS/Push) + دکمه ذخیره | ۳ ساعت |
| F7 | `GetLowStockProducts` | BackOffice | LowStockPage.razor | وصل API → فیلتر threshold + هشدار بصری | ۳ ساعت |
### 🟢 اولویت پایین — گزارش‌دهی و عملیات انبوه
| # | RPC | تارگت | صفحه | اقدام | تخمین |
|---|-----|-------|------|-------|-------|
| F8 | `GetInventorySummary` | BackOffice | InventoryMainPage.razor | کارت خلاصه بالای صفحه (تعداد کل + ارزش ریالی) | ۳ ساعت |
| F9 | `GetStockValueReport` | BackOffice | InventoryMainPage.razor | تب «گزارش ارزش» + دانلود Excel | ۴ ساعت |
| F10 | `BulkAddStock` | BackOffice | InventoryMainPage.razor | دکمه «افزودن دسته‌ای» + آپلود CSV/فرم چندتایی | ۶ ساعت |
| F11 | `BulkUpdateProductStock` | BackOffice | InventoryMainPage.razor | دکمه «بروزرسانی دسته‌ای» (Set/Add/Subtract) | ۶ ساعت |
| F12 | `GetConfigurationByKey` | Internal | — | بدون UI — استفاده داخلی بهینه بجای GetAll | ۰ |
---
## 📐 نقشه پیاده‌سازی
### فاز A — همراه پکیج‌بیس (فاز ۴ BIZ-PACKAGE-BASED-SYSTEM)
```
F1 (AssignFeatureToMembership) → با T4.6 (ماتریس PackageFeature) ادغام
F2 (ChangeNetworkParent) ✅ تکمیل → BO:`e020354`
F3 (CalculateOrderPV) ✅ تکمیل → FO:`3bffc13`
```
### فاز B — بعد از پکیج‌بیس (Sprint بعدی)
```
F4 → F7: بهبود UX فروشگاه و مشتری
تخمین: ۱۳ ساعت = ~۲ روز
```
### فاز C — آینده (بدون فوریت)
```
F8 → F12: گزارش‌دهی و عملیات انبوه
تخمین: ۱۹ ساعت = ~۳ روز
```
---
## 🔗 ارجاعات
| مستند | محتوا |
|-------|-------|
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت کامل ۳۴۲ RPC — ۱۲ نگهداری + ۱۲ آرشیو |
| [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی سیستم پکیج‌بیس — ۳۹ تغییر |
---
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۱۰ — F2+F3 تکمیل | باقی‌مانده فاز A: F1*
+39 -42
View File
@@ -2,7 +2,8 @@
> **مرجع:** [MAGIC-WALLET-SPEC](./MAGIC-WALLET-SPEC.md)
> **تخمین کل:** ~۷ روز کاری
> **وابستگی مستقل:** تکمیل ChargeDiscountWallet (ربطی به جادویی ندارد)
> **وضعیت:** ✅ کامل — همه ۶ فاز پیاده‌سازی و مرج شده
> **وابستگی مستقل:** تکمیل ChargeDiscountWallet (ربطی به جادویی ندارد) ✅
---
@@ -178,51 +179,47 @@
## Checklist پیاده‌سازی
- [ ] **فاز ۱:** WalletMode enum
- [ ] **فاز ۱:** UserWallet entity + 5 فیلد جدید
- [ ] **فاز ۱:** ClubMembershipCycle entity (جدید)
- [ ] **فاز ۱:** TransactionType + 2 مقدار
- [ ] **فاز ۱:** SystemConstants + 3 ثابت
- [ ] **فاز ۱:** EF Configuration (UserWallet + ClubMembershipCycle)
- [ ] **فاز ۱:** Migration: AddMagicWalletFields
- [ ] **فاز ۱:** Migration: AddClubMembershipCycle + data seed
- [ ] **فاز ۲:** Trigger ورود Magic (SubmitShopBuyOrder)
- [ ] **فاز ۲:** Trigger خروج Magic
- [ ] **فاز ۲:** ActivateClubMembership → ActivatedAt نگه‌داشته بشه + Cycle جدید
- [ ] **فاز ۲:** PurchaseCycleCount
- [ ] **فاز ۳:** InitiateMagicChargeCommand + Handler
- [ ] **فاز ۳:** VerifyMagicChargeCommand + Handler
- [ ] **فاز ۳:** MagicWalletController (HTTP callback)
- [ ] **فاز ۳:** gRPC Proto + Service
- [ ] **فاز ۳:** ۲ تراکنش + ۱ لاگ (اجباری)
- [ ] **فاز ۴:** فیلتر کمیسیون Magic (C# handler + SP)
- [ ] **فاز ۴:** تاریخ کمیسیون: ActivatedAt → Cycle.PackagePurchasedAt (C# + SP)
- [ ] **فاز ۵:** MagicWallet.razor
- [ ] **فاز ۵:** MagicPaymentCallback.razor
- [ ] **فاز ۵:** WalletService gRPC client
- [ ] **فاز ۵:** Profile + Wallet page updates
- [ ] **فاز ۶:** Daya restriction
- [ ] **فاز ۶:** Club activation restriction
- [x] **فاز ۱:** WalletMode enum
- [x] **فاز ۱:** UserWallet entity + 5 فیلد جدید
- [x] **فاز ۱:** ClubMembershipCycle entity (جدید)
- [x] **فاز ۱:** TransactionType + 2 مقدار
- [x] **فاز ۱:** SystemConstants + 3 ثابت
- [x] **فاز ۱:** EF Configuration (UserWallet + ClubMembershipCycle)
- [x] **فاز ۱:** Migration: AddMagicWalletFields (u21 — اعمال شده ✅)
- [x] **فاز ۱:** Migration: AddClubMembershipCycle + data seed (74 رکورد seed شده ✅)
- [x] **فاز ۲:** Trigger ورود Magic (SubmitShopBuyOrder)
- [x] **فاز ۲:** Trigger خروج Magic
- [x] **فاز ۲:** ActivateClubMembership → ActivatedAt نگه‌داشته بشه + Cycle جدید
- [x] **فاز ۲:** PurchaseCycleCount
- [x] **فاز ۳:** InitiateMagicChargeCommand + Handler
- [x] **فاز ۳:** VerifyMagicChargeCommand + Handler
- [x] **فاز ۳:** MagicWalletController (HTTP callback)
- [x] **فاز ۳:** gRPC Proto + Service
- [x] **فاز ۳:** ۲ تراکنش + ۱ لاگ (اجباری)
- [x] **فاز ۴:** فیلتر کمیسیون Magic (C# handler + SP)
- [x] **فاز ۴:** تاریخ کمیسیون: ActivatedAt → Cycle.PackagePurchasedAt (C# + SP)
- [x] **فاز ۵:** MagicWallet.razor
- [x] **فاز ۵:** MagicPaymentCallback — نتیجه پرداخت از طریق ?payment= query param در همان MagicWallet.razor هندل میشه
- [x] **فاز ۵:** WalletService gRPC client
- [x] **فاز ۵:** Profile + Wallet page updates
- [x] **فاز ۶:** Daya restriction (CheckAndProcessDayaLoansCommandHandler + FO Purchase UI)
- [x] **فاز ۶:** Club activation restriction (ActivateClubMembershipCommandHandler + WalletMode guard)
---
## وابستگی مستقل: تکمیل ChargeDiscountWallet
## وابستگی مستقل: تکمیل ChargeDiscountWallet
> ⚠️ **این کار ربطی به کیف‌پول جادویی ندارد** و باید مستقل انجام شود.
> **تکمیل شد** — مستقل از کیف‌پول جادویی پیاده‌سازی شد.
```
مشکل فعلی:
├── ChargeDiscountWalletCommandHandler — CQRS handler موجوده
├── VerifyDiscountWalletChargeCommandHandler — موجوده
├── Callback URL = "/api/wallet/verify-discount-charge" — ست شده
├── HTTP Controller endpoint — ❌ وجود ندارد
├── gRPC RPC — ❌ در proto تعریف نشده
── FrontOffice page — ❌ صفحه شارژ وجود ندارد
کار لازم:
── WalletController.cs → GET /api/wallet/verify-discount-charge
├── userwallet.proto → rpc ChargeDiscountWallet
├── UserWalletService.cs → implement RPC
├── FrontOffice → صفحه شارژ DiscountBalance + callback
└── تست end-to-end
انجام شده:
├── ChargeDiscountWalletCommandHandler — CQRS handler ✅
├── VerifyDiscountWalletChargeCommandHandler — ✅
├── PaymentCallbackController → GET /api/wallet/verify-discount-charge
├── userwallet.proto → rpc InitiateDiscountCharge ✅
├── UserWalletService.cs → InitiateDiscountCharge override ✅
── WalletService.cs (FO) → InitiateDiscountChargeAsync ✅
├── ChargeDiscountWallet.razor + .razor.cs (FO) ✅
├── RouteConstants → ChargeDiscountWallet ✅
── Wallet.razor → دکمه شارژ اعتباری ✅
```
+1 -1
View File
@@ -143,7 +143,7 @@ stateDiagram-v2
| قابلیت | Normal Mode | Magic Mode |
|--------|-------------|------------|
| خرید از فروشگاه عادی | ✅ | ✅ |
| خرید از فروشگاه تخفیفی | ✅ | ✅ (DiscountBalance قبلی) |
| خرید از فروشگاه اعتباری | ✅ | ✅ (DiscountBalance قبلی) |
| کمیسیون هفتگی | ✅ | ❌ |
| پورسانت ۲۵.۲M | ✅ | ❌ |
| شارژ جادویی ×2.5 | ❌ | ✅ |
+864
View File
@@ -0,0 +1,864 @@
# 🔄 نقشه‌راه تحول پکیج‌بیس — تسک‌های گام‌به‌گام
> **وضعیت:** در حال اجرا — **فاز ۰-۱۰ (تکمیل کد + استقرار staging) ✅** | NuGet v0.0.189 | تست باقی‌مانده
> **تاریخ:** ۱۴۰۴/۱۲/۰۸
> **پیش‌نیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/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`
```diff
+ 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`
```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;
}
```
### 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`
```diff
- 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
```bash
dotnet ef migrations add AddPackageBasedSystem
```
**شامل:**
- ستون‌های جدید Package
- جدول PackageFeatures
- FKها در 4 entity
- Unique constraint
### T1.9 — Data Migration Script
```sql
-- 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`
```diff
+ public long PackageId { get; set; }
+ public virtual Package Package { get; set; }
```
**EF Config:** اضافه Unique Index:
```csharp
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)
```sql
-- رکوردهای موجود → پکیج پایه
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 بهبود
**تغییرات:**
```diff
- 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 — تغییرات:**
```diff
wallet.WalletMode = WalletMode.Normal;
wallet.MagicCompletedAt = DateTime.UtcNow;
cycle.MagicCompletedAt = DateTime.UtcNow;
+ user.PackagePurchaseMethod = PackagePurchaseMethod.None;
+ membership.IsActive = false;
+ cycle.IsCurrentCycle = false;
```
**Guard تغییرات:**
```diff
- if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
- throw new RpcException("قبلاً پکیج خریداری شده");
+ if (user.PackagePurchaseMethod != PackagePurchaseMethod.None
+ && !HasCompletedMagicCycle(membership))
+ throw new RpcException("چرخه جاری هنوز تکمیل نشده");
```
### T2.7 — JWT Claims جدید
```diff
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)
```sql
-- 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: ارسال تنظیمات پکیج)
```csharp
// 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)
**تغییرات:**
```diff
- 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)
```csharp
// قبل: فقط 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 باید:**
> 1. ابتدا در staging با داده واقعی تست شود
> 2. نتایج قبل و بعد مقایسه شوند
> 3. Rollback plan آماده باشد
> 4. در production ابتدا read-only اجرا شود (بدون commit)
---
## مرحله ۷ — Migration + Cleanup (سه‌گانه) ✅
> ✅ تکمیل‌شده | ⏱️ **۱ روز** | ریسک: پایین
### فاز 7a — Cosmetic Cleanup ✅
> کامیت: CMS `469d97b` | FO `b82cac4` | BO `f1b0085`
**CMS:**
- حذف orphaned `PurchasePackage` handler (۳ فایل، بدون caller)
- فیکس doc-comments: `طلایی``پکیج` در ۶ فایل (enums, entities, handlers)
**FrontOffice:**
- حذف hardcoded `پکیج طلایی` از `MyPackages.razor` و `Packages.razor`
- اضافه `PackageTitle` property به `UserPackageStatusDto` record
**BackOffice:**
- تغییر label `پکیج طلایی``خرید پکیج` در `UserNetworkInfo.razor`
### فاز 7b — FrontOffice RPC Migration ✅
> کامیت: CMS `161f796` | FO `71f391a`
**CMS:**
- `CustomerPurchasePackage`: embed `orderId` در callback URL قبل از ارسال به درگاه
- `$"{request.CallbackUrl}{separator}orderId={purchase.Id}"`
**FrontOffice:**
- `Profile/Index.razor.cs`: مهاجرت `InitiateBasePackagePaymentAsync``CustomerPurchasePackageAsync`
- `Profile/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-throw `Unimplemented`)
- `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` | BO `89f5241` (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<int> ترتیب نمایش
- `ActivationFee` — MudNumericField<long> هزینه فعال‌سازی
- `DiscountMultiplier` — MudNumericField<double> ضریب تخفیف
- `MagicWalletMultiplier` — MudNumericField<double> ضریب کیف پول جادویی
- `MagicWalletMaxDeposit` — MudNumericField<long> سقف واریز جادویی
- `MagicWalletMaxCredit` — MudNumericField<long> سقف اعتبار جادویی
- `MaxBalancesPerLeg` — MudNumericField<int> حداکثر تعادل هر پا
- `MaxNetworkLevel` — MudNumericField<int> حداکثر سطح شبکه
- `IsActive` — MudCheckBox فعال/غیرفعال
- `IsBasePackage` — MudCheckBox پکیج پایه
- `SupportsDirectPurchase` — MudCheckBox پرداخت مستقیم
- `SupportsDayaPurchase` — MudCheckBox اعتبار دایا
**UpdateDialog.razor:** همان ۱۲ فیلد
**PackageMainPage Grid (۴ ستون جدید):**
- `Price` — فرمت‌شده با N0
- `SortOrder` — ترتیب
- `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 ثابت → از `PackageFeature` API داینامیک
- Specifications: از hardcoded → از `PackageFeature.IsHighlighted` API
- حذف ۵ hardcoded feature string + ۴ hardcoded specification
**PackageService.cs:**
- `PackageDto`: اضافه ۸ فیلد جدید (ActivationFee, DiscountMultiplier, MagicWalletMultiplier, etc.)
- `GetAllPackagesAsync`: مپ فیلدهای جدید از `CustomerPackageModel`
- `GetUserPackageStatusAsync`: از stub → اتصال واقعی به `GetUserPackageStatusAsync` RPC
**Packages.razor:**
- Un-exclude از build (حذف `<Content Remove>` + `<Compile Remove>`)
- جایگزینی ۳ feature bullet hardcoded → dynamic features:
- `SupportsDirectPurchase` → پرداخت مستقیم
- `SupportsDayaPurchase` → پرداخت با اعتبار دایا
- `DiscountMultiplier` → ضریب تخفیف: X.Xx
- `MagicWalletMultiplier` → کیف پول جادویی: X.Xx
- `IsBasePackage` → پکیج پایه ⭐
### فاز 8d — Proto Cleanup ✅
> کامیت: CMS `7554d70` (2 فایل، -103) | FO `40882c8` | BO `c96377a`
**حذف ۴ deprecated RPC:**
- `PurchaseGoldenPackage` — جایگزین: `CustomerPurchasePackage`
- `VerifyGoldenPackagePurchase` — جایگزین: `CustomerVerifyPackagePurchase`
- `InitiateBasePackagePayment` — جایگزین: `CustomerPurchasePackage`
- `VerifyBasePackagePayment` — جایگزین: `CustomerVerifyPackagePurchase`
**حذف ۸ deprecated message type:**
- `PurchaseGoldenPackageRequest` / `PurchaseGoldenPackageResponse`
- `VerifyGoldenPackagePurchaseRequest` / `VerifyGoldenPackagePurchaseResponse`
- `InitiateBasePackagePaymentRequest` / `InitiateBasePackagePaymentResponse`
- `VerifyBasePackagePaymentRequest` / `VerifyBasePackagePaymentResponse`
**حفظ شده:** `GetUserPackageStatus` RPC + messages (هنوز در استفاده)
**NuGet:** `0.0.185``0.0.186` (همه ریپوها)
### فاز 8e — Per-Package Commission Reports ✅
> کامیت: CMS `aaaf7fc` | FO `a956cb9` | BO `8be98ae`
**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)` + map `PackageId`/`PackageTitle`
- ۳ Response DTO: اضافه `PackageId` + `PackageTitle`
- `CommissionProfile.cs`: تنظیم mapping‌های Mapster برای admin + customer
**BackOffice (6 فایل):**
- کامپوننت جدید `PackageSelect.razor/.cs`: dropdown قابل استفاده مجدد با بارگذاری پکیج‌ها از `PackageContract`
- `UserPayouts.razor/.cs`: فیلتر PackageSelect + ستون پکیج با MudChip
- `BalancesReport.razor`: فیلتر PackageSelect + ستون پکیج با MudChip + mapping PackageTitle
**FrontOffice (7 فایل):**
- `CommissionDtos.cs`: اضافه `PackageId` + `PackageTitle` به `CommissionPayoutDto` و `WeeklyBalanceDto`
- `CommissionService.cs`: اضافه پارامتر `packageId` به `GetMyCommissionPayoutsAsync` و `GetMyWeeklyBalanceAsync`
- `CommissionDashboardPage.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` | FO `3bffc13` | BO `e020354`
**CMS (T4.13 — PackageFeature CRUD):**
- Proto: اضافه `repeated int64 feature_ids` به ۴ message (Create/Update Request, Get/GetAll Response)
- `CreateNewPackageCommand/Handler`: sync FeatureIds → ساخت `PackageFeature` records
- `UpdatePackageCommand/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`
```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`: اضافه `PackageService` injection، بارگذاری پکیج‌ها با `GetAllPackagesAsync()`
- `Checkout.razor`: دکمه‌های پرداخت شرطی بر اساس `SupportsDirectPurchase` و `SupportsDayaPurchase`
- اضافه `DayaLoanPayment()` method + alert برای عدم وجود روش پرداخت
- `Pack` record: اضافه `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` و `WeeklyBalanceDto`
- `CommissionService.cs`: اضافه پارامتر `packageId` به `GetMyCommissionPayoutsAsync` و `GetMyWeeklyBalanceAsync`
- `CommissionDashboardPage.razor`: اضافه dropdown فیلتر پکیج + ستون «پکیج» با MudChip + نمایش پکیج در card موبایل
- `CommissionDashboardPage.razor.cs`: inject `PackageService`، فیلد `_filterPackageId`، بارگذاری لیست پکیج‌ها
### T4.9 — FrontOffice: WeeklyBalance per-package (🆕 v3) ✅ FO:`a956cb9`
**پیاده‌سازی:**
- `WeeklyBalancePage.razor`: اضافه MudSelect فیلتر پکیج کنار WeekSelector + نمایش MudChip پکیج در بخش اطلاعات هفته
- `WeeklyBalancePage.razor.cs`: inject `PackageService`، فیلد `_filterPackageId`، ارسال به `CommissionService.GetMyWeeklyBalanceAsync`
### T4.10-T4.12 — BackOffice: گزارش‌های پورسانت per-package (🆕 v3) ✅ BO:`8be98ae`
**پیاده‌سازی:**
- کامپوننت جدید `PackageSelect.razor/.cs`: dropdown قابل استفاده مجدد با بارگذاری پکیج‌ها از `PackageContract`
- `UserPayouts.razor/.cs`: فیلتر PackageSelect + ستون پکیج با MudChip
- `BalancesReport.razor`: فیلتر PackageSelect + ستون پکیج با MudChip + mapping `PackageTitle`
### 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` + ساخت `PackageFeature` records
- `UpdatePackageCommand/Handler`: اضافه `FeatureIds` + sync (حذف قبلی‌ها + ساخت جدید)
- `GetPackageQueryHandler`: اضافه `.Include(x => x.PackageFeatures)` + map `FeatureIds`
- `GetAllPackageByFilterQueryHandler`: اضافه `.Include(x => x.PackageFeatures)` قبل از `PaginatedListAsync`
- NuGet: `0.0.187``0.0.188`
**BO پیاده‌سازی:**
- `CreateDialog.razor/.cs`: بارگذاری `ClubFeatures` از `ConfigurationContractClient` + checkbox matrix
- `UpdateDialog.razor/.cs`: همان pattern + pre-populate از `Model.FeatureIds`
- Mapster: `Adapt<UpdatePackageRequest>()` خودکار `FeatureIds` را map می‌کند
---
## مرحله ۹ — Q24-Q30 Business Decisions + History Infrastructure ✅
> ✅ تکمیل‌شده | وابستگی: مرحله ۸ | کامیت‌ها: CMS:`a1024a3`→`fdbb91d`→`10d2ca2` FO:`474d364` BO:`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 `.sql` resources
### 9b: Q27 History Tables Entities ✅ (CMS:`fdbb91d`)
**Entity‌های جدید:**
- `PackageHistory`: فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive + Action + PerformedBy + Reason
- `ClubMembershipCycleHistory`: فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt + Action + UserId + CycleNumber
**Enums جدید:**
- `PackageAction`: Created, Updated, Activated, Deactivated, PriceChanged, FeaturesChanged
- `ClubMembershipCycleAction`: 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
- `Package` implements `IHasHistory<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)
- [x] UI: کاشی‌های داینامیک FrontOffice
- [x] 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](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی فنی — **۴۸+ تغییر** (v3) + باگ‌ها |
| [PACKAGE-TRANSFORMATION-UX.md](PACKAGE-TRANSFORMATION-UX.md) | تاثیر UX بر فرانت‌ها |
| [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده |
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC |
---
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۸ — فاز ۰-۹d تکمیل (۳۰ کامیت: ۲۰ CMS + ۸ FO + ۶ BO) | NuGet v0.0.188 | باقی‌مانده: تست + deploy*
+494
View File
@@ -0,0 +1,494 @@
# 🏗️ تحلیل تحول پکیج‌بیس — تاثیر بر تجربه کاربر (UX)
> **وضعیت:** در حال تحلیل
> **تاریخ:** ۱۴۰۴/۱۲/۰۶
> **پیش‌نیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) v2
> **هدف:** مستندسازی تاثیر تغییر رویکرد پکیج‌بیس بر تجربه مشتری و ادمین در فرانت‌ها
---
## فهرست
1. [چشم‌انداز کلی](#۱-چشمانداز-کلی)
2. [تجربه مشتری (FrontOffice) — قبل و بعد](#۲-تجربه-مشتری-frontoffice--قبل-و-بعد)
3. [تجربه ادمین (BackOffice) — قبل و بعد](#۳-تجربه-ادمین-backoffice--قبل-و-بعد)
4. [تسک‌های تحول — مرحله‌به‌مرحله](#۴-تسکهای-تحول--مرحلهبهمرحله)
5. [پیش‌بینی نیازمندی‌های آینده](#۵-پیشبینی-نیازمندیهای-آینده)
6. [ماتریس تاثیرگذاری بر صفحات](#۶-ماتریس-تاثیرگذاری-بر-صفحات)
---
## ۱. چشم‌انداز کلی
### فلسفه تغییر
| بُعد | **فعلی (تک‌پکیج)** | **هدف (چند‌پکیج)** |
|------|-------------------|--------------------|
| **مدل قیمتی** | فقط ۵۶M تومان — "همه یا هیچ" | سطوح متنوع (نقره‌ای ۵.۶M, پایه ۵۶M, ...) — "ورود تدریجی" |
| **تجربه ورود** | سنگین — کاربر باید ۵۶M بپردازد | سبک — شروع از ۵.۶M و ارتقا بعدی |
| **چرخه عمر** | یک‌بار خرید → برای همیشه | چند‌بار خرید → هر چرخه Magic Wallet |
| **فیچرها** | ثابت — همه فیچرها برای همه | پویا — هر پکیج فیچرهای خودش |
| **کمیسیون** | یک Pool مشترک | Pool جداگانه هر پکیج |
| **مدیریت** | hardcoded — تغییر = deploy | داینامیک — ادمین از پنل تغییر می‌دهد |
### چه کسانی تاثیر می‌بینند؟
```
👤 مشتری (FrontOffice):
├── ثبت‌نام‌کننده جدید: گزینه‌های بیشتر → تصمیم‌گیری آسان‌تر
├── مشتری فعال: دکمه "ارتقا" + "خرید مجدد"
└── مشتری Magic: نمایش پیشرفت چرخه + آماده‌سازی خرید بعدی
👔 ادمین (BackOffice):
├── مدیر محصول: CRUD پکیج + ماتریس فیچر
├── مدیر مالی: Commission Pool جداگانه + گزارش‌ها
└── پشتیبان: فعالسازی دستی با انتخاب پکیج
```
---
## ۲. تجربه مشتری (FrontOffice) — قبل و بعد
### ۲.۱ صفحه لیست پکیج‌ها (`Packages.razor`)
#### قبل (فعلی):
```
┌─────────────────────────────────────────────┐
│ پکیج طلایی │
│ ──────────── │
│ ✅ دسترسی به باشگاه مشتریان │
│ ✅ کیف‌پول جادویی │
│ ✅ فروشگاه تخفیفی │
│ │
│ 💰 ۵۶,۰۰۰,۰۰۰ تومان │
│ │
│ [خرید پکیج] │
└─────────────────────────────────────────────┘
```
#### بعد (پکیج‌بیس):
```
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ 🥈 پکیج نقره‌ای │ │ 🏆 پکیج پایه │ │ 💎 پکیج ویژه │
│ ──────────── │ │ ──────────── │ │ ──────────── │
│ ✅ باشگاه مشتریان │ │ ✅ باشگاه مشتریان │ │ ✅ باشگاه مشتریان │
│ ✅ کیف‌پول جادویی │ │ ✅ کیف‌پول جادویی │ │ ✅ کیف‌پول جادویی │
│ ❌ فروشگاه تخفیفی │ │ ✅ فروشگاه تخفیفی │ │ ✅ فروشگاه تخفیفی │
│ ❌ پشتیبانی اختصاصی │ │ ❌ پشتیبانی اختصاصی │ │ ✅ پشتیبانی اختصاصی │
│ │ │ │ │ │
│ 💰 ۵,۶۰۰,۰۰۰ تومان │ │ 💰 ۵۶,۰۰۰,۰۰۰ تومان │ │ 💰 ??? تومان │
│ │ │ ⭐ محبوب‌ترین │ │ 🆕 بزودی │
│ [خرید] [جزئیات] │ │ [خرید] [جزئیات] │ │ [در انتظار] │
│ ────────────────── │ │ ────────────────── │ │ ────────────────── │
│ 📊 PV: 5,600,000 │ │ 📊 PV: 56,000,000 │ │ │
│ 🎁 هدیه: 11,200,000 │ │ 🎁 هدیه: 112,000,000 │ │ │
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
```
**تغییرات کلیدی:**
- کاشی‌ها از API می‌آیند (نه hardcoded)
- فیچرهای هر پکیج از `PackageFeature` خوانده می‌شود
- نمایش PV (Point Value) برای هر پکیج
- نمایش Gift Value (= `Price × DiscountMultiplier`)
- دکمه‌های شرطی: دایا فقط برای پکیج‌های `SupportsDayaPurchase`
- Badge «محبوب‌ترین» / «ارزان‌ترین» بر اساس `SortOrder`
### ۲.۲ صفحه پکیج‌های من (`MyPackages.razor`)
#### قبل:
```
وضعیت عضویت: فعال ✅
پکیج: طلایی
تاریخ فعالسازی: ۱۴۰۳/۰۹/۱۵
```
#### بعد:
```
┌─────────────────────────────────────────────────────────────┐
│ 📦 پکیج فعال: پکیج پایه │
│ ──────────── │
│ وضعیت: فعال ✅ | چرخه: ۲ | مدت: ۱۸۰ روز │
│ │
│ ┌──── کیف‌پول جادویی ────┐ │
│ │ موجودی: ۱۲,۳۰۰,۰۰۰ │ │
│ │ شارژ: ۴۳۵,۰۰۰,۰۰۰ │ │
│ │ سقف: ۱,۰۰۰,۰۰۰,۰۰۰ │ │
│ │ ████████░░░░ ۴۳.۵% │ │
│ └─────────────────────────┘ │
│ │
│ ┌──── PV انباشته ────┐ │
│ │ PV کل: ۸۹,۶۰۰,۰۰۰ │ │
│ │ آخرین سفارش: ۴.۲M │ │
│ └─────────────────────┘ │
│ │
│ ❌ چرخه جادویی تکمیل نشده — هنوز امکان خرید مجدد نیست │
│ ───── یا ───── │
│ ✅ چرخه جادویی تکمیل شد! [خرید پکیج جدید] │
└─────────────────────────────────────────────────────────────┘
┌─── تاریخچه چرخه‌ها ───┐
│ چرخه ۱: پایه — ۱۴۰۳/۰۹ تا ۱۴۰۴/۰۳ — ✅ تکمیل │
│ چرخه ۲: پایه — ۱۴۰۴/۰۳ تا ادامه دارد — 🔄 فعال │
└────────────────────────┘
```
**تغییرات کلیدی:**
- نمایش شماره چرخه و نوع پکیج
- نوار پیشرفت Magic Wallet (چقدر تا تکمیل چرخه)
- PV انباشته (از `CalculateOrderPV`)
- دکمه شرطی «خرید مجدد» (فقط بعد تکمیل چرخه)
- تاریخچه چرخه‌ها (از `ClubMembershipCycle`)
### ۲.۳ صفحه چک‌اوت (`Checkout.razor`)
#### قبل:
```
سبد خرید:
محصول A × 2 = ۲,۰۰۰,۰۰۰ تومان
مالیات (۹%): ۱۸۰,۰۰۰ تومان
────────────────
جمع: ۲,۱۸۰,۰۰۰ تومان
```
#### بعد:
```
سبد خرید:
محصول A × 2 = ۲,۰۰۰,۰۰۰ تومان
مالیات (۹%): ۱۸۰,۰۰۰ تومان
────────────────
جمع: ۲,۱۸۰,۰۰۰ تومان
📊 PV این سفارش: ۲,۰۰۰,۰۰۰ ← جدید
💎 PV انباشته: ۹۱,۶۰۰,۰۰۰ ← جدید
```
### ۲.۴ پرداخت پکیج — مودال خرید
#### قبل:
```
┌─── خرید پکیج طلایی ───┐
│ │
│ مبلغ: ۵۶,۰۰۰,۰۰۰ تومان │
│ │
│ [پرداخت آنلاین] │
│ [اقساط دایا] │
│ [پرداخت دستی] │
└──────────────────────────┘
```
#### بعد:
```
┌─── خرید پکیج نقره‌ای ───┐ ┌─── خرید پکیج پایه ───┐
│ │ │ │
│ مبلغ: ۵,۶۰۰,۰۰۰ تومان │ │ مبلغ: ۵۶,۰۰۰,۰۰۰ تومان│
│ │ │ │
│ سهم باشگاه: ۲,۵۲۰,۰۰۰ │ │ سهم باشگاه: ۲۵,۲۰۰,۰۰│
│ شارژ کیف‌پول: ۵,۶۰۰,۰۰۰ │ │ شارژ کیف‌پول: ۵۶,۰۰۰,۰│
│ هدیه تخفیفی: ۱۱,۲۰۰,۰۰۰ │ │ هدیه تخفیفی: ۱۱۲,۰۰۰,۰│
│ │ │ │
│ [پرداخت آنلاین] ✅ │ │ [پرداخت آنلاین] ✅ │
│ [اقساط دایا] ❌ ندارد │ │ [اقساط دایا] ✅ │
│ [پرداخت دستی] ✅ │ │ [پرداخت دستی] ✅ │
└────────────────────────────┘ └────────────────────────┘
```
**تغییرات کلیدی:**
- نمایش breakdown مالی: سهم باشگاه + شارژ کیف‌پول + هدیه تخفیفی
- دکمه‌های پرداخت شرطی بر اساس `SupportsDayaPurchase` / `SupportsDirectPurchase`
- متن قرارداد داینامیک بر اساس پکیج انتخاب‌شده
### ۲.۵ صفحه تنظیمات مشتری (`Settings.razor`) — جدید
```
┌─── تنظیمات اعلان‌ها ───┐
│ │
│ 📧 اعلان ایمیل: [✅] │
│ 📱 اعلان SMS: [✅] │
│ 🔔 اعلان Push: [❌] │
│ │
│ [ذخیره تغییرات] │
└──────────────────────────┘
```
> وصل به RPC: `UpdateCustomerSettings`
### ۲.۶ تاریخچه سفارشات — دکمه تکرار
```
┌─── تاریخچه سفارشات ────────────────────────────────────────┐
│ # │ تاریخ │ مبلغ │ وضعیت │ PV │ عملیات │
│───┼────────────┼────────────┼───────────┼───────────┼────────│
│ 1 │ ۱۴۰۴/۱۱/۰۲│ ۴,۲۰۰,۰۰۰ │ تحویل ✅ │ ۴,۲۰۰,۰۰ │ [🔄] [📍]│
│ 2 │ ۱۴۰۴/۱۰/۱۵│ ۱,۸۰۰,۰۰۰ │ ارسال 📦 │ ۱,۸۰۰,۰۰ │ [📍]│
│ 3 │ ۱۴۰۴/۰۹/۲۰│ ۳,۵۰۰,۰۰۰ │ تحویل ✅ │ ۳,۵۰۰,۰۰ │ [🔄] [📍]│
└─────────────────────────────────────────────────────────────┘
🔄 = تکرار سفارش (CustomerReorderPreviousOrder)
📍 = ردیابی سفارش (CustomerTrackOrder)
```
---
## ۳. تجربه ادمین (BackOffice) — قبل و بعد
### ۳.۱ مدیریت پکیج‌ها (`PackageMainPage.razor`)
#### قبل:
```
┌─── مدیریت پکیج‌ها ────────────────────────────────┐
│ # │ عنوان │ قیمت │ وضعیت │ عملیات │
│───┼──────────┼─────────────┼───────┼──────────────│
│ 1 │ طلایی │ ۵۶,۰۰۰,۰۰۰ │ فعال │ [ویرایش] │
└────────────────────────────────────────────────────┘
```
#### بعد:
```
┌─── مدیریت پکیج‌ها ──────────────────────────────────────────────────────┐
│ # │ عنوان │ قیمت │ سهم باشگاه │ ضریب │ دایا │ پایه │ ترتیب│ عملیات │
│───┼─────────┼─────────────┼────────────┼────────┼──────┼──────┼──────┼───────────────│
│ 1 │ نقره‌ای │ ۵,۶۰۰,۰۰۰ │ ۲,۵۲۰,۰۰۰ │ ×2.0 │ ❌ │ ❌ │ 1 │ [✏️] [📋] [❌] │
│ 2 │ پایه │ ۵۶,۰۰۰,۰۰۰ │ ۲۵,۲۰۰,۰۰│ ×2.0 │ ✅ │ ✅ │ 2 │ [✏️] [📋] [❌] │
└──────────────────────────────────────────────────────────────────────────┘
✏️ = ویرایش 📋 = مدیریت فیچرها ❌ = حذف
```
### ۳.۲ ماتریس فیچر × پکیج (`PackageFeatureMatrixPage.razor`) — صفحه جدید
```
┌─── ماتریس فیچر × پکیج ─────────────────────────────────────────┐
│ │
│ فیچر │ نقره‌ای │ پایه │ ویژه │
│ ─────────────────────────┼─────────┼────────┼──────────────────│
│ دسترسی به باشگاه │ ✅ │ ✅ │ ✅ │
│ کیف‌پول جادویی │ ✅ │ ✅ │ ✅ │
│ فروشگاه عادی │ ✅ │ ✅ │ ✅ │
│ فروشگاه تخفیفی │ ❌ │ ✅ │ ✅ │
│ محصولات ClubExclusive │ ❌ │ ✅ │ ✅ │
│ پشتیبانی اختصاصی │ ❌ │ ❌ │ ✅ │
│ کمیسیون شبکه │ ✅ │ ✅ │ ✅ │
│ ─────────────────────────┼─────────┼────────┼──────────────────│
│ │ [ذخیره] │ [ذخیره]│ [ذخیره] │
└─────────────────────────────────────────────────────────────────┘
ادمین با checkbox فیچرها را به هر پکیج اختصاص می‌دهد.
وصل به RPC: AssignFeatureToMembership
```
### ۳.۳ فعالسازی باشگاه (`ActivateClubDialog.razor`)
#### قبل:
```
فعالسازی باشگاه مشتریان
کاربر: علی محمدی
[فعالسازی] ← hardcoded 56M + همه فیچرها
```
#### بعد:
```
فعالسازی باشگاه مشتریان
کاربر: علی محمدی
پکیج: [▼ انتخاب پکیج ▼] ← dropdown از API
├── نقره‌ای (۵,۶۰۰,۰۰۰)
└── پایه (۵۶,۰۰۰,۰۰۰)
جزئیات:
سهم باشگاه: _________ (خودکار)
فیچرها: _________ (از ماتریس پکیج)
[فعالسازی]
```
### ۳.۴ داشبورد (`Index.razor`) — بهبود
```
┌─── آمار باشگاه ────────────────────────────────────────────────┐
│ │
│ 👥 کل اعضا: ۱,۲۴۰ │
│ 📦 پکیج نقره‌ای: ۸۲۰ | پکیج پایه: ۴۲۰ │
│ 💰 Pool نقره‌ای: ۲,۰۶۶,۴۰۰,۰۰۰ | Pool پایه: ۱۰,۵۸۴,۰۰۰,۰۰│
│ ⚠️ هشدار: ۱۲ محصول موجودی کم │
│ │
│ ┌── موجودی انبار ──┐ ┌── ارزش کل انبار ──┐ │
│ │ ۳,۴۵۰ آیتم │ │ ۸۹,۲۰۰,۰۰۰,۰۰۰ │ │
│ │ ۱۲ نوع محصول │ │ ریال │ │
│ └───────────────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
وصل به: GetInventorySummary, GetStockValueReport, GetLowStockProducts
```
### ۳.۵ مدیریت شبکه (`UserNetworkInfo.razor`) — بهبود
```
┌─── مدیریت شبکه ─────────────────────────────────────────┐
│ │
│ کاربر: سارا احمدی (ID: 1045) │
│ پکیج: نقره‌ای │ چرخه: ۱ │ PV: ۵,۶۰۰,۰۰۰ │
│ Parent: علی محمدی (ID: 1001) │
│ شاخه: چپ │ عمق: ۳ │
│ │
│ [جابجایی در شبکه] ← مودال: انتخاب parent جدید │
│ │
└───────────────────────────────────────────────────────────┘
وصل به: ChangeNetworkParent
```
---
## ۴. تسک‌های تحول — مرحله‌به‌مرحله
### مرحله ۱: زیرساخت Domain + DB (پایه)
> ⚠️ **بدون این مرحله هیچ‌کدام از تغییرات UI ممکن نیست**
| # | تسک | لایه | فایل‌ها | شرح |
|---|------|------|--------|------|
| T1.1 | اضافه ۷ فیلد به Package entity | Domain | Package.cs, PackageConfiguration.cs | SortOrder, IsActive, IsBasePackage, SupportsDayaPurchase, SupportsDirectPurchase, ActivationFee, DiscountMultiplier, MagicWalletMultiplier |
| T1.2 | ایجاد PackageFeature entity | Domain | PackageFeature.cs, PackageFeatureConfiguration.cs | join table: Package ↔ ClubFeature |
| T1.3 | اضافه PackageId به ClubMembership | Domain | ClubMembership.cs | FK nullable → بعد migration → required |
| T1.4 | اضافه PackageId به ClubMembershipCycle | Domain | ClubMembershipCycle.cs | FK nullable → بعد migration → required |
| T1.5 | اضافه PackageId به WeeklyCommissionPool | Domain | WeeklyCommissionPool.cs | FK + Unique(WeekDefinitionId, PackageId) |
| T1.6 | اضافه PackageId به UserCommissionPayout | Domain | UserCommissionPayout.cs | FK nullable |
| T1.7 | حذف ۵ SystemConstants | Domain | SystemConstants.cs | BasePackageAmount, DayaLoanAmount, ClubActivationFee, ClubMembershipGiftValue, MagicWalletMultiplier |
| T1.8 | EF Migration + Seed | Infrastructure | Migration file | ۲ پکیج + فیچرها + FKها |
| T1.9 | Data Migration Script | Infrastructure | SQL script | کاربران فعلی → PackageId = پکیج پایه |
| T1.10 | بروزرسانی Protoها | Proto | package.proto, clubmembership.proto, commission.proto | فیلدهای جدید |
### مرحله ۲: منطق کسب‌وکار (Handlers)
> **هر handler باید از Package entity مقادیر مالی بخواند**
| # | تسک | فایل | شرح |
|---|------|------|------|
| T2.1 | Generic Verify Handler | VerifyPackagePurchaseCommandHandler.cs | DiscountMultiplier از Package + ساخت UserPackagePurchase |
| T2.2 | Generic Purchase Handler | PurchasePackageCommandHandler.cs | حذف "طلایی" و ID=4 |
| T2.3 | ActivateClubMembership بهبود | ActivateClubMembershipCommandHandler.cs | فیچر از PackageFeature + ActivationFee از Package |
| T2.4 | EXIT Magic Mode ریست | UserOrderService.cs | PackagePurchaseMethod=None, membership.IsActive=false |
| T2.5 | Guards re-purchase | G1-G3 handlers | اجازه خرید اگر MagicCompletedAt پر |
| T2.6 | Re-contract | AcceptClubMembershipContractCommandHandler.cs | اجازه قرارداد مجدد |
| T2.7 | Manual Payment بهبود | CreateManualPaymentCommandHandler.cs | DiscountMultiplier از Package |
| T2.8 | Daya Loan بهبود | CheckAndProcessDayaLoansCommandHandler.cs | حذف ID=4 |
| T2.9 | PackageFeature CRUD | جدید | ادمین بتواند فیچر ↔ پکیج مدیریت کند |
| T2.10 | JWT claims جدید | JWT builder | اضافه CanRepurchase + PackageType |
### مرحله ۳: محاسبه پورسانت (Commission)
| # | تسک | فایل | شرح |
|---|------|------|------|
| T3.1 | SP WeeklyBalances + PackageId | sp_CalculateWeeklyBalances.sql | فیلتر بر اساس PackageId |
| T3.2 | SP CommissionPool + PackageId | sp_CalculateWeeklyCommissionPool.sql | Pool جداگانه هر پکیج |
| T3.3 | Loop روی پکیج‌ها | WeeklyCommissionCalculationService.cs | هر پکیج فعال → محاسبه جداگانه |
| T3.4 | ORM Strategy بهبود | OrmCommissionCalculationStrategy.cs | فیلتر PackageId |
| T3.5 | تست محاسبات | — | با داده واقعی staging |
### مرحله ۴: FrontOffice UI
| # | تسک | صفحه | شرح |
|---|------|------|------|
| T4.1 | کاشی‌های داینامیک | Packages.razor | لود از API + فیچر مقایسه |
| T4.2 | مودال پرداخت شرطی | PackageDetail.razor | دکمه دایا فقط اگر SupportsDayaPurchase |
| T4.3 | MyPackages re-purchase | MyPackages.razor | نوار پیشرفت + دکمه خرید مجدد |
| T4.4 | ActivationSection داینامیک | ActivationSection.razor | قیمت از پکیج |
| T4.5 | قرارداد داینامیک | ClubMembershipContractDialog.razor | متن متناسب با پکیج |
| T4.6 | PV در Checkout | Checkout.razor | نمایش PV سفارش |
| T4.7 | تکرار سفارش | Store Orders pages | دکمه 🔄 |
| T4.8 | ردیابی سفارش | OrderTracking.razor | وصل به API |
| T4.9 | تنظیمات اعلان | Settings.razor | فرم Email/SMS/Push |
### مرحله ۵: BackOffice UI
| # | تسک | صفحه | شرح |
|---|------|------|------|
| T5.1 | CRUD پکیج بهبود | PackageMainPage.razor | فیلدهای جدید + ستون‌های اضافه |
| T5.2 | ماتریس فیچر | PackageFeatureMatrixPage.razor (جدید) | checkbox grid |
| T5.3 | ActivateClub dropdown | ActivateClubDialog.razor | انتخاب پکیج |
| T5.4 | داشبورد بهبود | Index.razor | آمار Pool جداگانه + موجودی |
| T5.5 | شبکه بهبود | UserNetworkInfo.razor | جابجایی parent |
| T5.6 | موجودی کم | LowStockPage.razor | وصل API |
| T5.7 | گزارش ارزش انبار | InventoryMainPage.razor | تب گزارش |
| T5.8 | عملیات دسته‌ای | InventoryMainPage.razor | Bulk Add/Update |
### مرحله ۶: تست و استقرار
| # | تسک | شرح |
|---|------|------|
| T6.1 | تست خرید هر پکیج | ZarinPal + Manual |
| T6.2 | تست re-purchase | تکمیل چرخه → خرید مجدد |
| T6.3 | تست Commission Pool | جداگانه بودن هر پکیج |
| T6.4 | تست Migration | rollback plan |
| T6.5 | Deploy staging → production | blue-green |
---
## ۵. پیش‌بینی نیازمندی‌های آینده
### ۵.۱ نیازمندی‌های مشتری (که فعلاً اولویت پایین هستند)
| # | نیاز | RPC آماده? | توضیح |
|---|------|-----------|-------|
| N1 | ارتقای پکیج (نقره‌ای → پایه) | ❌ جدید | پرداخت تفاضل + فعالسازی فیچرهای جدید |
| N2 | مقایسه پکیج‌ها side-by-side | ❌ جدید | جدول فیچر مقایسه‌ای (client-side) |
| N3 | اعلان قبل از اتمام چرخه | ❌ جدید | Background service: 5 روز قبل → push/SMS |
| N4 | گزارش PV ماهانه | CalculateOrderPV ✅ | جدول PV هر ماه + نمودار |
| N5 | پروفایل شبکه | ❌ جدید | مشتری درخت خودش را ببیند |
### ۵.۲ نیازمندی‌های ادمین (که فعلاً اولویت پایین هستند)
| # | نیاز | RPC آماده? | توضیح |
|---|------|-----------|-------|
| N6 | پکیج تخفیفی زمان‌دار | ❌ جدید | پکیج با قیمت ویژه برای مدت محدود |
| N7 | گزارش تبدیل (conversion) | ❌ جدید | چند نفر از نقره‌ای به پایه ارتقا دادند |
| N8 | هشدار Pool خالی | ❌ جدید | اگر Pool یک پکیج خالی شد → هشدار |
| N9 | export گزارش مالی | GetStockValueReport ✅ | دانلود Excel |
| N10 | تخصیص فیچر bulk | AssignFeatureToMembership ✅ | فیچر به همه اعضای یک پکیج |
---
## ۶. ماتریس تاثیرگذاری بر صفحات
### FrontOffice
| صفحه | تغییر | شدت | مرحله |
|------|-------|------|-------|
| Packages.razor | بازنویسی کامل — کاشی‌های داینامیک | 🔴 | مرحله ۴ |
| PackageDetail.razor | فیچرها از API + دکمه شرطی | 🟡 | مرحله ۴ |
| MyPackages.razor | چرخه + پیشرفت + re-purchase | 🔴 | مرحله ۴ |
| Checkout.razor | PV display | 🟢 | مرحله ۴ |
| ActivationSection.razor | قیمت داینامیک | 🟡 | مرحله ۴ |
| ClubMembershipContractDialog.razor | متن داینامیک | 🟡 | مرحله ۴ |
| PaymentCallback.razor | تغییر JWT claims | 🟡 | مرحله ۴ |
| MembershipPage.razor | نمایش نوع پکیج | 🟢 | مرحله ۴ |
| Settings.razor | فرم اعلان جدید | 🟡 | مرحله ۴ |
| Store Orders | دکمه تکرار + ردیابی | 🟡 | مرحله ۴ |
### BackOffice
| صفحه | تغییر | شدت | مرحله |
|------|-------|------|-------|
| PackageMainPage.razor | ستون‌های جدید + CRUD بهبود | 🟡 | مرحله ۵ |
| PackageFeatureMatrixPage.razor | **صفحه کاملاً جدید** | 🔴 | مرحله ۵ |
| ActivateClubDialog.razor | dropdown پکیج | 🟡 | مرحله ۵ |
| Index.razor (Dashboard) | آمار Pool جداگانه + موجودی | 🟡 | مرحله ۵ |
| UserNetworkInfo.razor | جابجایی + نمایش پکیج | 🟡 | مرحله ۵ |
| ClubMembers.razor | ستون پکیج | 🟢 | مرحله ۵ |
| Statistics.razor | چارت توزیع پکیج | 🟡 | مرحله ۵ |
| InventoryMainPage.razor | خلاصه + گزارش + bulk | 🟡 | مرحله ۵ |
| LowStockPage.razor | وصل API | 🟢 | مرحله ۵ |
---
## 🔗 ارجاعات
| مستند | ربط |
|-------|-----|
| [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی فنی ۳۹ تغییر |
| [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده |
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC |
| [BUSINESS-01-CLUB-COMMISSION.md](../business/BUSINESS-01-CLUB-COMMISSION.md) | مستند باشگاه و کمیسیون |
| [BUSINESS-04-USER-MEMBERSHIP.md](../business/BUSINESS-04-USER-MEMBERSHIP.md) | مستند عضویت کاربر |
---
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶*
+54 -4
View File
@@ -1,7 +1,7 @@
# ⚙️ معماری CMS و زیرساخت فنی
> **منابع ادغام‌شده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس ZarinPal Verify + Callback URL امنیت + appsettings.Development.json)
---
@@ -139,6 +139,8 @@ public class CreateProductCommandHandler
| `SitePageService` | sitepage.proto | GetPage, SaveSettings |
| `CategoryService` | category.proto | GetCategories, Create, Update |
| `SystemConfigService` | config.proto | GetConfig, UpdateConfig |
| `UserWalletService` | userwallet.proto | GetCustomerWallet, InitiateMagicCharge, GetMagicWalletStatus |
| `UserWalletHistoryService` | userwallethistory.proto | *(renamed from UserWalletChangeLogService)* |
### ۴.۲ PaginationState (مشترک)
@@ -180,7 +182,8 @@ Engine: MSSQL 2022-CU16, Collation=Arabic_CI_AS
| SitePages | صفحات سایت | ~10 |
| UserClubMemberships | عضویت باشگاه | ~500 |
| UserContracts | قراردادها (SignGuid, SignedPdfFile) | ~500 |
| UserWallets | کیف‌پول (Balance, NetworkBalance, DiscountBalance) | ~5K |
| UserWallets | کیف‌پول (Balance, NetworkBalance, DiscountBalance, WalletMode) | ~5K |
| ClubMembershipCycles | دوره‌های عضویت (CycleNumber, PackagePurchasedAt, IsCurrentCycle) | ~500 |
| Transactions | تراکنش‌ها | ~5K |
| SystemConfigurations | تنظیمات | ~30 |
| ChatMessages | پیام‌های چاتیکا | ~1K |
@@ -193,6 +196,45 @@ Engine: MSSQL 2022-CU16, Collation=Arabic_CI_AS
| `sp_CalculateWeeklyBalances` | محاسبه بالانس هفتگی هر عضو |
| `sp_CalculateWeeklyCommissionPool` | توزیع Pool هفتگی |
### ۵.۴ SP Auto-Deploy Worker (Q26)
```csharp
// StoredProcedureDeploymentService : IHostedService
// در startup:
// 1. خواندن فایل‌های .sql از embedded resource
// 2. مقایسه checksum با جدول __SPChecksums
// 3. فقط SP‌های تغییریافته re-deploy می‌شوند
```
---
## ۵.۵ History Tracking System (Q27)
### IHasHistory<T> Interface
```csharp
public interface IHasHistory<THistory> where THistory : BaseAuditableEntity, new()
{
THistory CreateHistorySnapshot(string action, string? performedBy);
}
```
### HistoryTrackingSaveChangesInterceptor
- **مکان:** `Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs`
- **مکانیسم:** `SaveChangesInterceptor` — قبل از `SaveChanges` اجرا می‌شود
- **شناسایی:** از `ChangeTracker` entity‌هایی که `IHasHistory<>` پیاده‌سازی کردن (Modified/Added)
- **Auto-fill:** فیلدهای `Old*` از `entry.OriginalValues` با naming convention (مثلاً `OldPrice``OriginalValues["Price"]`)
- **Entity‌های فعال:** `Package``PackageHistory`
### History Tables
| جدول | Entity مرتبط | فیلدهای Old/New |
|------|-------------|----------------|
| `PackageHistories` | Package | Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive |
| `ClubMembershipCycleHistories` | ClubMembershipCycle | IsCurrentCycle, MagicStartedAt, MagicCompletedAt |
| `UserWalletHistories` | UserWallet | *(renamed from UserWalletChangeLogs — RenameTable migration)* |
---
## ۶. حذف BFF / Gateway
@@ -263,7 +305,15 @@ FrontOffice Service Layer:
"WorkerCount": 4
},
"Kavenegar": { "ApiKey": "***" },
"ZarinPal": { "MerchantId": "***" },
"DayaLoan": { "UseMock": true }
"ZarinPal": { "MerchantId": "***", "UseSandbox": true },
"DayaLoan": { "UseMock": true },
"CmsBaseUrl": "https://cms.se.kbs1.ir",
"FrontOfficeBaseUrl": "http://localhost:5268"
}
```
> **⚠️ نکات مهم appsettings:**
> - `CmsBaseUrl` — برای callback URL‌های درگاه (شارژ کیف‌پول جادویی/اعتباری)
> - `FrontOfficeBaseUrl` — برای redirect بعد پرداخت (خرید پکیج/تراکنش عمومی)
> - `appsettings.Development.json` — URL‌های localhost برای توسعه محلی
> - همه callback URL‌ها از config خوانده می‌شوند — هیچ URL از ورودی کاربر نمی‌آید (امنیت Open Redirect)
+24 -2
View File
@@ -1,7 +1,7 @@
# 🖥️ BackOffice و FrontOffice — معماری UI
> **منابع ادغام‌شده:** `BACKOFFICE-ARCHITECTURE.md`, `BACKOFFICE-STORE-UNIFICATION.md`, `UI-MODERNIZATION-PLAN.md`, `UI-UNIFICATION-PLAN.md`, `PHASE-1-COMPLETE.md`, `PHASE-3-COMPLETE.md`, `PRODUCT-IMAGES-SQUARE.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس‌های پرداخت Phase 11 + صفحه موفقیت + تومان/ریال + امنیت Callback)
---
@@ -37,6 +37,12 @@ BackOffice/src/BackOffice/
│ ├── Orders/
│ ├── Users/
│ ├── Club/
│ │ ├── ClubMembers.razor ← + UserAutoComplete فیلتر در تولبار
│ │ └── ActivateClubDialog.razor ← UserAutoComplete بجای MudNumericField
│ ├── Wallet/
│ │ └── WalletManagementPage.razor ← TemplateColumn با UserName + ID
│ ├── AutoComplete/
│ │ └── UserAutoComplete.razor ← کامپوننت مشترک جستجوی کاربر
│ ├── Blog/
│ ├── Inventory/
│ ├── SitePages/
@@ -111,6 +117,10 @@ FrontOffice/src/FrontOffice/
│ │ ├── Dashboard.razor ← داشبورد باشگاه
│ │ ├── NetworkTree.razor ← نمای درخت
│ │ └── Contract.razor ← امضای قرارداد
│ ├── Profile/
│ │ ├── Index.razor ← داشبورد پروفایل + تایل Magic
│ │ ├── MagicWallet.razor ← 🪄 کیف‌پول جادویی
│ │ └── PaymentCallback.razor ← 💳 صفحه نتیجه پرداخت (TransactionId + موجودی واقعی)
│ ├── Blog/
│ ├── Auth/
│ │ ├── Login.razor
@@ -119,10 +129,13 @@ FrontOffice/src/FrontOffice/
├── Services/
│ ├── ProductService.cs ← با GetProductsPagedAsync
│ ├── ClubService.cs
│ ├── WalletService.cs ← + MagicWalletStatus, InitiateMagicChargeAsync
│ ├── VATService.cs ← VAT 10% از سرور + LocalStorage cache
│ └── ...
└── Shared/
├── AppImage.razor
├── ProductCard.razor ← مشترک بین Store و DiscountStore
├── PackagePurchaseDialog.razor ← دیالوگ ۲-مرحله‌ای خرید پکیج (NEW)
└── LoadMoreButton.razor
```
@@ -136,7 +149,7 @@ FrontOffice/src/FrontOffice/
|------|--------|-------|--------|
| **Phase 1** | پایه MudBlazor v8 | ارتقا MudBlazor، Layout اصلی | ✅ 100% |
| **Phase 2** | صفحات محصول | Card grid، فیلتر، جزئیات | ✅ 100% |
| **Phase 3** | فروشگاه تخفیفی | UI DiscountStore + hybrid pay | ✅ 100% |
| **Phase 3** | فروشگاه اعتباری | UI DiscountStore + hybrid pay | ✅ 100% |
| **Phase 4** | باشگاه | داشبورد، درخت، قرارداد | ✅ 100% |
| **Phase 5** | محتوا | بلاگ، Site Pages | ✅ 100% |
| **Phase 6** | نهایی‌سازی | تصاویر 1:1، lazy load، landing fix | ✅ 100% |
@@ -247,6 +260,15 @@ if (user.Identity?.IsAuthenticated == true) {
| Store Unification | ✅ | 100% |
| SitePage Typed Editors | ✅ | 100% |
| RTL Support | ✅ | 100% |
| Magic Wallet UI | ✅ | 100% |
| Proto ProjectReference | ✅ | 100% |
| UserAutoComplete کامپوننت | ✅ | 100% |
| نمایش نام کاربر در Wallet | ✅ | 100% |
| فعال‌سازی دکمه‌های درگاه | ✅ | 100% |
| PackagePurchaseDialog | ✅ | 100% |
| Toman/Rial فیکس نمایش قیمت | ✅ | 100% |
| صفحه نتیجه پرداخت (PaymentCallback) | ✅ | 100% |
| امنیت Callback URL | ✅ | 100% |
| Mobile Responsive (Phase 7) | ⬜ | 0% |
| Dark Mode | ⬜ | 0% |
| PWA | ⬜ | 0% |
+305 -99
View File
@@ -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`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس URL پروداکشن + چری‌پیک فیکس‌های WalletChangeLog/Validation/Expiry)
---
@@ -77,31 +77,126 @@ ENTRYPOINT ["dotnet", "CMSMicroservice.dll"]
### ۳.۱ Manifests ساختار
مانیفست‌های K8s **داخل ریپوی CMS** نگهداری می‌شن و توسط CI/CD اعمال می‌شن:
```
deployment/k8s-manifests/
├── cms-deployment.yaml
├── cms-service.yaml
├── backoffice-deployment.yaml
├── backoffice-service.yaml
├── frontoffice-deployment.yaml
├── frontoffice-service.yaml
├── db-statefulset.yaml
├── db-service.yaml
├── ingress.yaml
├── configmap.yaml
└── secrets.yaml
CMS/
k8s/
staging/
cms-config.yaml ← K8s Secret (appsettings.Staging.json)
cms-deployment.yaml ← PVC + Deployment + Service + Ingress
production/
cms-config.yaml ← K8s Secret (appsettings.Production.json)
cms-deployment.yaml ← PVC + Deployment + Service + Ingress
```
### ۳.۲ مثال Deployment
> ⚠️ **هر دو محیط از namespace `default` استفاده می‌کنن.**
### ۳.۲ PersistentVolume برای آپلود فایل
فایل‌های آپلود‌شده (عکس محصولات، بلاگ، آواتار و ...) در `/app/Uploads` ذخیره می‌شن.
برای جلوگیری از حذف فایل‌ها با ریستارت Pod، یک **PersistentVolumeClaim** مونت شده:
```yaml
# PVC — 20Gi ذخیره‌سازی دائمی
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: cms-uploads-pvc
namespace: default
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 20Gi
```
```yaml
# Volume Mount در Deployment
volumeMounts:
- name: cms-uploads
mountPath: /app/Uploads
volumes:
- name: cms-uploads
persistentVolumeClaim:
claimName: cms-uploads-pvc
```
| تنظیم | مقدار |
|--------|-------|
| **PVC Name** | `cms-uploads-pvc` |
| **Mount Path** | `/app/Uploads` |
| **Access Mode** | `ReadWriteOnce` |
| **حجم** | `20Gi` |
| **StorageClass** | `local-path` (K3s default) |
| **Replicas** | `1` (محدودیت RWO) |
> 💡 **نکته مهم:** چون `ReadWriteOnce` هست، فقط **1 replica** می‌تونه بنویسه. برای 2+ replica نیاز به NFS/CephFS با `ReadWriteMany` هست.
### ۳.۳ تنظیمات محیطی (K8s Secret)
تنظیمات حساس (ConnectionString, Email, SMS, ZarinPal) **در K8s Secret** نگهداری می‌شن — نه داخل Docker image.
فایل `appsettings.{Environment}.json` از Secret به `/app/` مونت می‌شه و .NET اون رو override می‌خونه.
```mermaid
flowchart LR
S["K8s Secret<br/>cms-appsettings"] -->|volumeMount| F["/app/appsettings.*.json"]
F --> D[".NET reads config"]
I["Docker Image<br/>appsettings.json (base)"] --> D
```
| محیط | `ASPNETCORE_ENVIRONMENT` | فایل Config (از Secret) |
|------|---------------------------|-------------|
| **Staging** | `Staging` | `appsettings.Staging.json` |
| **Production** | `Production` | `appsettings.Production.json` |
**Secret manifest** (`cms-config.yaml`):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: cms-appsettings
namespace: default
type: Opaque
stringData:
appsettings.Staging.json: | # یا appsettings.Production.json
{ "ConnectionStrings": { ... }, "ZarinPal": { ... }, ... }
```
**Volume mount در Deployment:**
```yaml
volumeMounts:
- name: cms-config
mountPath: /app/appsettings.Staging.json
subPath: appsettings.Staging.json
readOnly: true
volumes:
- name: cms-config
secret:
secretName: cms-appsettings
```
env var‌های K8s manifest (فقط environment و URL):
```yaml
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Staging" # یا "Production"
- name: ASPNETCORE_URLS
value: "http://+:8080"
```
> 💡 **تغییر config بدون deploy:** `kubectl edit secret cms-appsettings && kubectl rollout restart deployment/cms`
### ۳.۴ مثال Deployment (واقعی)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cms
namespace: foursat
namespace: default
spec:
replicas: 2
replicas: 1
selector:
matchLabels:
app: cms
@@ -109,104 +204,141 @@ spec:
spec:
containers:
- name: cms
image: foursat/cms:latest
image: 194.5.195.53:30080/admin/cms:latest
imagePullPolicy: Always
ports:
- containerPort: 5001
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Staging"
- name: ASPNETCORE_URLS
value: "http://+:8080"
volumeMounts:
- name: cms-uploads
mountPath: /app/Uploads
- name: cms-config
mountPath: /app/appsettings.Staging.json
subPath: appsettings.Staging.json
readOnly: true
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
grpc:
port: 5001
initialDelaySeconds: 15
readinessProbe:
grpc:
port: 5001
requests: { memory: "512Mi", cpu: "500m" }
limits: { memory: "1Gi", cpu: "1000m" }
volumes:
- name: cms-uploads
persistentVolumeClaim:
claimName: cms-uploads-pvc
- name: cms-config
secret:
secretName: cms-appsettings
```
### ۳.۳ Ingress
### ۳.۵ Ingress
**Staging:**
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foursat-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
rules:
- host: foursat.ir
http:
paths:
- path: /
backend:
service:
name: frontoffice
port: { number: 5003 }
- path: /admin
backend:
service:
name: backoffice
port: { number: 80 }
- host: cms.se.kbs1.ir
```
> ⚠️ **هشدار:** Ingress-nginx نسخه‌های قبل از 1.9.0 مشکل امنیتی CVE-2023-5044 دارند. حتماً بروزرسانی کنید.
**Production:**
```yaml
spec:
ingressClassName: nginx
tls:
- hosts: [cms.kbs1.ir, cms.kbs2.ir]
secretName: cms-tls
rules:
- host: cms.kbs2.ir
- host: cms.kbs1.ir
```
> ⚠️ **هشدار:** از `spec.ingressClassName: nginx` استفاده کنید، نه `kubernetes.io/ingress.class` annotation (deprecated).
### ۳.۶ جداسازی appsettings در Git
هر برنچ فقط فایل config مربوط به محیط خودش رو داره:
| برنچ | `appsettings.json` | `appsettings.Staging.json` | `appsettings.Production.json` |
|------|---|---|---|
| `kub-stage` | ✅ | ✅ | ❌ حذف شده |
| `production` | ✅ | ❌ حذف شده | ✅ |
**چرا؟** چون config اصلی از K8s Secret میاد (`cms-config.yaml`)، فایل‌های محیط دیگه داخل ایمیج اضافی و گمراه‌کننده‌ان.
همچنین وقتی merge/cherry-pick می‌کنید، فایل config محیط دیگه دیگه conflict ایجاد نمی‌کنه.
> ⚠️ **کامیت‌های حذف فایل config رو هرگز cherry-pick نکنید به برنچ دیگه!**
> `e72673c` (حذف Production از staging) و `3ebe0f9` (حذف Staging از production)
### ۳.۷ خلاصه: چه چیزهایی دائمی هستند (مستقل از ایمیج)
| چه چیزی | مکانیزم K8s | محل Mount |
|---------|-------------|------------|
| **فایل‌های آپلود** (عکس، آواتار، ...) | `PersistentVolumeClaim` | `/app/Uploads` |
| **تنظیمات اپلیکیشن** (DB, SMS, IPG, ...) | `Secret` (`cms-appsettings`) | `/app/appsettings.{Env}.json` |
---
## ۴. CI/CD Pipeline
### ۴.۱ Gitea Actions Workflow
### ۴.۱ Gitea Actions Workflows (CMS)
```yaml
name: Build and Deploy
on:
push:
branches: [kub-stage, production]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release
- name: Test
run: dotnet test --no-build -c Release
- name: Docker Build & Push
run: |
docker build -t $REGISTRY/foursat/cms:${{ github.sha }} .
docker push $REGISTRY/foursat/cms:${{ github.sha }}
- name: Deploy to K8s
if: github.ref == 'refs/heads/production'
run: |
kubectl set image deployment/cms cms=$REGISTRY/foursat/cms:${{ github.sha }}
فایل‌های پایپلاین:
```
CMS/.gitea/workflows/
├── kub-deploy.yml ← Staging (branch: kub-stage)
├── prod-deploy.yml ← Production (branch: production)
└── cms-stage.yml ← قدیمی (IIS روی Windows — غیرفعال)
```
### ۴.۲ شاخه‌ها
### ۴.۲ فلوی Staging (`kub-deploy.yml`)
| شاخه | محیط | Deploy |
|------|------|--------|
| `kub-stage` | Staging (194.5.195.53) | Auto |
| `production` | Production (45.149.79.127) | Manual trigger |
| `main` | — | Development only |
```mermaid
flowchart TD
A["Push to kub-stage"] --> B["Start Docker daemon"]
B --> C["Clone repo"]
C --> D["Pack & Push Proto NuGet"]
D --> E["Docker build → tag :latest"]
E --> F["Push to 194.5.195.53:30080"]
F --> G["SCP cms-config.yaml + cms-deployment.yaml"]
G --> H["kubectl apply -f cms-config.yaml (Secret)"]
H --> I["kubectl apply -f cms-deployment.yaml"]
I --> J["kubectl rollout restart"]
J --> K["✅ Deployed to Staging"]
```
### ۴.۳ فلوی Production (`prod-deploy.yml`)
```mermaid
flowchart TD
A["Push to production"] --> B["Start Docker daemon"]
B --> C["Clone repo"]
C --> D["Pack & Push Proto NuGet"]
D --> E["Docker build → tag :sha + :prod"]
E --> F["Push to 194.5.195.53:30080"]
F --> G["SCP cms-config.yaml + cms-deployment.yaml"]
G --> H["kubectl apply -f cms-config.yaml (Secret)"]
H --> I["kubectl apply -f cms-deployment.yaml"]
I --> J["kubectl set image → sha"]
J --> K["✅ Deployed to Production"]
```
### ۴.۴ شاخه‌ها و محیط‌ها
| شاخه | محیط | سرور | Image Tag | Deploy |
|------|------|------|-----------|--------|
| `kub-stage` | Staging | 194.5.195.53 | `:latest` | Auto |
| `production` | Production | 45.149.79.127 | `:sha` + `:prod` | Auto |
### ۴.۵ نکات مهم CI/CD
- **Proto NuGet:** هر deploy ابتدا proto packages رو build و به Nexus push می‌کنه
- **Manifest apply:** پایپلاین ابتدا `cms-config.yaml` (Secret) رو apply می‌کنه، بعد `cms-deployment.yaml`
→ Secret + PVC + Deployment + Service + Ingress هر بار اعمال می‌شه
- **Image registry:** `194.5.195.53:30080` (داخلی Nexus) — نه `git.se.kbs1.ir`
- **Config دائمی:** تنظیمات در K8s Secret هست، نه داخل Docker image — تغییر config بدون rebuild ایمیج ممکنه
- **جداسازی برنچ:** هر برنچ فقط appsettings محیط خودش رو داره (بخش ۳.۶)
---
@@ -320,11 +452,85 @@ flowchart TD
## ۹. مانیتورینگ و Health Check
### ۹.۱ مرج پروداکشن (اسفند ۱۴۰۴)
| ریپو | شاخه مبدأ | commit | نکات |
|------|------------|--------|------|
| **CMS** | `kub-stage``production` | `eb1b249` | حل conflict در `appsettings.Production.json` + حذف migration تکراری `u21` |
| **FrontOffice** | `kub-stage``production` | `f02d082` | 21 فایل، 400 insertion + فیکس GwUrl به `cms.kbs2.ir` |
| **BackOffice** | `kub-stage``production` | `bdea2e8` | 36 فایل، بدون conflict |
### ۹.۲ کامیت‌های PVC و اصلاحات K8s (تیر ۱۴۰۴)
| commit | شرح |
|--------|------|
| `3153fd8` | feat: add PersistentVolume for CMS uploads + apply manifests in CI/CD |
| `68da3f4` | fix: staging uses namespace default, not foursat |
| `e41747a` | fix: production ingress — add cms.kbs2.ir, use ingressClassName |
| `2d6c95e` | fix: use local registry 194.5.195.53:30080 instead of git.se.kbs1.ir |
| `f8dc4ab` | fix: staging ASPNETCORE_ENVIRONMENT=Staging, remove secretKeyRef |
| `de83c31` | fix: production uses namespace default + remove foursat namespace references |
| `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
# k8s-health-check.sh
kubectl get pods -n foursat
kubectl top pods -n foursat
kubectl logs deployment/cms -n foursat --tail=50
# فیکس مستقیم روی سرور (بدون نیاز به 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
kubectl top pods
kubectl logs deployment/cms --tail=50
# بررسی PVC
kubectl get pvc cms-uploads-pvc
kubectl exec deployment/cms -- ls /app/Uploads | wc -l
# تست سرویس‌ها
grpcurl -plaintext localhost:5001 list # لیست سرویس‌ها
+39 -9
View File
@@ -1,7 +1,7 @@
# 🔄 مهاجرت داده، BFF و Gateway
> **منابع ادغام‌شده:** `BACKOFFICE-BFF-MIGRATION.md`, `customer-facing-capabilities-codex.md`, `DATA-TABLE-MAPPINGS.md`, `DATAMIGRATION-README.md`, `FRONTOFFICE-TO-CMS-MIGRATION.md`, `GATEWAY-REMOVAL-MIGRATION-PLAN.md`, `MIGRATION-PROGRESS.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: DataMigration Tool + EF Staging Migrations)
---
@@ -136,15 +136,15 @@ Proto-generated classes مستقیم در UI استفاده می‌شوند
```
DataMigration/
├── FourSat.DataMigration/ ← Console app
│ ├── Program.cs
│ ├── Migrators/
│ ├── UserMigrator.cs
│ │ ── ProductMigrator.cs
│ ├── OrderMigrator.cs
│ │ └── ClubMigrator.cs
├── FourSat.DataMigration/ ← Console app (.NET 9 + Dapper + Polly + Serilog)
│ ├── Program.cs ← Entry point
│ ├── appsettings.json ← Source/Target connection strings + TruncateTargetTables
│ ├── Services/
│ │ ── MigrationService.cs ← Smart retry, FK disable/enable, fallback table names
│ ├── Scripts/
│ │ └── PostMigration_DataTransformation.sql ← Guardشده با IF COL_LENGTH/OBJECT_ID
│ └── Mappings/
│ └── TableMappings.cs
│ └── TableMappings.cs ← Source → Target table/column mappings
└── FourSat.GeographySeeder/ ← Seed geography data
├── Program.cs
└── Data/
@@ -152,6 +152,21 @@ DataMigration/
└── cities.json
```
### ۵.۱.۱ ویژگی‌های DataMigration Tool (اسفند ۱۴۰۴)
| ویژگی | توضیح |
|--------|--------|
| **Smart Retry** | فقط خطاهای transient SQL (deadlock, timeout, transport) — نه خطاهای منطقی |
| **FK Disable/Enable** | `ALTER TABLE NOCHECK/CHECK CONSTRAINT ALL` حول هر مهاجرت |
| **TruncateTargetTables** | حل duplicate key (`IX_ClubMembership_UserId`) هنگام re-run |
| **Fallback Table Name** | اگر جدول rename شده (`UserWalletChangeLogs``UserWalletHistories`) |
| **PostMigration Guards** | همه مراحل با `IF COL_LENGTH`/`OBJECT_ID` برای سازگاری با هر دو schema |
| **Polly Retry** | exponential backoff (2s, 8s, 32s) + لاگ structured |
| **Serilog** | لاگ فایل + کنسول با جزئیات هر جدول |
> **کامیت‌ها:** `0e8c6fd``8385c90` (MERGE fix) → `31cc464` (FK+truncate+PostMigration)
> **وضعیت:** Local only — بدون remote (در workspace `DataMigration/` قرار دارد)
### ۵.۲ Data Table Mappings
| جدول مبدأ (قدیم) | جدول مقصد (CMS) | نکات |
@@ -178,6 +193,18 @@ DataMigration/
| `populate-weekly-commission-pools.sql` | داده تاریخی Pool |
| `update_products_price_10_percent.sql` | افزایش قیمت ۱۰% |
### ۵.۴ Migrationهای EF Core اجراشده روی Production/Staging (اسفند ۱۴۰۴)
| Migration | توضیح | DB |
|-----------|--------|----||
| `ExpandDiscountProductFullInformation` | گسترش فیلدهای محصول تخفیفی | KBS (Production `45.149.79.127`) |
| `AddMagicWalletFields` (u21) | فیلدهای کیف‌پول جادویی + ClubMembershipCycle | KBS (Production) |
| ۵۵ migration کامل | از Initial تا `Q27_HistoryTables_And_RenameWalletHistory` | KBS Staging (`185.252.31.42,2019/KBS`) |
| ۵۵ migration کامل | از Initial تا `Q27_HistoryTables_And_RenameWalletHistory` | App DB (`194.5.195.53,31433/Foursat`) |
> ✅ **نکته:** CMS به ۲ DB مختلف وصل می‌شود — هر دو باید migrate شوند.
> ✅ Migration `u21` در زمان merge تکراری بود — فایل تکراری حذف شد.
---
## ۶. Geography Seeder
@@ -226,3 +253,6 @@ FourSat.GeographySeeder:
| Data Migration (Club/Network) | ✅ | 100% |
| Geography Seeder | ✅ | 100% |
| Proto package unification | ✅ | 100% |
| EF Migration پروداکشن | ✅ | 100% |
| DataMigration Tool (Prod→Staging) | ✅ | 100% |
| EF Migration استیجینگ (KBS + Foursat) | ✅ | 100% |
+12 -2
View File
@@ -1,7 +1,7 @@
# 🔌 API، Protobuf و یکپارچه‌سازی خارجی
> **منابع ادغام‌شده:** `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `REMAINING-TASKS.md`, `chatika-integration.md`, `payment-gateway.md`, `club-feature-management-services.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet gRPC RPCs)
---
@@ -125,6 +125,13 @@ service SystemConfigService {
rpc UpdateConfig (UpdateConfigRequest) returns (Empty);
rpc GetAllConfigs (Empty) returns (ConfigListResponse);
}
// ===== userwallet.proto ===== (NEW — Magic Wallet)
service UserWalletService {
rpc GetCustomerWallet (GetCustomerWalletRequest) returns (GetCustomerWalletResponse);
rpc InitiateMagicCharge (InitiateMagicChargeRequest) returns (InitiateMagicChargeResponse);
rpc GetMagicWalletStatus (GetMagicWalletStatusRequest) returns (MagicWalletStatusResponse);
}
```
### ۲.۲ Shared Messages
@@ -299,9 +306,12 @@ flowchart TD
A["CMS/src/Protos/*.proto"] --> B["pack-protos.sh"]
B --> C["Foursat.CMSMicroservice.Protobuf.nupkg\nv1.0.x"]
C --> D["Push to BaGet / Nexus"]
D --> E["BackOffice + FrontOffice\nPackageReference"]
D --> E["BackOffice\nPackageReference"]
D --> F["FrontOffice\nProjectReference ✅"]
```
> ⚠️ FrontOffice از NuGet package به **ProjectReference** مستقیم سوییچ شده (برای دسترسی به پروتوهای جدید Magic Wallet)
---
## ۶. Remaining Tasks / Integration Gaps
+246
View File
@@ -0,0 +1,246 @@
# TECH-06 — جریان شارژ Pool کمیسیون هفتگی
> تاریخ: ۱۴۰۵/۰۲/۱۰
> وضعیت: **باگ شناسایی‌شده — منتظر Fix**
> مرتبط با: `ActivateClubMembershipCommandHandler.cs` · `AcceptClubMembershipContractCommandHandler.cs` · `CreateManualPaymentCommandHandler.cs`
---
## ۱. مسیر مشتری جدید (اولین خرید پکیج)
```mermaid
sequenceDiagram
actor U as کاربر (FrontOffice)
participant CB as PaymentCallback.razor
participant PI as Profile/Index.razor
participant CD as ClubMembershipContractDialog
participant CMS as CMS (gRPC)
U->>CB: بازگشت از درگاه<br/>?type=package&orderId=X&Authority=Y
CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
Note over CMS: PackageService.VerifyPackagePurchase()
CMS->>CMS: تأیید با درگاه ✓
CMS->>CMS: ActivateClubMembership(ForceActivation=false)
Note over CMS: isNewMembership = true
CMS->>CMS: ClubMembership(IsActive=false) ایجاد
CMS->>CMS: ClubMembershipCycle #1 ایجاد
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
CMS-->>CB: Success=true
CB->>CB: RefreshToken<br/>HasPurchasedPackage=true<br/>IsClubMemberActive=false
U->>PI: کلیک "بازگشت به پروفایل"
PI->>PI: OnAfterRenderAsync<br/>CheckAndShowClubContractModal()
Note over PI: HasPurchasedPackage=true<br/>AND IsClubMemberActive=false → نمایش مودال
PI->>CD: DialogService.ShowAsync (غیرقابل بستن)
U->>CD: مطالعه قرارداد + درخواست OTP
CD->>CMS: CreateNewOtpToken(purpose=signClubContract)
CMS-->>CD: OTP ارسال شد
U->>CD: وارد کردن OTP ۶ رقمی
CD->>CMS: AcceptClubMembershipContract(otp, signGuid)
Note over CMS: AcceptClubMembershipContractCommandHandler
CMS->>CMS: IsActive == false → guard رد می‌شه ✓
CMS->>CMS: IsActive = true
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ دوم ❌
CMS-->>CD: Success=true
CD->>PI: dialog.Close(Ok)
PI->>PI: LoadUserAuthInfo → IsClubMemberActive=true
```
> **نتیجه**: Pool برای عضو جدید **۲ برابر** شارژ می‌شود.
---
## ۲. مسیر خرید مجدد (بعد از تکمیل چرخه Magic)
```mermaid
sequenceDiagram
actor U as کاربر (FrontOffice)
participant CB as PaymentCallback.razor
participant PI as Profile/Index.razor
participant CMS as CMS (gRPC)
Note over CMS: وضعیت: IsActive=true<br/>IsCurrentCycle=true (باگ B6 — ریست نشده)
U->>CB: بازگشت از درگاه (خرید مجدد)
CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
CMS->>CMS: ActivateClubMembership(ForceActivation=false)
Note over CMS: isNewMembership = false<br/>existingMembership.IsActive=true<br/>hasCurrentCycle=true
CMS->>CMS: return true زودهنگام ❌
Note over CMS: Cycle جدید ساخته نمی‌شه ❌<br/>Pool شارژ نمی‌شه ❌
CMS-->>CB: Success=true
CB->>CB: RefreshToken → IsClubMemberActive=true
U->>PI: بازگشت به پروفایل
PI->>PI: IsClubMemberActive=true<br/>→ مودال نمایش داده نمی‌شه ✓
Note over PI,CMS: Pool هرگز شارژ نشد ❌<br/>Cycle جدید وجود ندارد ❌
```
> **نتیجه**: Pool برای خرید مجدد **هرگز** شارژ نمی‌شود. ریشه مشکل: باگ B6 — `IsCurrentCycle` هنگام خروج از Magic ریست نمی‌شود.
---
## ۳. مسیر ادمین (BackOffice — فعال‌سازی دستی)
```mermaid
sequenceDiagram
actor A as ادمین (BackOffice)
participant DL as ActivateClubDialog.razor
participant CMS as CMS (gRPC)
A->>DL: باز کردن دیالوگ فعال‌سازی برای کاربر X
DL->>DL: انتخاب UserId و PackageId
A->>DL: کلیک "تایید و فعال‌سازی"
DL->>CMS: ActivateClubMembership(UserId=X, ForceActivation=true)
Note over CMS: ActivateClubMembershipCommandHandler<br/>skip همه validation‌های مالی
alt کاربر جدید (isNewMembership=true)
CMS->>CMS: ClubMembership(IsActive=false) ایجاد
CMS->>CMS: Cycle #1 ایجاد
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
Note over CMS: کاربر هنوز عضو فعال نیست!<br/>IsActive=false
Note over A,CMS: کاربر باید به FO رود و قرارداد امضا کند
Note over A,CMS: AcceptContract → Pool += fee ← شارژ دوم ❌
else خرید مجدد (isNewMembership=false، IsCurrentCycle ریست شده)
CMS->>CMS: IsActive=true, hasCurrentCycle=false → ادامه می‌دهد
CMS->>CMS: Cycle جدید ایجاد
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ یک بار ✅
end
CMS-->>DL: Empty (success)
DL->>A: "عضویت با موفقیت فعال شد"
Note over A,CMS: AcceptContract از BO هرگز فراخوانی نمی‌شود
```
> **نتیجه**: ادمین برای کاربر جدید نیز باعث double-charge می‌شود (چون کاربر بعداً از FO قرارداد امضا می‌کند). برای خرید مجدد رفتار درست است.
---
## ۵. خلاصه باگ‌ها
| سناریو | Pool شارژ واقعی | Pool شارژ انتظاری | Cycle ساخته می‌شود | وضعیت |
|--------|----------------|-------------------|--------------------|--------|
| مشتری جدید (IPG) | **2×fee** | 1×fee | ✅ بله | ❌ Double-charge |
| خرید مجدد مشتری | **0×fee** | 1×fee | ❌ خیر (B6) | ❌ هرگز شارژ نمی‌شود |
| ادمین — ForceActivate کاربر جدید | **2×fee** | 1×fee | ✅ بله | ❌ Double-charge |
| ادمین — ForceActivate خرید مجدد | **1×fee** | 1×fee | ✅ بله | ✅ درست |
| ادمین — ManualPayment (پرداخت دستی) | **1×fee** | 1×fee | ❌ خیر | ⚠️ Pool درست، ولی والدین امتیاز نمی‌گیرند |
---
## ۵. ریشه مشکلات
### باگ A — Double-charge در عضو جدید
**فایل**: `ActivateClubMembershipCommandHandler.cs` — بخش Pool (خط ~۳۱۱)
**علت**: هنگامی که `isNewMembership=true`، Pool شارژ می‌شود؛ بعداً `AcceptContract` هم Pool را شارژ می‌کند.
**Fix**: شارژ Pool در `ActivateClubMembership` را فقط برای `!isNewMembership` انجام بده:
```csharp
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
// عضو جدید: Pool توسط AcceptClubMembershipContract شارژ می‌شه (هنگام امضای قرارداد)
// خرید مجدد: قرارداد مجدد امضا نمی‌شه — Pool همین‌جا شارژ می‌شه
if (!isNewMembership)
{
// ... کد موجود شارژ Pool ...
}
```
### باگ B6 — خرید مجدد کار نمی‌کند
**فایل**: `UserOrderService.cs` — بخش خروج از Magic
**علت**: هنگام خروج از Magic، `cycle.IsCurrentCycle` به `false` ریست نمی‌شود → `ActivateClubMembership` با `hasCurrentCycle=true` زودهنگام برمی‌گردد.
**Fix**: در `ExitMagicMode`:
```csharp
cycle.IsCurrentCycle = false; // ← اضافه شود
```
### باگ C — پرداخت دستی: Cycle هرگز ساخته نمی‌شود
**فایل**: `CreateManualPaymentCommandHandler.cs`
**علت**: پرداخت دستی `ActivateClubMembership` را صدا نمی‌زند → هیچ `ClubMembershipCycle` ساخته نمی‌شود → SP این کاربر را به عنوان "عضو جدید" برای والدینش حساب نمی‌کند.
**تأثیر**: Pool یک‌بار شارژ می‌شود (توسط AcceptContract ✓) ولی balance والدین در sp_CalculateWeeklyBalances افزایش نمی‌یابد (چون Cycle ندارد ❌).
---
## ۴. مسیر پرداخت دستی (BackOffice — ManualPayment)
```mermaid
sequenceDiagram
actor A as ادمین (BackOffice)
participant DL as ManualPaymentDialog.razor
participant CMS as CMS (gRPC)
actor U as کاربر (FrontOffice)
participant PI as Profile/Index.razor
participant CD as ClubMembershipContractDialog
A->>DL: باز کردن دیالوگ پرداخت دستی
DL->>DL: انتخاب کاربر + پکیج + نوع پرداخت + تصویر رسید
A->>DL: کلیک "ثبت پرداخت"
DL->>CMS: CreateManualPayment(userId, packageId, type, referenceNumber)
Note over CMS: CreateManualPaymentCommandHandler
CMS->>CMS: Transaction(DepositExternal1) ایجاد
CMS->>CMS: ManualPayment(Status=Approved) ایجاد ← بدون نیاز به تایید دو مرحله
CMS->>CMS: wallet.Balance += package.Price
CMS->>CMS: wallet.DiscountBalance += package.Price × DiscountMultiplier
CMS->>CMS: user.PackagePurchaseMethod = DirectPurchase
Note over CMS: ❌ ActivateClubMembership صدا زده نمی‌شود<br/>❌ ClubMembershipCycle ساخته نمی‌شود<br/>❌ Pool شارژ نمی‌شود
CMS-->>DL: ManualPaymentId
DL->>A: "پرداخت دستی با موفقیت ثبت شد"
Note over A,U: کاربر باید به FO مراجعه کند
U->>PI: ورود به پروفایل
PI->>PI: OnAfterRenderAsync → CheckAndShowClubContractModal()
Note over PI: HasPurchasedPackage=true (PackagePurchaseMethod=DirectPurchase)<br/>IsClubMemberActive=false → نمایش مودال
PI->>CD: DialogService.ShowAsync (غیرقابل بستن)
U->>CD: امضای قرارداد + OTP
CD->>CMS: AcceptClubMembershipContract(otp, signGuid)
Note over CMS: AcceptClubMembershipContractCommandHandler
CMS->>CMS: user.ClubMembership == null → isNewMembership = true
CMS->>CMS: ClubMembership(IsActive=true) ایجاد
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ یک‌بار ✅
Note over CMS: ❌ ClubMembershipCycle هرگز ساخته نمی‌شود<br/>(AcceptContract از Cycle خبری ندارد)
CMS-->>CD: Success=true
CD->>PI: dialog.Close(Ok)
```
> **نتیجه**:
> - Pool: **1×** شارژ می‌شود ✅ (درست)
> - `ClubMembershipCycle`: **هرگز ساخته نمی‌شود**
> - در `sp_CalculateWeeklyBalances`: کاربر `IsActive=true` دارد → خودش می‌تواند کمیسیون دریافت کند ✅
> - ولی والدین این کاربر **هیچ "عضو جدید" برای این هفته دریافت نمی‌کنند** ❌ (چون SP از `ClubMembershipCycles.PackagePurchasedAt` می‌خواند)
---
## ۶. validation داشبورد کمیسیون
پس از رفع باگ A، validation باید از **فقط یک منبع** استفاده کند:
```csharp
// درست: فقط ClubMembershipCycles.PackagePurchasedAt
// این جدول برای هر خرید (چه جدید چه مجدد) یک رکورد دارد
var activations = await _context.ClubMembershipCycles
.CountAsync(c => c.PackageId == packageId
&& c.PackagePurchasedAt >= weekDef.StartDate
&& c.PackagePurchasedAt < weekDef.EndDate);
```
> قبل از رفع باگ A، validation فعلی (firstActivations + cycleActivations) تصادفاً با double-charge جبران می‌شد.
+334
View File
@@ -0,0 +1,334 @@
# TECH-07 — لاگ کامل Session 1404/02/10 (2026-04-30)
> نوع سند: **گزارش کار**
> تاریخ: ۱۴۰۵/۰۲/۱۰
> مرتبط با: CMS · BackOffice · FrontOffice · Database
---
## فهرست مطالب
1. [بخش اول — رفع باگ Double-Charge Pool](#۱-رفع-باگ-double-charge-pool)
2. [بخش دوم — بررسی داده‌های هفته‌های ۲۲ و ۲۳](#۲-بررسی-داده‌های-هفته‌های-۲۲-و-۲۳)
3. [بخش سوم — ویژگی Network Tree (اطلاعات هفتگی)](#۳-ویژگی-network-tree-نوع-فعالسازی--پکیج)
4. [بخش چهارم — بهبود UI نمودار درختی](#۴-بهبود-ui-نمودار-درختی)
5. [بخش پنجم — رفع باگ Pagination فروشگاه تخفیف](#۵-رفع-باگ-pagination-فروشگاه-تخفیف)
6. [خلاصه فایل‌های تغییریافته](#خلاصه-فایل‌های-تغییریافته)
7. [وظایف باقی‌مانده (Pending)](#وظایف-باقی‌مانده)
---
## ۱. رفع باگ Double-Charge Pool
### مشکل
در جریان فعال‌سازی عضویت باشگاه، Pool کمیسیون هفتگی **دوبار** شارژ می‌شد:
- بار اول: در `ActivateClubMembership` (از طریق `VerifyPackagePurchase`)
- بار دوم: در `AcceptClubMembershipContract` (تأیید قرارداد توسط کاربر)
همچنین `CreateManualPayment` هم یک مسیر مستقل داشت که بدون Check هفته، Pool اشتباه را شارژ می‌کرد.
### ریشه مشکل
تابع `GetOrCreateCurrentWeeklyPool` بدون در نظر گرفتن هفته واقعی `PackagePurchasedAt`، Pool هفته جاری را انتخاب می‌کرد.
### فایل‌های اصلاح‌شده
#### `ActivateClubMembershipCommandHandler.cs`
```csharp
// قبل: همیشه Pool هفته جاری را شارژ می‌کرد
// بعد: فقط یک‌بار در محل صحیح (AcceptContract) شارژ می‌شود
// حذف: شارژ Pool از داخل ActivateClubMembership (for isNewMembership scenario)
```
#### `AcceptClubMembershipContractCommandHandler.cs`
```csharp
// اضافه: بررسی هفته قرارداد — اگر هفته PackagePurchasedAt با هفته جاری فرق دارد
// از Pool هفته مناسب استفاده می‌کند نه Pool هفته جاری
```
#### `CreateManualPaymentCommandHandler.cs`
```csharp
// اصلاح: Cross-week fix — Pool هفته صحیح بر اساس تاریخ پرداخت دستی
```
#### `sp_CalculateWeeklyCommissionPool.sql` (SP در Infrastructure)
```sql
-- اصلاح: IsCurrentCycle check برای جلوگیری از Double-Count
-- هر کاربر فقط یک‌بار در محاسبه Pool شمرده می‌شود
```
---
## ۲. بررسی داده‌های هفته‌های ۲۲ و ۲۳
### تشخیص
با اجرای diagnostic SQL روی DB، دو anomaly کشف شد:
#### هفته ۲۲ — Pool Ghost (PoolId=10056)
| فیلد | مقدار |
|------|-------|
| TotalPoolAmount | 2,520,000 |
| AllCycles | 0 |
| ریشه | هانیه سادات عشاقی (UserId=189) — خرید 1404/01/12 (هفته ۲۱) ولی AcceptContract در 17:02 دقیقه بعد Pool هفته ۲۲ را شارژ کرد |
**دلیل:** CreatedAt و ModifiedAt timestamp مغایرت داشت — Pool در هفته ۲۲ ایجاد شد اما Cycle در هفته ۲۱ بود.
**اصلاح دستی DB (Pending):**
```sql
UPDATE CMS.WeeklyCommissionPools
SET TotalPoolAmount = 0, LastModified = GETUTCDATE()
WHERE Id = 10056 AND WeekDefinitionId = 22;
```
#### هفته ۲۳ — Pool ناقص (PoolId=10054)
| فیلد | مقدار |
|------|-------|
| TotalPoolAmount | 0 |
| IsCalculated | False |
| ریشه | محمدصادق عسلی (UserId=190) — خرید هفته ۲۳، Cycle وجود دارد ولی Pool=0 (قبل از fix) |
**اصلاح دستی DB (Pending):**
```sql
UPDATE CMS.WeeklyCommissionPools
SET TotalPoolAmount = 2520000, LastModified = GETUTCDATE()
WHERE Id = 10054 AND WeekDefinitionId = 23;
```
---
## ۳. ویژگی Network Tree (نوع فعالسازی + پکیج)
### هدف
صفحه `/network/tree` در BackOffice باید در هر node نشان دهد:
- آیا این کاربر در هفته انتخابی **عضو جدید** بوده یا **تمدید کرده**؟
- نام پکیجی که خریداری کرده؟
### پیاده‌سازی Full-Stack
#### الف) SP_GetNetworkTree (dbbkup/SP_GetNetworkTree.sql)
```sql
-- اضافه شد:
OUTER APPLY (
SELECT TOP 1 cc.*
FROM CMS.ClubMembershipCycles cc
WHERE cc.ClubMembershipId = cm.Id
AND cc.PackagePurchasedAt >= @WeekStartDate
AND cc.PackagePurchasedAt < @WeekEndDate
AND (@ActivationWeekDefinitionId IS NULL OR @WeekStartDate IS NOT NULL)
) AS cc_target
-- ستون‌های جدید در output:
IsActivatedInTargetWeek -- آیا در هفته انتخابی فعال شده؟
IsNewActivation -- 1=اولین فعالسازی (CycleNumber=1), 0=تمدید, NULL=بدون Cycle
PackageName -- نام پکیج اون هفته
PackageId -- شناسه پکیج
```
**نکته:** منطق هفته‌بندی از `cm.ActivatedAt` به `Cycle.PackagePurchasedAt` تغییر کرد.
**Deploy SP:**
```
SP مستقیم روی DB اجرا شد (نه EmbeddedResource Infrastructure)
اجرا شد در: /tmp/DbDiag با C# script
تأیید شد: SELECT OBJECT_ID('[CMS].[GetNetworkTree]') → موجود
```
#### ب) Application Layer
**`NetworkTreeNodeDto.cs`** — فیلدهای جدید:
```csharp
bool? IsNewActivation
string? PackageName
long? PackageId
```
**`NetworkTreeDto.cs`** — همین فیلدها
**`GetNetworkTreeQueryHandler.cs`**:
```csharp
// خواندن از DataReader:
IsNewActivation = reader.IsDBNull(reader.GetOrdinal("IsNewActivation"))
? null
: reader.GetInt32(reader.GetOrdinal("IsNewActivation")) == 1,
PackageName = reader["PackageName"] as string,
PackageId = reader.IsDBNull(reader.GetOrdinal("PackageId"))
? null
: reader.GetInt64(reader.GetOrdinal("PackageId"))
```
#### ج) Proto (networkmembership.proto)
```protobuf
// NetworkTreeNodeModel — فیلدهای جدید:
google.protobuf.BoolValue is_new_activation = 22;
string package_name = 23;
google.protobuf.Int64Value package_id = 24;
```
**NuGet Package:** `Foursat.CMSMicroservice.Protobuf` → از `0.0.194` به **`0.0.195`** bump و push شد.
#### د) Mapping (NetworkMembershipProfile.cs)
```csharp
PackageName = node.PackageName ?? string.Empty,
PackageId = node.PackageId.HasValue ? node.PackageId.Value : null,
IsNewActivation = node.IsNewActivation.HasValue ? node.IsNewActivation.Value : null
```
#### هـ) BackOffice — NetworkTreeViewer.razor
**DataGrid — دو ستون جدید:**
```razor
<!-- ستون نوع فعالسازی -->
<PropertyColumn Property="x => x.IsNewActivation" Title="نوع فعالسازی">
@if (context.Item.IsNewActivation == true)
{
<MudChip Color="Color.Success">🆕 عضو جدید</MudChip>
}
else if (context.Item.IsNewActivation == false)
{
<MudChip Color="Color.Secondary">🔄 خرید مجدد</MudChip>
}
</PropertyColumn>
<!-- ستون پکیج -->
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
```
**JS (jsNodes):**
```js
isNewActivation: n.IsNewActivation,
packageName: n.PackageName ?? ""
```
---
## ۴. بهبود UI نمودار درختی
### مشکل اولیه
بج «🆕 جدید» با `position: absolute` از گوشه کارت بیرون می‌زد و با محتوای دیگر برخورد می‌کرد.
### فایل‌های تغییریافته
#### `admin-org-chart.js` (wwwroot/js)
**ساختار کارت بازنویسی شد:**
```
┌─────────────────────────────┐
│ [Avatar] نام کاربر │
│ پکیج نقره... │ ← inline زیر اسم
│ L13 چپ عضو جدید │ ← pill در meta row
├─────────────────────────────┤
│ ✓ فعال 1404/12/24 │
└─────────────────────────────┘
```
**تغییرات:**
- `activationTypeBadge` (absolute positioning) → `activationTypePill` (inline span)
- `highlightBadge` (✨ floating) → حذف شد
- `packageBadge` به زیر اسم کاربر منتقل شد (نه footer)
- ابعاد کارت: `160×80``178×92` px
#### `admin-org-chart.css` (wwwroot/css)
```css
/* جدید: activation pill به جای badge */
.admin-node-card .activation-pill { /* inline flex */ }
.admin-node-card .new-member-pill { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
.admin-node-card .renewal-pill { background: #ede7f6; color: #5e35b1; border: 1px solid #b39ddb; }
/* بهبود: پکیج روشن‌تر */
.admin-node-card .package-name-badge { background: #eceff1; color: #546e7a; border: 1px solid #b0bec5; }
```
---
## ۵. رفع باگ Pagination فروشگاه تخفیف
### مشکل
در صفحه `/discount-store/products`، دکمه «نمایش محصولات بیشتر» کار نمی‌کرد — همیشه صفحه اول برمی‌گشت.
### ریشه مشکل
**فایل غایب:** `DiscountProductProfile.cs` (Mapster) وجود نداشت.
**جریان mapping:**
```
GetDiscountProductsRequest (proto)
↓ request.Adapt<GetDiscountProductsQuery>()
GetDiscountProductsQuery
```
بدون profile، auto-mapping دو مشکل داشت:
1. `request.SearchQuery (string)``query.SearchTerm (string?)` → نامتطابق نام، NULL می‌شد
2. `request.PageNumber (int)``query.PaginationQuery.PageNumber`**Mapster نمی‌توانست به nested object مپ کند**`PaginationQuery = null` → default: `PageNumber=1` همیشه!
### راه‌حل
**فایل جدید:** `CMS/src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs`
```csharp
config.NewConfig<GetDiscountProductsRequest, GetDiscountProductsQuery>()
.Map(dest => dest.SearchTerm,
src => string.IsNullOrEmpty(src.SearchQuery) ? null : src.SearchQuery)
.Map(dest => dest.CategoryId,
src => src.CategoryId != null ? src.CategoryId.Value : (long?)null)
.Map(dest => dest.IsActive,
src => src.IsActive != null ? src.IsActive.Value : (bool?)null)
.Map(dest => dest.PaginationQuery, src => new PaginationState
{
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
PageSize = src.PageSize > 0 ? src.PageSize : 12
});
```
همچنین `GetDiscountProductsResponseDto → GetDiscountProductsResponse` هم به صورت صریح مپ شد تا `MetaData` و `Models` درست انتقال یابند.
---
## خلاصه فایل‌های تغییریافته
| فایل | نوع تغییر | پروژه |
|------|-----------|-------|
| `ActivateClubMembershipCommandHandler.cs` | Fix — حذف Double-Charge | CMS Application |
| `AcceptClubMembershipContractCommandHandler.cs` | Fix — Cross-week Pool | CMS Application |
| `CreateManualPaymentCommandHandler.cs` | Fix — Cross-week Pool | CMS Application |
| `sp_CalculateWeeklyCommissionPool.sql` | Fix — IsCurrentCycle | CMS Infrastructure |
| `SP_GetNetworkTree.sql` | Feature — IsNewActivation, PackageName, PackageId | DB/dbbkup |
| `NetworkTreeNodeDto.cs` | Feature — فیلدهای جدید | CMS Application |
| `NetworkTreeDto.cs` | Feature — فیلدهای جدید | CMS Application |
| `GetNetworkTreeQueryHandler.cs` | Feature — خواندن فیلدهای جدید | CMS Application |
| `networkmembership.proto` | Feature — ۳ فیلد جدید در NetworkTreeNodeModel | Protobuf |
| `NetworkMembershipProfile.cs` | Feature — mapping فیلدهای جدید | CMS WebApi |
| `NetworkTreeViewer.razor` | Feature — DataGrid ستون‌های جدید | BackOffice |
| `admin-org-chart.js` | Feature+Fix — inline pill، پکیج زیر اسم | BackOffice wwwroot |
| `admin-org-chart.css` | Feature+Fix — استایل pill‌های مرتب | BackOffice wwwroot |
| `DiscountProductProfile.cs` | Fix — Pagination mapping صحیح | CMS WebApi (جدید) |
### NuGet Package
| پکیج | نسخه قبل | نسخه جدید |
|------|----------|-----------|
| `Foursat.CMSMicroservice.Protobuf` | 0.0.194 | **0.0.195** |
---
## وظایف باقی‌مانده
### ضروری — اصلاح داده‌های DB
```sql
BEGIN TRANSACTION;
-- هفته ۲۲: Pool Ghost (هانیه سادات عشاقی ← AcceptContract هفته اشتباه)
UPDATE CMS.WeeklyCommissionPools
SET TotalPoolAmount = 0, LastModified = GETUTCDATE()
WHERE Id = 10056 AND WeekDefinitionId = 22;
-- هفته ۲۳: Pool ناقص (محمدصادق عسلی ← Pool قبل از Fix ایجاد شده بود)
UPDATE CMS.WeeklyCommissionPools
SET TotalPoolAmount = 2520000, LastModified = GETUTCDATE()
WHERE Id = 10054 AND WeekDefinitionId = 23;
COMMIT;
```
### بهبود آینده
- [ ] `SP_GetNetworkTree.sql` به Infrastructure EmbeddedResource اضافه شود (auto-deploy)
- [ ] `DiscountProductDto` در Application — اضافه کردن فیلد `Created` از DB
- [ ] تست pagination فروشگاه پس از restart CMS
+303
View File
@@ -0,0 +1,303 @@
# TECH-08 — لاگ Session 1404/02/23 (2026-05-13)
> نوع سند: **گزارش کار**
> تاریخ: ۱۴۰۵/۰۲/۲۳
> مرتبط با: CMS · FrontOffice
> کامیت CMS: `683ed37` (branch: `kub-stage`)
> کامیت FrontOffice: `231da2c` (branch: `kub-stage`)
---
## فهرست مطالب
1. [هدف و خلاصه](#هدف-و-خلاصه)
2. [تغییرات CMS (Backend)](#تغییرات-cms-backend)
3. [تغییرات FrontOffice](#تغییرات-frontoffice)
4. [معماری GuestActionGate](#معماری-guestactiongate)
5. [فلوچارت تجربه کاربر](#فلوچارت-تجربه-کاربر)
6. [فایل‌های تغییر یافته](#فایلهای-تغییر-یافته)
---
## هدف و خلاصه
هدف این session:
1. **نمایش ۶ محصول پرفروش معمولی + ۶ محصول پرفروش فروشگاه اعتباری** در لندینگ پیج FrontOffice، زیر هدر اصلی (۳ محصول در هر ردیف، دو section مجزا).
2. **دسترسی guest** (کاربر بدون لاگین) به مرور محصولات برای پرزنت به مشتریان بالقوه.
3. **Hybrid auth flow**: کاربر guest محصولات را می‌بیند؛ اگر روی "افزودن به سبد" کلیک کرد، مودال لاگین باز می‌شود و پس از ورود موفق، عمل به صورت خودکار انجام می‌شود.
---
## تغییرات CMS (Backend)
### ۱. `discountproduct.proto`
```proto
// اضافه شده به GetDiscountProductsRequest
google.protobuf.StringValue sort_by = 9;
// اضافه شده به DiscountProductDto
int32 sale_count = 12;
```
**چرا:** برای واکشی پرفروش‌ترین محصولات فروشگاه اعتباری باید امکان sort بر اساس `sale_count` وجود داشته باشد. قبلاً این فیلد در DTO برگردانده نمی‌شد.
### ۲. `CMSMicroservice.Protobuf.csproj`
نسخه از `0.0.195` به `0.0.196` بالا رفت تا پکیج NuGet جدید publish شود.
### ۳. `GetDiscountProductsQuery.cs`
```csharp
public string? SortBy { get; set; }
```
### ۴. `GetDiscountProductsQueryHandler.cs`
```csharp
// قبل: همیشه OrderByDescending(p => p.Created)
// بعد: dynamic sort با fallback
if (!string.IsNullOrEmpty(request.SortBy))
query = query.ApplyOrder(request.SortBy);
else
query = query.OrderByDescending(p => p.Created);
// و در SELECT:
SaleCount = p.SaleCount,
```
از extension method موجود `ApplyOrder` (کتابخانه `System.Linq.Dynamic.Core`) استفاده شد تا نیازی به تغییر جداگانه نباشد.
### ۵. `DiscountProductProfile.cs` (Mapster)
```csharp
// Request mapping
.Map(dest => dest.SortBy, src => string.IsNullOrEmpty(src.SortBy) ? null : src.SortBy)
// Response mapping
SaleCount = p.SaleCount,
```
---
## تغییرات FrontOffice
### ۱. `GuestActionGate.cs` (فایل جدید)
```
FrontOffice.Main/Utilities/GuestActionGate.cs
```
سرویس utility جدید که هر action نیازمند لاگین را wrap می‌کند:
```csharp
public async Task<bool> RunAsync(Func<Task> action)
{
if (await _authService.IsAuthenticatedAsync())
{
await action();
return true;
}
await _authDialogService.ShowAuthDialogAsync();
if (await _authService.IsAuthenticatedAsync())
{
await action();
return true;
}
return false;
}
```
در `ConfigureServices.cs` به صورت Scoped ثبت شد:
```csharp
services.AddScoped<GuestActionGate>();
```
### ۲. `ProductService.cs`
```csharp
public Task<ProductListResult> GetTopSellingAsync(int count = 6)
=> GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count);
```
### ۳. `DiscountProductService.cs`
```csharp
// پارامتر جدید به GetProductsAsync اضافه شد
public async Task<DiscountProductListResult> GetProductsAsync(
..., string? sortBy = null)
{
if (!string.IsNullOrWhiteSpace(sortBy))
request.SortBy = sortBy;
...
}
public Task<DiscountProductListResult> GetTopSellingAsync(int count = 6)
=> GetProductsAsync(page: 1, pageSize: count, sortBy: "SaleCount desc");
```
### ۴. `Index.razor` و `Index.razor.cs`
دو section جدید در لندینگ پیج زیر hero اضافه شد:
**Section 1 — محصولات پرفروش معمولی:**
- عنوان: "محصولات پرفروش"
- ۶ کارت (۳ در هر ردیف با MudGrid)
- هر کارت: تصویر، نام، قیمت با VAT، دکمه "افزودن به سبد"
- دکمه "بیشتر" → `/products`
**Section 2 — محصولات پرفروش فروشگاه اعتباری:**
- عنوان: "فروشگاه اعتباری"
- ۶ کارت (۳ در هر ردیف)
- هر کارت: تصویر، نام، قیمت، درصد تخفیف
- دکمه "بیشتر" → `/discount-store`
**Loading state:** در حین بارگذاری یک spinner نشان داده می‌شود و سپس section‌ها fade-in می‌شوند.
**Data loading (parallel):**
```csharp
var topRegTask = ProductService.GetTopSellingAsync(6);
var topDiscTask = DiscountProductService.GetTopSellingAsync(6);
var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2);
await Task.WhenAll(topRegTask, topDiscTask, featuredPostsTask);
```
**Cart actions با GuestActionGate:**
```csharp
private async Task AddRegularToCart(Product p)
=> await GuestGate.RunAsync(() => Cart.Add(p, 1));
private async Task AddDiscountToCart(DiscountProductCard p)
=> await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
```
### ۵. Hybridize کردن صفحات موجود
#### صفحات لیست و جزئیات محصول (GuestActionGate):
| فایل | تغییر |
|------|-------|
| `Store/Products.razor.cs` | `AddToCart``GuestGate.RunAsync(...)` |
| `Store/ProductDetail.razor.cs` | `AddToCart` و `RemoveFromCart``GuestGate.RunAsync(...)` |
| `DiscountStore/Products.razor.cs` | `AddToCart``GuestGate.RunAsync(...)` |
| `DiscountStore/ProductDetail.razor.cs` | `AddToCart``GuestGate.RunAsync(...)` |
#### صفحات Cart و Checkout (Soft Auth Gate):
```csharp
protected override async Task OnInitializedAsync()
{
if (!await AuthService.IsAuthenticatedAsync())
{
await AuthDialogService.ShowAuthDialogAsync();
}
// ادامه بارگذاری...
}
```
این pattern روی:
- `Store/Cart.razor.cs`
- `Store/CheckoutSummary.razor.cs`
- `DiscountStore/Cart.razor.cs`
- `DiscountStore/Checkout.razor.cs`
اعمال شد. اگر guest مستقیماً وارد سبد خرید شود، مودال لاگین نشان داده می‌شود.
### ۶. `MembershipPage.razor` (fix متنی)
```diff
- شارژ ۵۶ میلیون تومان کیف پول فروشگاه اعتباری
+ شارژ برابر ارزش پکیج فعال در کیف پول فروشگاه اعتباری
```
متن hardcode‌شده با مقدار دینامیک جایگزین شد.
---
## معماری GuestActionGate
```
کاربر کلیک می‌کند
GuestActionGate.RunAsync(action)
├─► آیا لاگین است؟ ──YES──► action() اجرا می‌شود ✅
NO
AuthDialogService.ShowAuthDialogAsync()
(مودال OTP باز می‌شود)
├─► آیا لاگین شد؟ ──YES──► action() اجرا می‌شود ✅
NO (بستن مودال)
return false (هیچ اتفاقی نمی‌افتد) ❌
```
این pattern **defense-in-depth** است: `CartService.Add` هم به تنهایی چک `IsAuthenticatedAsync` دارد؛ `GuestActionGate` لایه UX روی آن اضافه می‌کند.
---
## فلوچارت تجربه کاربر
```
کاربر وارد لندینگ پیج می‌شود (بدون لاگین)
├─► ۶ محصول پرفروش معمولی نمایش داده می‌شود
├─► ۶ محصول پرفروش اعتباری نمایش داده می‌شود
├─► "بیشتر" کلیک → /products یا /discount-store
│ (صفحات لیست کامل، بدون لاگین قابل مرور)
├─► روی محصول کلیک → صفحه جزئیات
│ (بدون لاگین قابل مشاهده)
└─► "افزودن به سبد" کلیک
مودال لاگین (OTP)
├─► ورود موفق → محصول به سبد اضافه می‌شود ✅
└─► بستن مودال → هیچ اتفاقی نمی‌افتد
```
---
## فایل‌های تغییر یافته
### CMS — کامیت `683ed37`
```
src/CMSMicroservice.Protobuf/Protos/discountproduct.proto (+2)
src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj (~2)
src/CMSMicroservice.Application/DiscountShopCQ/Queries/
GetDiscountProducts/GetDiscountProductsQuery.cs (+1)
GetDiscountProducts/GetDiscountProductsQueryHandler.cs (+7 -3)
src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs (+3)
```
### FrontOffice — کامیت `231da2c`
```
src/FrontOffice.Main/Utilities/GuestActionGate.cs (NEW +42)
src/FrontOffice.Main/ConfigureServices.cs (+1)
src/FrontOffice.Main/Utilities/ProductService.cs (+3)
src/FrontOffice.Main/Utilities/DiscountProductService.cs (+8)
src/FrontOffice.Main/Pages/Index.razor (+~180)
src/FrontOffice.Main/Pages/Index.razor.cs (+45)
src/FrontOffice.Main/Pages/Store/Products.razor.cs (+5)
src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs (+5)
src/FrontOffice.Main/Pages/Store/Cart.razor.cs (+8)
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs (+8)
src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs (+5)
src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs (+5)
src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs (+8)
src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs (+8)
src/FrontOffice.Main/Pages/Club/MembershipPage.razor (~1)
```