# 📦 سیستم انبارداری یکپارچه (Unified Inventory Management)

> **تاریخ ایجاد:** ۱۲ دی ۱۴۰۴ (1 January 2026)  
> **تاریخ تکمیل:** ۱۳ دی ۱۴۰۴ (3 January 2026)  
> **وضعیت:** ✅ **COMPLETED** (CMS + BFF همگام)  
> **اولویت:** 🟡 Medium

---

## ✅ خلاصه اجرایی (Implementation Summary)

### فایل‌های ایجاد شده در CMS:

#### Domain Layer
- `Domain/Entities/InventoryItem.cs` - موجودیت اصلی انبار
- `Domain/Entities/StockMovement.cs` - تاریخچه حرکات انبار  
- `Domain/Entities/Warehouse.cs` - انبارها
- `Domain/Enums/StockMovementType.cs` - انواع حرکات انبار
- `Domain/Enums/ProductType.cs` - نوع محصول (معمولی/تخفیفی)

#### Infrastructure Layer
- `Infrastructure/Persistence/Configurations/InventoryItemConfiguration.cs`
- `Infrastructure/Persistence/Configurations/StockMovementConfiguration.cs`
- `Infrastructure/Persistence/Configurations/WarehouseConfiguration.cs`
- `Infrastructure/Persistence/Repositories/InventoryItemRepository.cs`
- `Infrastructure/Persistence/Repositories/StockMovementRepository.cs`
- `Infrastructure/Services/InventoryService.cs` - سرویس اصلی (663 خط)
- `Infrastructure/Persistence/Migrations/20251231234634_AddInventorySystem.cs`

#### Application Layer
- `Application/Common/Interfaces/IInventoryService.cs` - (217 خط)
- `Application/Common/Interfaces/Repositories/IInventoryItemRepository.cs`
- `Application/Common/Interfaces/Repositories/IStockMovementRepository.cs`
- `Application/Features/InventoryItems/Commands/InventoryItemCommands.cs`
- `Application/Features/InventoryItems/Queries/InventoryItemQueries.cs`
- `Application/Features/InventoryItems/Handlers/InventoryItemCommandHandlers.cs`
- `Application/Features/InventoryItems/Handlers/InventoryItemQueryHandlers.cs`
- `Application/Features/Warehouses/Commands/WarehouseCommands.cs`
- `Application/Features/Warehouses/Queries/WarehouseQueries.cs`
- `Application/Features/Warehouses/Handlers/WarehouseCommandHandlers.cs`
- `Application/Features/Warehouses/Handlers/WarehouseQueryHandlers.cs`
- `Application/Features/StockMovements/Commands/StockMovementCommands.cs`
- `Application/Features/StockMovements/Queries/StockMovementQueries.cs`
- `Application/Features/StockMovements/Handlers/StockMovementCommandHandlers.cs`
- `Application/Features/StockMovements/Handlers/StockMovementQueryHandlers.cs`

#### gRPC Layer
- `CMSMicroservice.Protobuf/Protos/inventory.proto` - (530 خط)
- `WebApi/Services/InventoryService.cs` - gRPC service (235 خط)

### قابلیت‌های اصلی:
- ✅ مدیریت موجودی محصولات معمولی و تخفیفی
- ✅ رزرو موجودی برای سفارشات pending
- ✅ تایید فروش و کسر موجودی
- ✅ برگشت کالا و افزایش موجودی
- ✅ ورود کالا به انبار (Restock)
- ✅ تعدیل موجودی (Adjustment)
- ✅ ثبت ضایعات و مفقودی
- ✅ لیست محصولات کم‌موجود (Low Stock)
- ✅ تاریخچه کامل حرکات انبار
- ✅ همگام‌سازی خودکار با `Product.RemainingCount`
- ✅ پشتیبانی از چند انبار (Multi-Warehouse ready)

### یکپارچه‌سازی با Handlers:
- ✅ `CreateProductCommandHandler` - ایجاد خودکار InventoryItem
- ✅ `CreateDiscountProductCommandHandler` - ایجاد خودکار InventoryItem
- ✅ `PlaceOrderCommandHandler` - رزرو موجودی
- ✅ `CompleteOrderPaymentCommandHandler` - تایید فروش
- ✅ `CancelOrderCommandHandler` - آزادسازی رزرو

### باقیمانده (نیاز به تکمیل):
- ⬜ Migration داده‌های موجود به سیستم جدید (Phase 5)
- ⬜ تست‌های واحد و یکپارچه‌سازی (Phase 7)
- ⬜ UI در BackOffice (صفحات راه ندارند - فقط منو در NavMenu اضافه شده)

---

## 🔄 همگام‌سازی BFF با CMS (۳ ژانویه ۲۰۲۶)

### فایل‌های آپدیت شده در BackOffice.BFF:

#### Proto Layer
- `BackOffice.BFF.Inventory.Protobuf/Protos/inventory.proto` - همگام با CMS

#### Application Layer - Commands
- `InventoryCQ/Commands/AddStock/` - حذف Success, Message
- `InventoryCQ/Commands/AdjustStock/` - Note→Reason, +ReferenceNumber
- `InventoryCQ/Commands/RecordLoss/` - Note→Reason, +ReferenceNumber
- `InventoryCQ/Commands/UpdateInventorySettings/` - InventoryItemId→Id

#### Application Layer - Queries
- `InventoryCQ/Queries/GetAllInventoryItems/` - PageIndex→Page, SearchTerm→Search
- `InventoryCQ/Queries/GetStockMovements/` - PageIndex→Page, +ProductTitle
- `InventoryCQ/Queries/GetLowStockItems/` - حذف Count
- `InventoryCQ/Queries/GetAllWarehouses/` - ActiveOnly→IsActive

#### WebApi Layer
- `Common/Mappings/InventoryProfile.cs` - بازنویسی کامل

### تغییرات کلیدی Proto:
| آیتم | CMS/BFF قدیم | CMS/BFF جدید |
|------|-------------|--------------|
| ProductType | `REGULAR`, `DISCOUNT` | `REGULAR_PRODUCT`, `DISCOUNT_PRODUCT` |
| StockMovementType | Sequential (0-9) | Grouped (10, 20, 30, 40, 50) |
| Pagination | `page_index` | `page` |
| Search | `search_term` | `search` |
| Product field | `product_name` | `product_title` |

### وضعیت Build:
```
CMS: Build succeeded. 0 Warning(s) 0 Error(s)
BFF: Build succeeded. 0 Warning(s) 0 Error(s)
```

---

### BFF Layer:
- ✅ `Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs`
- ✅ `Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs`

