# 📦 Product Bundle Feature (پکیج محصولات)

> **وضعیت:** ⏸️ Postponed - مستند شده برای پیاده‌سازی آینده
> 
> **تاریخ:** ۱۲ دی ۱۴۰۴ (1 January 2026)

---

## 📋 خلاصه نیازمندی

امکان ایجاد **پکیج محصولات** که:
- یک محصول با نوع "پکیج" ایجاد می‌شود (همه فیلدها مثل محصول عادی)
- این پکیج شامل **چند محصول** است
- هنگام **خرید پکیج**، موجودی **تمام محصولات داخل** کم می‌شود
- هنگام **مرجوعی**، موجودی تمام محصولات برمی‌گردد

---

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

### 1. Domain Layer

#### 1.1 Enum جدید: `ProductTypeCategory`
```csharp
// CMSMicroservice.Domain/Enums/ProductTypeCategory.cs
public enum ProductTypeCategory
{
    Simple = 1,   // محصول ساده
    Bundle = 2    // پکیج (بسته محصولات)
}
```

#### 1.2 فیلد جدید در `Product` Entity
```csharp
// Product.cs - اضافه کردن فیلد
public ProductTypeCategory TypeCategory { get; set; } = ProductTypeCategory.Simple;
```

#### 1.3 Entity جدید: `ProductBundleItem` (جدول واسط)
```csharp
// CMSMicroservice.Domain/Entities/ProductBundleItem.cs
public class ProductBundleItem : BaseAuditableEntity
{
    /// <summary>
    /// شناسه محصول پکیج (والد)
    /// </summary>
    public long BundleProductId { get; set; }
    public virtual Product BundleProduct { get; set; } = null!;
    
    /// <summary>
    /// شناسه محصول داخل پکیج (فرزند)
    /// </summary>
    public long ChildProductId { get; set; }
    public virtual Product ChildProduct { get; set; } = null!;
    
    /// <summary>
    /// تعداد این محصول در پکیج
    /// </summary>
    public int Quantity { get; set; } = 1;
}
```

### 2. Infrastructure Layer

#### 2.1 DbContext Configuration
```csharp
// ApplicationDbContext.cs
public DbSet<ProductBundleItem> ProductBundleItems => Set<ProductBundleItem>();

// Configuration
modelBuilder.Entity<ProductBundleItem>(entity =>
{
    entity.ToTable("ProductBundleItems", "CMS");
    
    entity.HasOne(x => x.BundleProduct)
        .WithMany(p => p.BundleItems)
        .HasForeignKey(x => x.BundleProductId)
        .OnDelete(DeleteBehavior.Cascade);
    
    entity.HasOne(x => x.ChildProduct)
        .WithMany()
        .HasForeignKey(x => x.ChildProductId)
        .OnDelete(DeleteBehavior.Restrict);
    
    // یک محصول فقط یکبار در یک پکیج
    entity.HasIndex(x => new { x.BundleProductId, x.ChildProductId }).IsUnique();
});
```

#### 2.2 آپدیت `InventoryService.ConfirmSaleAsync()`
```csharp
public async Task<bool> ConfirmSaleAsync(
    long productId,
    ProductType productType,
    int quantity,
    long? orderId = null,
    CancellationToken ct = default)
{
    // چک کردن آیا محصول پکیج است
    var product = await _dbContext.Products
        .Include(p => p.BundleItems)
            .ThenInclude(bi => bi.ChildProduct)
        .FirstOrDefaultAsync(p => p.Id == productId, ct);
    
    if (product?.TypeCategory == ProductTypeCategory.Bundle)
    {
        // کم کردن موجودی تمام محصولات داخل پکیج
        foreach (var bundleItem in product.BundleItems)
        {
            await ConfirmSaleForSingleProduct(
                bundleItem.ChildProductId,
                productType,
                quantity * bundleItem.Quantity,  // ضرب در تعداد خرید شده
                orderId,
                ct);
        }
        return true;
    }
    
    // محصول ساده - روال عادی
    return await ConfirmSaleForSingleProduct(productId, productType, quantity, orderId, ct);
}
```

### 3. Application Layer

#### 3.1 آپدیت `CreateNewProductsCommand`
```csharp
public record CreateNewProductsCommand : IRequest<long>
{
    // ... existing fields ...
    
    public ProductTypeCategory TypeCategory { get; init; } = ProductTypeCategory.Simple;
    
    /// <summary>
    /// لیست محصولات داخل پکیج (فقط وقتی TypeCategory == Bundle)
    /// </summary>
    public List<BundleItemDto>? BundleItems { get; init; }
}

public record BundleItemDto
{
    public long ProductId { get; init; }
    public int Quantity { get; init; } = 1;
}
```

#### 3.2 Repository جدید: `IProductBundleItemRepository`
```csharp
public interface IProductBundleItemRepository : IRepository<ProductBundleItem>
{
    Task<List<ProductBundleItem>> GetByBundleProductIdAsync(long bundleProductId, CancellationToken ct = default);
    Task SetBundleItemsAsync(long bundleProductId, List<(long ProductId, int Quantity)> items, CancellationToken ct = default);
}
```

