18 KiB
Plan: فعالسازی مرحلهای Handler های کامنتشده
تاریخ: 8 دسامبر 2025
وضعیت: در انتظار اجرا
تعداد کل فایلهای کامنتشده: 37 فایل در 3 دسته
خلاصه اجرایی
در BackOffice.BFF.Application.csproj سه دسته handler کامنت شدهاند:
- DiscountOrderCQ/ (18 فایل) - عدم تطابق Proto با CMS
- DiscountShoppingCartCQ/ (16 فایل) - عدم تطابق Proto
- CommissionCQ/Commands/ProcessWithdrawal/ (3 فایل) - مشکل تبدیل StringValue
مرحله 1: ProcessWithdrawal (30 دقیقه - ساده ✅)
ویژگی بیزینسی
مدیریت درخواستهای برداشت کمیسیون توسط ادمین (تایید/رد)
فایلهای دخیل
CommissionCQ/Commands/ProcessWithdrawal/
├── ProcessWithdrawalCommand.cs
├── ProcessWithdrawalCommandHandler.cs
└── ProcessWithdrawalCommandValidator.cs
مشکل فعلی
// Handler - خط 25
var grpcRequest = new ProcessWithdrawalRequest
{
PayoutId = request.WithdrawalId,
IsApproved = true, // ⚠️ هاردکد شده!
Reason = request.AdminNote != null
? new StringValue { Value = request.AdminNote }
: null
};
تغییرات مورد نیاز
1. آپدیت Command
// ProcessWithdrawalCommand.cs
public record ProcessWithdrawalCommand : IRequest<ProcessWithdrawalResponseDto>
{
public long WithdrawalId { get; init; }
public bool IsApproved { get; init; } // ✅ جدید
public string? Reason { get; init; } // نامگذاری مجدد از AdminNote
}
2. آپدیت Handler
// ProcessWithdrawalCommandHandler.cs
var grpcRequest = new ProcessWithdrawalRequest
{
PayoutId = request.WithdrawalId,
IsApproved = request.IsApproved, // ✅ از command گرفته شود
Reason = !string.IsNullOrEmpty(request.Reason)
? new StringValue { Value = request.Reason }
: null
};
var response = await _context.Commissions.ProcessWithdrawalAsync(
grpcRequest,
cancellationToken);
return new ProcessWithdrawalResponseDto
{
Success = true,
Message = "Withdrawal processed successfully"
};
3. Uncomment WebApi Service
// CommissionService.cs - خطوط 90-96
public override async Task<Empty> ProcessWithdrawal(
ProcessWithdrawalRequest request,
ServerCallContext context)
{
await _dispatchRequestToCQRS.Handle<
ProcessWithdrawalRequest,
ProcessWithdrawalCommand,
ProcessWithdrawalResponseDto>(request, context);
return new Empty();
}
4. حذف از Exclude List
<!-- BackOffice.BFF.Application.csproj -->
<!-- حذف این خط: -->
<Compile Remove="CommissionCQ/Commands/ProcessWithdrawal/**/*.cs" />
چکلیست
- آپدیت ProcessWithdrawalCommand با فیلد IsApproved
- آپدیت ProcessWithdrawalCommandHandler - حذف hardcode
- Uncomment متد در CommissionService.cs
- حذف از exclude در Application.csproj
- Build و تست
مرحله 2: DiscountShoppingCartCQ (2-3 ساعت - متوسط 📊)
ویژگی بیزینسی
مدیریت سبد خرید فروشگاه تخفیف برای مشتریان
فایلهای دخیل
DiscountShoppingCartCQ/
├── Commands/ (12 فایل)
│ ├── AddToCart/ ✅ تطابق دارد
│ ├── RemoveFromCart/ ✅ تطابق دارد
│ ├── UpdateCartItemCount/ ✅ تطابق دارد
│ └── ClearCart/ ✅ تطابق دارد
└── Queries/ (4 فایل)
├── GetUserCart/ ⚠️ نیاز به Mapster
└── سایر queries...
مشکل فعلی: GetUserCartResponse
Handler انتظار دارد:
public class GetUserCartResponseDto
{
public long UserId { get; set; } // ❌ در CMS proto نیست
public decimal TotalPrice { get; set; }
public decimal TotalDiscountedPrice { get; set; } // ❌ نام متفاوت
public decimal TotalSavings { get; set; } // ❌ نام متفاوت
public List<CartItemDto> Items { get; set; }
}
public class CartItemDto
{
public long Id { get; set; } // ❌ در CMS proto نیست
public decimal DiscountedPrice { get; set; } // ❌ نام: final_price
public DateTime AddedAt { get; set; } // ❌ نام: created (Timestamp)
}
CMS Proto دارد:
message GetUserCartResponse {
repeated CartItemDto items = 1;
int64 total_price = 2;
int64 total_discount_amount = 3; // ← TotalSavings
int64 final_price = 4; // ← TotalDiscountedPrice
}
message CartItemDto {
int64 product_id = 1;
string product_title = 2;
string product_image_path = 3;
int64 unit_price = 4;
int32 max_discount_percent = 5;
int32 count = 6;
int64 total_price = 7;
int64 discount_amount = 8;
int64 final_price = 9; // ← DiscountedPrice
int32 product_remaining_count = 10;
google.protobuf.Timestamp created = 11; // ← AddedAt
}
تغییرات مورد نیاز
1. ساخت Mapster Profile
// BackOffice.BFF.WebApi/Common/Mappings/DiscountShoppingCartProfile.cs
using Mapster;
using BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart;
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class DiscountShoppingCartProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// Map GetUserCart Response
config.NewConfig<GetUserCartResponse, GetUserCartResponseDto>()
.MapWith(src => new GetUserCartResponseDto
{
// UserId باید از request گرفته شود (در handler)
TotalPrice = src.TotalPrice,
TotalDiscountedPrice = src.FinalPrice,
TotalSavings = src.TotalDiscountAmount,
Items = src.Items.Select(item => new CartItemDto
{
// Id ندارد - میتواند 0 باشد یا از product_id استفاده شود
Id = item.ProductId,
ProductId = item.ProductId,
ProductTitle = item.ProductTitle,
ProductImagePath = item.ProductImagePath,
UnitPrice = item.UnitPrice,
MaxDiscountPercent = item.MaxDiscountPercent,
Count = item.Count,
TotalPrice = item.TotalPrice,
DiscountedPrice = item.FinalPrice,
AddedAt = item.Created.ToDateTime()
}).ToList()
});
}
}
2. آپدیت Handler
// GetUserCartQueryHandler.cs
public async Task<GetUserCartResponseDto> Handle(
GetUserCartQuery request,
CancellationToken cancellationToken)
{
var grpcRequest = new GetUserCartRequest
{
UserId = request.UserId
};
var response = await _context.DiscountShoppingCarts.GetUserCartAsync(
grpcRequest,
cancellationToken: cancellationToken);
var result = TypeAdapter.Adapt(
response,
response.GetType(),
typeof(GetUserCartResponseDto)) as GetUserCartResponseDto;
// UserId را از request میگیریم چون در proto نیست
result.UserId = request.UserId;
return result;
}
3. حذف از Exclude List
<!-- BackOffice.BFF.Application.csproj -->
<!-- حذف این خط: -->
<Compile Remove="DiscountShoppingCartCQ/**/*.cs" />
چکلیست
- ساخت DiscountShoppingCartProfile.cs
- آپدیت GetUserCartQueryHandler با Mapster
- تست mapping با داده واقعی
- حذف از exclude در Application.csproj
- Build و تست endpoint
سوال کلیدی ⚠️
آیا BackOffice.BFF نیاز به مدیریت سبد خرید دارد؟
- اگر خیر: Document کنیم "Not applicable - FrontOffice only"
- اگر بله: Mapster profile بسازیم
مرحله 3: DiscountOrderCQ (6-8 ساعت - پیچیده 🔴)
ویژگی بیزینسی
مدیریت سفارشهای فروشگاه تخفیف توسط ادمین
فایلهای دخیل
DiscountOrderCQ/
├── Commands/ (10 فایل)
│ ├── PlaceOrder/ ⚠️ Proto مختلف
│ ├── CompletePayment/ ⚠️ Proto مختلف
│ ├── UpdateOrderStatus/ ⚠️ Proto مختلف
│ └── سایر commands...
└── Queries/ (8 فایل)
├── GetOrderById/ ⚠️ Proto مختلف
├── GetAllUserOrders/ ⚠️ Proto مختلف
└── سایر queries...
مشکل اصلی: دو Proto متفاوت
تفاوتهای کلیدی
1. PlaceOrderRequest
Handler انتظار دارد:
UserId, AddressId, DiscountBalanceAmount, GatewayAmount
CMS Proto دارد:
user_id, user_address_id, discount_balance_to_use, notes (StringValue)
2. PlaceOrderResponse
Handler انتظار دارد:
OrderId, TrackingCode, RequiresGatewayPayment, GatewayPayableAmount
CMS Proto دارد:
success, message, order_id, gateway_amount, payment_url (StringValue)
3. GetOrderByIdResponse - تفاوت اصلی
Handler انتظار دارد:
public class OrderDto
{
public long Id { get; set; }
public int Status { get; set; } // ← int
public string StatusTitle { get; set; } // ← محاسبه شده
public bool IsPaid { get; set; } // ← bool
public DateTime? PaidAt { get; set; }
public string UserFullName { get; set; }
public string UserMobile { get; set; }
public string DeliveryAddress { get; set; } // ← string
// ...
}
CMS Proto دارد:
message OrderDto {
int64 id = 1;
DeliveryStatus delivery_status = 2; // ← enum
bool payment_completed = 3; // ← bool
google.protobuf.Timestamp created = 4;
AddressDto address = 5; // ← object
// StatusTitle ندارد - باید derive شود
// PaidAt ندارد - باید از created استفاده شود
}
enum DeliveryStatus {
DELIVERY_PENDING = 0;
DELIVERY_PROCESSING = 1;
DELIVERY_SHIPPED = 2;
DELIVERY_DELIVERED = 3;
DELIVERY_CANCELLED = 4;
}
تصمیم کلیدی ⚠️
گزینه A: استفاده از CMS Proto (پیشنهادی)
- ✅ CMS قبلاً پیادهسازی شده
- ✅ ریسک کمتر
- ❌ نیاز به rewrite کردن 18 handler
- ❌ 6-8 ساعت کار
گزینه B: استفاده از BFF Proto
- ✅ Handler ها آماده هستند
- ❌ نیاز به آپدیت CMS microservice
- ❌ ریسک بالا
- ❌ تستهای بیشتر
تغییرات مورد نیاز (گزینه A)
1. ساخت Mapster Profile جامع
// DiscountOrderProfile.cs
public class DiscountOrderProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// PlaceOrder Command → CMS Request
config.NewConfig<PlaceOrderCommand, CMSPlaceOrderRequest>()
.MapWith(src => new CMSPlaceOrderRequest
{
UserId = src.UserId,
UserAddressId = src.AddressId,
DiscountBalanceToUse = src.DiscountBalanceAmount,
Notes = !string.IsNullOrEmpty(src.Notes)
? new StringValue { Value = src.Notes }
: null
});
// CMS Response → DTO
config.NewConfig<CMSPlaceOrderResponse, PlaceOrderResponseDto>()
.MapWith(src => new PlaceOrderResponseDto
{
OrderId = src.OrderId,
TrackingCode = src.OrderId.ToString(),
RequiresGatewayPayment = src.GatewayAmount > 0,
GatewayPayableAmount = src.GatewayAmount,
PaymentUrl = src.PaymentUrl?.Value
});
// GetOrderById - پیچیدهترین mapping
config.NewConfig<CMSOrderDto, OrderDto>()
.MapWith(src => new OrderDto
{
Id = src.Id,
Status = (int)src.DeliveryStatus,
StatusTitle = GetStatusTitle(src.DeliveryStatus),
IsPaid = src.PaymentCompleted,
PaidAt = src.PaymentCompleted
? src.Created.ToDateTime()
: null,
UserFullName = src.UserFullName,
UserMobile = src.UserMobile,
DeliveryAddress = FormatAddress(src.Address),
// ... بقیه فیلدها
});
// GetAllUserOrders
config.NewConfig<CMSGetAllUserOrdersResponse, GetAllUserOrdersResponseDto>()
.MapWith(src => new GetAllUserOrdersResponseDto
{
MetaData = new MetaData
{
PageNumber = src.MetaData.CurrentPage,
PageSize = src.MetaData.PageSize,
TotalPages = src.MetaData.TotalPage,
TotalCount = src.MetaData.TotalCount
},
Orders = src.Orders.Select(o => new OrderSummaryDto
{
Id = o.Id,
Status = (int)o.DeliveryStatus,
StatusTitle = GetStatusTitle(o.DeliveryStatus),
// ...
}).ToList()
});
}
private static string GetStatusTitle(DeliveryStatus status) => status switch
{
DeliveryStatus.DeliveryPending => "در انتظار پردازش",
DeliveryStatus.DeliveryProcessing => "در حال پردازش",
DeliveryStatus.DeliveryShipped => "ارسال شده",
DeliveryStatus.DeliveryDelivered => "تحویل داده شده",
DeliveryStatus.DeliveryCancelled => "لغو شده",
_ => "نامشخص"
};
private static string FormatAddress(AddressDto address)
{
if (address == null) return string.Empty;
return $"{address.Province}, {address.City}, {address.Street}, " +
$"پلاک {address.PlateNumber}, واحد {address.Unit}";
}
}
2. بازنویسی Handlers (نمونه)
// PlaceOrderCommandHandler.cs
public async Task<PlaceOrderResponseDto> Handle(
PlaceOrderCommand request,
CancellationToken cancellationToken)
{
var grpcRequest = TypeAdapter.Adapt(
request,
request.GetType(),
typeof(CMSPlaceOrderRequest)) as CMSPlaceOrderRequest;
var response = await _context.DiscountOrders.PlaceOrderAsync(
grpcRequest,
cancellationToken: cancellationToken);
return TypeAdapter.Adapt(
response,
response.GetType(),
typeof(PlaceOrderResponseDto)) as PlaceOrderResponseDto;
}
3. حذف از Exclude List
<!-- BackOffice.BFF.Application.csproj -->
<!-- حذف این خط: -->
<Compile Remove="DiscountOrderCQ/**/*.cs" />
چکلیست
- تصمیمگیری: CMS proto یا BFF proto؟
- مقایسه دقیق line-by-line دو proto
- ساخت DiscountOrderProfile.cs جامع
- بازنویسی PlaceOrderCommandHandler
- بازنویسی CompletePaymentCommandHandler
- بازنویسی UpdateOrderStatusCommandHandler
- بازنویسی GetOrderByIdQueryHandler
- بازنویسی GetAllUserOrdersQueryHandler
- تست کامل flow: Place → Pay → Update → Get
- حذف از exclude در Application.csproj
- Build و تست همه endpoints
جدول خلاصه
| Handler Group | تعداد فایل | Proto Source | BFF Proto | Mapster Profile | پیچیدگی | زمان تخمینی | اولویت بیزینسی |
|---|---|---|---|---|---|---|---|
| ProcessWithdrawal | 3 | BFF.Commission | ✅ | ❌ نیاز نیست | ساده | 30 دقیقه | متوسط ⚠️ |
| DiscountShoppingCart | 16 | CMS | ✅ (متفاوت) | ❌ باید ساخت | متوسط | 2-3 ساعت | متوسط ⚠️ |
| DiscountOrder | 18 | CMS | ✅ (کاملاً متفاوت) | ❌ باید ساخت | پیچیده | 6-8 ساعت | بالا 🔴 |
| جمع کل | 37 | - | - | - | - | 8.5-11.5 ساعت | - |
سوالات کلیدی برای تصمیمگیری
1. DiscountShoppingCartCQ
سوال: آیا BackOffice نیاز به مدیریت سبد خرید دارد؟
- اگر خیر: این feature فقط برای FrontOffice است → Document و نگهداری exclude
- اگر بله: ادمین باید بتواند سبد خرید کاربران را ببیند → Mapster profile بسازیم
2. DiscountOrderCQ
سوال: کدام proto را استفاده کنیم؟
- گزینه A (پیشنهادی): CMS proto
- Handler ها را rewrite میکنیم
- 6-8 ساعت کار
- ریسک کم
- گزینه B: BFF proto
- CMS microservice را آپدیت میکنیم
- زمان نامشخص
- ریسک بالا
3. اولویت اجرا
کدام مرحله اول اجرا شود؟
- پیشنهاد: ProcessWithdrawal (سریعترین ROI)
- سپس: بر اساس نیاز بیزینسی
مراحل بعدی
فوری
- ✅ تصمیم: DiscountShoppingCart نیاز هست؟
- ✅ تصمیم: DiscountOrder از کدام proto؟
- ✅ شروع با ProcessWithdrawal (30 دقیقه)
کوتاهمدت
- اگر نیاز: DiscountShoppingCart (2-3 ساعت)
- Planning دقیق DiscountOrder (1 ساعت)
میانمدت
- پیادهسازی DiscountOrder (6-8 ساعت)
- تست integration کامل
- Document کردن تغییرات
تاریخ آخرین آپدیت: 8 دسامبر 2025
وضعیت: منتظر تصمیمگیری و شروع اجرا
مسئول: تیم توسعه BackOffice.BFF