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