10 KiB
Refactoring DiscountProduct Form - Session 2026-01-05
هدف
یکسانسازی فرم ایجاد/ویرایش محصولات تخفیفی (DiscountProduct) با فرم محصولات عادی (Product) برای بهبود تجربه کاربری و افزودن قابلیتهای حرفهای.
تغییرات انجام شده
1. Frontend - ProductFormDialog.razor
قبل:
- استفاده از MudGrid
- TextField ساده برای مسیر تصویر (بدون قابلیت آپلود)
- TextField چند خطی برای توضیحات
- MudSelect ساده برای دستهبندیها
- فاقد پیشنمایش تصویر
بعد:
- تغییر Layout: از MudGrid به MudStack
- آپلود تصویر:
- MudFileUpload برای انتخاب تصویر اصلی و بندانگشتی
- پیشنمایش تصویر قبل از آپلود
- نمایش تصویر موجود در حالت ویرایش
- ویرایشگر HTML:
- استفاده از MudHtmlEditor برای FullInformation
- قابلیت فرمتدهی متن (bold, italic, لیست و...)
- دستهبندی:
- استفاده از MudSelect با MultiSelection
- لود خودکار DiscountCategory ها (نه Category های عادی)
- نمایش تعداد دستهبندیهای انتخاب شده
- فیلدهای اختصاصی حفظ شده:
- MaxDiscountPercent
- IsActive
- SortOrder
فایل: /home/masoud/Apps/project/FourSat/BackOffice/src/BackOffice/Pages/DiscountShop/Components/ProductFormDialog.razor
2. Proto File - discountproduct.proto
تغییرات:
message CreateDiscountProductRequest {
// ... فیلدهای قبلی
ImageFileModel image_file = 12; // جدید
ImageFileModel thumbnail_file = 13; // جدید
}
message UpdateDiscountProductRequest {
// ... فیلدهای قبلی
ImageFileModel image_file = 12; // جدید
ImageFileModel thumbnail_file = 13; // جدید
}
// تعریف مدل جدید
message ImageFileModel {
bytes file = 1;
string mime = 2;
string file_name = 3;
}
فایل: /home/masoud/Apps/project/FourSat/BackOffice.BFF/src/Protobufs/BackOffice.BFF.DiscountProduct.Protobuf/Protos/discountproduct.proto
3. DTOs - Backend
CreateDiscountProductDto و UpdateDiscountProductDto:
// پراپرتیهای جدید اضافه شده:
public byte[]? ImageFile { get; set; }
public string? ImageMime { get; set; }
public string? ImageFileName { get; set; }
public byte[]? ThumbnailFile { get; set; }
public string? ThumbnailMime { get; set; }
public string? ThumbnailFileName { get; set; }
فایلها:
/home/masoud/Apps/project/FourSat/BackOffice/src/BackOffice/Services/DiscountProduct/IDiscountProductService.cs
4. Service Layer - DiscountProductService
تغییرات CreateAsync و UpdateAsync:
// افزودن لاجیک آپلود فایل
if (dto.ImageFile != null && !string.IsNullOrEmpty(dto.ImageMime)) {
request.ImageFile = new ImageFileModel {
File = ByteString.CopyFrom(dto.ImageFile),
Mime = dto.ImageMime,
FileName = dto.ImageFileName
};
}
فایل: /home/masoud/Apps/project/FourSat/BackOffice/src/BackOffice/Services/DiscountProduct/DiscountProductService.cs
5. Page Integration - DiscountProductsMainPage
تغییرات در OpenCreateDialog و OpenEditDialog:
var dto = new CreateDiscountProductDto {
// ... سایر فیلدها
InitialCount = 0, // تغییر: همیشه 0 - موجودی از طریق Inventory مدیریت میشود
ImageFile = formData.ImageFile,
ImageMime = formData.ImageMime,
ImageFileName = formData.ImageFileName,
ThumbnailFile = formData.ThumbnailFile,
ThumbnailMime = formData.ThumbnailMime,
ThumbnailFileName = formData.ThumbnailFileName
};
فایل: /home/masoud/Apps/project/FourSat/BackOffice/src/BackOffice/Pages/DiscountShop/DiscountProductsMainPage.razor
6. BFF - Validators
اصلاح CreateDiscountProductCommandValidator:
// قبل: الزامی بودن ShortInformation و FullInformation
RuleFor(x => x.ShortInformation)
.NotEmpty().WithMessage("اطلاعات کوتاه محصول الزامی است")
// بعد: اختیاری شدن با چک طول
RuleFor(x => x.ShortInformation)
.MaximumLength(500)
.When(x => !string.IsNullOrEmpty(x.ShortInformation));
RuleFor(x => x.FullInformation)
.MaximumLength(10000)
.When(x => !string.IsNullOrEmpty(x.FullInformation));
فایلها:
/home/masoud/Apps/project/FourSat/BackOffice.BFF/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs/home/masoud/Apps/project/FourSat/BackOffice.BFF/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs
7. BFF - Command Handlers
اصلاح CreateDiscountProductCommandHandler:
// افزودن null coalescing برای جلوگیری از ArgumentNullException
var createRequest = new CreateDiscountProductRequest {
ShortInfomation = request.ShortInformation ?? string.Empty,
FullInformation = request.FullInformation ?? string.Empty,
// ...
};
// افزودن لاجیک آپلود فایل به File Storage
if (request.ImageFile != null && request.ImageFile.File.Length > 0) {
var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(...);
if (imageFileInfo != null)
createRequest.ImagePath = imageFileInfo.File;
}
فایلها:
/home/masoud/Apps/project/FourSat/BackOffice.BFF/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs/home/masoud/Apps/project/FourSat/BackOffice.BFF/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs
8. CMS - Validators
اصلاح CreateDiscountProductCommandValidator:
// حذف الزامی بودن ImagePath و ThumbnailPath
// قبل:
RuleFor(v => v.ImagePath).NotEmpty()
RuleFor(v => v.ThumbnailPath).NotEmpty()
// بعد: حذف شده - فایلها از طریق BFF آپلود میشوند
// ShortInformation و FullInformation اختیاری شدن
RuleFor(v => v.ShortInfomation)
.MaximumLength(500)
.When(v => !string.IsNullOrEmpty(v.ShortInfomation));
فایلها:
/home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs/home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs
9. Bug Fixes
9.1. Foreign Key Constraint Error
مشکل: استفاده از CategoryMultiSelectCombo که Category های عادی را لود میکرد، در حالی که DiscountProduct نیاز به DiscountCategory دارد.
راهحل:
- برگشت به MudSelect ساده
- لود DiscountCategory از IDiscountCategoryService
- Flatten کردن tree برای نمایش در dropdown
9.2. GetDiscountProducts NullReferenceException
مشکل: response.MetaData یا response.Models میتوانست null باشد.
راهحل:
Products = response.Models?.Select(p => new DiscountProductDto {
Created = p.Created?.ToDateTime() ?? DateTime.MinValue
}).ToList() ?? new List<DiscountProductDto>()
فایل: /home/masoud/Apps/project/FourSat/BackOffice.BFF/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs
9.3. GetDiscountCategories Handler Bugs
مشکلها:
response.Categoriesبدون null check- شرطهای
string.IsNullOrWhiteSpaceمعکوس بودند ParentCategoryIdچک نادرستnode.Childrenبدون null check
راهحل:
Categories = response.Categories?.Select(MapCategory).ToList()
?? new List<DiscountCategoryTreeDto>()
// اصلاح شرطها
Description = !string.IsNullOrWhiteSpace(node.Description) ? node.Description : string.Empty,
ParentCategoryId = node.ParentCategoryId != 0 ? node.ParentCategoryId : null,
ChildCategories = node.Children?.Select(MapCategory).ToList()
?? new List<DiscountCategoryTreeDto>()
فایل: /home/masoud/Apps/project/FourSat/BackOffice.BFF/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs
Build و Deploy
Build Status
- ✅ BackOffice: 0 errors, 248 warnings (MudBlazor style warnings only)
- ✅ BackOffice.BFF: 0 errors
- ✅ CMS: 0 errors
دستور Build Proto
bash /home/masoud/Apps/project/FourSat/BackOffice/build-deps.sh
فایلهای نیاز به Deploy
-
BackOffice.BFF (stage)
- تغییرات: Validators, Command Handlers, Query Handlers
-
CMS (stage)
- تغییرات: Validators
-
BackOffice (local)
- در حال اجرا با
dotnet watch - تغییرات بلافاصله اعمال میشود
- در حال اجرا با
نتیجه نهایی
قابلیتهای جدید:
✅ آپلود تصویر با drag & drop
✅ پیشنمایش تصویر قبل از ذخیره
✅ ویرایشگر HTML برای محتوای غنی
✅ انتخاب چندگانه دستهبندی با UI بهتر
✅ مدیریت صحیح DiscountCategory ها
✅ حذف الزامی بودن فیلدهای اختیاری
✅ رفع تمام خطاهای NullReference
تفاوت با قبل:
| قبل | بعد |
|---|---|
| TextField برای مسیر تصویر | آپلود فایل واقعی |
| TextField ساده | HTML Editor |
| CategoryMultiSelectCombo (اشتباه) | MudSelect با DiscountCategory |
| الزامی بودن توضیحات | اختیاری |
| خطای Foreign Key | حل شده |
نکات مهم
- موجودی (InitialCount): همیشه 0 قرار میگیرد - موجودی باید از صفحه Inventory مدیریت شود
- دستهبندی: حتماً DiscountCategory استفاده شود نه Category معمولی
- Validators: فیلدهای اختیاری با
.When()چک شوند - Null Safety: همیشه
??و?.استفاده شود در LINQ queries
مراجع
- Proto Guide:
/home/masoud/Apps/project/FourSat/PROTO-PACKAGING-GUIDE.md - Build Script:
/home/masoud/Apps/project/FourSat/BackOffice/build-deps.sh