# Plan: فعال‌سازی مرحله‌ای Handler های کامنت‌شده

**تاریخ:** 8 دسامبر 2025  
**وضعیت:** ✅ کامل شده  
**آخرین به‌روزرسانی:** 1 ژانویه 2026

---

## خلاصه اجرایی

در `BackOffice.BFF.Application.csproj` سه دسته handler کامنت شده بودند که همه فعال شدند:

1. **DiscountOrderCQ/** (18 فایل) - ✅ فعال شد
2. **DiscountShoppingCartCQ/** (16 فایل) - ✅ فعال شد و با CMS proto هماهنگ شد
3. **CommissionCQ/Commands/ProcessWithdrawal/** (3 فایل) - ✅ قبلاً فعال شده بود

**همچنین در BackOffice UI (1 ژانویه 2026):**
- فعال‌سازی همه سرویس‌ها در `ConfigureService.cs`
- ثبت gRPC Clients برای DiscountProduct, DiscountCategory, DiscountOrder, Tag, ProductTag, PublicMessage
- ثبت Application Services

---

## تغییرات انجام‌شده (1 ژانویه 2026)

### BackOffice UI - فعال‌سازی فرانت‌اند

#### ConfigureService.cs
- فعال‌سازی using statements برای همه proto clients
- ثبت gRPC Clients در DI container
- ثبت Application Services (IDiscountProductService, IDiscountCategoryService, etc.)

#### صفحات فعال شده:
| Route | صفحه |
|-------|------|
| `/discount-products` | مدیریت محصولات تخفیفی |
| `/discount-categories` | مدیریت دسته‌بندی‌ها |
| `/discount-orders` | مدیریت سفارشات |
| `/tags` | مدیریت تگ‌ها |
| `/public-messages` | پیام‌های عمومی |

---

## تغییرات انجام‌شده (13 ژانویه 2025)

### DiscountShoppingCartCQ - اصلاحات Proto

#### RemoveFromCart
- `CartItemId` → `ProductId` (مطابق با proto)

#### UpdateCartItemCount  
- `CartItemId` → `ProductId` (مطابق با proto)

#### GetUserCart Response
- حذف `UserId` از response
- `TotalDiscountedPrice` → `TotalDiscountAmount`
- `TotalSavings` → حذف شد
- اضافه شدن `FinalPrice`

#### CartItemDto
- حذف `Id`
- `DiscountedPrice` → `DiscountAmount`
- `AddedAt` → `Created`
- اضافه شدن `FinalPrice`, `ProductRemainingCount`

---

## مرحله 1: ProcessWithdrawal ✅ (قبلاً انجام شده)

### ویژگی بیزینسی
مدیریت درخواست‌های برداشت کمیسیون توسط ادمین (تایید/رد)

### فایل‌های دخیل
```
CommissionCQ/Commands/ProcessWithdrawal/
├── ProcessWithdrawalCommand.cs
├── ProcessWithdrawalCommandHandler.cs  
└── ProcessWithdrawalCommandValidator.cs
```

### مشکل فعلی
```csharp
// Handler - خط 25
var grpcRequest = new ProcessWithdrawalRequest
{
    PayoutId = request.WithdrawalId,
    IsApproved = true,  // ⚠️ هاردکد شده!
    Reason = request.AdminNote != null 
        ? new StringValue { Value = request.AdminNote } 
        : null
};
```

### تغییرات مورد نیاز

#### 1. آپدیت Command
```csharp
// 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
```csharp
// 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
```csharp
// 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
```xml
<!-- 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 انتظار دارد:
```csharp
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 دارد:
```protobuf
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
```csharp
// 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
```csharp
// 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
```xml
<!-- 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 انتظار دارد:
```csharp
UserId, AddressId, DiscountBalanceAmount, GatewayAmount
```

CMS Proto دارد:
```protobuf
user_id, user_address_id, discount_balance_to_use, notes (StringValue)
```

**2. PlaceOrderResponse**

Handler انتظار دارد:
```csharp
OrderId, TrackingCode, RequiresGatewayPayment, GatewayPayableAmount
```

CMS Proto دارد:
```protobuf
success, message, order_id, gateway_amount, payment_url (StringValue)
```

**3. GetOrderByIdResponse - تفاوت اصلی**

Handler انتظار دارد:
```csharp
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 دارد:
```protobuf
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 جامع
```csharp
// 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 (نمونه)
```csharp
// 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
```xml
<!-- 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)
- سپس: بر اساس نیاز بیزینسی

---

## مراحل بعدی

### فوری
1. ✅ تصمیم: DiscountShoppingCart نیاز هست؟
2. ✅ تصمیم: DiscountOrder از کدام proto؟
3. ✅ شروع با ProcessWithdrawal (30 دقیقه)

### کوتاه‌مدت
4. اگر نیاز: DiscountShoppingCart (2-3 ساعت)
5. Planning دقیق DiscountOrder (1 ساعت)

### میان‌مدت
6. پیاده‌سازی DiscountOrder (6-8 ساعت)
7. تست integration کامل
8. Document کردن تغییرات

---

**تاریخ آخرین آپدیت:** 8 دسامبر 2025  
**وضعیت:** منتظر تصمیم‌گیری و شروع اجرا  
**مسئول:** تیم توسعه BackOffice.BFF