### BackOffice (Frontend):
- ✅ منوی "انبارداری" در `NavMenu.razor` اضافه شده
- ⬜ صفحه `/inventory/low-stock` - نیاز به ایجاد
- ⬜ صفحه `/products/bulk-edit` - موجود ولی نیاز به بررسی

---

## 🎯 هدف

ایجاد یک سیستم انبارداری مرکزی که موجودی هر دو فروشگاه (معمولی و تخفیفی) را از یک نقطه مدیریت کند، با قابلیت:

- ردیابی حرکات موجودی (ورود، خروج، تعدیل)
- رزرو موقت برای سفارشات pending
- هشدار کمبود موجودی
- گزارش‌گیری جامع

---

## 📊 وضعیت فعلی

### ساختار موجود

| جنبه | فروشگاه معمولی (Product) | فروشگاه تخفیفی (DiscountProduct) |
|------|--------------------------|----------------------------------|
| فیلد موجودی | `RemainingCount` | `RemainingCount` |
| موجودی اولیه | `remaining_count` در Create | `initial_count` در Create |
| آپدیت دسته‌جمعی | ✅ بله (SET/ADD/SUBTRACT) | ❌ خیر |
| کوئری کم‌موجودی | ✅ بله | ❌ خیر |
| چک موجودی در سبد | ✅ بله | ✅ بله |
| کسر موجودی در سفارش | ✅ بله | ✅ بله |

### مشکلات فعلی

1. **عدم ردیابی حرکات:** هیچ لاگی از تغییرات موجودی نگهداری نمی‌شود
2. **عدم رزرو موقت:** موجودی در زمان پرداخت کسر می‌شود، نه در زمان افزودن به سبد
3. **عدم یکپارچگی:** هر فروشگاه مستقل مدیریت می‌شود
4. **عدم پشتیبانی چند انبار:** برای آینده محدودیت دارد

---

## 🏗️ معماری پیشنهادی

### Entity های جدید

#### 1. InventoryItem (موجودی کالا)

```csharp
public class InventoryItem : BaseAuditableEntity
{
    // شناسه محصول (یکی از دو فیلد زیر پر است)
    public long? ProductId { get; set; }
    public Product? Product { get; set; }
    
    public long? DiscountProductId { get; set; }
    public DiscountProduct? DiscountProduct { get; set; }
    
    // نوع محصول
    public ProductType ProductType { get; set; }
    
    // موجودی
    public int Quantity { get; set; }              // موجودی فعلی
    public int ReservedQuantity { get; set; }      // رزرو شده برای سفارشات pending
    public int AvailableQuantity => Quantity - ReservedQuantity;  // قابل فروش
    
    // تنظیمات
    public int LowStockThreshold { get; set; } = 10;  // آستانه هشدار کم‌موجودی
    public int ReorderPoint { get; set; } = 5;        // نقطه سفارش مجدد
    public int MaxStockLevel { get; set; } = 1000;    // حداکثر موجودی
    
    // آمار
    public DateTime? LastRestockedAt { get; set; }    // آخرین ورود کالا
    public DateTime? LastSoldAt { get; set; }         // آخرین فروش
    
    // انبار (برای آینده)
    public long WarehouseId { get; set; } = 1;        // پیش‌فرض: انبار اصلی
    public Warehouse? Warehouse { get; set; }
    
    // Navigation
    public ICollection<StockMovement> StockMovements { get; set; } = new List<StockMovement>();
}
```

#### 2. StockMovement (حرکات انبار)

```csharp
public class StockMovement : BaseAuditableEntity
{
    public long InventoryItemId { get; set; }
    public InventoryItem InventoryItem { get; set; } = null!;
    
    // نوع حرکت
    public StockMovementType MovementType { get; set; }
    
    // مقادیر
    public int Quantity { get; set; }                  // مقدار تغییر (مثبت یا منفی)
    public int QuantityBefore { get; set; }            // موجودی قبل
    public int QuantityAfter { get; set; }             // موجودی بعد
    
    // مرجع
    public long? OrderId { get; set; }                 // شناسه سفارش (برای فروش/برگشت)
    public long? DiscountOrderId { get; set; }         // شناسه سفارش تخفیفی
    public string? ReferenceNumber { get; set; }       // شماره مرجع (فاکتور ورود و...)
    
    // توضیحات
    public string? Note { get; set; }                  // یادداشت
    
    // کاربر
    public long? PerformedByUserId { get; set; }       // کاربری که عملیات را انجام داد
}
```

#### 3. Warehouse (انبار) - اختیاری برای آینده

```csharp
public class Warehouse : BaseAuditableEntity
{
    public string Name { get; set; } = null!;          // نام انبار
    public string Code { get; set; } = null!;          // کد انبار
    public string? Address { get; set; }               // آدرس
    public bool IsDefault { get; set; }                // انبار پیش‌فرض
    public bool IsActive { get; set; } = true;
    
    // Navigation
    public ICollection<InventoryItem> InventoryItems { get; set; } = new List<InventoryItem>();
}
```

### Enum های جدید

```csharp
public enum ProductType
{
    RegularProduct = 1,     // محصول فروشگاه معمولی
    DiscountProduct = 2     // محصول فروشگاه تخفیفی
}

public enum StockMovementType
{
    // ورودی
    InitialStock = 1,       // موجودی اولیه
    Restock = 2,            // ورود کالا
    Return = 3,             // برگشت از مشتری
    TransferIn = 4,         // انتقال از انبار دیگر
    
    // خروجی
    Sale = 10,              // فروش
    Damaged = 11,           // ضایعات
    Lost = 12,              // مفقودی
    TransferOut = 13,       // انتقال به انبار دیگر
    
    // تعدیل
    AdjustmentPlus = 20,    // تعدیل افزایشی
    AdjustmentMinus = 21,   // تعدیل کاهشی
    
    // رزرو
    Reserved = 30,          // رزرو برای سفارش pending
    Released = 31           // آزادسازی رزرو (لغو سفارش)
}
```

---

## 🔧 سرویس‌ها و Handlers

### IInventoryService Interface

