Files
docs/SHOP-UNIFICATION.md
T

522 lines
28 KiB
Markdown

# یکسان‌سازی فروشگاه عادی و فروشگاه تخفیفی (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 ← قیمت (ریال)
```