### 4. Proto/gRPC Layer

#### 4.1 آپدیت `products.proto`
```protobuf
enum ProductTypeCategory {
    PRODUCT_TYPE_SIMPLE = 0;
    PRODUCT_TYPE_BUNDLE = 1;
}

message BundleItemMessage {
    int64 product_id = 1;
    int32 quantity = 2;
}

message CreateNewProductsRequest {
    // ... existing fields ...
    ProductTypeCategory type_category = 15;
    repeated BundleItemMessage bundle_items = 16;
}

message ProductDto {
    // ... existing fields ...
    ProductTypeCategory type_category = 20;
    repeated BundleItemMessage bundle_items = 21;
}
```

---

## 📊 دیاگرام رابطه‌ها

```
┌─────────────────┐
│    Products     │
├─────────────────┤
│ Id              │◄──────────────────┐
│ Title           │                   │
│ TypeCategory    │ ← Simple/Bundle   │
│ ...             │                   │
└────────┬────────┘                   │
         │                            │
         │ 1:N (Bundle → Items)       │
         ▼                            │
┌─────────────────────┐               │
│  ProductBundleItems │               │
├─────────────────────┤               │
│ Id                  │               │
│ BundleProductId (FK)│───────────────┘
│ ChildProductId (FK) │───────────────┐
│ Quantity            │               │
└─────────────────────┘               │
                                      │
         ┌────────────────────────────┘
         ▼
┌─────────────────┐
│    Products     │
│  (Child Item)   │
└─────────────────┘
```

---

## 🔄 Flow خرید پکیج

```
1. کاربر پکیج را به سبد اضافه می‌کند
   └── CartItem { ProductId: 100, Count: 2 }  // پکیج شامل 3 محصول

2. سفارش ثبت می‌شود
   └── PlaceOrderCommandHandler.ReserveStock()
       ├── Check: Product.TypeCategory == Bundle
       ├── Get: BundleItems = [
       │     { ChildProductId: 10, Quantity: 1 },
       │     { ChildProductId: 20, Quantity: 2 },
       │     { ChildProductId: 30, Quantity: 1 }
       │   ]
       └── Reserve:
           ├── Product 10: Reserve 2×1 = 2 عدد
           ├── Product 20: Reserve 2×2 = 4 عدد
           └── Product 30: Reserve 2×1 = 2 عدد

3. پرداخت موفق
   └── ConfirmSaleAsync()
       ├── Product 10: -2 از موجودی
       ├── Product 20: -4 از موجودی
       └── Product 30: -2 از موجودی

4. مرجوعی (در صورت نیاز)
   └── ProcessReturnAsync()
       ├── Product 10: +2 به موجودی
       ├── Product 20: +4 به موجودی
       └── Product 30: +2 به موجودی
```

---

## ⚠️ محدودیت‌ها و قوانین

1. **محصول پکیج خودش موجودی ندارد** - فقط موجودی محصولات داخلش مهم است
2. **پکیج داخل پکیج ممنوع** - فقط محصولات ساده (`Simple`) می‌توانند داخل پکیج باشند
3. **حذف محصول از پکیج** - اگر محصولی در پکیج استفاده شده، نمی‌تواند حذف شود
4. **موجودی قابل فروش پکیج** = `MIN(موجودی هر محصول داخل / تعداد آن در پکیج)`

---

## 📁 فایل‌های جدید/تغییریافته

### فایل‌های جدید:
- `CMSMicroservice.Domain/Enums/ProductTypeCategory.cs`
- `CMSMicroservice.Domain/Entities/ProductBundleItem.cs`
- `CMSMicroservice.Application/Features/ProductBundleItems/*`
- `CMSMicroservice.Infrastructure/Repositories/ProductBundleItemRepository.cs`

### فایل‌های تغییریافته:
- `CMSMicroservice.Domain/Entities/Product.cs` - اضافه کردن `TypeCategory` و `BundleItems`
- `CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs` - DbSet و Configuration
- `CMSMicroservice.Infrastructure/Services/InventoryService.cs` - منطق پکیج
- `CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/*`
- `CMSMicroservice.Protobuf/Protos/products.proto`
- Order Handlers (Reserve, Confirm, Release)

---

## ⏱️ تخمین زمان

| تسک | زمان تخمینی |
|-----|-------------|
| Domain entities & enums | 30 دقیقه |
| EF Migration | 15 دقیقه |
| Repository | 30 دقیقه |
| InventoryService update | 1 ساعت |
| CQRS handlers | 1 ساعت |
| Proto & gRPC | 45 دقیقه |
| تست و دیباگ | 1 ساعت |
| **جمع** | **~5 ساعت** |

---

## 📝 یادداشت‌ها

- این فیچر با پکیج عضویت (`Package` entity موجود) متفاوت است
- نیاز به تست دقیق منطق انبارداری دارد
- UI نیاز به multi-select برای انتخاب محصولات داخل پکیج دارد

---

*این داکیومنت برای پیاده‌سازی آینده نگهداری می‌شود.*