```csharp
public interface IInventoryService
{
    // موجودی
    Task<InventoryItem?> GetInventoryAsync(long productId, ProductType type, CancellationToken ct = default);
    Task<int> GetAvailableQuantityAsync(long productId, ProductType type, CancellationToken ct = default);
    
    // رزرو (برای سفارش pending)
    Task<bool> ReserveStockAsync(long productId, ProductType type, int quantity, long? orderId, CancellationToken ct = default);
    Task<bool> ReleaseReservationAsync(long productId, ProductType type, int quantity, long? orderId, CancellationToken ct = default);
    
    // کسر موجودی (پرداخت موفق)
    Task<bool> ConfirmSaleAsync(long productId, ProductType type, int quantity, long? orderId, CancellationToken ct = default);
    
    // برگشت
    Task<bool> ProcessReturnAsync(long productId, ProductType type, int quantity, long? orderId, string? note, CancellationToken ct = default);
    
    // ورود کالا
    Task<bool> AddStockAsync(long productId, ProductType type, int quantity, string? referenceNumber, string? note, CancellationToken ct = default);
    
    // تعدیل
    Task<bool> AdjustStockAsync(long productId, ProductType type, int newQuantity, string? note, CancellationToken ct = default);
    
    // گزارش
    Task<List<InventoryItem>> GetLowStockItemsAsync(ProductType? type = null, CancellationToken ct = default);
    Task<List<StockMovement>> GetStockMovementsAsync(long productId, ProductType type, DateTime? from, DateTime? to, CancellationToken ct = default);
}
```

### CQRS Handlers

#### Commands

| Command | توضیح |
|---------|-------|
| `AddStockCommand` | ورود کالا به انبار |
| `AdjustStockCommand` | تعدیل موجودی |
| `ReserveStockCommand` | رزرو برای سفارش |
| `ReleaseStockCommand` | آزادسازی رزرو |
| `ConfirmSaleCommand` | تایید فروش و کسر از موجودی |
| `ProcessReturnCommand` | برگشت کالا |
| `BulkUpdateStockCommand` | آپدیت دسته‌جمعی |

#### Queries

| Query | توضیح |
|-------|-------|
| `GetInventoryItemQuery` | دریافت موجودی یک محصول |
| `GetInventoryItemsQuery` | لیست موجودی‌ها با فیلتر |
| `GetLowStockItemsQuery` | آیتم‌های کم‌موجودی |
| `GetStockMovementsQuery` | تاریخچه حرکات انبار |
| `GetInventoryReportQuery` | گزارش انبارگردانی |

---

## 📋 مراحل پیاده‌سازی (Implementation Roadmap)

> **مجموع تخمینی:** 10-12 روز کاری  
> **پیش‌نیاز:** آشنایی با معماری CQRS و ساختار پروژه CMS

---

### 🔵 Phase 1: زیرساخت Domain (2 روز)

**هدف:** ایجاد Entity ها و Enum ها در لایه Domain

#### Day 1: Enums و Entity های اصلی

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 1.1 | ایجاد `ProductType` enum | `Domain/Enums/ProductType.cs` | ✅ |
| 1.2 | ایجاد `StockMovementType` enum | `Domain/Enums/StockMovementType.cs` | ✅ |
| 1.3 | ایجاد `InventoryItem` entity | `Domain/Entities/InventoryItem.cs` | ✅ |
| 1.4 | ایجاد `StockMovement` entity | `Domain/Entities/StockMovement.cs` | ✅ |
| 1.5 | ایجاد `Warehouse` entity (اختیاری) | `Domain/Entities/Warehouse.cs` | ✅ |

#### Day 2: EF Core Configurations

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 1.6 | ایجاد `InventoryItemConfiguration` | `Infrastructure/Data/Configurations/InventoryItemConfiguration.cs` | ✅ |
| 1.7 | ایجاد `StockMovementConfiguration` | `Infrastructure/Data/Configurations/StockMovementConfiguration.cs` | ✅ |
| 1.8 | ایجاد `WarehouseConfiguration` | `Infrastructure/Data/Configurations/WarehouseConfiguration.cs` | ✅ |
| 1.9 | اضافه کردن DbSet ها به `ApplicationDbContext` | `Infrastructure/Data/ApplicationDbContext.cs` | ✅ |
| 1.10 | ایجاد Migration | `dotnet ef migrations add AddInventorySystem` | ✅ |
| 1.11 | اعمال Migration | `dotnet ef database update` | ✅ |

**خروجی Phase 1:**
- ✅ جداول `InventoryItems`, `StockMovements`, `Warehouses` در دیتابیس
- ✅ Index های لازم روی `ProductId`, `DiscountProductId`, `ProductType`

---

### 🟢 Phase 2: سرویس Inventory (2 روز)

**هدف:** پیاده‌سازی `IInventoryService` و متدهای اصلی

#### Day 3: Interface و متدهای اصلی

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 2.1 | ایجاد `IInventoryService` interface | `Application/Common/Interfaces/IInventoryService.cs` | ✅ |
| 2.2 | ایجاد `InventoryService` class | `Infrastructure/Services/InventoryService.cs` | ✅ |
| 2.3 | پیاده‌سازی `InitializeInventoryAsync` | در `InventoryService.cs` | ✅ |
| 2.4 | پیاده‌سازی `GetInventoryAsync` | در `InventoryService.cs` | ✅ |
| 2.5 | پیاده‌سازی `GetAvailableQuantityAsync` | در `InventoryService.cs` | ✅ |

#### Day 4: متدهای عملیاتی

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 2.6 | پیاده‌سازی `AddStockAsync` | در `InventoryService.cs` | ✅ |
| 2.7 | پیاده‌سازی `AdjustStockAsync` | در `InventoryService.cs` | ✅ |
| 2.8 | پیاده‌سازی `ReserveStockAsync` | در `InventoryService.cs` | ✅ |
| 2.9 | پیاده‌سازی `ReleaseReservationAsync` | در `InventoryService.cs` | ✅ |
| 2.10 | پیاده‌سازی `ConfirmSaleAsync` | در `InventoryService.cs` | ✅ |
| 2.11 | پیاده‌سازی `ProcessReturnAsync` | در `InventoryService.cs` | ✅ |
| 2.12 | پیاده‌سازی `SyncRemainingCountAsync` (private) | در `InventoryService.cs` | ✅ |
| 2.13 | پیاده‌سازی `LogMovementAsync` (private) | در `InventoryService.cs` | ✅ |
| 2.14 | ثبت سرویس در DI | `Infrastructure/DependencyInjection.cs` | ✅ |

**خروجی Phase 2:**
- ✅ `InventoryService` کامل و قابل استفاده
- ✅ همه متدها با Sync به `RemainingCount`

---

### 🟡 Phase 3: CQRS Handlers (2 روز)

**هدف:** ایجاد Commands و Queries برای Inventory

#### Day 5: Commands

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 3.1 | `AddStockCommand` + Handler + Validator | `Application/Inventory/Commands/AddStock/` | ✅ |
| 3.2 | `AdjustStockCommand` + Handler + Validator | `Application/Inventory/Commands/AdjustStock/` | ✅ |
| 3.3 | `ReserveStockCommand` + Handler + Validator | `Application/Inventory/Commands/ReserveStock/` | ✅ |
| 3.4 | `ReleaseStockCommand` + Handler + Validator | `Application/Inventory/Commands/ReleaseStock/` | ✅ |
| 3.5 | `ConfirmSaleCommand` + Handler + Validator | `Application/Inventory/Commands/ConfirmSale/` | ✅ |
| 3.6 | `ProcessReturnCommand` + Handler + Validator | `Application/Inventory/Commands/ProcessReturn/` | ✅ |
| 3.7 | `BulkAdjustStockCommand` + Handler + Validator | `Application/Inventory/Commands/BulkAdjustStock/` | ✅ |

