feat: enhance category selection in ProductFormDialog with multi-selection text and improved error handling
Build and Deploy / build (push) Failing after 16s
Build and Deploy / build (push) Failing after 16s
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
# 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
|
||||
|
||||
#### تغییرات:
|
||||
```protobuf
|
||||
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:
|
||||
```csharp
|
||||
// پراپرتیهای جدید اضافه شده:
|
||||
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:
|
||||
```csharp
|
||||
// افزودن لاجیک آپلود فایل
|
||||
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:
|
||||
```csharp
|
||||
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:
|
||||
```csharp
|
||||
// قبل: الزامی بودن 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:
|
||||
```csharp
|
||||
// افزودن 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:
|
||||
```csharp
|
||||
// حذف الزامی بودن 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 باشد.
|
||||
|
||||
**راهحل**:
|
||||
```csharp
|
||||
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
|
||||
**مشکلها**:
|
||||
1. `response.Categories` بدون null check
|
||||
2. شرطهای `string.IsNullOrWhiteSpace` معکوس بودند
|
||||
3. `ParentCategoryId` چک نادرست
|
||||
4. `node.Children` بدون null check
|
||||
|
||||
**راهحل**:
|
||||
```csharp
|
||||
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
|
||||
bash /home/masoud/Apps/project/FourSat/BackOffice/build-deps.sh
|
||||
```
|
||||
|
||||
### فایلهای نیاز به Deploy
|
||||
1. **BackOffice.BFF** (stage)
|
||||
- تغییرات: Validators, Command Handlers, Query Handlers
|
||||
|
||||
2. **CMS** (stage)
|
||||
- تغییرات: Validators
|
||||
|
||||
3. **BackOffice** (local)
|
||||
- در حال اجرا با `dotnet watch`
|
||||
- تغییرات بلافاصله اعمال میشود
|
||||
|
||||
## نتیجه نهایی
|
||||
|
||||
### قابلیتهای جدید:
|
||||
✅ آپلود تصویر با drag & drop
|
||||
✅ پیشنمایش تصویر قبل از ذخیره
|
||||
✅ ویرایشگر HTML برای محتوای غنی
|
||||
✅ انتخاب چندگانه دستهبندی با UI بهتر
|
||||
✅ مدیریت صحیح DiscountCategory ها
|
||||
✅ حذف الزامی بودن فیلدهای اختیاری
|
||||
✅ رفع تمام خطاهای NullReference
|
||||
|
||||
### تفاوت با قبل:
|
||||
| قبل | بعد |
|
||||
|-----|-----|
|
||||
| TextField برای مسیر تصویر | آپلود فایل واقعی |
|
||||
| TextField ساده | HTML Editor |
|
||||
| CategoryMultiSelectCombo (اشتباه) | MudSelect با DiscountCategory |
|
||||
| الزامی بودن توضیحات | اختیاری |
|
||||
| خطای Foreign Key | حل شده |
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **موجودی (InitialCount)**: همیشه 0 قرار میگیرد - موجودی باید از صفحه Inventory مدیریت شود
|
||||
2. **دستهبندی**: حتماً DiscountCategory استفاده شود نه Category معمولی
|
||||
3. **Validators**: فیلدهای اختیاری با `.When()` چک شوند
|
||||
4. **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`
|
||||
@@ -116,6 +116,7 @@
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
MultiSelection="true"
|
||||
MultiSelectionTextFunc="@(new Func<List<string>, string>(GetMultiSelectionText))"
|
||||
Disabled="_loading"
|
||||
T="long">
|
||||
@foreach (var category in _categories)
|
||||
@@ -193,9 +194,11 @@
|
||||
{
|
||||
var tree = await CategoryService.GetCategoriesAsync(isActive: true);
|
||||
_categories = FlattenCategories(tree);
|
||||
Console.WriteLine($"Loaded {_categories.Count} categories");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading categories: {ex}");
|
||||
Snackbar.Add($"خطا در بارگذاری دستهبندیها: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
@@ -226,6 +229,14 @@
|
||||
return category.Title;
|
||||
}
|
||||
|
||||
private string GetMultiSelectionText(List<string> selectedValues)
|
||||
{
|
||||
if (selectedValues == null || !selectedValues.Any())
|
||||
return "دستهبندی انتخاب نشده";
|
||||
|
||||
return $"{selectedValues.Count} دستهبندی انتخاب شده";
|
||||
}
|
||||
|
||||
private async Task OnMainImageSelected(IBrowserFile? file)
|
||||
{
|
||||
_mainImageFile = file;
|
||||
|
||||
Reference in New Issue
Block a user