Files
docs/SHOP-UNIFICATION.md
T

28 KiB
Raw Blame History

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