#### Day 6: Queries

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 3.8 | `GetInventoryItemQuery` + Handler | `Application/Inventory/Queries/GetInventoryItem/` | ✅ |
| 3.9 | `GetInventoryItemsQuery` + Handler (با Pagination) | `Application/Inventory/Queries/GetInventoryItems/` | ✅ |
| 3.10 | `GetLowStockItemsQuery` + Handler | `Application/Inventory/Queries/GetLowStockItems/` | ✅ |
| 3.11 | `GetStockMovementsQuery` + Handler | `Application/Inventory/Queries/GetStockMovements/` | ✅ |
| 3.12 | `GetInventoryReportQuery` + Handler | `Application/Inventory/Queries/GetInventoryReport/` | ✅ |

**خروجی Phase 3:**
- ✅ 7 Command با Validator
- ✅ 5 Query با Pagination

---

### 🟠 Phase 4: یکپارچه‌سازی با Product Handlers (1.5 روز)

**هدف:** تغییر Handler های موجود برای استفاده از `InventoryService`

#### Day 7: Product Handlers

| # | تسک | توضیح | وضعیت |
|---|------|-------|--------|
| 4.1 | آپدیت `CreateProductCommandHandler` | اضافه کردن `InitializeInventoryAsync` | ✅ |
| 4.2 | آپدیت `CreateDiscountProductCommandHandler` | اضافه کردن `InitializeInventoryAsync` | ✅ |
| 4.3 | آپدیت `UpdateProductCommandHandler` | اضافه کردن `AdjustStockAsync` (اگر موجودی تغییر کرد) | ⬜ |
| 4.4 | آپدیت `UpdateDiscountProductCommandHandler` | اضافه کردن `AdjustStockAsync` | ⬜ |
| 4.5 | آپدیت `BulkUpdateProductStockCommandHandler` | استفاده از `BulkAdjustStockAsync` | ⬜ |

#### Day 7.5: Order Handlers

| # | تسک | توضیح | وضعیت |
|---|------|-------|--------|
| 4.6 | آپدیت `PlaceOrderCommandHandler` | اضافه کردن `ReserveStockAsync` | ✅ |
| 4.7 | آپدیت `PlaceDiscountOrderCommandHandler` | اضافه کردن `ReserveStockAsync` | ✅ |
| 4.8 | آپدیت `CompleteOrderPaymentCommandHandler` | تغییر به `ConfirmSaleAsync` | ✅ |
| 4.9 | آپدیت `CompleteDiscountOrderPaymentCommandHandler` | تغییر به `ConfirmSaleAsync` | ✅ |
| 4.10 | آپدیت `CancelOrderCommandHandler` | اضافه کردن `ReleaseReservationAsync` | ✅ |
| 4.11 | آپدیت `CancelDiscountOrderCommandHandler` | اضافه کردن `ReleaseReservationAsync` | ⬜ |

**خروجی Phase 4:**
- ✅ همه Handler های Product از `InventoryService` استفاده می‌کنند
- ✅ همه Handler های Order از Reserve/Confirm/Release استفاده می‌کنند

---

### 🔴 Phase 5: Migration داده‌ها (0.5 روز)

**هدف:** انتقال داده‌های موجود به سیستم جدید

#### Day 8 (نیمه اول):

| # | تسک | توضیح | وضعیت |
|---|------|-------|--------|
| 5.1 | ایجاد Seed Script برای Warehouse پیش‌فرض | `INSERT INTO Warehouses (Name, Code, IsDefault) VALUES ('انبار اصلی', 'WH-001', 1)` | ⬜ |
| 5.2 | ایجاد Migration Script برای Products | انتقال `RemainingCount` به `InventoryItem` | ⬜ |
| 5.3 | ایجاد Migration Script برای DiscountProducts | انتقال `RemainingCount` به `InventoryItem` | ⬜ |
| 5.4 | ایجاد StockMovement های اولیه | یک رکورد `InitialStock` برای هر محصول | ⬜ |
| 5.5 | تست صحت داده‌ها | `SELECT COUNT(*) FROM Products WHERE RemainingCount != (SELECT Quantity FROM InventoryItems WHERE ProductId = Products.Id)` | ⬜ |

```sql
-- Migration Script نمونه:
INSERT INTO InventoryItems (ProductId, DiscountProductId, ProductType, Quantity, ReservedQuantity, WarehouseId, CreatedAt)
SELECT Id, NULL, 1, RemainingCount, 0, 1, GETUTCDATE()
FROM Products
WHERE NOT EXISTS (SELECT 1 FROM InventoryItems WHERE ProductId = Products.Id);

INSERT INTO InventoryItems (ProductId, DiscountProductId, ProductType, Quantity, ReservedQuantity, WarehouseId, CreatedAt)
SELECT NULL, Id, 2, RemainingCount, 0, 1, GETUTCDATE()
FROM DiscountProducts
WHERE NOT EXISTS (SELECT 1 FROM InventoryItems WHERE DiscountProductId = DiscountProducts.Id);
```

**خروجی Phase 5:**
- ✅ همه محصولات موجود `InventoryItem` دارند
- ✅ داده‌ها sync و سالم هستند

---

### 🟣 Phase 6: Proto و gRPC (1 روز)

**هدف:** Expose کردن API های Inventory از طریق gRPC

#### Day 8 (نیمه دوم) + Day 9:

| # | تسک | فایل | وضعیت |
|---|------|------|--------|
| 6.1 | ایجاد `inventory.proto` | `Protobufs/inventory.proto` | ✅ |
| 6.2 | کامپایل Proto | `pack-protos.sh` | ✅ |
| 6.3 | ایجاد `InventoryService.cs` در WebApi | `WebApi/Services/InventoryService.cs` | ✅ |
| 6.4 | ایجاد Mapping Profile | `Application/Common/Mappings/InventoryMappingProfile.cs` | ✅ |
| 6.5 | ثبت سرویس gRPC در `Program.cs` | `WebApi/Program.cs` | ✅ |

