Files
BackOffice/docs/DISCOUNT-PRODUCT-FORM-REFACTORING.md
T
2026-01-05 21:51:39 +03:30

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

مشکل‌ها:

  1. response.Categories بدون null check
  2. شرط‌های string.IsNullOrWhiteSpace معکوس بودند
  3. ParentCategoryId چک نادرست
  4. 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

  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