Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -1,10 +1,32 @@
|
||||
# 📚 FourSat Documentation Index
|
||||
|
||||
> آخرین بروزرسانی: February 10, 2026
|
||||
> آخرین بروزرسانی: February 17, 2026
|
||||
> ۲۲۰ فایل → ۳۰ فایل (تجمیع ۳ فازی + cleanup نهایی)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 راهنمای سریع — کدام مستند را باید ببینم؟
|
||||
|
||||
| میخواهم بدانم... | مستند |
|
||||
|-------------------|-------|
|
||||
| **کل تغییرات BackOffice چه بوده؟** | [`BackOffice/docs/BACKOFFICE-CHANGELOG.md`](../BackOffice/docs/BACKOFFICE-CHANGELOG.md) |
|
||||
| **ساختار و معماری BackOffice چیست؟** | [`ui-modernization/BACKOFFICE-ARCHITECTURE.md`](ui-modernization/BACKOFFICE-ARCHITECTURE.md) |
|
||||
| **یکسانسازی فروشگاهها چه بوده؟** | [`ui-modernization/BACKOFFICE-STORE-UNIFICATION.md`](ui-modernization/BACKOFFICE-STORE-UNIFICATION.md) |
|
||||
| **وضعیت فروشگاه تخفیفی؟** | [`business/DISCOUNT-STORE-STATUS.md`](business/DISCOUNT-STORE-STATUS.md) |
|
||||
| **بیزینس فروشگاه تخفیفی چگونه کار میکند؟** | [`business/discount-shop-business.md`](business/discount-shop-business.md) |
|
||||
| **سیستم کمیسیون چگونه کار میکند؟** | [`business/club-commission-system-complete.md`](business/club-commission-system-complete.md) |
|
||||
| **چگونه deploy کنم؟** | [`deployment/OFFLINE-DEPLOYMENT-GUIDE.md`](deployment/OFFLINE-DEPLOYMENT-GUIDE.md) |
|
||||
| **وضعیت CI/CD چیست؟** | [`deployment/CICD-PIPELINE-GUIDE.md`](deployment/CICD-PIPELINE-GUIDE.md) |
|
||||
| **مشخصات سرور و زیرساخت؟** | [`deployment/INFRASTRUCTURE-GUIDE.md`](deployment/INFRASTRUCTURE-GUIDE.md) |
|
||||
| **مهاجرت BFF→CMS چگونه بوده؟** | [`migration/BACKOFFICE-BFF-MIGRATION.md`](migration/BACKOFFICE-BFF-MIGRATION.md) |
|
||||
| **مهاجرت FrontOffice→CMS؟** | [`migration/FRONTOFFICE-TO-CMS-MIGRATION.md`](migration/FRONTOFFICE-TO-CMS-MIGRATION.md) |
|
||||
| **نقشه نوسازی UI فرانت؟** | [`ui-modernization/UI-MODERNIZATION-PLAN.md`](ui-modernization/UI-MODERNIZATION-PLAN.md) |
|
||||
| **معماری مدیریت فایل و تصاویر؟** | [`cms/FILE-MANAGEMENT-ARCHITECTURE.md`](cms/FILE-MANAGEMENT-ARCHITECTURE.md) |
|
||||
| **فیکس فلوی ثبتنام FrontOffice؟** | [`cms/REGISTRATION-FLOW-FIXES.md`](cms/REGISTRATION-FLOW-FIXES.md) |
|
||||
| **Audit report کامل BackOffice؟** | [`BackOffice/docs/BACKOFFICE-AUDIT.md`](../BackOffice/docs/BACKOFFICE-AUDIT.md) |
|
||||
|
||||
---
|
||||
|
||||
## 📂 business/ — مستندات بیزنسی (۷ فایل)
|
||||
|
||||
| فایل | توضیح |
|
||||
@@ -17,7 +39,7 @@
|
||||
| [discount-shop-business.md](business/discount-shop-business.md) | فروشگاه تخفیفی: پرداخت ترکیبی، درصد تخفیف، entity design |
|
||||
| [manual-payment-system.md](business/manual-payment-system.md) | پرداخت دستی: کارت به کارت، تأیید ادمین، آپلود FMS |
|
||||
|
||||
## 📂 cms/ — مستندات فنی CMS (۱۳ فایل)
|
||||
## 📂 cms/ — مستندات فنی CMS (۱۵ فایل)
|
||||
|
||||
| فایل | توضیح |
|
||||
|------|-------|
|
||||
@@ -35,6 +57,8 @@
|
||||
| [email-sms-configuration.md](cms/email-sms-configuration.md) | تنظیمات SMS/Email: Kavenegar templates، Gmail |
|
||||
| [PRODUCT-BUNDLE-FEATURE.md](cms/PRODUCT-BUNDLE-FEATURE.md) | 🟡 فیچر آینده: طراحی Product Bundle |
|
||||
| [FRONTOFFICE-RELEASE-NOTES-v1.5.0.md](cms/FRONTOFFICE-RELEASE-NOTES-v1.5.0.md) | 🆕 یادداشت انتشار FrontOffice v1.5.0 (فارسی): هفتهنما، گزارش هفتگی، امتیاز انتقالی |
|
||||
| [FILE-MANAGEMENT-ARCHITECTURE.md](cms/FILE-MANAGEMENT-ARCHITECTURE.md) | 🆕 معماری جامع مدیریت فایل: IFileManager, LocalFileManager, ImagePathResolverInterceptor, UploadsController (HTTP سرو عمومی + FMS Fallback), ذخیره دیسکی |
|
||||
| [REGISTRATION-FLOW-FIXES.md](cms/REGISTRATION-FLOW-FIXES.md) | 🆕 فیکس فلوی ثبتنام: ایجاد کاربر جدید در VerifyOtpToken، رفع lookup موبایل AcceptContract، رفع sync IsCompleteRegister |
|
||||
|
||||
## 📂 deployment/ — مستندات استقرار (۴ فایل)
|
||||
|
||||
@@ -46,6 +70,14 @@
|
||||
| [SERVER-MIRRORS-CONFIG.md](deployment/SERVER-MIRRORS-CONFIG.md) | تنظیمات mirror: K3s registries.yaml، containerd |
|
||||
| [INGRESS-NGINX-WARNING.md](deployment/INGRESS-NGINX-WARNING.md) | ⚠️ هشدار K3s: مشکل hostNetwork در ingress-nginx |
|
||||
|
||||
## 📂 ui-modernization/ — مستندات نوسازی UI (۳ فایل)
|
||||
|
||||
| فایل | توضیح |
|
||||
|------|-------|
|
||||
| [UI-MODERNIZATION-PLAN.md](ui-modernization/UI-MODERNIZATION-PLAN.md) | 🆕 طرح جامع نوسازی UI فرانتآفیس: سیستم بلاگ، صفحات دینامیک، لندینگ، Mobile-First — ۷ فاز، ~۱۲۴ فایل جدید |
|
||||
| [BACKOFFICE-ARCHITECTURE.md](ui-modernization/BACKOFFICE-ARCHITECTURE.md) | 🆕 مرجع معماری BackOffice: ساختار پوشهها، الگوهای BasePageComponent/Hub/CodeBehind/ExcelExport، مسیرها، permissionها، نقشه NavMenu |
|
||||
| [BACKOFFICE-STORE-UNIFICATION.md](ui-modernization/BACKOFFICE-STORE-UNIFICATION.md) | 🆕 یکسانسازی فروشگاه عادی و تخفیفی: NavMenu restructure، حذف آمار سفارشات، رفع PaymentDate، بازنویسی ۴ صفحه |
|
||||
|
||||
## 📂 migration/ — مستندات مهاجرت BFF→CMS (۶ فایل)
|
||||
|
||||
| فایل | توضیح |
|
||||
@@ -69,4 +101,8 @@
|
||||
| فاز ۳ (ساختاردهی + merge) | **28** | ۱۳ |
|
||||
| cleanup نهایی (+2 فایل جدید) | **30** | — |
|
||||
| session CI/CD + Admin fix (+2) | **32** | — |
|
||||
| **نهایی** | **32 + INDEX** | **۱۹۱ فایل حذف/ادغام** |
|
||||
| session UI Modernization plan (+1) | **33** | — |
|
||||
| session Store Unification (+2 docs) | **35** | — |
|
||||
| session File Mgmt + Content (+1 doc) | **36** | — |
|
||||
| session Registration Flow Fix (+1 doc) | **37** | — |
|
||||
| **نهایی** | **37 + INDEX** | **۱۹۱ فایل حذف/ادغام** |
|
||||
|
||||
@@ -0,0 +1,521 @@
|
||||
# یکسانسازی فروشگاه عادی و فروشگاه تخفیفی (BackOffice)
|
||||
|
||||
**تاریخ:** ۱۳۹۴/۱۱/۲۴ (2026-02-13)
|
||||
**وضعیت:** ✅ فاز ۱ تا ۶ — تکمیل شده (CMS + BackOffice Build Succeeded — 0 Error)
|
||||
|
||||
---
|
||||
|
||||
## هدف
|
||||
|
||||
هر دو فروشگاه (عادی و تخفیفی) از نظر **UI/UX، ساختار صفحات، اکشنها و قابلیتها** عینبهعین یکسان باشند.
|
||||
**تنها تفاوت مجاز:** منطق پرداخت — فروشگاه تخفیفی از کیفپول تخفیفی + درگاه، فروشگاه عادی فقط از کیفپول عادی.
|
||||
|
||||
### معیار یکسانسازی
|
||||
- **مبنا:** فروشگاه عادی (Products, Category, UserOrder)
|
||||
- **استثنا:** مزایای بدیهی فروشگاه تخفیفی به فروشگاه عادی هم اضافه شد
|
||||
- **Bulk Operations:** بیخیال شد (طبق درخواست کاربر)
|
||||
|
||||
---
|
||||
|
||||
## تفاوتهای زیرساختی (تغییر نکرده — بیتأثیر روی UX)
|
||||
|
||||
| موضوع | فروشگاه عادی | فروشگاه تخفیفی |
|
||||
|---|---|---|
|
||||
| ارتباط با سرور | gRPC مستقیم (`ProductsContractClient`) | سرویس اینترفیس (`IDiscountProductService`) که داخلاً gRPC صدا میزنه |
|
||||
| مدل داده | Protobuf models | C# DTOs |
|
||||
|
||||
> **نکته:** هر دو در نهایت از همان gRPC backend استفاده میکنند. تفاوت فقط در لایه abstraction است و تأثیری روی UX ندارد.
|
||||
|
||||
---
|
||||
|
||||
## تغییرات انجامشده
|
||||
|
||||
### ۱. صفحه محصولات (`DiscountProductsMainPage`)
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| نمایش تصویر | `MudAvatar` | کامپوننت `Image` (مطابق فروشگاه عادی) |
|
||||
| برش عنوان | `Substring(0, 20) + "…"` | `Truncate(20, true)` (extension method مشترک) |
|
||||
| گالری تصاویر | `ProductFormDialog` (کلاینتساید) | `GalleryDialog` سرور-محور (مطابق فروشگاه عادی) |
|
||||
| پیشنمایش تصویر | HTML inline در `ShowMessageBox` | `ImagePreviewDialog` (مطابق فروشگاه عادی) |
|
||||
| عنوان تولبار | «مدیریت محصولات تخفیفی» | «مدیریت محصولات» |
|
||||
|
||||
**اکشنهای جدید اضافهشده:**
|
||||
- ✅ دکمه «مدیریت دستهبندی (درگ و دراپ)» → ناوبری به `ProductCategoriesDragDropPage`
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor`
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
### ۲. صفحه دستهبندیها (`DiscountCategoriesMainPage`)
|
||||
|
||||
**اکشنهای جدید اضافهشده:**
|
||||
- ✅ دکمه «مدیریت محصولات این دسته (درگ و دراپ)» → ناوبری به `CategoryProductsDragDropPage`
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor`
|
||||
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
### ۳. صفحه سفارشات (`DiscountOrdersMainPage`) — بازنویسی کامل
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| الگوی فیلتر | فیلترهای inline با دکمه جستجو | `BasePageComponent` با OnSubmit/OnClear (مطابق فروشگاه عادی) |
|
||||
| لایهبندی | `MudPaper` تو در تو | `BasePageComponent > Filters + Content` |
|
||||
| اکشنها | فقط مشاهده جزئیات + تغییر وضعیت | جزئیات + تغییر وضعیت + حذف (مطابق فروشگاه عادی) |
|
||||
| آیکونهای اکشن | `Visibility` + `Edit` | `Info` + `LocalShipping` + `DeleteOutline` (مطابق فروشگاه عادی) |
|
||||
| ساختار تولبار | بدون تولبار | تولبار با عنوان + دکمه Excel (مطابق فروشگاه عادی) |
|
||||
|
||||
**ستونها (حفظ شده — خاص فروشگاه تخفیفی):**
|
||||
- شماره سفارش، تاریخ ثبت، مبلغ کل، **تخفیف کیفپول**، **پرداخت درگاه**، تعداد آیتم، وضعیت، پرداخت
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountOrdersMainPage.razor` ← بازنویسی کامل
|
||||
- `Pages/DiscountShop/DiscountOrdersMainPage.razor.cs` ← بازنویسی کامل
|
||||
|
||||
---
|
||||
|
||||
### ۴. گزارش فروش (`SalesReports`)
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| نمودارها | روند فروش + محصولات پرفروش | روند فروش + محصولات پرفروش + **وضعیت سفارشها** |
|
||||
| عنوان | «گزارش فروش فروشگاه تخفیفی» | «گزارش فروش فروشگاه» |
|
||||
| توضیح | «آمار فروش، تخفیف و وضعیت سفارشهای فروشگاه تخفیفی...» | «آمار فروش و وضعیت سفارشها بر اساس بازه تاریخ و وضعیت سفارش» |
|
||||
|
||||
**نمودار جدید:**
|
||||
- ✅ «وضعیت سفارشها» — نمودار میلهای تعداد سفارش بر اساس وضعیت (مطابق فروشگاه عادی)
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/SalesReports.razor`
|
||||
|
||||
---
|
||||
|
||||
### ۵. هاب سفارشات (`DiscountShopHub`)
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| عنوان | «فروشگاه تخفیفی» | «سفارشات فروشگاه تخفیفی» |
|
||||
| آیکون تب سفارشات | `ShoppingCart` | `ReceiptLong` (مطابق `OrdersHub` فروشگاه عادی) |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountShopHub.razor`
|
||||
|
||||
---
|
||||
|
||||
### ۶. تغییرات فروشگاه عادی (مزایای تخفیفی ← عادی)
|
||||
|
||||
#### CategoryMainPage
|
||||
- ✅ ستون **«ترتیب»** (`SortOrder`) اضافه شد (از تخفیفی)
|
||||
- ✅ **غیرفعالسازی حذف** دستهبندی دارای زیردسته (از تخفیفی)
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/Category/CategoryMainPage.razor`
|
||||
- `Pages/Category/CategoryMainPage.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
## تغییرات فاز ۲ — ارتقاء به Admin RPC و فیلدهای پیشرفته
|
||||
|
||||
### ۷. سرویس سفارشات تخفیفی — سوئیچ به Admin RPC
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| RPC مورد استفاده | `GetUserOrders` (user-scoped) | `GetAllDiscountOrders` (admin-scoped) |
|
||||
| DTO | `OrderSummaryDto` (محدود) | `AdminOrderDto` (کامل با user_full_name, user_mobile, shipping_address, payment_date, vat_amount...) |
|
||||
| فیلترها | فقط userId, paymentCompleted, deliveryStatus | userId, paymentStatus, deliveryStatus, userMobile, trackingCode, fromDate, toDate, minAmount, maxAmount |
|
||||
| گزارش فروش | محاسبه کلاینتساید + N+1 (۵۰ فراخوان gRPC جداگانه!) | `GetDiscountSalesReport` RPC سرور-ساید با fallback |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Services/DiscountOrder/IDiscountOrderService.cs` ← فیلترهای جدید + DTOهای گزارش فروش
|
||||
- `Services/DiscountOrder/DiscountOrderService.cs` ← سوئیچ به `GetAllDiscountOrdersAsync` + `GetDiscountSalesReportAsync`
|
||||
|
||||
### ۸. ستونها و فیلترهای ادمین در سفارشات تخفیفی
|
||||
|
||||
**ستونهای جدید اضافهشده:**
|
||||
- ✅ **نام کاربر** (لینک به پروفایل — مطابق فروشگاه عادی)
|
||||
- ✅ **موبایل کاربر**
|
||||
- ✅ **وضعیت پرداخت** (Pending/Completed/Failed/Refunded — مطابق فروشگاه عادی)
|
||||
- ✅ **تاریخ پرداخت**
|
||||
- ✅ **آدرس** (truncated با tooltip — مطابق فروشگاه عادی)
|
||||
- ✅ **وضعیت ارسال** (جدا از وضعیت پرداخت — مطابق فروشگاه عادی)
|
||||
|
||||
**فیلترهای جدید اضافهشده:**
|
||||
- ✅ شناسه سفارش
|
||||
- ✅ جستجوی کاربر (UserAutoComplete)
|
||||
- ✅ موبایل کاربر
|
||||
- ✅ کد رهگیری
|
||||
- ✅ از تاریخ / تا تاریخ
|
||||
- ✅ وضعیت پرداخت (Pending/Completed/Failed/Refunded)
|
||||
- ✅ وضعیت ارسال
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountOrdersMainPage.razor` ← ستونها + فیلترها
|
||||
- `Pages/DiscountShop/DiscountOrdersMainPage.razor.cs` ← فیلدها + متدهای وضعیت پرداخت
|
||||
|
||||
### ۹. گزارش فروش تخفیفی — حذف مشکل N+1
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| محصولات پرفروش | ۵۰ فراخوان gRPC جداگانه (`GetByIdAsync` × 50) | یک فراخوان `GetDiscountSalesReport` |
|
||||
| خلاصه آماری | محاسبه کلاینتساید | سرور-ساید (دقیقتر + سریعتر) |
|
||||
| نمودار روند | GroupBy کلاینتساید | `SalesPeriodDto` از سرور |
|
||||
| جدول سفارشها | فقط شماره سفارش | شناسه + نام کاربر (لینک) |
|
||||
| خروجی Excel/PDF | فقط OrderNumber | شناسه + نام کاربر + موبایل |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/SalesReports.razor`
|
||||
|
||||
### ۱۰. دستهبندی فروشگاه عادی — فیلد ImagePath
|
||||
|
||||
- ✅ فیلد `ImagePath` به دیالوگ ایجاد/ویرایش دستهبندی اضافه شد (مطابق فروشگاه تخفیفی)
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/Category/Components/CreateOrUpdateCategoryDialog.razor`
|
||||
- `Pages/Category/Components/CreateOrUpdateCategoryDialog.razor.cs`
|
||||
|
||||
### ۱۱. گزارش فروش عادی — نمودار محصولات پرفروش + خروجی PDF
|
||||
|
||||
- ✅ نمودار «محصولات پرفروش (بر اساس مبلغ)» از FactorDetails سفارشات
|
||||
- ✅ دکمه خروجی PDF (نسخه متنی) — مطابق فروشگاه تخفیفی
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/UserOrder/OrderSalesReports.razor`
|
||||
- `Pages/UserOrder/OrderSalesReports.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
## تغییرات فاز ۳ — تگهای محصول و نهاییسازی
|
||||
|
||||
### ۱۲. تگهای محصول در فروشگاه تخفیفی
|
||||
|
||||
- ✅ دکمه «تگهای محصول» (`Label` icon) به ستون عملیات صفحه محصولات تخفیفی اضافه شد
|
||||
- ✅ از همان `AssignTagsDialog` فروشگاه عادی استفاده شد (کامپوننت مشترک)
|
||||
- ✅ سرویس `ProductTagContract` مشترک بین هر دو فروشگاه — بدون نیاز به API جدید
|
||||
|
||||
> **نکته:** `ProductTagContract` یک سرویس ژنریک `product_id ↔ tag_id` است و محدود به فروشگاه خاصی نیست.
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor` ← دکمه تگ
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` ← متد `OpenTagAssignment` + using
|
||||
|
||||
---
|
||||
|
||||
## تغییرات فاز ۴ — یکسانسازی دیالوگها و جزئیات تکمیلی
|
||||
|
||||
### ۱۳. دیالوگ جزئیات سفارش تخفیفی — Timeline + ویرایش Inline
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| Timeline وضعیت | ❌ فاقد | ✅ ۵ مرحله (ثبت → پرداخت → آمادهسازی → ارسال → تحویل/مرجوعی) |
|
||||
| ویرایش وضعیت | ❌ فقط خواندنی | ✅ درونخطی (وضعیت + کد رهگیری + یادداشت ادمین) |
|
||||
| دکمه ذخیره | ❌ فقط «بستن» | ✅ «ثبت تغییرات» + اسپینر بارگذاری |
|
||||
| هشدارهای شرطی | ❌ فاقد | ✅ هشدار لغو/مرجوعی + اطلاعرسانی ارسال |
|
||||
| Code-behind | `@code` درونخطی | فایل جداگانه `.razor.cs` |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/Components/OrderDetailsDialog.razor` ← Timeline + ویرایش inline
|
||||
- `Pages/DiscountShop/Components/OrderDetailsDialog.razor.cs` ← فایل جدید — timeline + save logic
|
||||
|
||||
### ۱۴. دیالوگ تغییر وضعیت فروشگاه عادی — ارتقاء
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| فرم | `MudStack` ساده | `MudForm` با validation |
|
||||
| فیلدها | فقط وضعیت | ✅ وضعیت + **کد رهگیری** + **توضیحات ارسال** |
|
||||
| هشدارهای شرطی | ❌ فاقد | ✅ هشدار مرجوعی + اطلاعرسانی ارسال |
|
||||
| دکمه | رنگ ثابت `Primary` | ✅ رنگ داینامیک بر اساس وضعیت + اسپینر |
|
||||
| عنوان | بدون آیکون | ✅ آیکون `Edit` + عنوان |
|
||||
| RPC | فقط `UpdateOrderStatusAsync` | ✅ `UpdateOrderStatusAsync` + `UpdateUserOrderAsync` (کد رهگیری + توضیحات) |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/UserOrder/Components/ChangeOrderStatusDialog.razor`
|
||||
- `Pages/UserOrder/Components/ChangeOrderStatusDialog.razor.cs`
|
||||
|
||||
### ۱۵. گزارش فروش تخفیفی — Empty chart guard + ترتیب نمودارها
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| نمودار روند فروش | بدون بررسی خالی بودن | ✅ نمایش «داده کافی برای نمایش نمودار وجود ندارد» |
|
||||
| ترتیب نمودارها | روند → پرفروش → وضعیت | ✅ روند → **وضعیت** → پرفروش (مطابق فروشگاه عادی) |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/SalesReports.razor`
|
||||
|
||||
### ۱۶. گزارش فروش عادی — لینک نام کاربر
|
||||
|
||||
- ✅ نام کاربر در جدول سفارشها از متن ساده به `MudLink` تبدیل شد (لینک به پروفایل کاربر)
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/UserOrder/OrderSalesReports.razor`
|
||||
|
||||
### ۱۷. دیالوگ دستهبندی فروشگاه عادی — MudForm + UX
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| اعتبارسنجی | ❌ بدون validation | ✅ `MudForm` با `Required` + `RequiredError` |
|
||||
| فیلد فعال | `MudCheckBox` | ✅ `MudSwitch` رنگی (مطابق فروشگاه تخفیفی) |
|
||||
| Helper text | ❌ فاقد | ✅ «عدد کمتر = اولویت بالاتر» + «برای دسته اصلی خالی بگذارید» |
|
||||
| دکمه ذخیره | همیشه «ثبت» | ✅ «ذخیره تغییرات» / «ایجاد دستهبندی» (داینامیک) |
|
||||
| اسپینر بارگذاری | ❌ فاقد | ✅ `MudProgressCircular` + غیرفعالسازی دکمه |
|
||||
| عنوان | بدون آیکون | ✅ آیکون `Edit`/`Add` + عنوان داینامیک |
|
||||
| غیرفعالسازی دکمه | ❌ فاقد | ✅ غیرفعال تا validation سبز نشود |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/Category/CreateOrUpdateCategoryDialog.razor`
|
||||
- `Pages/Category/CreateOrUpdateCategoryDialog.razor.cs`
|
||||
|
||||
### ۱۸. سفارشات تخفیفی — لغو سفارش واقعی
|
||||
|
||||
| تغییر | قبل | بعد |
|
||||
|---|---|---|
|
||||
| دکمه «حذف» | آیکون `DeleteOutline` — فقط snackbar (بدون API) | ✅ آیکون `Cancel` — فراخوان `UpdateStatusAsync` با `Cancelled` |
|
||||
| متن تأییدیه | «آیا از حذف مطمئن هستید؟» | «آیا از لغو سفارش مطمئن هستید؟ وضعیت به لغو شده تغییر میکند» |
|
||||
| عملکرد | ❌ هیچ | ✅ واقعی — `UpdateStatusAsync(Cancelled)` |
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/DiscountShop/DiscountOrdersMainPage.razor` ← آیکون + tooltip
|
||||
- `Pages/DiscountShop/DiscountOrdersMainPage.razor.cs` ← فراخوان API
|
||||
|
||||
---
|
||||
|
||||
## تغییرات فاز ۵ — فرمتبندی قیمت، زیردسته و برچسب فیلدها
|
||||
|
||||
### ۵.۱ فرمتبندی قیمت در لیست محصولات عادی
|
||||
|
||||
**مشکل:** ستون قیمت عدد خام بدون separator و بدون واحد نشان میداد.
|
||||
**راهحل:** تبدیل `PropertyColumn` به `TemplateColumn` با `Price.ToString("N0") ریال` — مطابق فروشگاه تخفیفی.
|
||||
|
||||
**فایل تغییر یافته:**
|
||||
- `Pages/Products/ProductsMainPage.razor` ← TemplateColumn + فرمت N0 + ریال
|
||||
|
||||
### ۵.۲ دکمه «افزودن زیردسته» در دستهبندیهای عادی
|
||||
|
||||
**مشکل:** فروشگاه تخفیفی دکمه «افزودن زیردسته» در ستون عملیات داشت ولی فروشگاه عادی نداشت.
|
||||
**راهحل:** افزودن `MudIconButton` با آیکون `CreateNewFolder` + رنگ `Color.Primary` + متد `CreateSubcategory(parent)` که دیالوگ را با `ParentId = parent.Id` باز میکند.
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/Category/CategoryMainPage.razor` ← دکمه جدید در ستون عملیات
|
||||
- `Pages/Category/CategoryMainPage.razor.cs` ← متد `CreateSubcategory`
|
||||
|
||||
### ۵.۳ برچسب واحد ریال در دیالوگهای محصول
|
||||
|
||||
**مشکل:** فیلد قیمت در دیالوگهای ایجاد/ویرایش محصول عادی `Label="قیمت"` داشت — بدون واحد.
|
||||
**راهحل:** تغییر به `Label="قیمت (ریال)"` — مطابق فروشگاه تخفیفی.
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `Pages/Products/Components/CreateDialog.razor` ← قیمت (ریال)
|
||||
- `Pages/Products/Components/UpdateDialog.razor` ← قیمت (ریال)
|
||||
|
||||
---
|
||||
|
||||
## تغییرات فاز ۶ — تغییرات بکند (Proto + Handler + gRPC Service)
|
||||
|
||||
این فاز تمام موارد "محدودیت API" که در فازهای قبلی شناسایی شده بودند را حل میکند.
|
||||
|
||||
### ۶.۱ ستون وضعیت فعال/غیرفعال محصولات (`is_active`)
|
||||
|
||||
**مشکل:** پروتوباف `GetAllProductsByFilterResponseModel` فیلد `is_active` نداشت. محصولات عادی ستون وضعیت نداشتند.
|
||||
**راهحل (end-to-end):**
|
||||
- **Proto:** افزودن `bool is_active = 15` به `GetAllProductsByFilterResponseModel` + `google.protobuf.BoolValue is_active = 15` به `GetAllProductsByFilterFilter`
|
||||
- **DTO:** افزودن `bool IsActive` به `CustomerProductModel`
|
||||
- **Query:** افزودن `bool? IsActive` به `GetCustomerProductsByFilterQuery`
|
||||
- **Handler:** فیلتر `IsDeleted != IsActive` + مپ `IsActive = !p.IsDeleted`
|
||||
- **gRPC Service:** مپ `IsActive` در فیلتر و ریسپانس `ProductsService`
|
||||
- **Frontend:** ستون `TemplateColumn` با `MudChip` رنگی در `ProductsMainPage.razor`
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `CMS/.../Protos/products.proto`
|
||||
- `CMS/.../GetCustomerProductsByFilterResponseDto.cs`
|
||||
- `CMS/.../GetCustomerProductsByFilterQuery.cs`
|
||||
- `CMS/.../GetCustomerProductsByFilterQueryHandler.cs`
|
||||
- `CMS/.../Services/ProductsService.cs`
|
||||
- `BackOffice/.../Pages/Products/ProductsMainPage.razor`
|
||||
|
||||
### ۶.۲ ستون تعداد محصولات دستهبندی (`product_count`)
|
||||
|
||||
**مشکل:** پروتوباف `GetAllCategoryByFilterResponseModel` فیلد `product_count` نداشت.
|
||||
**راهحل (end-to-end):**
|
||||
- **Proto:** افزودن `int32 product_count = 9` به `GetAllCategoryByFilterResponseModel`
|
||||
- **DTO:** افزودن `int ProductCount` به `GetAllCategoryByFilterResponseModel` (C#)
|
||||
- **Handler:** تغییر از `ProjectToType<>()` (Mapster auto-map) به manual `Select()` با `ProductCount = x.ProductCategories.Count`
|
||||
- **gRPC Service:** auto-map Mapster (نام یکسان)
|
||||
- **Frontend:** ستون `PropertyColumn` جدید در `CategoryMainPage.razor`
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `CMS/.../Protos/category.proto`
|
||||
- `CMS/.../GetAllCategoryByFilterResponseDto.cs`
|
||||
- `CMS/.../GetAllCategoryByFilterQueryHandler.cs`
|
||||
- `BackOffice/.../Pages/Category/CategoryMainPage.razor`
|
||||
|
||||
### ۶.۳ فیلتر بازه تاریخ سفارشات (`from_date` / `to_date`)
|
||||
|
||||
**مشکل:** پروتوباف `GetAllUserOrderByFilterFilter` فقط یک `payment_date` داشت. امکان فیلتر بازه تاریخ وجود نداشت.
|
||||
**راهحل (end-to-end):**
|
||||
- **Proto:** افزودن `google.protobuf.Timestamp from_date = 11` و `google.protobuf.Timestamp to_date = 12` به فیلتر
|
||||
- **gRPC Service:** مپ `FromDate` از `from_date` (با fallback به `payment_date`)، `ToDate` از `to_date`
|
||||
- **Handler:** بدون تغییر — از قبل `FromDate`/`ToDate` را ساپورت میکرد
|
||||
- **Frontend:** افزودن `MudDatePicker` دوم (تا تاریخ)، مپ به `Filter.FromDate`/`Filter.ToDate`
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `CMS/.../Protos/userorder.proto`
|
||||
- `CMS/.../Services/UserOrderService.cs`
|
||||
- `BackOffice/.../Pages/UserOrder/UserOrderMainPage.razor`
|
||||
- `BackOffice/.../Pages/UserOrder/UserOrderMainPage.razor.cs`
|
||||
|
||||
### ۶.۴ تصویر محصول در آیتمهای سفارش تخفیفی (`image_path`)
|
||||
|
||||
**مشکل:** `OrderItemDto` در proto و C# فیلد `image_path` نداشت. آیتمهای سفارش بدون تصویر بودند.
|
||||
**راهحل (end-to-end):**
|
||||
- **Proto:** افزودن `string image_path = 9` و `string thumbnail_path = 10` به `OrderItemDto` در `discountorder.proto`
|
||||
- **Backend DTO:** افزودن `ImagePath`/`ThumbnailPath` به `OrderItemDto` (C# Application layer)
|
||||
- **Handler:** مپ `ImagePath = od.Product.ImagePath` در `GetOrderByIdQueryHandler` (Product nav property از قبل Include شده بود)
|
||||
- **Frontend DTO:** افزودن فیلدها به `IDiscountOrderService.OrderItemDto`
|
||||
- **Frontend Service:** مپ در `DiscountOrderService`
|
||||
- **Frontend Dialog:** `MudImage` + `MudStack` برای نمایش تصویر کنار نام محصول
|
||||
|
||||
**فایلهای تغییر یافته:**
|
||||
- `CMS/.../Protos/discountorder.proto`
|
||||
- `CMS/.../GetOrderByIdQuery.cs` (OrderItemDto)
|
||||
- `CMS/.../GetOrderByIdQueryHandler.cs`
|
||||
- `BackOffice/.../Services/DiscountOrder/IDiscountOrderService.cs`
|
||||
- `BackOffice/.../Services/DiscountOrder/DiscountOrderService.cs`
|
||||
- `BackOffice/.../Pages/DiscountShop/Components/OrderDetailsDialog.razor`
|
||||
|
||||
---
|
||||
|
||||
## وضعیت مقایسهای نهایی
|
||||
|
||||
### محصولات
|
||||
| قابلیت | عادی | تخفیفی |
|
||||
|---|:---:|:---:|
|
||||
| لیست با MudDataGrid + server-side | ✅ | ✅ |
|
||||
| فیلتر با BasePageComponent | ✅ | ✅ |
|
||||
| نمایش تصویر (Image component) | ✅ | ✅ |
|
||||
| برش عنوان (Truncate) | ✅ | ✅ |
|
||||
| ایجاد/ویرایش/حذف | ✅ | ✅ |
|
||||
| گالری تصاویر (GalleryDialog سرور-محور) | ✅ | ✅ |
|
||||
| پیشنمایش تصویر (ImagePreviewDialog) | ✅ | ✅ |
|
||||
| مدیریت دستهبندی (درگ و دراپ) | ✅ | ✅ |
|
||||
| خروجی Excel | ✅ | ✅ |
|
||||
| تگهای محصول (AssignTagsDialog مشترک) | ✅ | ✅ |
|
||||
| فرمت قیمت (N0 + ریال) | ✅ | ✅ |
|
||||
| ستون وضعیت فعال/غیرفعال | ✅ | ✅ |
|
||||
| Bulk Edit/Delete/Toggle | ✅ | ❌ (طبق درخواست — بیخیال) |
|
||||
|
||||
### دستهبندیها
|
||||
| قابلیت | عادی | تخفیفی |
|
||||
|---|:---:|:---:|
|
||||
| درخت سایدبار | ✅ | ✅ |
|
||||
| گرید با MudDataGrid | ✅ | ✅ |
|
||||
| ستونها: شناسه، نام لاتین، عنوان، والد، ترتیب، فعال | ✅ | ✅ |
|
||||
| ستون تعداد محصولات | ✅ | ✅ |
|
||||
| ایجاد/ویرایش/حذف | ✅ | ✅ |
|
||||
| غیرفعالسازی حذف دارای زیردسته | ✅ | ✅ |
|
||||
| افزودن زیردسته | ✅ | ✅ |
|
||||
| درگاندراپ محصولات دسته | ✅ | ✅ |
|
||||
|
||||
### سفارشات
|
||||
| قابلیت | عادی | تخفیفی |
|
||||
|---|:---:|:---:|
|
||||
| BasePageComponent با فیلتر | ✅ | ✅ |
|
||||
| MudDataGrid + تولبار | ✅ | ✅ |
|
||||
| فیلتر شناسه / کاربر / تاریخ / وضعیت | ✅ | ✅ |
|
||||
| فیلتر بازه تاریخ (از تاریخ + تا تاریخ) | ✅ | ✅ |
|
||||
| ستون نام کاربر (لینک به پروفایل) | ✅ | ✅ |
|
||||
| ستون وضعیت پرداخت (Chip رنگی) | ✅ | ✅ |
|
||||
| ستون وضعیت ارسال (Chip رنگی) | ✅ | ✅ |
|
||||
| ستون تاریخ پرداخت | ✅ | ✅ |
|
||||
| ستون آدرس (truncated + tooltip) | ✅ | ✅ |
|
||||
| جزئیات سفارش (Timeline + ویرایش inline) | ✅ | ✅ |
|
||||
| تصویر محصول در آیتمهای سفارش | ✅ | ✅ |
|
||||
| تغییر وضعیت (MudForm + کد رهگیری + هشدار) | ✅ | ✅ |
|
||||
| لغو سفارش | ✅ | ✅ (via UpdateStatus) |
|
||||
| خروجی Excel | ✅ | ✅ |
|
||||
| ستونهای مالی تخفیف (DiscountBalanceUsed/GatewayAmount) | ❌ (مربوط نیست) | ✅ |
|
||||
| اعمال تخفیف | ✅ | ❌ (API ندارد) |
|
||||
|
||||
### گزارش فروش
|
||||
| قابلیت | عادی | تخفیفی |
|
||||
|---|:---:|:---:|
|
||||
| فیلتر تاریخ + وضعیت | ✅ | ✅ |
|
||||
| کارتهای خلاصه | ✅ | ✅ |
|
||||
| نمودار روند فروش | ✅ | ✅ |
|
||||
| نمودار وضعیت سفارشها | ✅ | ✅ |
|
||||
| نمودار محصولات پرفروش | ✅ | ✅ |
|
||||
| خروجی Excel | ✅ | ✅ |
|
||||
| خروجی PDF | ✅ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## موارد باقیمانده
|
||||
|
||||
این موارد نیاز به تغییرات بیشتر دارند:
|
||||
|
||||
| مورد | جزئیات | وضعیت |
|
||||
|---|---|---|
|
||||
| **اعمال تخفیف سفارش تخفیفی** | `discountorder.proto` فاقد `ApplyDiscountToOrder` RPC | نیاز به RPC جدید + لاجیک سرور |
|
||||
| **لغو سفارش با بازپرداخت** | لغو وضعیت ✅ ولی refund/بازگشت موجودی نیاز به RPC اختصاصی | نیاز به `CancelOrder` RPC |
|
||||
|
||||
> **نکته:** موارد قبلی (is_active، product_count، from_date/to_date، image_path) در **فاز ۶** حل شدند.
|
||||
|
||||
---
|
||||
|
||||
## ساختار فایلهای تغییریافته
|
||||
|
||||
```
|
||||
CMS/src/
|
||||
├── CMSMicroservice.Protobuf/Protos/
|
||||
│ ├── products.proto ← is_active (filter + response field 15)
|
||||
│ ├── category.proto ← product_count (response field 9)
|
||||
│ ├── userorder.proto ← from_date/to_date (filter fields 11,12)
|
||||
│ └── discountorder.proto ← image_path/thumbnail_path (OrderItemDto fields 9,10)
|
||||
├── CMSMicroservice.Application/
|
||||
│ ├── ProductsCQ/Queries/GetCustomerProductsByFilter/
|
||||
│ │ ├── GetCustomerProductsByFilterQuery.cs ← IsActive filter
|
||||
│ │ ├── GetCustomerProductsByFilterQueryHandler.cs ← IsActive filter + mapping
|
||||
│ │ └── GetCustomerProductsByFilterResponseDto.cs ← IsActive field
|
||||
│ ├── CategoryCQ/Queries/GetAllCategoryByFilter/
|
||||
│ │ ├── GetAllCategoryByFilterQueryHandler.cs ← manual Select + ProductCount
|
||||
│ │ └── GetAllCategoryByFilterResponseDto.cs ← ProductCount field
|
||||
│ └── DiscountShopCQ/Queries/GetOrderById/
|
||||
│ ├── GetOrderByIdQuery.cs ← ImagePath/ThumbnailPath
|
||||
│ └── GetOrderByIdQueryHandler.cs ← Product image mapping
|
||||
└── CMSMicroservice.WebApi/Services/
|
||||
├── ProductsService.cs ← IsActive filter + response mapping
|
||||
└── UserOrderService.cs ← FromDate/ToDate mapping
|
||||
|
||||
BackOffice/src/BackOffice/
|
||||
├── Services/DiscountOrder/
|
||||
│ ├── IDiscountOrderService.cs ← OrderItemDto + ImagePath/ThumbnailPath
|
||||
│ └── DiscountOrderService.cs ← image mapping
|
||||
├── Pages/
|
||||
│ ├── Category/
|
||||
│ │ ├── CategoryMainPage.razor ← ستون ProductCount + SortOrder + زیردسته
|
||||
│ │ ├── CategoryMainPage.razor.cs ← HasChildren + CreateSubcategory
|
||||
│ │ ├── CreateOrUpdateCategoryDialog.razor ← MudForm + ImagePath
|
||||
│ │ └── CreateOrUpdateCategoryDialog.razor.cs ← validation + loading
|
||||
│ ├── DiscountShop/
|
||||
│ │ ├── DiscountShopHub.razor
|
||||
│ │ ├── DiscountProductsMainPage.razor/.cs
|
||||
│ │ ├── DiscountCategoriesMainPage.razor/.cs
|
||||
│ │ ├── DiscountOrdersMainPage.razor/.cs
|
||||
│ │ ├── SalesReports.razor
|
||||
│ │ └── Components/
|
||||
│ │ ├── OrderDetailsDialog.razor ← تصویر محصول + Timeline
|
||||
│ │ └── OrderDetailsDialog.razor.cs
|
||||
│ ├── UserOrder/
|
||||
│ │ ├── UserOrderMainPage.razor ← فیلتر بازه تاریخ (از + تا)
|
||||
│ │ ├── UserOrderMainPage.razor.cs ← FromDate/ToDate mapping
|
||||
│ │ ├── OrderSalesReports.razor/.cs
|
||||
│ │ └── Components/ChangeOrderStatusDialog.razor/.cs
|
||||
│ └── Products/
|
||||
│ ├── ProductsMainPage.razor ← ستون IsActive + فرمت قیمت
|
||||
│ └── Components/
|
||||
│ ├── CreateDialog.razor ← قیمت (ریال)
|
||||
│ └── UpdateDialog.razor ← قیمت (ریال)
|
||||
```
|
||||
@@ -0,0 +1,196 @@
|
||||
# فروشگاه تخفیفی — وضعیت پیادهسازی و تسکها
|
||||
|
||||
> **تاریخ:** ۱۴۰۴/۱۱/۲۲ (2026-02-11)
|
||||
> **آخرین بروزرسانی:** ۱۴۰۴/۱۱/۲۳
|
||||
> **وضعیت کلی:** بکند کامل ✅ | بکآفیس کامل ✅ | فرانتآفیس کامل ✅
|
||||
|
||||
---
|
||||
|
||||
## ۱. خلاصه بیزینس
|
||||
|
||||
فروشگاه تخفیفی یک فروشگاه **مجزا** از فروشگاه معمولی است که:
|
||||
- محصولات خاص خود را دارد (`DiscountProduct` — نه `Product`)
|
||||
- پرداخت **ترکیبی** (Hybrid) دارد:
|
||||
- بخشی از **موجودی کیف پول تخفیفی** (`DiscountBalance`) کسر میشود
|
||||
- مابقی از **درگاه پرداخت** (IPG) پرداخت میشود
|
||||
- هر محصول یک `MaxDiscountPercent` دارد (مثلاً ۳۰٪) — حداکثر درصدی که از کیف تخفیفی قابل پرداخت است
|
||||
- مالیات فقط روی مبلغ درگاه محاسبه میشود
|
||||
|
||||
---
|
||||
|
||||
## ۲. وضعیت لایهها
|
||||
|
||||
### ✅ Domain Entities — کامل (۷ entity)
|
||||
|
||||
| Entity | مسیر | توضیح |
|
||||
|--------|------|-------|
|
||||
| `DiscountProduct` | `CMS/.../Entities/DiscountStore/` | محصول (Title, Price, MaxDiscountPercent, RemainingCount, ...) |
|
||||
| `DiscountProductCategory` | ↑ | دستهبندی درختی |
|
||||
| `DiscountProductCategoryMapping` | ↑ | M:N محصول ↔ دستهبندی |
|
||||
| `DiscountProductImage` | ↑ | گالری تصاویر |
|
||||
| `DiscountShoppingCart` | ↑ | سبد خرید (UserId, ProductId, Count) |
|
||||
| `DiscountOrder` | ↑ | سفارش (TotalAmount, DiscountBalanceUsed, GatewayAmountPaid, VAT) |
|
||||
| `DiscountOrderDetail` | ↑ | جزئیات سفارش (UnitPrice, DiscountPercent, DiscountAmount, FinalPrice) |
|
||||
|
||||
### ✅ EF Configurations — کامل (۷ فایل + ۶ migration)
|
||||
|
||||
### ✅ Application (CQRS) — کامل (~۵۰ فایل)
|
||||
- DiscountProductCQ: Create, Update, Delete, GetById, GetProducts + Image CRUD
|
||||
- DiscountCategoryCQ: Create, Update, Delete, GetCategories
|
||||
- DiscountOrderCQ: PlaceOrder, CompleteOrderPayment, UpdateOrderStatus, GetById, GetUserOrders, GetAll, SalesReport
|
||||
- DiscountShoppingCartCQ: AddToCart, RemoveFromCart, UpdateCount, GetUserCart, ClearCart
|
||||
- WalletCQ: ChargeDiscountWallet, VerifyDiscountWalletCharge
|
||||
|
||||
### ✅ Proto Definitions — کامل (۴ فایل)
|
||||
|
||||
| Proto | Namespace | RPCs |
|
||||
|-------|-----------|------|
|
||||
| `discountproduct.proto` | `CMSMicroservice.Protobuf.Protos.DiscountProduct` | DiscountProductContract (10 RPCs) |
|
||||
| `discountcategory.proto` | `CMSMicroservice.Protobuf.Protos.DiscountCategory` | DiscountCategoryContract (4 RPCs) |
|
||||
| `discountshoppingcart.proto` | `CMSMicroservice.Protobuf.Protos.DiscountShoppingCart` | DiscountShoppingCartContract (5 RPCs) |
|
||||
| `discountorder.proto` | `CMSMicroservice.Protobuf.Protos.DiscountOrder` | DiscountOrderContract (7 RPCs) |
|
||||
|
||||
### ✅ gRPC Services (CMS WebApi) — کامل (۴ سرویس + mapping)
|
||||
|
||||
### ✅ BackOffice (Admin Panel) — کامل
|
||||
- ۴ صفحه: محصولات، دستهبندیها، سفارشات، گزارش فروش
|
||||
- ۵ کامپوننت: فرم محصول، فرم دستهبندی، گالری، جزئیات سفارش، تغییر وضعیت
|
||||
- ۶ سرویس: DiscountProduct, DiscountCategory, DiscountOrder (+ interfaces)
|
||||
- NavMenu: بخش "فروشگاه تخفیفی" با ۳ لینک (محصولات، دستهبندیها، سفارشات و گزارش)
|
||||
- **یکسانسازی UI (بهمن ۱۴۰۴):** تمام صفحات فروشگاه تخفیفی بازنویسی شدند تا از `BasePageComponent` استفاده کنند و ظاهری یکسان با فروشگاه عادی داشته باشند → [جزئیات](../ui-modernization/BACKOFFICE-STORE-UNIFICATION.md)
|
||||
|
||||
### ✅ FrontOffice (مشتری) — پیادهسازی شده!
|
||||
|
||||
**فایلهای اضافه/ویرایش شده:**
|
||||
|
||||
| فایل | نوع | توضیح |
|
||||
|------|------|-------|
|
||||
| `Utilities/RouteConstants.cs` | ویرایش | اضافه شدن بخش `DiscountStore` (6 مسیر) |
|
||||
| `ConfigureServices.cs` | ویرایش | ثبت 3 سرویس + 4 gRPC client |
|
||||
| `Utilities/DiscountProductService.cs` | جدید | سرویس محصولات تخفیفی (GetProducts, GetById, GetCategories) |
|
||||
| `Utilities/DiscountCartService.cs` | جدید | سرویس سبد خرید تخفیفی (Add, Remove, Update, Clear) |
|
||||
| `Utilities/DiscountOrderService.cs` | جدید | سرویس سفارش تخفیفی (PlaceOrder, CompletePayment, GetOrders) |
|
||||
| `Pages/DiscountStore/Products.razor(.cs)` | جدید | لیست محصولات (جستجو + فیلتر دستهبندی + صفحهبندی) |
|
||||
| `Pages/DiscountStore/ProductDetail.razor(.cs)` | جدید | جزئیات محصول + گالری + افزودن به سبد |
|
||||
| `Pages/DiscountStore/Cart.razor(.cs)` | جدید | سبد خرید (Desktop: Table / Mobile: Cards) |
|
||||
| `Pages/DiscountStore/Checkout.razor(.cs)` | جدید | پرداخت ترکیبی (آدرس + اسلایدر تخفیف + درگاه) |
|
||||
| `Pages/DiscountStore/Orders.razor(.cs)` | جدید | لیست سفارشات (پرداخت/ارسال) |
|
||||
| `Pages/DiscountStore/OrderDetail.razor(.cs)` | جدید | جزئیات سفارش + خلاصه مالی |
|
||||
| `Pages/Profile/Index.razor.cs` | ویرایش | تایل "فروشگاه تخفیفی" در داشبورد |
|
||||
| `Shared/MainLayout.razor` | ویرایش | لینک ناوبری دسکتاپ + drawer موبایل |
|
||||
| `wwwroot/css/site.css` | ویرایش | ریجن CSS اختصاصی Discount Store |
|
||||
|
||||
---
|
||||
|
||||
## ۳. تسکهای FrontOffice (ترتیب اجرا)
|
||||
|
||||
### تسک ۱: Routes — اضافه کردن مسیرها
|
||||
```
|
||||
فایل: RouteConstants.cs
|
||||
اضافه: public static class DiscountStore {
|
||||
Products = "/discount-store"
|
||||
ProductDetail = "/discount-store/product/"
|
||||
Cart = "/discount-store/cart"
|
||||
Checkout = "/discount-store/checkout"
|
||||
Orders = "/discount-store/orders"
|
||||
OrderDetail = "/discount-store/order/"
|
||||
}
|
||||
```
|
||||
|
||||
### تسک ۲: gRPC Clients — ثبت DI
|
||||
```
|
||||
فایل: ConfigureServices.cs
|
||||
اضافه:
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountCategory;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
|
||||
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountProductContract.DiscountProductContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountCategoryContract.DiscountCategoryContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountShoppingCartContract.DiscountShoppingCartContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountOrderContract.DiscountOrderContractClient>);
|
||||
```
|
||||
|
||||
### تسک ۳: Services — سرویسهای FrontOffice
|
||||
```
|
||||
فایلهای جدید در Utilities/:
|
||||
DiscountProductService.cs — GetProducts (فیلتر + صفحهبندی), GetById, GetCategories
|
||||
DiscountCartService.cs — Add, Remove, Update, GetCart, Clear + event OnChange
|
||||
DiscountOrderService.cs — PlaceOrder, CompletePayment, GetUserOrders, GetOrderById
|
||||
```
|
||||
|
||||
### تسک ۴: صفحات Blazor
|
||||
```
|
||||
فایلهای جدید در Pages/DiscountStore/:
|
||||
Products.razor + .cs — لیست محصولات (فیلتر دستهبندی + جستجو + صفحهبندی)
|
||||
ProductDetail.razor + .cs — جزئیات محصول + گالری + افزودن به سبد
|
||||
Cart.razor + .cs — سبد خرید (نمایش تخفیف هر آیتم)
|
||||
Checkout.razor + .cs — پرداخت (انتخاب آدرس + تعیین مبلغ از تخفیفی + درگاه)
|
||||
Orders.razor + .cs — لیست سفارشات
|
||||
OrderDetail.razor + .cs — جزئیات سفارش + وضعیت ارسال
|
||||
```
|
||||
|
||||
### تسک ۵: Dashboard Tile
|
||||
```
|
||||
فایل: Profile/Index.razor.cs
|
||||
اضافه: تایل "فروشگاه تخفیفی" بعد از تایل "فروشگاه" موجود
|
||||
```
|
||||
|
||||
### تسک ۶: Navigation
|
||||
```
|
||||
فایل: MainLayout.razor
|
||||
اضافه: لینک "فروشگاه تخفیفی" در drawer موبایل + bottom nav (اختیاری)
|
||||
```
|
||||
|
||||
### تسک ۷: CSS
|
||||
```
|
||||
فایل: site.css
|
||||
اضافه: استایلهای اختصاصی (checkout progress, discount badge, ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۴. فلوی پرداخت (مهم!)
|
||||
|
||||
```
|
||||
کاربر سبد خرید دارد
|
||||
↓
|
||||
صفحه Checkout:
|
||||
├─ انتخاب آدرس تحویل
|
||||
├─ نمایش خلاصه سبد:
|
||||
│ هر محصول: قیمت × تعداد
|
||||
│ تخفیف هر محصول: price × count × maxDiscountPercent / 100
|
||||
│ جمع کل / جمع تخفیف / مبلغ درگاه
|
||||
├─ موجودی تخفیفی کاربر: XXX تومان
|
||||
├─ کاربر تعیین میکند چقدر از تخفیفی استفاده کند (≤ سقف مجاز)
|
||||
└─ [پرداخت]
|
||||
↓
|
||||
PlaceOrder RPC:
|
||||
├─ بررسی موجودی + محاسبه
|
||||
├─ ساخت سفارش (Pending)
|
||||
├─ رزرو موجودی انبار
|
||||
├─ اگر gateway_amount > 0 → payment_url برگردانده میشود
|
||||
└─ اگر gateway_amount = 0 → سفارش مستقیم تکمیل
|
||||
↓
|
||||
ریدایرکت به درگاه (اگر لازم باشد)
|
||||
↓
|
||||
CompleteOrderPayment RPC (بعد از callback):
|
||||
├─ success → کسر DiscountBalance + تأیید فروش + ثبت تراکنش
|
||||
└─ failure → آزادسازی رزرو انبار + لغو سفارش
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۵. تخمین زمان
|
||||
|
||||
| تسک | تخمین |
|
||||
|-----|-------|
|
||||
| Routes + DI + Services | ۱ ساعت |
|
||||
| Products + ProductDetail | ۲ ساعت |
|
||||
| Cart | ۱ ساعت |
|
||||
| Checkout (پیچیدهترین بخش) | ۲ ساعت |
|
||||
| Orders + OrderDetail | ۱ ساعت |
|
||||
| Dashboard tile + Nav | ۰.۵ ساعت |
|
||||
| CSS + Polish | ۰.۵ ساعت |
|
||||
| **مجموع** | **~۸ ساعت** |
|
||||
@@ -0,0 +1,251 @@
|
||||
# 📁 معماری مدیریت فایل و تصاویر — CMS
|
||||
|
||||
> **تاریخ:** ۱۴۰۴/۱۱/۲۸ (February 17, 2026)
|
||||
> **وضعیت:** ✅ عملیاتی
|
||||
> **Build:** 0 Error (هر ۳ پروژه) ✅
|
||||
|
||||
---
|
||||
|
||||
## ۱. پیشزمینه
|
||||
|
||||
سیستم قبلی از **FMS (File Management Service)** در آدرس `https://dl.afrino.co` استفاده میکرد که غیرقابل دسترس/ناسازگار شده بود. در چندین فاز، معماری فایلها به صورت کامل بازنویسی شد:
|
||||
|
||||
| فاز | شرح | وضعیت |
|
||||
|-----|------|-------|
|
||||
| ۱. حذف FMS | حذف کامل ۳ فایل مرده FMS | ✅ |
|
||||
| ۲. حالت base64 | ذخیره data URI مستقیم در DB | ✅ (بازنشسته) |
|
||||
| ۳. ذخیره دیسکی | فایل در دیسک + مسیر در DB + تبدیل به base64 هنگام serve | ✅ |
|
||||
| ۴. **سرو HTTP عمومی** | **اندپوینت `/uploads/{path}` + Fallback FMS** | **✅ جدید** |
|
||||
|
||||
---
|
||||
|
||||
## ۲. معماری نهایی
|
||||
|
||||
```
|
||||
BackOffice (Blazor WASM)
|
||||
│
|
||||
│ MudFileUpload → IBrowserFile → byte[] → gRPC ImageFileModel
|
||||
│
|
||||
▼
|
||||
CMS gRPC Service
|
||||
│
|
||||
│ proto ImageFileModel → Command.ImageFileBytes
|
||||
│
|
||||
▼
|
||||
MediatR Handler
|
||||
│
|
||||
│ IFileManager.UploadImageAsync(folder, bytes, mime, name)
|
||||
│
|
||||
▼
|
||||
LocalFileManager
|
||||
│
|
||||
├─ Main Image → Uploads/Images/{folder}/{guid}.jpg (1200×1200, JPEG Q75)
|
||||
├─ Thumbnail → Uploads/Images/{folder}/{guid}_thumb.jpg (300×300, JPEG Q75)
|
||||
│
|
||||
│ Returns: { Main.Path, Thumbnail.Path } (relative paths stored in DB)
|
||||
│
|
||||
▼
|
||||
ImagePathResolverInterceptor (gRPC response)
|
||||
│
|
||||
│ Walks all response fields → reads file from disk → data:{mime};base64,{bytes}
|
||||
│
|
||||
▼
|
||||
BackOffice / FrontOffice ← receives base64 data URI directly in proto fields
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۳. اجزای کلیدی
|
||||
|
||||
### ۳.۱ `IFileManager` — Interface
|
||||
|
||||
**مسیر:** `Application/Common/FileManager/IFileManager.cs`
|
||||
|
||||
```csharp
|
||||
public interface IFileManager
|
||||
{
|
||||
Task<UploadResult> UploadAsync(string folder, byte[] file, string mime, string? fileName, CancellationToken ct);
|
||||
Task<ImageUploadResult> UploadImageAsync(string folder, byte[] file, string mime, string? fileName, CancellationToken ct);
|
||||
Task DeleteAsync(string path, CancellationToken ct);
|
||||
string? ResolveImageUrl(string? path);
|
||||
}
|
||||
```
|
||||
|
||||
- **`UploadAsync`** — آپلود فایل خام
|
||||
- **`UploadImageAsync`** — بهینهسازی + ساخت thumbnail خودکار (SixLabors.ImageSharp)
|
||||
- **`ResolveImageUrl`** — تبدیل مسیر نسبی به data URI (base64)
|
||||
|
||||
### ۳.۲ `LocalFileManager` — پیادهسازی
|
||||
|
||||
**مسیر:** `Infrastructure/Services/LocalFileManager.cs`
|
||||
|
||||
| ویژگی | مقدار |
|
||||
|-------|-------|
|
||||
| ریشه آپلود | `FileStorage:UploadPath` یا `AppContext.BaseDirectory/Uploads` |
|
||||
| فرمت تصویر اصلی | JPEG, Quality 75, حداکثر 1200×1200 |
|
||||
| فرمت thumbnail | JPEG, Quality 75, حداکثر 300×300 |
|
||||
| نامگذاری فایل | `{Guid}.jpg` + `{Guid}_thumb.jpg` |
|
||||
| DI Registration | `services.AddSingleton<IFileManager, LocalFileManager>()` |
|
||||
|
||||
### ۳.۳ `ImagePathResolverInterceptor` — gRPC Interceptor
|
||||
|
||||
**مسیر:** `WebApi/Interceptors/ImagePathResolverInterceptor.cs`
|
||||
|
||||
اینترسپتور **خودکار** تمام فیلدهای تصویری را در responseهای gRPC پیدا کرده و مسیر نسبی را به data URI تبدیل میکند.
|
||||
|
||||
**فیلدهای شناساییشده:**
|
||||
- `image_path`, `thumbnail_path`, `image_thumbnail_path`
|
||||
- `featured_image_path`, `featured_image_thumbnail_path`
|
||||
- `hero_image_path`, `product_thumbnail_path`
|
||||
- `avatar_path`, `avatar_url`
|
||||
|
||||
**قابلیتها:**
|
||||
- Walk بازگشتی پیامهای proto
|
||||
- پشتیبانی از `string` ساده و `Google.Protobuf.WellKnownTypes.StringValue`
|
||||
- پشتیبانی از فیلدهای `repeated` (collectionهای تو در تو)
|
||||
- اگر مقدار `data:` یا `http` باشد → رد میشود (تبدیل نمیشود)
|
||||
|
||||
### ۳.۴ `LoggingBehaviour` — پاکسازی لاگ
|
||||
|
||||
**مسیر:** `WebApi/Common/Behaviours/LoggingBehaviour.cs`
|
||||
|
||||
- فرمت لاگ: `JsonFormatter.Default.Format()` به جای `{@Request}`
|
||||
- پاکسازی فیلدهای باینری با regex (`File`, `ImageFile`, `image_file`, `file`)
|
||||
- محدودیت طول لاگ: حداکثر 2000 کاراکتر
|
||||
|
||||
### ۳.۵ `UploadsController` — سرو عمومی فایلها (HTTP) 🆕
|
||||
|
||||
**مسیر:** `WebApi/Controllers/UploadsController.cs`
|
||||
|
||||
اندپوینت عمومی REST برای سرو مستقیم تصاویر بدون نیاز به base64. مناسب برای بارگذاری تصاویر در تگ `<img>` و کاهش پهنای باند.
|
||||
|
||||
| ویژگی | مقدار |
|
||||
|-------|-------|
|
||||
| مسیر | `GET /uploads/{**path}` |
|
||||
| احراز هویت | `[AllowAnonymous]` — عمومی |
|
||||
| کش مرورگر | `ResponseCache 86400` ثانیه (۲۴ ساعت) |
|
||||
| Content-Type | تشخیص خودکار از پسوند فایل (`FileExtensionContentTypeProvider`) |
|
||||
| Range Requests | ✅ فعال (`enableRangeProcessing: true`) |
|
||||
| محافظت مسیر | جلوگیری از path traversal (`..`, `\`, `Path.GetFullPath` validation) |
|
||||
|
||||
**FMS Fallback:**
|
||||
اگر فایل محلی وجود نداشته باشد و تنظیم `FMS:Address` پر باشد:
|
||||
1. فایل از `{FMS:Address}/{relativePath}` دانلود میشود
|
||||
2. Content-Type بررسی میشود (فقط `image/*` و `application/pdf` مجاز)
|
||||
3. فایل روی دیسک محلی ذخیره و کش میشود
|
||||
4. سپس فایل محلی سرو میشود
|
||||
|
||||
```
|
||||
Client → GET /uploads/Images/BlogPosts/abc.jpg
|
||||
│
|
||||
├─ فایل محلی وجود دارد? → سرو مستقیم از دیسک
|
||||
│
|
||||
└─ فایل محلی وجود ندارد?
|
||||
└─ FMS:Address تنظیم شده?
|
||||
├─ بله → دانلود از dl.afrino.co → ذخیره محلی → سرو
|
||||
└─ خیر → 404 Not Found
|
||||
```
|
||||
|
||||
**وابستگیها:**
|
||||
- `IHttpClientFactory` با named client `"FMS"` (timeout: 30 ثانیه)
|
||||
- ثبت در `Program.cs`: `builder.Services.AddHttpClient("FMS", ...)`
|
||||
|
||||
---
|
||||
|
||||
## ۴. Proto Messages — ImageFileModel
|
||||
|
||||
هر حوزه (DiscountProduct, BlogPost, SitePage) پیام مستقل `ImageFileModel` خود را دارد:
|
||||
|
||||
### DiscountProduct
|
||||
```protobuf
|
||||
message ImageFileModel {
|
||||
bytes file = 1;
|
||||
string mime = 2;
|
||||
string file_name = 3;
|
||||
}
|
||||
```
|
||||
**استفاده در:** `CreateDiscountProductRequest`, `UpdateDiscountProductRequest`
|
||||
|
||||
### BlogPost
|
||||
```protobuf
|
||||
message BlogImageFileModel {
|
||||
bytes file = 1;
|
||||
string mime = 2;
|
||||
string file_name = 3;
|
||||
}
|
||||
```
|
||||
**استفاده در:** `CreateBlogPostRequest`, `UpdateBlogPostRequest`
|
||||
|
||||
### SitePage
|
||||
```protobuf
|
||||
message SitePageImageFileModel {
|
||||
bytes file = 1;
|
||||
string mime = 2;
|
||||
string file_name = 3;
|
||||
}
|
||||
```
|
||||
**استفاده در:** `UpdateSitePageRequest`, `CreateSitePageSectionRequest`, `UpdateSitePageSectionRequest`
|
||||
|
||||
---
|
||||
|
||||
## ۵. جریان آپلود تصویر (مثال: BlogPost)
|
||||
|
||||
```
|
||||
1. کاربر در BackOffice → MudFileUpload → انتخاب فایل
|
||||
2. BlogPostEditDialog.OnImageSelected()
|
||||
→ IBrowserFile.OpenReadStream() → byte[] + ContentType + FileName
|
||||
→ پیشنمایش base64 در UI
|
||||
3. Submit → BlogPostEditDto { ImageFile = bytes, ImageMime, ImageFileName }
|
||||
4. BlogPostService.CreateAsync()
|
||||
→ BlogImageFileModel { File = ByteString.CopyFrom(bytes), Mime, FileName }
|
||||
→ gRPC CreateBlogPostRequest
|
||||
5. CMS BlogPostService (gRPC) → CreateBlogPostCommand
|
||||
{ ImageFileBytes = request.ImageFile.File.ToByteArray(), ... }
|
||||
6. CreateBlogPostCommandHandler.Handle()
|
||||
→ _fileManager.UploadImageAsync("Images/BlogPosts", bytes, mime, name)
|
||||
→ post.FeaturedImagePath = result.Main.Path
|
||||
→ post.FeaturedImageThumbnailPath = result.Thumbnail.Path
|
||||
7. Response → ImagePathResolverInterceptor
|
||||
→ featured_image_path → data:image/jpeg;base64,...
|
||||
→ featured_image_thumbnail_path → data:image/jpeg;base64,...
|
||||
8. BackOffice / FrontOffice → نمایش مستقیم base64 data URI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۶. فایلهای حذفشده (کد مرده FMS)
|
||||
|
||||
| فایل | شرح |
|
||||
|------|------|
|
||||
| `Infrastructure/Services/FmsFileManager.cs` | پیادهسازی قدیمی FMS (HTTP upload) |
|
||||
| `Application/Common/FileManager/FileManagementService.cs` | سرویس قدیمی مدیریت فایل |
|
||||
| `Application/Common/FileManager/IFileManagementService.cs` | اینترفیس قدیمی |
|
||||
|
||||
---
|
||||
|
||||
## ۷. تنظیمات
|
||||
|
||||
### `appsettings.json` (CMS)
|
||||
```json
|
||||
{
|
||||
"FileStorage": {
|
||||
"UploadPath": "/app/Uploads"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### محدودیت حجم gRPC
|
||||
```csharp
|
||||
// Program.cs
|
||||
services.AddGrpc(o => o.MaxReceiveMessageSize = 50 * 1024 * 1024); // 50MB
|
||||
```
|
||||
|
||||
### FrontOffice — `UrlUtility.GetImageUrl()`
|
||||
```csharp
|
||||
public static string GetImageUrl(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path)) return string.Empty;
|
||||
if (path.StartsWith("data:") || path.StartsWith("http")) return path;
|
||||
return $"{DownloadUrl?.TrimEnd('/')}/{path.TrimStart('/')}";
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,187 @@
|
||||
# 🔐 فیکس فلوی ثبتنام / ورود FrontOffice
|
||||
|
||||
**تاریخ:** بهمن ۱۴۰۴ (February 2026)
|
||||
|
||||
---
|
||||
|
||||
## 📋 خلاصه
|
||||
|
||||
بررسی کامل فلوی ثبتنام و ورود FrontOffice از UI تا دیتابیس انجام شد. **۳ باگ بحرانی** شناسایی و رفع شده:
|
||||
|
||||
| # | شدت | مشکل | فایل |
|
||||
|---|------|-------|------|
|
||||
| 1 | 🔴 بحرانی | کاربران جدید ثبتنام نمیشوند | `UserCQ/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs` |
|
||||
| 2 | 🔴 بحرانی | امضای قرارداد همیشه شکست میخورد | `UserCQ/AcceptContract/AcceptContractCommandHandler.cs` |
|
||||
| 3 | 🟡 متوسط | منوی کناری وضعیت نادرست نشان میدهد | `FrontOffice.Main/Utilities/AuthService.cs` |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 باگ ۱ — کاربران جدید ثبتنام نمیشوند
|
||||
|
||||
### مشکل
|
||||
|
||||
FrontOffice از `UserContract.UserContractClient` (user.proto) استفاده میکند → `UserCQ/VerifyOtpTokenCommandHandler`. این handler وقتی کاربر یافت نمیشد فقط خطای **«کاربر یافت نشد»** برمیگرداند و کاربر جدید ایجاد **نمیکرد**.
|
||||
|
||||
لاجیک ایجاد کاربر (شامل: اعتبارسنجی کد معرف، درخت باینری، موقعیت شاخه) در `OtpTokenCQ/VerifyOtpTokenCommandHandler` بود — سرویسی که FrontOffice اصلاً از آن استفاده نمیکند.
|
||||
|
||||
### رفع
|
||||
|
||||
اضافه شدن لاجیک کامل ایجاد کاربر جدید به `UserCQ/VerifyOtpTokenCommandHandler`:
|
||||
|
||||
```
|
||||
if (user == null)
|
||||
{
|
||||
// ۱. اعتبارسنجی کد معرف (ParentReferralCode) — الزامی
|
||||
// ۲. بررسی وجود معرف و فعال بودن عضویت باشگاه
|
||||
// ۳. بررسی ظرفیت (حداکثر ۲ زیرمجموعه مستقیم)
|
||||
// ۴. تعیین شاخه (چپ اول، بعد راست)
|
||||
// ۵. ایجاد User + UserRole + UserWallet
|
||||
// ۶. رویدادهای دامنه: CreateNewUserEvent, CreateNewUserRoleEvent, CreateNewUserWalletEvent
|
||||
// ۷. بارگذاری مجدد کاربر با روابط کامل
|
||||
// ۸. تولید JWT token
|
||||
}
|
||||
```
|
||||
|
||||
### اعتبارسنجیها
|
||||
|
||||
| مرحله | شرط | پیام خطا |
|
||||
|-------|------|---------|
|
||||
| کد معرف | خالی یا null | «کد معرف الزامی است» |
|
||||
| معرف | وجود نداشته باشد | «معرف وجود ندارد» |
|
||||
| عضویت باشگاه | غیرفعال باشد | «لینک دعوت معرف فعال نیست» |
|
||||
| ظرفیت | بیش از ۱ فرزند | «ظرفیت معرف تکمیل است» |
|
||||
| شاخه | هر دو پُر باشند | «ظرفیت معرف تکمیل است» |
|
||||
|
||||
### فایل
|
||||
|
||||
`CMS/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs`
|
||||
|
||||
---
|
||||
|
||||
## 🔴 باگ ۲ — امضای قرارداد همیشه شکست میخورد
|
||||
|
||||
### مشکل
|
||||
|
||||
`AcceptContractCommandHandler` از `_currentUserService.Username` برای پیدا کردن OTP و کاربر بر اساس شماره موبایل استفاده میکرد:
|
||||
|
||||
```csharp
|
||||
// ❌ قبل — Username = "{FirstName} {LastName}" نه شماره موبایل!
|
||||
var otpToken = await _context.OtpTokens
|
||||
.Where(x => x.Mobile == _currentUserService.Username ...)
|
||||
var user = await _context.Users
|
||||
.Where(x => x.Mobile == _currentUserService.Username ...)
|
||||
```
|
||||
|
||||
**`CurrentUserService.Username`** مقدار `ClaimTypes.Name` را برمیگرداند که در JWT به صورت `"{FirstName} {LastName}"` ذخیره شده — **نه شماره موبایل!**
|
||||
|
||||
### رفع
|
||||
|
||||
اول کاربر بر اساس `UserId` (از `ClaimTypes.NameIdentifier`) پیدا شود، سپس از `user.Mobile` برای جستجوی OTP استفاده شود:
|
||||
|
||||
```csharp
|
||||
// ✅ بعد — ابتدا کاربر از UserId پیدا شود
|
||||
var userId = long.Parse(_currentUserService.UserId);
|
||||
var user = await _context.Users
|
||||
.Where(x => x.Id == userId)...
|
||||
|
||||
var otpToken = await _context.OtpTokens
|
||||
.Where(x => x.Mobile == user.Mobile ...)
|
||||
```
|
||||
|
||||
### فایل
|
||||
|
||||
`CMS/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs`
|
||||
|
||||
---
|
||||
|
||||
## 🟡 باگ ۳ — `IsCompleteRegister()` داده قدیمی میخواند
|
||||
|
||||
### مشکل
|
||||
|
||||
متد sync در `AuthService`:
|
||||
|
||||
```csharp
|
||||
// ❌ قبل — GetAwaiter() بدون GetResult() عملیات async را اجرا نمیکند
|
||||
InitUserAuthInfo().GetAwaiter();
|
||||
```
|
||||
|
||||
`GetAwaiter()` فقط یک شیء awaiter برمیگرداند ولی عملیات را اجرا **نمیکند**. در نتیجه `_userAuthInfo` مقداردهی نمیشود و منوی کناری (`MainLayout.razor`) وضعیت نادرست نشان میدهد.
|
||||
|
||||
### رفع
|
||||
|
||||
```csharp
|
||||
// ✅ بعد — عملیات async را همگام اجرا میکند
|
||||
InitUserAuthInfo().GetAwaiter().GetResult();
|
||||
```
|
||||
|
||||
### محل استفاده
|
||||
|
||||
`MainLayout.razor` خطوط ۱۰۶ و ۱۰۹:
|
||||
```razor
|
||||
Disabled="@(!AuthService.IsCompleteRegister())"
|
||||
```
|
||||
|
||||
### فایل
|
||||
|
||||
`FrontOffice/src/FrontOffice.Main/Utilities/AuthService.cs`
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ معماری فلوی ثبتنام (بعد از فیکس)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FrontOffice (Blazor WASM) │
|
||||
│ │
|
||||
│ LoginPage → SendOtp → VerifyOtp(mobile, code, referral) │
|
||||
│ ↓ gRPC-Web │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ CMS Backend (gRPC) │
|
||||
│ │
|
||||
│ UserContract.VerifyOtpToken │
|
||||
│ ↓ │
|
||||
│ UserCQ/VerifyOtpTokenCommandHandler │
|
||||
│ │ │
|
||||
│ ├── OTP صحیح؟ → ❌ خطا │
|
||||
│ │ │
|
||||
│ ├── کاربر موجود؟ → ✅ تولید JWT Token │
|
||||
│ │ │
|
||||
│ └── کاربر جدید؟ │
|
||||
│ ├── اعتبارسنجی کد معرف │
|
||||
│ ├── بررسی ظرفیت درخت باینری │
|
||||
│ ├── ایجاد User + UserRole + UserWallet │
|
||||
│ ├── رویدادهای دامنه │
|
||||
│ └── تولید JWT Token │
|
||||
│ │
|
||||
│ بعد از ثبتنام → RegisterWizard: │
|
||||
│ Step 1: اطلاعات شخصی │
|
||||
│ Step 2: امضای قرارداد (AcceptContract + OTP) │
|
||||
│ Step 3: خرید پکیج │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Database │
|
||||
│ │
|
||||
│ Users ─── UserRoles ─── UserWallets │
|
||||
│ └── NetworkParentId, LegPosition (Binary Tree) │
|
||||
│ └── UserContracts, ClubMembership │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 فایلهای تغییر یافته
|
||||
|
||||
| فایل | تغییر |
|
||||
|------|-------|
|
||||
| `CMS/.../UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs` | اضافه شدن لاجیک ایجاد کاربر جدید (86→165 خط) |
|
||||
| `CMS/.../UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs` | تغییر lookup از Username به UserId |
|
||||
| `FrontOffice/.../Utilities/AuthService.cs` | اضافه شدن `.GetResult()` به `GetAwaiter()` |
|
||||
|
||||
---
|
||||
|
||||
## ✅ بیلد
|
||||
|
||||
```
|
||||
CMS: 0 Error(s) ✅
|
||||
FrontOffice: 0 Error(s) ✅
|
||||
BackOffice: 0 Error(s) ✅
|
||||
```
|
||||
@@ -0,0 +1,359 @@
|
||||
# 🏗️ BackOffice — مرجع معماری و الگوها
|
||||
|
||||
> **تاریخ:** ۱۴۰۴/۱۱/۲۴ (February 13, 2026)
|
||||
> **پروژه:** BackOffice Admin Panel (Blazor WebAssembly)
|
||||
|
||||
---
|
||||
|
||||
## ۱. معماری کلی
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ BackOffice │
|
||||
│ (Blazor WebAssembly) │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ MudBlazor│ │ Mapster │ │ DateTimeCvt │ │
|
||||
│ │ v8 │ │ (mapping)│ │ (تاریخ شمسی) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Pages / Components / Shared │ │
|
||||
│ │ BasePageComponent, Hub Pages, Dialogs │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ gRPC Clients │ │ HTTP REST Services│ │
|
||||
│ │ (Protobuf) │ │ (DiscountShop) │ │
|
||||
│ └──────┬───────┘ └────────┬─────────┘ │
|
||||
└─────────┼──────────────────────┼─────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ CMS Microservice │
|
||||
│ (ASP.NET Core + gRPC) │
|
||||
│ Domain → Application (CQRS) → Infra │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۲. Technology Stack
|
||||
|
||||
| لایه | تکنولوژی | نسخه |
|
||||
|------|----------|------|
|
||||
| Frontend Framework | Blazor WebAssembly | .NET 9 |
|
||||
| UI Library | MudBlazor | v8 |
|
||||
| Backend Communication (عادی) | gRPC / Protobuf | — |
|
||||
| Backend Communication (تخفیفی) | HTTP REST | — |
|
||||
| Object Mapping | Mapster | — |
|
||||
| تاریخ شمسی | DateTimeConverterCL | — |
|
||||
| Client State | Blazored.LocalStorage | — |
|
||||
| Auth | JWT Role-based | Administrator, Admin, Author |
|
||||
| Permission | IAuthorizationService.HasPermissionAsync | 18 permission |
|
||||
|
||||
---
|
||||
|
||||
## ۳. ساختار پوشهها
|
||||
|
||||
```
|
||||
BackOffice/src/BackOffice/
|
||||
├── Common/
|
||||
│ ├── BaseComponents/ ← کامپوننتهای پایه (BasePageComponent, DateRangePicker, Image)
|
||||
│ ├── Utilities/ ← RouteConstance, Extensions, Helpers
|
||||
│ └── ...
|
||||
├── Pages/
|
||||
│ ├── Category/ ← دستهبندی فروشگاه عادی
|
||||
│ ├── Products/ ← محصولات فروشگاه عادی
|
||||
│ ├── UserOrder/ ← سفارشات + گزارش فروش (Hub)
|
||||
│ ├── DiscountShop/ ← فروشگاه تخفیفی (محصولات + دستهبندی + سفارشات)
|
||||
│ │ └── Components/ ← دیالوگها و کامپوننتهای اختصاصی
|
||||
│ ├── Inventory/ ← انبارداری (4 صفحه)
|
||||
│ ├── Package/ ← پکیجها
|
||||
│ ├── Commission/ ← کمیسیون (5 صفحه)
|
||||
│ ├── Network/ ← شبکه (4 صفحه)
|
||||
│ ├── Club/ ← باشگاه مشتریان (Hub: اعضا + آمار + فیچرها)
|
||||
│ ├── Blog/ ← بلاگ (Hub: پست + دستهبندی + تگ)
|
||||
│ ├── Content/ ← صفحات سایت
|
||||
│ ├── Wallet/ ← کیفپول (تبها: لیست + تاریخچه)
|
||||
│ ├── Contract/ ← قراردادها
|
||||
│ ├── SystemManagement/ ← سیستم (Hub: تنظیمات + Worker + Health)
|
||||
│ └── ...
|
||||
├── Services/
|
||||
│ ├── DiscountProduct/ ← IDiscountProductService + implementation
|
||||
│ ├── DiscountCategory/ ← IDiscountCategoryService + implementation
|
||||
│ ├── DiscountOrder/ ← IDiscountOrderService + implementation
|
||||
│ └── Authorization/ ← IAuthorizationService
|
||||
├── Shared/
|
||||
│ ├── MainLayout.razor ← لایوت اصلی (AppBar + NavMenu + MudContainer)
|
||||
│ ├── NavMenu.razor ← منوی ناوبری
|
||||
│ ├── GlobalSearch.razor ← جستجوی سراسری
|
||||
│ └── AppBreadcrumb.razor ← Breadcrumb فارسی
|
||||
└── wwwroot/
|
||||
├── js/main.js ← jsSaveAsFile (Excel export)
|
||||
└── appsettings.json ← تنظیمات endpoints
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۴. الگوهای اصلی
|
||||
|
||||
### ۴.۱ BasePageComponent — پترن صفحات لیست
|
||||
|
||||
**هر صفحه لیست** از `BasePageComponent` استفاده میکند:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ BasePageComponent │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ 📋 Filter Panel (collapsible) │ │
|
||||
│ │ [فیلد ۱] [فیلد ۲] [فیلد ۳] │ │
|
||||
│ │ [پاک کردن فیلتر] [جستجو] │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ 📊 Content (DataGrid) │ │
|
||||
│ │ ToolBar: [عنوان] [Excel] [+] │ │
|
||||
│ │ Columns: ... │ │
|
||||
│ │ Pager: 20/50/100 │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**فایل:** `Common/BaseComponents/BasePageComponent.razor`
|
||||
|
||||
**پراپرتیها:**
|
||||
- `RenderFragment Filters` — محتوای فیلتر
|
||||
- `RenderFragment Content` — محتوای اصلی
|
||||
- `EventCallback OnSubmitClick` — کلیک جستجو
|
||||
- `EventCallback OnClearFilterClick` — کلیک پاک کردن
|
||||
- `bool IsFiltered` — آیا فیلتر فعال است (نشاندهنده badge «فعال»)
|
||||
|
||||
---
|
||||
|
||||
### ۴.۲ Hub Pages — پترن ادغام صفحات
|
||||
|
||||
صفحات مرتبط در یک Hub با `MudTabs` ادغام میشوند:
|
||||
|
||||
| Hub | Routeها | تبها |
|
||||
|-----|---------|-------|
|
||||
| `OrdersHub` | `/OrdersPage/`, `/OrdersSalesReportsPage/` | سفارشات + گزارش فروش |
|
||||
| `DiscountShopHub` | `/discount-shop`, `/discount-orders`, `/discount-sales-reports` | سفارشات + گزارش فروش |
|
||||
| `ClubHub` | `/club`, `/club/members`, `/club/statistics` | اعضا + آمار |
|
||||
| `BlogHub` | `/blog`, `/blog/posts`, `/blog/categories`, `/tags` | پست + دستهبندی + تگ |
|
||||
| `SystemHub` | `/system`, `/system/configuration`, `/system/worker-control`, `/system/health` | تنظیمات + Worker + Health |
|
||||
|
||||
---
|
||||
|
||||
### ۴.۳ Code-Behind — پترن جداسازی markup/logic
|
||||
|
||||
```
|
||||
MyPage.razor → فقط HTML/Razor markup
|
||||
MyPage.razor.cs → partial class + [Inject] + methods
|
||||
```
|
||||
|
||||
**قوانین:**
|
||||
1. فایلهایی که سرویس inject دارند **باید** code-behind داشته باشند (محدودیت Razor source generator)
|
||||
2. سرویسهای global (`_Imports.razor`) **نباید** دوباره `[Inject]` شوند
|
||||
3. `namespace` باید با مسیر فایل match کند
|
||||
|
||||
**سرویسهای Global (از `_Imports.razor`):**
|
||||
|
||||
| سرویس | نام متغیر | توضیح |
|
||||
|--------|-----------|-------|
|
||||
| `IDialogService` | `DialogService` | دیالوگ MudBlazor |
|
||||
| `ISnackbar` | `Snackbar` | نوتیفیکیشن MudBlazor |
|
||||
| `IJSRuntime` | `jsRuntime` | ⚠️ حرف کوچک `j` |
|
||||
| `NavigationManager` | `Navigation` | ناوبری |
|
||||
| `ILocalStorageService` | `LocalStorageService` | ذخیره محلی |
|
||||
| `AuthenticationStateProvider` | `AuthenticationStateProvider` | احراز هویت |
|
||||
|
||||
---
|
||||
|
||||
### ۴.۴ Excel Export — پترن خروجی CSV
|
||||
|
||||
```csharp
|
||||
private async Task ExportToExcel()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("ستون ۱,ستون ۲,ستون ۳"); // هدر فارسی
|
||||
foreach (var item in items)
|
||||
{
|
||||
sb.AppendLine($"{EscapeCsv(item.Col1)},{item.Col2},{item.Col3}");
|
||||
}
|
||||
var bytes = Encoding.UTF8.GetPreamble() // UTF-8 BOM
|
||||
.Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "filename.csv", base64);
|
||||
}
|
||||
|
||||
private string EscapeCsv(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
**صفحات دارای Excel:** Products, UserOrders, ClubMembers, WithdrawalRequests, WeeklyReports, StockMovements, Users, DiscountOrders, ManualPayments, Inventory, DiscountProducts
|
||||
|
||||
---
|
||||
|
||||
### ۴.۵ Server-Side DataGrid — پترن بارگذاری صفحهای
|
||||
|
||||
```razor
|
||||
<MudDataGrid T="MyDto"
|
||||
ServerData="LoadServerData"
|
||||
Height="calc(100vh - 240px)"
|
||||
FixedHeader="true"
|
||||
Hover="true" Dense="true">
|
||||
```
|
||||
|
||||
```csharp
|
||||
private async Task<GridData<MyDto>> LoadServerData(GridState<MyDto> state)
|
||||
{
|
||||
var filter = new MyFilter
|
||||
{
|
||||
PageNumber = state.Page + 1, // MudDataGrid is 0-based
|
||||
PageSize = state.PageSize
|
||||
};
|
||||
var (items, totalCount, _) = await MyService.GetAsync(filter);
|
||||
return new GridData<MyDto> { Items = items, TotalItems = totalCount };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۴.۶ Permission System
|
||||
|
||||
NavMenu از `IAuthorizationService.HasPermissionAsync()` برای نمایش/مخفی کردن آیتمها استفاده میکند:
|
||||
|
||||
| Permission | صفحه(ها) |
|
||||
|-----------|----------|
|
||||
| `dashboard.view` | داشبورد |
|
||||
| `packages.manage` | پکیجها |
|
||||
| `products.manage` | محصولات + دستهبندی + ویرایش دستهجمعی |
|
||||
| `orders.view` | سفارشات |
|
||||
| `inventory.manage` | انبارداری (4 صفحه) |
|
||||
| `discountshop.manage` | فروشگاه تخفیفی |
|
||||
| `users.view` | کاربران |
|
||||
| `roles.manage` | نقشها |
|
||||
| `manualpayments.create` | پرداخت دستی |
|
||||
| `blog.manage` | بلاگ |
|
||||
| `sitepages.manage` | صفحات سایت |
|
||||
| `publicmessages.view` | پیامهای عمومی |
|
||||
| `settings.manage_configuration` | تنظیمات سیستم |
|
||||
|
||||
---
|
||||
|
||||
## ۵. مسیرهای (Routing)
|
||||
|
||||
### مسیرهای ثابت (`RouteConstance.cs`)
|
||||
```
|
||||
/ → Dashboard
|
||||
/PackagePage/ → Packages
|
||||
/ProductsPage/ → Products
|
||||
/CategoryPage/ → Categories
|
||||
/OrdersPage/ → Orders Hub
|
||||
/OrdersSalesReportsPage/ → Orders Sales Reports
|
||||
/InventoryPage/ → Inventory
|
||||
/InventoryLowStockPage/ → Low Stock
|
||||
/InventoryWarehousesPage/ → Warehouses
|
||||
/InventoryMovementsPage/ → Stock Movements
|
||||
/UserPage/ → Users
|
||||
/RolePage/ → Roles
|
||||
/ProductsBulkEditPage/ → Bulk Edit
|
||||
/ProductCategoriesPage/ → Product-Category DragDrop
|
||||
/CategoryProductsPage/ → Category-Product DragDrop
|
||||
```
|
||||
|
||||
### مسیرهای hardcode (فروشگاه تخفیفی + سایر)
|
||||
```
|
||||
/discount-products → Discount Products
|
||||
/discount-categories → Discount Categories
|
||||
/discount-shop → Discount Orders Hub
|
||||
/discount-orders → Discount Orders
|
||||
/discount-sales-reports → Discount Sales Reports
|
||||
/commission/* → Commission pages
|
||||
/network/* → Network pages
|
||||
/club/* → Club pages
|
||||
/blog/* → Blog pages
|
||||
/wallets → Wallets
|
||||
/contracts → Contracts
|
||||
/payment/manual-payments → Manual Payments
|
||||
/system/* → System pages
|
||||
/settings → Settings
|
||||
/content/pages → Content Pages
|
||||
/public-messages → Public Messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۶. ارتباط فروشگاه عادی vs تخفیفی
|
||||
|
||||
| جنبه | فروشگاه عادی | فروشگاه تخفیفی |
|
||||
|------|-------------|---------------|
|
||||
| **سرویس محصولات** | gRPC `ProductsContractClient` | HTTP `IDiscountProductService` |
|
||||
| **سرویس دستهبندی** | gRPC `CategoryContractClient` | HTTP `IDiscountCategoryService` |
|
||||
| **سرویس سفارشات** | gRPC `UserOrderContractClient` | HTTP `IDiscountOrderService` |
|
||||
| **Entity بکند** | `Product` | `DiscountProduct` |
|
||||
| **پرداخت** | فقط درگاه | ترکیبی (کیف تخفیفی + درگاه) |
|
||||
| **فیلد اختصاصی** | — | `MaxDiscountPercent` |
|
||||
| **UI Pattern** | BasePageComponent | BasePageComponent (یکسان) |
|
||||
| **ستونها** | یکسان | یکسان + ستون تخفیف |
|
||||
|
||||
---
|
||||
|
||||
## ۷. نقشه NavMenu
|
||||
|
||||
```
|
||||
داشبورد
|
||||
─────────────────────
|
||||
کمیسیون و شبکه
|
||||
├── کمیسیون (NavGroup)
|
||||
│ ├── داشبورد کمیسیون
|
||||
│ ├── گزارشهای هفتگی
|
||||
│ ├── پرداخت کاربران
|
||||
│ ├── درخواستهای برداشت [Badge]
|
||||
│ └── گزارش برداشتها
|
||||
├── شبکه (NavGroup)
|
||||
│ ├── درخت شبکه
|
||||
│ ├── گزارش موجودیها
|
||||
│ └── آمار شبکه
|
||||
└── باشگاه مشتریان (NavGroup)
|
||||
├── اعضا و آمار
|
||||
└── فیچرهای باشگاه
|
||||
─────────────────────
|
||||
فروشگاه [AuthorizeView: Administrator]
|
||||
├── پکیجها
|
||||
├── فروشگاه عادی (NavGroup)
|
||||
│ ├── محصولات
|
||||
│ ├── دستهبندیها
|
||||
│ └── سفارشات و گزارش
|
||||
├── انبارداری (NavGroup)
|
||||
│ ├── موجودی انبار
|
||||
│ ├── محصولات کمموجود
|
||||
│ ├── مدیریت انبارها
|
||||
│ └── تاریخچه تغییرات
|
||||
└── فروشگاه تخفیفی (NavGroup)
|
||||
├── محصولات
|
||||
├── دستهبندیها
|
||||
└── سفارشات و گزارش
|
||||
─────────────────────
|
||||
مدیریت [AuthorizeView: Administrator]
|
||||
├── کاربران
|
||||
├── نقشها
|
||||
├── پرداخت دستی
|
||||
├── کیفپول
|
||||
└── قراردادها
|
||||
─────────────────────
|
||||
مدیریت محتوا
|
||||
├── بلاگ
|
||||
├── صفحات سایت
|
||||
└── پیامهای عمومی
|
||||
─────────────────────
|
||||
سیستم [AuthorizeView: Administrator]
|
||||
├── مدیریت سیستم
|
||||
└── نسخه اپلیکیشنها
|
||||
─────────────────────
|
||||
تنظیمات
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
# 🏪 یکسانسازی فروشگاه عادی و تخفیفی — BackOffice
|
||||
|
||||
> **تاریخ:** ۱۴۰۴/۱۱/۲۴ (February 13, 2026)
|
||||
> **وضعیت:** ✅ کامل
|
||||
> **Build:** 0 Error ✅
|
||||
|
||||
---
|
||||
|
||||
## ۱. هدف
|
||||
|
||||
فروشگاه عادی و فروشگاه تخفیفی در پنل مدیریت باید از نظر **ظاهری و UX** کاملاً یکسان باشند.
|
||||
قبل از این تغییرات، صفحات فروشگاه تخفیفی ظاهر و ساختار متفاوتی داشتند. هدف این فاز:
|
||||
|
||||
1. **NavMenu** — جداسازی دو فروشگاه در گروهبندیهای مجزا
|
||||
2. **دستهبندیها** — ظاهر یکسان با فروشگاه عادی (ستونها، درخت، اکشنها)
|
||||
3. **محصولات** — ظاهر یکسان (گالری، فیلترها، ستونهای گرید، اکسپورت)
|
||||
4. **سفارشات** — حذف گزارشهای کوچک اضافی، فقط لیست خالص + رفع باگ لیست خالی
|
||||
|
||||
---
|
||||
|
||||
## ۲. خلاصه تغییرات
|
||||
|
||||
### ۲.۱ بازسازی NavMenu
|
||||
|
||||
| قبل | بعد |
|
||||
|-----|-----|
|
||||
| یک بخش «فروشگاه» با زیرگروههای محصولات + دستهبندی + سفارش + ویرایش دستهجمعی | دو گروه مجزا: «فروشگاه عادی» و «فروشگاه تخفیفی» |
|
||||
| ویرایش دستهجمعی در منو | حذف شد از منو |
|
||||
| انبارداری داخل فروشگاه | انبارداری گروه مجزا |
|
||||
| پکیجها داخل فروشگاه | پکیجها آیتم مستقل |
|
||||
|
||||
**ساختار جدید:**
|
||||
```
|
||||
فروشگاه (بخش)
|
||||
├── پکیجها (مستقل)
|
||||
├── فروشگاه عادی (NavGroup)
|
||||
│ ├── محصولات → /ProductsPage/
|
||||
│ ├── دستهبندیها → /CategoryPage/
|
||||
│ └── سفارشات و گزارش → /OrdersPage/
|
||||
├── انبارداری (NavGroup مستقل)
|
||||
│ ├── موجودی انبار
|
||||
│ ├── محصولات کمموجود
|
||||
│ ├── مدیریت انبارها
|
||||
│ └── تاریخچه تغییرات
|
||||
└── فروشگاه تخفیفی (NavGroup)
|
||||
├── محصولات → /discount-products
|
||||
├── دستهبندیها → /discount-categories
|
||||
└── سفارشات و گزارش → /discount-orders
|
||||
```
|
||||
|
||||
**فایل:** `Shared/NavMenu.razor`
|
||||
|
||||
---
|
||||
|
||||
### ۲.۲ رفع لیست خالی سفارشات + حذف گزارشهای کوچک
|
||||
|
||||
**مشکل ۱ — لیست خالی:**
|
||||
- `PaymentDate.ToDateTime()` بدون null check باعث exception در WASM میشد
|
||||
- Exception در Blazor WASM silent است و grid خالی نشان میدهد
|
||||
- **رفع:** اضافه کردن `@if (context.Item.PaymentDate != null)` با fallback `"-"`
|
||||
|
||||
**مشکل ۲ — گزارشهای اضافی:**
|
||||
- کارتهای آماری (تعداد سفارشات + مجموع مبلغ) و نمودار Bar وضعیت ارسال بالای گرید بودند
|
||||
- این آمار اضافی بود چون تب جداگانه «گزارش فروش» وجود دارد
|
||||
- **رفع:** حذف کامل `MudGrid` (کارتها)، `MudChart` (نمودار)، فیلدهای `_stats`/`_statusChartLabels`/`_statusChartSeries`، متد `UpdateStats()`، کلاس `OrderStatsViewModel`
|
||||
- عنوان تولبار از «سفارشهای کاربر» به «لیست سفارشات» تغییر کرد
|
||||
|
||||
**فایلها:**
|
||||
- `Pages/UserOrder/UserOrderMainPage.razor`
|
||||
- `Pages/UserOrder/UserOrderMainPage.razor.cs`
|
||||
|
||||
---
|
||||
|
||||
### ۲.۳ بازنویسی صفحه محصولات تخفیفی
|
||||
|
||||
**قبل:** markup سفارشی بدون `BasePageComponent`، ستونهای ساده، بدون image preview
|
||||
**بعد:** کاملاً مطابق با `ProductsMainPage` فروشگاه عادی
|
||||
|
||||
| ویژگی | قبل | بعد |
|
||||
|-------|-----|-----|
|
||||
| Wrapper | markup دستی | `BasePageComponent` |
|
||||
| فیلترها | جستجو + دستهبندی | جستجو + دستهبندی + وضعیت + موجودی |
|
||||
| ستون عنوان | متن ساده | تصویر inline (MudAvatar) + متن truncate + tooltip |
|
||||
| ستون موجودی | عدد ساده | چیپ رنگی (قرمز/نارنجی/سبز) |
|
||||
| ستون وضعیت | متن | چیپ Error/Success |
|
||||
| خروجی Excel | ✅ (داشت) | ✅ (حفظ شد) |
|
||||
| گالری تصاویر | ✅ (داشت) | ✅ (حفظ شد) |
|
||||
| Server-side paging | ✅ | ✅ |
|
||||
|
||||
**فایلها:**
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor` — بازنویسی کامل
|
||||
- `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` — بازنویسی کامل (code-behind)
|
||||
|
||||
---
|
||||
|
||||
### ۲.۴ بازنویسی صفحه دستهبندیهای تخفیفی
|
||||
|
||||
**قبل:** markup دستی بدون `BasePageComponent`، ستونهای متفاوت
|
||||
**بعد:** کاملاً مطابق با `CategoryMainPage` فروشگاه عادی
|
||||
|
||||
| ویژگی | قبل | بعد |
|
||||
|-------|-----|-----|
|
||||
| Wrapper | markup دستی | `BasePageComponent` |
|
||||
| لایوت | درخت + گرید | درخت (3 col) + گرید (9 col) — بدون تغییر |
|
||||
| ستونها | شناسه، عنوان، توضیحات، وضعیت | شناسه، نام لاتین، عنوان، دستهبندی والد، تعداد محصولات، ترتیب، فعال؟ |
|
||||
| ستون والد | نداشت | resolve نام والد از لیست |
|
||||
| ستون محصولات | نداشت | چیپ Info |
|
||||
| ستون ترتیب | نداشت | PropertyColumn |
|
||||
| فیلتر | داخل page | داخل `BasePageComponent` |
|
||||
| حذف با فرزند | disabled | disabled (حفظ شد) |
|
||||
|
||||
**فایلها:**
|
||||
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor` — بازنویسی کامل
|
||||
- `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` — ایجاد (code-behind جدید)
|
||||
|
||||
---
|
||||
|
||||
## ۳. فایلهای تغییر یافته
|
||||
|
||||
| فایل | نوع تغییر | توضیح |
|
||||
|------|----------|-------|
|
||||
| `Shared/NavMenu.razor` | ✏️ ویرایش | بازسازی ساختار فروشگاه |
|
||||
| `Pages/UserOrder/UserOrderMainPage.razor` | ✏️ ویرایش | حذف آمار، رفع PaymentDate |
|
||||
| `Pages/UserOrder/UserOrderMainPage.razor.cs` | ✏️ ویرایش | حذف فیلدها/متدهای آمار |
|
||||
| `Pages/DiscountShop/DiscountProductsMainPage.razor` | 🔄 بازنویسی | BasePageComponent + ستونهای جدید |
|
||||
| `Pages/DiscountShop/DiscountProductsMainPage.razor.cs` | 🔄 بازنویسی | code-behind کامل |
|
||||
| `Pages/DiscountShop/DiscountCategoriesMainPage.razor` | 🔄 بازنویسی | BasePageComponent + ستونهای جدید |
|
||||
| `Pages/DiscountShop/DiscountCategoriesMainPage.razor.cs` | 🆕 ایجاد | code-behind جدید (از @code درونخطی) |
|
||||
|
||||
---
|
||||
|
||||
## ۴. الگوی پیادهسازی — BasePageComponent
|
||||
|
||||
تمام صفحات لیست در BackOffice از `BasePageComponent` استفاده میکنند:
|
||||
|
||||
```razor
|
||||
<BasePageComponent @ref="_basePage" OnClearFilterClick="OnFilterCleared" OnSubmitClick="OnFilterSubmit">
|
||||
<Filters>
|
||||
<!-- فیلدهای فیلتر در MudItem -->
|
||||
</Filters>
|
||||
<Content>
|
||||
<!-- MudDataGrid اصلی -->
|
||||
</Content>
|
||||
</BasePageComponent>
|
||||
```
|
||||
|
||||
**در code-behind:**
|
||||
```csharp
|
||||
private BasePageComponent _basePage = default!;
|
||||
|
||||
private async Task OnFilterSubmit()
|
||||
{
|
||||
_basePage.IsFiltered = true;
|
||||
// اعمال فیلتر
|
||||
}
|
||||
|
||||
private async Task OnFilterCleared()
|
||||
{
|
||||
_basePage.IsFiltered = false;
|
||||
// ریست فیلترها
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۵. الگوی Code-Behind
|
||||
|
||||
به دلیل محدودیت Razor source generator در پروژه، **همه فایلهایی که سرویس inject دارند باید code-behind داشته باشند**:
|
||||
|
||||
```
|
||||
Page.razor → فقط markup (بدون @code)
|
||||
Page.razor.cs → partial class با [Inject] و منطق
|
||||
```
|
||||
|
||||
**نکته مهم:** سرویسهای global از `_Imports.razor` نباید دوباره با `[Inject]` تعریف شوند:
|
||||
- ❌ `[Inject] public IDialogService DialogService { get; set; }` — از قبل global
|
||||
- ❌ `[Inject] public ISnackbar Snackbar { get; set; }` — از قبل global
|
||||
- ❌ `[Inject] public IJSRuntime jsRuntime { get; set; }` — از قبل global (حرف کوچک!)
|
||||
- ✅ `[Inject] public IDiscountProductService DiscountProductService { get; set; }` — باید inject شود
|
||||
|
||||
---
|
||||
|
||||
## ۶. مقایسه نهایی فروشگاه عادی و تخفیفی
|
||||
|
||||
| جنبه | فروشگاه عادی | فروشگاه تخفیفی | وضعیت |
|
||||
|------|-------------|---------------|-------|
|
||||
| ارتباط با بکند | gRPC/Protobuf | HTTP REST (IDiscountXxxService) | تفاوت ذاتی |
|
||||
| BasePageComponent | ✅ | ✅ | 🟢 یکسان |
|
||||
| فیلترهای محصول | جستجو+دستهبندی+وضعیت | جستجو+دستهبندی+وضعیت+موجودی | 🟢 یکسان+ |
|
||||
| ستونهای محصول | تصویر+عنوان، قیمت، موجودی (چیپ)، وضعیت (چیپ) | تصویر+عنوان، قیمت، تخفیف، موجودی (چیپ)، وضعیت (چیپ) | 🟢 یکسان+ |
|
||||
| گالری تصاویر | ✅ GalleryDialog | ✅ ProductImageGallery | 🟢 هر دو دارند |
|
||||
| خروجی Excel | ✅ | ✅ | 🟢 یکسان |
|
||||
| درخت دستهبندی | ✅ | ✅ | 🟢 یکسان |
|
||||
| ستونهای دستهبندی | شناسه+نام+عنوان+والد+محصولات+ترتیب+فعال | شناسه+نام+عنوان+والد+محصولات+ترتیب+فعال | 🟢 یکسان |
|
||||
| سفارشات Hub | MudTabs (سفارشات + گزارش فروش) | MudTabs (سفارشات + گزارش فروش) | 🟢 یکسان |
|
||||
@@ -0,0 +1,118 @@
|
||||
# فاز ۱ — موجودیتهای بکند CMS ✅ تکمیل شد
|
||||
|
||||
> **تاریخ تکمیل:** ۱۴۰۴/۰۴/۲۱ (2026-02-11)
|
||||
> **وضعیت:** ✅ تکمیل — بیلد موفق + Migration ساخته شد
|
||||
|
||||
---
|
||||
|
||||
## خلاصه کارهای انجام شده
|
||||
|
||||
### 1.1 موجودیتهای دامین (8 فایل)
|
||||
|
||||
| فایل | مسیر | توضیح |
|
||||
|------|------|-------|
|
||||
| `BlogPostStatus.cs` | `Domain/Enums/` | enum: Draft=0, Published=1, Scheduled=2, Archived=3 |
|
||||
| `BlogPost.cs` | `Domain/Entities/Blog/` | پست بلاگ — عنوان، اسلاگ، خلاصه، محتوای HTML، تصویر، وضعیت، شمارنده بازدید |
|
||||
| `BlogCategory.cs` | `Domain/Entities/Blog/` | دستهبندی بلاگ — عنوان، اسلاگ، آیکون، ترتیب |
|
||||
| `BlogPostCategory.cs` | `Domain/Entities/Blog/` | جدول واسط پست-دستهبندی (Many-to-Many) |
|
||||
| `BlogPostTag.cs` | `Domain/Entities/Blog/` | جدول واسط پست-تگ (از Tag موجود استفاده شد) |
|
||||
| `BlogPostImage.cs` | `Domain/Entities/Blog/` | گالری تصاویر پست — مسیر، عنوان جایگزین، ترتیب |
|
||||
| `SitePage.cs` | `Domain/Entities/Content/` | صفحات سایت (درباره ما، تماس با ما) — با کلید یکتا |
|
||||
| `SitePageSection.cs` | `Domain/Entities/Content/` | بخشهای هر صفحه — محتوای HTML، آیکون، تصویر، داده اضافی JSON |
|
||||
|
||||
### 1.2 تنظیمات Entity Framework (7 فایل)
|
||||
|
||||
| فایل | مسیر | ایندکسها |
|
||||
|------|------|----------|
|
||||
| `BlogPostConfiguration.cs` | `Configurations/Blog/` | Slug (unique), Status, PublishedAt, IsFeatured, AuthorUserId, Status+PublishedAt |
|
||||
| `BlogCategoryConfiguration.cs` | `Configurations/Blog/` | Slug (unique), IsActive |
|
||||
| `BlogPostCategoryConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId + BlogCategoryId |
|
||||
| `BlogPostTagConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId + TagId |
|
||||
| `BlogPostImageConfiguration.cs` | `Configurations/Blog/` | FK: BlogPostId |
|
||||
| `SitePageConfiguration.cs` | `Configurations/Content/` | PageKey (unique) |
|
||||
| `SitePageSectionConfiguration.cs` | `Configurations/Content/` | SitePageId + SectionKey (compound) |
|
||||
|
||||
### 1.3 DbContext (2 فایل ویرایش شده)
|
||||
|
||||
- `IApplicationDbContext.cs` — افزودن 7 DbSet
|
||||
- `ApplicationDbContext.cs` — افزودن 7 DbSet property
|
||||
|
||||
### 1.4 تعاریف Proto (4 فایل + csproj)
|
||||
|
||||
| فایل | RPCها | csharp_namespace |
|
||||
|------|-------|-----------------|
|
||||
| `blogpost.proto` | 11 RPC (CRUD + Publish/Archive/ViewCount + BySlug + Published/Featured) | `BlogPost` |
|
||||
| `blogcategory.proto` | 6 RPC (CRUD + GetAll + GetActive) | `BlogCategory` |
|
||||
| `blogpostimage.proto` | 4 RPC (Add/Delete/Get/Reorder) | `BlogPostImage` |
|
||||
| `sitepage.proto` | 8 RPC (Get/GetByKey/Update/GetAll + Section CRUD + Reorder) | `SitePage` |
|
||||
|
||||
### 1.5 لایه CQRS Application (≈50 فایل)
|
||||
|
||||
#### BlogPost Commands (6 گروه، 14 فایل)
|
||||
- `CreateBlogPost` — Command + Handler + Validator (با اعتبارسنجی اسلاگ regex)
|
||||
- `UpdateBlogPost` — Command + Handler + Validator (الگوی delete-recreate برای دستهبندی/تگ)
|
||||
- `DeleteBlogPost` — Command + Handler (soft-delete)
|
||||
- `PublishBlogPost` — Command + Result + Handler (تنظیم Status و PublishedAt)
|
||||
- `ArchiveBlogPost` — Command + Result + Handler
|
||||
- `IncrementViewCount` — Command + Handler
|
||||
|
||||
#### BlogPost Queries (5 گروه، 10 فایل)
|
||||
- `GetBlogPost` — Query + DTO + Handler (با Include chain)
|
||||
- `GetBlogPostBySlug` — Query + Handler (بازاستفاده از BlogPostDto)
|
||||
- `GetAllBlogPosts` — Query + ResponseDto + Handler (فیلتر + مرتبسازی + صفحهبندی)
|
||||
- `GetPublishedBlogPosts` — Query + Handler (مشتریمحور، فقط Published)
|
||||
- `GetFeaturedBlogPosts` — Query + Handler (برای لندینگ پیج)
|
||||
|
||||
#### BlogCategory CQRS (11 فایل)
|
||||
- Commands: Create + Update + Delete (با Validator)
|
||||
- Queries: GetBlogCategory + GetAllBlogCategories + GetActiveBlogCategories
|
||||
|
||||
#### BlogPostImage CQRS (8 فایل)
|
||||
- Commands: Add + Delete + Reorder (با ImageSortItem)
|
||||
- Queries: GetBlogPostImages
|
||||
|
||||
#### SitePage CQRS (14 فایل)
|
||||
- Commands: UpdateSitePage + CreateSection + UpdateSection + DeleteSection + ReorderSections
|
||||
- Queries: GetSitePage + GetSitePageByKey + GetAllSitePages
|
||||
|
||||
### 1.6 سرویسهای gRPC WebApi (4 فایل)
|
||||
|
||||
| سرویس | الگو | توضیح |
|
||||
|-------|------|-------|
|
||||
| `BlogPostService.cs` | ترکیبی (دستی + dispatcher) | مپینگ دستی برای لیستها و RepeatedField |
|
||||
| `BlogCategoryService.cs` | ترکیبی | dispatcher برای CRUD ساده، دستی برای لیستها |
|
||||
| `BlogPostImageService.cs` | ترکیبی | dispatcher + مپینگ دستی Reorder |
|
||||
| `SitePageService.cs` | ترکیبی | dispatcher + مپینگ دستی Sections |
|
||||
|
||||
### 1.7 Mapping Profiles (2 فایل)
|
||||
|
||||
- `BlogPostProfile.cs` — مپینگ PublishBlogPostResult و ArchiveBlogPostResult
|
||||
- `BlogCategoryProfile.cs` — مپینگ long → CreateBlogCategoryResponse
|
||||
|
||||
### 1.8 EF Migration
|
||||
|
||||
- `20260210232742_AddBlogAndContentEntities.cs` — ایجاد 7 جدول جدید
|
||||
- **Build:** ✅ موفق (0 Error, warnings مربوط به کد قدیمی)
|
||||
|
||||
---
|
||||
|
||||
## آمار فاز ۱
|
||||
|
||||
| متریک | تعداد |
|
||||
|-------|-------|
|
||||
| فایلهای جدید | ~65 |
|
||||
| فایلهای ویرایش شده | ~4 |
|
||||
| موجودیتهای دامین | 7 (+1 enum) |
|
||||
| تنظیمات EF | 7 |
|
||||
| تعاریف Proto | 4 |
|
||||
| RPCهای gRPC | 29 |
|
||||
| Commands CQRS | 16 |
|
||||
| Queries CQRS | 12 |
|
||||
| سرویسهای WebApi | 4 |
|
||||
| جداول دیتابیس جدید | 7 |
|
||||
|
||||
---
|
||||
|
||||
## فاز بعدی
|
||||
|
||||
**فاز ۲ — پنل مدیریت بلاگ (BackOffice)** — صفحات Blazor WASM برای مدیریت پستها، دستهبندیها، تصاویر و صفحات سایت.
|
||||
@@ -0,0 +1,104 @@
|
||||
# فاز ۳: صفحات محتوای پویا (Dynamic Content Pages) ✅
|
||||
|
||||
## 📋 خلاصه
|
||||
تبدیل صفحات **درباره ما** و **تماس با ما** از محتوای هاردکد (hardcoded) به محتوای پویا که از CMS (سرویس SitePage) بارگذاری میشود، با پشتیبانی fallback به محتوای پیشفرض.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ معماری
|
||||
|
||||
```
|
||||
FrontOffice (Blazor Server)
|
||||
├── About.razor/cs ─── SitePageService ──► gRPC ──► CMS SitePageContract
|
||||
└── Contact.razor/cs ─── SitePageService ──► gRPC ──► CMS SitePageContract
|
||||
```
|
||||
|
||||
### الگوی Fallback:
|
||||
```
|
||||
OnInitializedAsync() → SitePageService.GetByKeyAsync("about")
|
||||
├── ✅ Data received → Render dynamic content
|
||||
└── ❌ Error/null → Render hardcoded fallback content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 فایلهای ایجاد/تغییر یافته
|
||||
|
||||
### فایلهای جدید:
|
||||
| فایل | توضیحات |
|
||||
|------|---------|
|
||||
| `FrontOffice/src/FrontOffice.Main/Utilities/SitePageService.cs` | سرویس SitePage + DTOs (SitePageDto, SitePageSectionDto) |
|
||||
| `dbbkup/SeedSitePages.sql` | اسکریپت Seed Data برای درج محتوای اولیه صفحات |
|
||||
|
||||
### فایلهای تغییر یافته:
|
||||
| فایل | تغییرات |
|
||||
|------|---------|
|
||||
| `FrontOffice/src/FrontOffice.Main/ConfigureServices.cs` | اضافه شدن SitePageService + SitePageContractClient به DI |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/About.razor` | تبدیل به محتوای پویا با fallback |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/About.razor.cs` | اضافه شدن OnInitializedAsync + بارگذاری sections |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/Contact.razor` | تبدیل hero/info/social به پویا، فرم بدون تغییر |
|
||||
| `FrontOffice/src/FrontOffice.Main/Pages/Contact.razor.cs` | اضافه شدن OnInitializedAsync + ExtraData DTOs |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 جزئیات فنی
|
||||
|
||||
### SitePageService
|
||||
```csharp
|
||||
public class SitePageService
|
||||
{
|
||||
Task<SitePageDto?> GetByKeyAsync(string pageKey) // "about" | "contact"
|
||||
}
|
||||
```
|
||||
|
||||
### SitePageDto Helpers
|
||||
```csharp
|
||||
GetSection(string sectionKey) // e.g. "vision", "mission", "contact-info"
|
||||
GetSections(string prefix) // e.g. "value-" → value-1, value-2, ...
|
||||
```
|
||||
|
||||
### SitePageSectionDto.GetExtraData<T>()
|
||||
JSON deserializer برای فیلد ExtraData — استفاده شده در Contact:
|
||||
- `ContactInfoData`: address, phone, email, hours
|
||||
- `SocialMediaData`: telegram, instagram, linkedin, whatsapp
|
||||
|
||||
---
|
||||
|
||||
## 📄 SectionKey Mapping
|
||||
|
||||
### صفحه درباره ما (PageKey: `about`)
|
||||
| SectionKey | کاربرد | فیلدهای اصلی |
|
||||
|------------|--------|--------------|
|
||||
| `vision` | کارت چشمانداز | Title, HtmlContent, IconName |
|
||||
| `mission` | کارت مأموریت | Title, HtmlContent, IconName |
|
||||
| `value-1` ... `value-6` | کارتهای ارزشها | Title, HtmlContent, IconName |
|
||||
| `team-1` ... `team-3` | کارتهای اعضای تیم | Title(نام), Subtitle(سمت), HtmlContent(توضیحات), ImagePath(آواتار) |
|
||||
|
||||
### صفحه تماس با ما (PageKey: `contact`)
|
||||
| SectionKey | کاربرد | فیلدهای اصلی |
|
||||
|------------|--------|--------------|
|
||||
| `contact-info` | اطلاعات تماس | ExtraData → `{address, phone, email, hours}` |
|
||||
| `social-media` | شبکههای اجتماعی | ExtraData → `{telegram, instagram, linkedin, whatsapp}` |
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Seed Data
|
||||
فایل `dbbkup/SeedSitePages.sql` شامل:
|
||||
- **2 صفحه**: about, contact
|
||||
- **13 سکشن**: 2 (vision/mission) + 6 (values) + 3 (team) + 2 (contact-info/social-media)
|
||||
- تمام محتوای فعلی hardcoded به عنوان داده اولیه درج شده
|
||||
|
||||
---
|
||||
|
||||
## ✅ بیلد
|
||||
```
|
||||
FrontOffice.Main: 0 Error(s), Build succeeded
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📌 نکات مهم
|
||||
1. **فرم تماس** (Contact Form) بدون تغییر باقی ماند — منطق سمت کلاینت است نه محتوای CMS
|
||||
2. **Fallback**: اگر CMS در دسترس نباشد، محتوای hardcoded نمایش داده میشود
|
||||
3. **Loading State**: صفحه About دارای حالت loading با spinner
|
||||
4. آیکونها در CMS به صورت string ذخیره میشوند (مثل `@Icons.Material.Filled.Security`)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user