```protobuf
// inventory.proto
syntax = "proto3";
package cms.inventory;

service InventoryService {
  rpc GetInventoryItem(GetInventoryItemRequest) returns (InventoryItemResponse);
  rpc GetInventoryItems(GetInventoryItemsRequest) returns (GetInventoryItemsResponse);
  rpc AddStock(AddStockRequest) returns (OperationResponse);
  rpc AdjustStock(AdjustStockRequest) returns (OperationResponse);
  rpc GetLowStockItems(GetLowStockItemsRequest) returns (GetLowStockItemsResponse);
  rpc GetStockMovements(GetStockMovementsRequest) returns (GetStockMovementsResponse);
}
```

**خروجی Phase 6:**
- ✅ gRPC endpoint برای Inventory
- ✅ قابل استفاده توسط BackOffice و BFF

---

### ⚪ Phase 7: تست و QA (1 روز)

**هدف:** اطمینان از صحت عملکرد سیستم

#### Day 10:

| # | تسک | توضیح | وضعیت |
|---|------|-------|--------|
| 7.1 | Unit Test برای `InventoryService` | تست همه متدها | ⬜ |
| 7.2 | Unit Test برای Validators | تست validation rules | ⬜ |
| 7.3 | Integration Test برای CQRS Handlers | تست end-to-end | ⬜ |
| 7.4 | Manual Test: ایجاد محصول | چک کردن ایجاد خودکار InventoryItem | ⬜ |
| 7.5 | Manual Test: ثبت سفارش | چک کردن Reserve → Confirm flow | ⬜ |
| 7.6 | Manual Test: لغو سفارش | چک کردن Release | ⬜ |
| 7.7 | Manual Test: Sync | چک کردن sync به `RemainingCount` | ⬜ |

**خروجی Phase 7:**
- ✅ پوشش تست > 80%
- ✅ همه سناریوها تست شده

---

## 📊 خلاصه فازبندی

| Phase | عنوان | مدت | تسک‌ها | وابستگی |
|-------|-------|-----|--------|---------|
| 1 | زیرساخت Domain | 2 روز | 11 | - |
| 2 | سرویس Inventory | 2 روز | 14 | Phase 1 |
| 3 | CQRS Handlers | 2 روز | 12 | Phase 2 |
| 4 | یکپارچه‌سازی | 1.5 روز | 11 | Phase 2 |
| 5 | Migration داده | 0.5 روز | 5 | Phase 1, 2 |
| 6 | Proto و gRPC | 1 روز | 5 | Phase 3 |
| 7 | تست و QA | 1 روز | 7 | همه |

**مجموع:** ~10 روز کاری | **65 تسک**

---

## 🚀 ترتیب اجرا (Critical Path)

```
Day 1-2:  Phase 1 (زیرساخت)
              │
Day 3-4:  Phase 2 (سرویس) ─────────┐
              │                     │
Day 5-6:  Phase 3 (Handlers)        │
              │                     │
Day 7:    Phase 4 (یکپارچه‌سازی) ◄──┘
              │
Day 8:    Phase 5 (Migration) + Phase 6 شروع
              │
Day 9:    Phase 6 (Proto) ادامه
              │
Day 10:   Phase 7 (تست)
```

---

## 🔄 Backward Compatibility و رابطه با `RemainingCount`

### ❓ سوال کلیدی: تکلیف `RemainingCount` فعلی چیست؟

الان در جداول `Product` و `DiscountProduct` فیلد `RemainingCount` داریم که مستقیم موجودی رو نگه می‌داره. سه گزینه داریم:

| گزینه | توضیح | مزایا | معایب |
|-------|-------|-------|-------|
| **A: حذف کامل** | `RemainingCount` از Product حذف شود | Clean architecture | همه کدها باید تغییر کنند! |
| **B: Cache (پیشنهادی) ✅** | `RemainingCount` حفظ شود و sync شود | Backward compatible | داده duplicate |
| **C: Computed** | `RemainingCount` به property محاسباتی تبدیل شود | بدون duplicate | نیاز به Join در همه Query ها |

### گزینه انتخاب شده: B - حفظ `RemainingCount` به عنوان Cache

```
Product                          InventoryItem
├── Title                        ├── ProductId → Product.Id
├── Price                        ├── Quantity = 50  ← منبع اصلی (Source of Truth)
└── RemainingCount = 50 (sync)   └── ReservedQuantity = 5
         ↑
      همیشه sync می‌شود
```

**قانون طلایی:**
> ⚠️ **هیچ‌وقت** مستقیم `RemainingCount` را آپدیت نکنید. فقط از `InventoryService` استفاده کنید که خودش sync می‌کند.

```csharp
// در InventoryService - بعد از هر تغییر موجودی:
private async Task SyncRemainingCountAsync(InventoryItem item, CancellationToken ct)
{
    if (item.ProductType == ProductType.RegularProduct)
    {
        var product = await _context.Products.FindAsync(item.ProductId);
        if (product != null)
            product.RemainingCount = item.Quantity;
    }
    else if (item.ProductType == ProductType.DiscountProduct)
    {
        var product = await _context.DiscountProducts.FindAsync(item.DiscountProductId);
        if (product != null)
            product.RemainingCount = item.Quantity;
    }
}
```

### مزایا:
- ✅ کدهای موجود بدون تغییر کار می‌کنند
- ✅ Proto ها نیاز به تغییر ندارند
- ✅ Admin UI و FrontOffice بدون تغییر
- ✅ Query های سریع روی Product (بدون Join)
- ✅ Migration تدریجی امکان‌پذیر است

### معایب:
- ⚠️ داده duplicate (ولی sync می‌شود)
- ⚠️ کمی overhead در هر عملیات

---

## 📌 اصل ایجاد موجودی - Product First Principle

### قانون اساسی:

> ⚠️ **InventoryItem وابسته به Product است.** محصول باید ابتدا در جدول `Product` یا `DiscountProduct` ایجاد شود، سپس رکورد `InventoryItem` متصل به آن ایجاد می‌شود.

```
ایجاد Product (Id = 1)  →  ایجاد InventoryItem (ProductId = 1)  →  StockMovement(InitialStock)
         ↑                            ↑                                    ↑
      Step 1                       Step 2                               Step 3
```

### ❓ سوال: برای ورود کالا به انبار، اول باید محصول ثبت شده باشد؟

**بله، حتماً!** چون `InventoryItem.ProductId` یک Foreign Key به جدول `Product` است.

### دو رویکرد پیاده‌سازی:

| رویکرد | توضیح | مزایا | معایب |
|--------|-------|-------|-------|
| **A: خودکار (پیشنهادی) ✅** | موقع CreateProduct خودکار InventoryItem ساخته می‌شود | ساده، یکپارچه، بدون ریسک | - |
| **B: جداگانه** | اول Product با InitialCount=0، بعد جداگانه AddStock | انعطاف بیشتر | پیچیدگی UI، ریسک محصول بدون موجودی |

---

### رویکرد A: خودکار (پیشنهادی ✅)

در `CreateProductCommandHandler` بلافاصله بعد از ایجاد Product، رکورد `InventoryItem` هم ساخته می‌شود:

```csharp
// CreateProductCommandHandler:
var product = new Product { Title = ..., Price = ... };
await _context.Products.AddAsync(product);
await _context.SaveChangesAsync();

// ✅ خودکار InventoryItem ایجاد می‌شود
await _inventoryService.InitializeInventoryAsync(
    product.Id, 
    ProductType.RegularProduct, 
    request.InitialCount,  // می‌تواند 0 باشد
    ct);
```

**نتیجه:**
- `Product` ایجاد می‌شود
- `InventoryItem` با `Quantity = InitialCount` ایجاد می‌شود
- `StockMovement(InitialStock)` ثبت می‌شود
- `Product.RemainingCount` sync می‌شود

---

### رویکرد B: جداگانه (اختیاری برای آینده)

```csharp
// Step 1: ایجاد محصول با موجودی 0
var product = new Product { Title = ..., RemainingCount = 0 };
// InventoryItem با Quantity = 0 ایجاد می‌شود

// Step 2: بعداً از UI انبار، ورود کالا
await _inventoryService.AddStockAsync(
    productId, 
    ProductType.RegularProduct, 
    100,           // مقدار ورودی
    "INV-001",     // شماره فاکتور
    "ورود اولیه از تامین‌کننده",
    ct);
```

**کاربرد:** وقتی می‌خواهید محصول را ثبت کنید ولی کالا هنوز به انبار نرسیده.

---

### 📊 مکانیزم همگام‌سازی (Sync)

هر وقت موجودی در `InventoryItem` تغییر کند، **خودکار** به `Product.RemainingCount` sync می‌شود:

| عملیات | تغییر در InventoryItem | Sync به Product |
|--------|----------------------|-----------------|
| **ورود کالا (Restock)** | `Quantity += X` | `RemainingCount = Quantity` |
| **فروش (Sale)** | `Quantity -= X` | `RemainingCount = Quantity` |
| **تعدیل (Adjust)** | `Quantity = NewValue` | `RemainingCount = NewValue` |
| **برگشت (Return)** | `Quantity += X` | `RemainingCount = Quantity` |
| **رزرو (Reserve)** | فقط `ReservedQuantity` تغییر | ❌ sync نمی‌شود |

> 💡 **نکته:** رزرو فقط `ReservedQuantity` را تغییر می‌دهد و `Quantity` ثابت می‌ماند. بنابراین `RemainingCount` تغییر نمی‌کند تا موقع پرداخت.

---

## 🔄 سناریوهای تغییر موجودی - قبل و بعد

### 1️⃣ ایجاد محصول جدید

| مرحله | قبل (فعلی) | بعد (با Inventory) |
|-------|-----------|-------------------|
| **Admin UI** | `InitialCount` وارد می‌کنه | ❌ بدون تغییر |
| **Proto** | `initial_count` در Request | ❌ بدون تغییر |
| **Handler** | `Product.RemainingCount = initial_count` | ✅ `InventoryService.InitializeInventoryAsync()` |
| **نتیجه** | فقط Product آپدیت | Product + InventoryItem + StockMovement(InitialStock) |

```csharp
// CreateProductCommandHandler - بعد از تغییر:
var product = new Product { Title = request.Title, Price = request.Price, ... };
await _context.Products.AddAsync(product, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);

// ایجاد رکورد انبار + sync به RemainingCount
await _inventoryService.InitializeInventoryAsync(
    product.Id, 
    ProductType.RegularProduct, 
    request.InitialCount,
    cancellationToken);
```

---

### 2️⃣ ویرایش محصول (تغییر موجودی دستی توسط ادمین)

| مرحله | قبل (فعلی) | بعد (با Inventory) |
|-------|-----------|-------------------|
| **Admin UI** | `RemainingCount` ویرایش می‌کنه | ❌ بدون تغییر |
| **Proto** | `remaining_count` در Request | ❌ بدون تغییر |
| **Handler** | `Product.RemainingCount = new_value` | ✅ `InventoryService.AdjustStockAsync()` |
| **نتیجه** | فقط Product آپدیت | InventoryItem + StockMovement(Adjustment) + Product sync |

```csharp
// UpdateProductCommandHandler - بعد از تغییر:
var product = await _context.Products.FindAsync(request.Id, cancellationToken);

// اگر موجودی تغییر کرده:
if (request.RemainingCount.HasValue && request.RemainingCount != product.RemainingCount)
{
    await _inventoryService.AdjustStockAsync(
        product.Id,
        ProductType.RegularProduct,
        request.RemainingCount.Value,
        "تعدیل دستی توسط ادمین",
        cancellationToken);
    // این متد خودش Product.RemainingCount رو هم sync می‌کنه
}

// بقیه فیلدها (بدون تغییر)
product.Title = request.Title;
product.Price = request.Price;
// ...
```

---

### 3️⃣ خرید مشتری (سفارش از FrontOffice)

| مرحله | قبل (فعلی) | بعد (با Inventory) |
|-------|-----------|-------------------|
| **FrontOffice UI** | سفارش ثبت می‌شه | ❌ بدون تغییر |
| **PlaceOrder** | هیچ کاری با موجودی نمی‌کنه! 😱 | ✅ `ReserveStockAsync()` |
| **CompletePayment** | `Product.RemainingCount -= qty` | ✅ `ConfirmSaleAsync()` |
| **CancelOrder** | هیچ کاری نمی‌کنه | ✅ `ReleaseReservationAsync()` |

```csharp
// PlaceOrderCommandHandler - بعد از تغییر:
foreach (var item in cartItems)
{
    // رزرو موجودی (سفارش pending)
    var reserved = await _inventoryService.ReserveStockAsync(
        item.ProductId,
        ProductType.RegularProduct,
        item.Quantity,
        order.Id,
        cancellationToken);
    
    if (!reserved)
        throw new InsufficientStockException($"موجودی کافی برای محصول {item.ProductId} وجود ندارد");
}

// CompleteOrderPaymentCommandHandler - بعد از تغییر:
foreach (var item in order.OrderDetails)
{
    // تبدیل رزرو به فروش واقعی + کسر از موجودی
    await _inventoryService.ConfirmSaleAsync(
        item.ProductId,
        ProductType.RegularProduct,
        item.Quantity,
        order.Id,
        cancellationToken);
}

// CancelOrderCommandHandler - بعد از تغییر:
foreach (var item in order.OrderDetails)
{
    // آزادسازی رزرو
    await _inventoryService.ReleaseReservationAsync(
        item.ProductId,
        ProductType.RegularProduct,
        item.Quantity,
        order.Id,
        cancellationToken);
}
```

---

### 4️⃣ Bulk Update موجودی (آپدیت دسته‌جمعی)

| مرحله | قبل (فعلی) | بعد (با Inventory) |
|-------|-----------|-------------------|
| **Admin UI** | لیست محصولات + مقدار جدید | ❌ بدون تغییر |
| **Handler** | مستقیم `RemainingCount` آپدیت | ✅ `BulkAdjustStockAsync()` |
| **نتیجه** | فقط Products آپدیت | برای هر محصول: StockMovement ثبت + sync |

---

### 5️⃣ برگشت کالا (Return) - قابلیت جدید

| مرحله | قبل (فعلی) | بعد (با Inventory) |
|-------|-----------|-------------------|
| **وضعیت** | پیاده‌سازی نشده ❌ | ✅ `ProcessReturnAsync()` |

```csharp
// ProcessReturnCommandHandler - جدید:
await _inventoryService.ProcessReturnAsync(
    request.ProductId,
    ProductType.RegularProduct,
    request.Quantity,
    request.OrderId,
    request.Note,
    cancellationToken);
```

---

## 📋 خلاصه تغییرات لازم در Handlers

### ❌ نیاز به تغییر ندارند:
- Admin UI (فرم‌های ایجاد/ویرایش محصول)
- Proto definitions (همه فیلدها حفظ می‌شوند)
- FrontOffice UI (صفحات خرید)
- API signatures

### ✅ نیاز به تغییر دارند:

| Handler | نوع تغییر | توضیح |
|---------|----------|-------|
| `CreateProductCommandHandler` | اضافه | `InitializeInventoryAsync()` |
| `CreateDiscountProductCommandHandler` | اضافه | `InitializeInventoryAsync()` |
| `UpdateProductCommandHandler` | اضافه | `AdjustStockAsync()` (اگر موجودی تغییر کرد) |
| `UpdateDiscountProductCommandHandler` | اضافه | `AdjustStockAsync()` |
| `BulkUpdateProductStockCommandHandler` | تغییر | استفاده از `BulkAdjustStockAsync()` |
| `PlaceOrderCommandHandler` | اضافه | `ReserveStockAsync()` |
| `PlaceDiscountOrderCommandHandler` | اضافه | `ReserveStockAsync()` |
| `CompleteOrderPaymentCommandHandler` | تغییر | از `ConfirmSaleAsync()` به جای direct update |
| `CompleteDiscountOrderPaymentCommandHandler` | تغییر | از `ConfirmSaleAsync()` |
| `CancelOrderCommandHandler` | اضافه | `ReleaseReservationAsync()` |
| `CancelDiscountOrderCommandHandler` | اضافه | `ReleaseReservationAsync()` |

---

## 🔧 پیاده‌سازی InventoryService

```csharp
public class InventoryService : IInventoryService
{
    private readonly IApplicationDbContext _context;
    
    public InventoryService(IApplicationDbContext context)
    {
        _context = context;
    }

    /// <summary>
    /// ایجاد موجودی اولیه برای محصول جدید
    /// </summary>
    public async Task InitializeInventoryAsync(
        long productId, 
        ProductType type, 
        int quantity, 
        CancellationToken ct)
    {
        var item = new InventoryItem
        {
            ProductId = type == ProductType.RegularProduct ? productId : null,
            DiscountProductId = type == ProductType.DiscountProduct ? productId : null,
            ProductType = type,
            Quantity = quantity,
            ReservedQuantity = 0,
            LastRestockedAt = DateTime.UtcNow
        };
        
        await _context.InventoryItems.AddAsync(item, ct);
        
        // لاگ حرکت انبار
        await LogMovementAsync(item, StockMovementType.InitialStock, quantity, null, "موجودی اولیه", ct);
        
        // Sync به Product.RemainingCount
        await SyncRemainingCountAsync(item, ct);
        
        await _context.SaveChangesAsync(ct);
    }

    /// <summary>
    /// رزرو موجودی برای سفارش pending
    /// </summary>
    public async Task<bool> ReserveStockAsync(
        long productId, 
        ProductType type, 
        int quantity, 
        long? orderId, 
        CancellationToken ct)
    {
        var item = await GetInventoryItemAsync(productId, type, ct);
        
        if (item == null || item.AvailableQuantity < quantity)
            return false;
        
        item.ReservedQuantity += quantity;
        
        await LogMovementAsync(item, StockMovementType.Reserved, quantity, orderId, null, ct);
        await _context.SaveChangesAsync(ct);
        
        return true;
    }

    /// <summary>
    /// آزادسازی رزرو (لغو سفارش)
    /// </summary>
    public async Task ReleaseReservationAsync(
        long productId, 
        ProductType type, 
        int quantity, 
        long? orderId, 
        CancellationToken ct)
    {
        var item = await GetInventoryItemAsync(productId, type, ct);
        
        if (item == null) return;
        
        item.ReservedQuantity = Math.Max(0, item.ReservedQuantity - quantity);
        
        await LogMovementAsync(item, StockMovementType.Released, quantity, orderId, "آزادسازی رزرو", ct);
        await _context.SaveChangesAsync(ct);
    }

    /// <summary>
    /// تایید فروش و کسر از موجودی (بعد از پرداخت موفق)
    /// </summary>
    public async Task ConfirmSaleAsync(
        long productId, 
        ProductType type, 
        int quantity, 
        long? orderId, 
        CancellationToken ct)
    {
        var item = await GetInventoryItemAsync(productId, type, ct);
        
        if (item == null) 
            throw new InventoryNotFoundException(productId, type);
        
        var quantityBefore = item.Quantity;
        
        item.Quantity -= quantity;
        item.ReservedQuantity = Math.Max(0, item.ReservedQuantity - quantity);
        item.LastSoldAt = DateTime.UtcNow;
        
        await LogMovementAsync(item, StockMovementType.Sale, -quantity, orderId, null, ct);
        await SyncRemainingCountAsync(item, ct);
        await _context.SaveChangesAsync(ct);
    }

    /// <summary>
    /// تعدیل موجودی (ویرایش دستی توسط ادمین)
    /// </summary>
    public async Task AdjustStockAsync(
        long productId, 
        ProductType type, 
        int newQuantity, 
        string? note, 
        CancellationToken ct)
    {
        var item = await GetInventoryItemAsync(productId, type, ct);
        
        if (item == null)
        {
            // اگر InventoryItem نداره، ایجادش کن
            await InitializeInventoryAsync(productId, type, newQuantity, ct);
            return;
        }
        
        var difference = newQuantity - item.Quantity;
        var movementType = difference >= 0 
            ? StockMovementType.AdjustmentPlus 
            : StockMovementType.AdjustmentMinus;
        
        item.Quantity = newQuantity;
        
        await LogMovementAsync(item, movementType, difference, null, note, ct);
        await SyncRemainingCountAsync(item, ct);
        await _context.SaveChangesAsync(ct);
    }

    /// <summary>
    /// ورود کالا به انبار
    /// </summary>
    public async Task AddStockAsync(
        long productId, 
        ProductType type, 
        int quantity, 
        string? referenceNumber, 
        string? note, 
        CancellationToken ct)
    {
        var item = await GetInventoryItemAsync(productId, type, ct);
        
        if (item == null)
        {
            await InitializeInventoryAsync(productId, type, quantity, ct);
            return;
        }
        
        item.Quantity += quantity;
        item.LastRestockedAt = DateTime.UtcNow;
        
        await LogMovementAsync(item, StockMovementType.Restock, quantity, null, note, ct, referenceNumber);
        await SyncRemainingCountAsync(item, ct);
        await _context.SaveChangesAsync(ct);
    }

    /// <summary>
    /// برگشت کالا از مشتری
    /// </summary>
    public async Task ProcessReturnAsync(
        long productId, 
        ProductType type, 
        int quantity, 
        long? orderId, 
        string? note, 
        CancellationToken ct)
    {
        var item = await GetInventoryItemAsync(productId, type, ct);
        
        if (item == null)
            throw new InventoryNotFoundException(productId, type);
        
        item.Quantity += quantity;
        
        await LogMovementAsync(item, StockMovementType.Return, quantity, orderId, note, ct);
        await SyncRemainingCountAsync(item, ct);
        await _context.SaveChangesAsync(ct);
    }

    // ============ Private Helpers ============

    private async Task<InventoryItem?> GetInventoryItemAsync(long productId, ProductType type, CancellationToken ct)
    {
        return type == ProductType.RegularProduct
            ? await _context.InventoryItems.FirstOrDefaultAsync(x => x.ProductId == productId, ct)
            : await _context.InventoryItems.FirstOrDefaultAsync(x => x.DiscountProductId == productId, ct);
    }

    private async Task SyncRemainingCountAsync(InventoryItem item, CancellationToken ct)
    {
        if (item.ProductType == ProductType.RegularProduct && item.ProductId.HasValue)
        {
            var product = await _context.Products.FindAsync(new object[] { item.ProductId.Value }, ct);
            if (product != null)
                product.RemainingCount = item.Quantity;
        }
        else if (item.ProductType == ProductType.DiscountProduct && item.DiscountProductId.HasValue)
        {
            var product = await _context.DiscountProducts.FindAsync(new object[] { item.DiscountProductId.Value }, ct);
            if (product != null)
                product.RemainingCount = item.Quantity;
        }
    }

    private async Task LogMovementAsync(
        InventoryItem item, 
        StockMovementType type, 
        int quantity,
        long? orderId,
        string? note,
        CancellationToken ct,
        string? referenceNumber = null)
    {
        var movement = new StockMovement
        {
            InventoryItemId = item.Id,
            MovementType = type,
            Quantity = quantity,
            QuantityBefore = item.Quantity - quantity,
            QuantityAfter = item.Quantity,
            OrderId = item.ProductType == ProductType.RegularProduct ? orderId : null,
            DiscountOrderId = item.ProductType == ProductType.DiscountProduct ? orderId : null,
            ReferenceNumber = referenceNumber,
            Note = note
        };
        
        await _context.StockMovements.AddAsync(movement, ct);
    }
}
```

---

## 📊 مدل داده - ERD

```
┌─────────────────┐       ┌──────────────────┐       ┌─────────────────┐
│    Warehouse    │       │  InventoryItem   │       │  StockMovement  │
├─────────────────┤       ├──────────────────┤       ├─────────────────┤
│ Id              │──────<│ WarehouseId      │       │ Id              │
│ Name            │       │ Id               │──────<│ InventoryItemId │
│ Code            │       │ ProductId?       │       │ MovementType    │
│ IsDefault       │       │ DiscountProductId│       │ Quantity        │
│ IsActive        │       │ ProductType      │       │ QuantityBefore  │
└─────────────────┘       │ Quantity         │       │ QuantityAfter   │
                          │ ReservedQuantity │       │ OrderId?        │
                          │ LowStockThreshold│       │ ReferenceNumber │
┌─────────────────┐       │ LastRestockedAt  │       │ Note            │
│    Product      │──────>│ LastSoldAt       │       │ PerformedByUserId│
├─────────────────┤       └──────────────────┘       │ CreatedAt       │
│ Id              │                                  └─────────────────┘
│ RemainingCount  │ (sync)
└─────────────────┘

┌─────────────────┐
│ DiscountProduct │──────>
├─────────────────┤
│ Id              │
│ RemainingCount  │ (sync)
└─────────────────┘
```

---

## 🔔 هشدارها و نوتیفیکیشن

### Low Stock Alert (اختیاری - Phase آینده)

```csharp
public class LowStockNotificationService
{
    public async Task CheckAndNotifyAsync()
    {
        var lowStockItems = await _inventoryService.GetLowStockItemsAsync();
        
        foreach (var item in lowStockItems)
        {
            // ارسال نوتیفیکیشن به ادمین
            // Email / SMS / Dashboard Alert
        }
    }
}
```

---

## 📈 گزارشات پیشنهادی

1. **گزارش موجودی فعلی** - لیست همه محصولات با موجودی
2. **گزارش کم‌موجودی** - محصولات زیر آستانه
3. **گزارش حرکات انبار** - ورودی/خروجی در بازه زمانی
4. **گزارش انبارگردانی** - مقایسه موجودی سیستم با فیزیکی
5. **گزارش ضایعات** - کالاهای خراب/مفقود شده
6. **گزارش گردش کالا** - پرفروش‌ترین / کم‌فروش‌ترین

---

## ⚠️ نکات مهم

1. **Transaction Safety:** همه عملیات موجودی باید در Transaction باشند
2. **Concurrency:** استفاده از RowVersion برای جلوگیری از race condition
3. **Audit Trail:** همه تغییرات با جزئیات کامل لاگ شوند
4. **Performance:** Index روی ProductId و ProductType

---

## 🔗 فایل‌های مرتبط

- [BACKLOG.md](../05-TASKS/BACKLOG.md) - لیست کارها
- [CMS Domain Entities](../../CMS/src/CMSMicroservice.Domain/Entities/)
- [CMS Application](../../CMS/src/CMSMicroservice.Application/)

---

**نویسنده:** GitHub Copilot  
**تاریخ آخرین به‌روزرسانی:** 1 January 2026
