Implement Inventory Management Service with CRUD operations for warehouses and inventory items, stock operations, and bulk processing capabilities.
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m48s

This commit is contained in:
masoodafar-web
2026-01-02 00:45:37 +03:30
parent f0117eb1d5
commit 1cf501711f
50 changed files with 10699 additions and 101 deletions
@@ -61,6 +61,11 @@ public interface IApplicationDbContext
DbSet<State> States { get; }
DbSet<City> Cities { get; }
// ============= Inventory Management =============
DbSet<Warehouse> Warehouses { get; }
DbSet<InventoryItem> InventoryItems { get; }
DbSet<StockMovement> StockMovements { get; }
/// <summary>
/// دسترسی به DatabaseFacade برای اجرای raw SQL و Stored Procedures
/// </summary>
@@ -0,0 +1,216 @@
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.Common.Interfaces;
/// <summary>
/// سرویس مدیریت موجودی - لایه بالاتر برای عملیات business
/// این سرویس مسئول همگام‌سازی موجودی بین InventoryItem و Product.RemainingCount است
/// </summary>
public interface IInventoryService
{
#region Initialization
/// <summary>
/// ایجاد رکورد موجودی برای محصول جدید
/// این متد باید در CreateProductCommandHandler و CreateDiscountProductCommandHandler فراخوانی شود
/// </summary>
/// <param name="productId">شناسه محصول (Product.Id یا DiscountProduct.Id)</param>
/// <param name="productType">نوع محصول (RegularProduct یا DiscountProduct)</param>
/// <param name="initialQuantity">موجودی اولیه</param>
/// <param name="warehouseId">شناسه انبار (پیش‌فرض: انبار اصلی)</param>
/// <param name="lowStockThreshold">آستانه هشدار کم‌موجودی</param>
/// <param name="ct">CancellationToken</param>
/// <returns>شناسه InventoryItem ایجاد شده</returns>
Task<long> InitializeInventoryAsync(
long productId,
ProductType productType,
int initialQuantity,
long? warehouseId = null,
int lowStockThreshold = 10,
CancellationToken ct = default);
#endregion
#region Query Operations
/// <summary>
/// دریافت موجودی یک محصول
/// </summary>
Task<InventoryItem?> GetInventoryAsync(
long productId,
ProductType productType,
long? warehouseId = null,
CancellationToken ct = default);
/// <summary>
/// دریافت موجودی قابل فروش (Quantity - ReservedQuantity)
/// </summary>
Task<int> GetAvailableQuantityAsync(
long productId,
ProductType productType,
long? warehouseId = null,
CancellationToken ct = default);
/// <summary>
/// بررسی اینکه آیا موجودی کافی برای فروش وجود دارد
/// </summary>
Task<bool> CheckAvailabilityAsync(
long productId,
ProductType productType,
int requiredQuantity,
long? warehouseId = null,
CancellationToken ct = default);
/// <summary>
/// دریافت لیست محصولات کم‌موجود
/// </summary>
Task<List<InventoryItem>> GetLowStockItemsAsync(
ProductType? productType = null,
long? warehouseId = null,
int count = 50,
CancellationToken ct = default);
/// <summary>
/// دریافت تاریخچه حرکات موجودی
/// </summary>
Task<List<StockMovement>> GetStockMovementsAsync(
long productId,
ProductType productType,
DateTime? fromDate = null,
DateTime? toDate = null,
CancellationToken ct = default);
#endregion
#region Order Flow Operations
/// <summary>
/// رزرو موجودی برای سفارش pending
/// این متد در PlaceOrderCommandHandler فراخوانی می‌شود
/// فقط ReservedQuantity را افزایش می‌دهد، Quantity تغییر نمی‌کند
/// </summary>
/// <param name="productId">شناسه محصول</param>
/// <param name="productType">نوع محصول</param>
/// <param name="quantity">تعداد رزرو</param>
/// <param name="orderId">شناسه سفارش (Order.Id یا DiscountOrder.Id)</param>
/// <param name="ct">CancellationToken</param>
/// <returns>true اگر رزرو موفق بود</returns>
Task<bool> ReserveStockAsync(
long productId,
ProductType productType,
int quantity,
long? orderId = null,
CancellationToken ct = default);
/// <summary>
/// آزادسازی رزرو (لغو سفارش یا timeout)
/// این متد در CancelOrderCommandHandler فراخوانی می‌شود
/// </summary>
Task<bool> ReleaseReservationAsync(
long productId,
ProductType productType,
int quantity,
long? orderId = null,
CancellationToken ct = default);
/// <summary>
/// تایید فروش - کسر واقعی موجودی
/// این متد در CompleteOrderPaymentCommandHandler فراخوانی می‌شود
/// ReservedQuantity کاهش می‌یابد، Quantity کاهش می‌یابد، Product.RemainingCount sync می‌شود
/// </summary>
Task<bool> ConfirmSaleAsync(
long productId,
ProductType productType,
int quantity,
long? orderId = null,
CancellationToken ct = default);
#endregion
#region Stock Management Operations
/// <summary>
/// ورود کالا به انبار (Restock)
/// </summary>
/// <param name="productId">شناسه محصول</param>
/// <param name="productType">نوع محصول</param>
/// <param name="quantity">تعداد ورودی</param>
/// <param name="referenceNumber">شماره مرجع (مثل شماره فاکتور خرید)</param>
/// <param name="note">یادداشت</param>
/// <param name="performedByUserId">شناسه کاربر انجام‌دهنده</param>
/// <param name="ct">CancellationToken</param>
Task<bool> AddStockAsync(
long productId,
ProductType productType,
int quantity,
string? referenceNumber = null,
string? note = null,
long? performedByUserId = null,
CancellationToken ct = default);
/// <summary>
/// تعدیل موجودی (تنظیم به مقدار جدید)
/// </summary>
Task<bool> AdjustStockAsync(
long productId,
ProductType productType,
int newQuantity,
string? note = null,
long? performedByUserId = null,
CancellationToken ct = default);
/// <summary>
/// ثبت برگشت کالا از مشتری
/// </summary>
Task<bool> ProcessReturnAsync(
long productId,
ProductType productType,
int quantity,
long? orderId = null,
string? note = null,
long? performedByUserId = null,
CancellationToken ct = default);
/// <summary>
/// ثبت ضایعات/مفقودی
/// </summary>
Task<bool> RecordLossAsync(
long productId,
ProductType productType,
int quantity,
StockMovementType lossType, // Damaged or Lost
string? note = null,
long? performedByUserId = null,
CancellationToken ct = default);
#endregion
#region Bulk Operations
/// <summary>
/// رزرو موجودی برای چند آیتم (یک سفارش با چند محصول)
/// </summary>
Task<bool> BulkReserveStockAsync(
IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items,
long? orderId = null,
CancellationToken ct = default);
/// <summary>
/// آزادسازی رزرو برای چند آیتم
/// </summary>
Task<bool> BulkReleaseReservationAsync(
IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items,
long? orderId = null,
CancellationToken ct = default);
/// <summary>
/// تایید فروش برای چند آیتم
/// </summary>
Task<bool> BulkConfirmSaleAsync(
IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items,
long? orderId = null,
CancellationToken ct = default);
#endregion
}
@@ -0,0 +1,152 @@
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.Common.Interfaces.Repositories;
/// <summary>
/// Repository interface برای مدیریت موجودی محصولات
/// </summary>
public interface IInventoryItemRepository
{
#region Read Operations
/// <summary>
/// دریافت آیتم موجودی بر اساس شناسه
/// </summary>
Task<InventoryItem?> GetByIdAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت آیتم موجودی بر اساس محصول معمولی
/// </summary>
Task<InventoryItem?> GetByProductIdAsync(long productId, long warehouseId = 1, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت آیتم موجودی بر اساس محصول تخفیفی
/// </summary>
Task<InventoryItem?> GetByDiscountProductIdAsync(long discountProductId, long warehouseId = 1, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت تمام آیتم‌های موجودی یک انبار
/// </summary>
Task<List<InventoryItem>> GetByWarehouseIdAsync(long warehouseId, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت محصولات کم‌موجود
/// </summary>
Task<List<InventoryItem>> GetLowStockItemsAsync(ProductType? productType = null, long warehouseId = 1, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت محصولات با موجودی صفر
/// </summary>
Task<List<InventoryItem>> GetOutOfStockItemsAsync(ProductType? productType = null, long warehouseId = 1, CancellationToken cancellationToken = default);
/// <summary>
/// جستجوی آیتم‌های موجودی با فیلتر
/// </summary>
Task<List<InventoryItem>> SearchAsync(
string? searchTerm = null,
ProductType? productType = null,
long? warehouseId = null,
int? minQuantity = null,
int? maxQuantity = null,
int skip = 0,
int take = 50,
CancellationToken cancellationToken = default);
/// <summary>
/// شمارش کل آیتم‌های موجودی با فیلتر
/// </summary>
Task<int> CountAsync(
string? searchTerm = null,
ProductType? productType = null,
long? warehouseId = null,
int? minQuantity = null,
int? maxQuantity = null,
CancellationToken cancellationToken = default);
#endregion
#region Write Operations
/// <summary>
/// افزودن آیتم موجودی جدید
/// </summary>
Task<InventoryItem> AddAsync(InventoryItem inventoryItem, CancellationToken cancellationToken = default);
/// <summary>
/// بروزرسانی آیتم موجودی
/// </summary>
Task UpdateAsync(InventoryItem inventoryItem, CancellationToken cancellationToken = default);
/// <summary>
/// حذف آیتم موجودی
/// </summary>
Task DeleteAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// بروزرسانی موجودی (با ثبت حرکت)
/// </summary>
Task<bool> UpdateQuantityAsync(
long inventoryItemId,
int quantityChange,
StockMovementType movementType,
string? note = null,
string? referenceNumber = null,
long? orderId = null,
long? discountOrderId = null,
long? performedByUserId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// رزرو موجودی
/// </summary>
Task<bool> ReserveQuantityAsync(
long inventoryItemId,
int quantity,
string? note = null,
string? referenceNumber = null,
long? orderId = null,
long? discountOrderId = null,
long? performedByUserId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// آزاد کردن موجودی رزرو شده
/// </summary>
Task<bool> ReleaseReservedQuantityAsync(
long inventoryItemId,
int quantity,
string? note = null,
string? referenceNumber = null,
long? orderId = null,
long? discountOrderId = null,
long? performedByUserId = null,
CancellationToken cancellationToken = default);
#endregion
#region Bulk Operations
/// <summary>
/// بروزرسانی انبوه موجودی چندین محصول
/// </summary>
Task<bool> BulkUpdateQuantityAsync(
List<(long InventoryItemId, int QuantityChange, string? Note)> updates,
StockMovementType movementType,
string? referenceNumber = null,
long? performedByUserId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// رزرو انبوه موجودی چندین محصول
/// </summary>
Task<bool> BulkReserveQuantityAsync(
List<(long InventoryItemId, int Quantity, string? Note)> reservations,
string? referenceNumber = null,
long? orderId = null,
long? discountOrderId = null,
long? performedByUserId = null,
CancellationToken cancellationToken = default);
#endregion
}
@@ -0,0 +1,146 @@
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.Common.Interfaces.Repositories;
/// <summary>
/// Repository interface برای مدیریت حرکات موجودی
/// </summary>
public interface IStockMovementRepository
{
#region Read Operations
/// <summary>
/// دریافت حرکت موجودی بر اساس شناسه
/// </summary>
Task<StockMovement?> GetByIdAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت تاریخچه حرکات یک آیتم موجودی
/// </summary>
Task<List<StockMovement>> GetByInventoryItemIdAsync(
long inventoryItemId,
StockMovementType? movementType = null,
DateTime? fromDate = null,
DateTime? toDate = null,
int skip = 0,
int take = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// دریافت حرکات مربوط به یک سفارش
/// </summary>
Task<List<StockMovement>> GetByOrderIdAsync(long orderId, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت حرکات مربوط به یک سفارش تخفیفی
/// </summary>
Task<List<StockMovement>> GetByDiscountOrderIdAsync(long discountOrderId, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت حرکات بر اساس شماره مرجع
/// </summary>
Task<List<StockMovement>> GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت حرکات بر اساس نوع حرکت
/// </summary>
Task<List<StockMovement>> GetByMovementTypeAsync(
StockMovementType movementType,
DateTime? fromDate = null,
DateTime? toDate = null,
int skip = 0,
int take = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// دریافت آخرین حرکات موجودی
/// </summary>
Task<List<StockMovement>> GetRecentMovementsAsync(
int count = 50,
StockMovementType? movementType = null,
CancellationToken cancellationToken = default);
/// <summary>
/// جستجوی حرکات موجودی با فیلتر پیشرفته
/// </summary>
Task<List<StockMovement>> SearchAsync(
long? inventoryItemId = null,
StockMovementType? movementType = null,
DateTime? fromDate = null,
DateTime? toDate = null,
string? referenceNumber = null,
long? orderId = null,
long? discountOrderId = null,
long? performedByUserId = null,
int skip = 0,
int take = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// شمارش حرکات موجودی با فیلتر
/// </summary>
Task<int> CountAsync(
long? inventoryItemId = null,
StockMovementType? movementType = null,
DateTime? fromDate = null,
DateTime? toDate = null,
string? referenceNumber = null,
long? orderId = null,
long? discountOrderId = null,
long? performedByUserId = null,
CancellationToken cancellationToken = default);
#endregion
#region Write Operations
/// <summary>
/// افزودن حرکت موجودی جدید
/// </summary>
Task<StockMovement> AddAsync(StockMovement stockMovement, CancellationToken cancellationToken = default);
/// <summary>
/// حذف حرکت موجودی (نرم‌افزاری)
/// </summary>
Task DeleteAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// افزودن انبوه حرکات موجودی
/// </summary>
Task<List<StockMovement>> BulkAddAsync(List<StockMovement> stockMovements, CancellationToken cancellationToken = default);
#endregion
#region Analytics & Reports
/// <summary>
/// گزارش خلاصه حرکات در بازه زمانی
/// </summary>
Task<Dictionary<StockMovementType, int>> GetMovementSummaryAsync(
DateTime fromDate,
DateTime toDate,
long? inventoryItemId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// گزارش حجم ورود و خروج روزانه
/// </summary>
Task<List<(DateTime Date, int InboundQuantity, int OutboundQuantity)>> GetDailyMovementVolumeAsync(
DateTime fromDate,
DateTime toDate,
long? inventoryItemId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// گزارش بیشترین حرکات محصولات
/// </summary>
Task<List<(long InventoryItemId, string ProductName, int MovementCount, int TotalQuantityChange)>> GetTopMovingProductsAsync(
DateTime fromDate,
DateTime toDate,
int count = 10,
StockMovementType? movementType = null,
CancellationToken cancellationToken = default);
#endregion
}
@@ -0,0 +1,111 @@
using CMSMicroservice.Domain.Entities;
namespace CMSMicroservice.Application.Common.Interfaces.Repositories;
/// <summary>
/// Repository interface برای مدیریت انبارها
/// </summary>
public interface IWarehouseRepository
{
#region Read Operations
/// <summary>
/// دریافت انبار بر اساس شناسه
/// </summary>
Task<Warehouse?> GetByIdAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت انبار بر اساس کد
/// </summary>
Task<Warehouse?> GetByCodeAsync(string code, CancellationToken cancellationToken = default);
/// <summary>
/// دریافت انبار پیش‌فرض
/// </summary>
Task<Warehouse?> GetDefaultWarehouseAsync(CancellationToken cancellationToken = default);
/// <summary>
/// دریافت تمام انبارهای فعال
/// </summary>
Task<List<Warehouse>> GetActiveWarehousesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// دریافت تمام انبارها
/// </summary>
Task<List<Warehouse>> GetAllAsync(bool includeInactive = false, CancellationToken cancellationToken = default);
/// <summary>
/// جستجوی انبارها
/// </summary>
Task<List<Warehouse>> SearchAsync(
string? searchTerm = null,
bool? isActive = null,
int skip = 0,
int take = 50,
CancellationToken cancellationToken = default);
/// <summary>
/// شمارش انبارها
/// </summary>
Task<int> CountAsync(
string? searchTerm = null,
bool? isActive = null,
CancellationToken cancellationToken = default);
/// <summary>
/// بررسی وجود انبار با کد مشخص
/// </summary>
Task<bool> ExistsByCodeAsync(string code, long? excludeId = null, CancellationToken cancellationToken = default);
#endregion
#region Write Operations
/// <summary>
/// افزودن انبار جدید
/// </summary>
Task<Warehouse> AddAsync(Warehouse warehouse, CancellationToken cancellationToken = default);
/// <summary>
/// بروزرسانی انبار
/// </summary>
Task UpdateAsync(Warehouse warehouse, CancellationToken cancellationToken = default);
/// <summary>
/// حذف انبار (نرم‌افزاری)
/// </summary>
Task DeleteAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// فعال/غیرفعال کردن انبار
/// </summary>
Task SetActiveStatusAsync(long id, bool isActive, CancellationToken cancellationToken = default);
/// <summary>
/// تنظیم انبار پیش‌فرض
/// </summary>
Task SetAsDefaultAsync(long id, CancellationToken cancellationToken = default);
#endregion
#region Analytics
/// <summary>
/// گزارش آمار کلی انبار
/// </summary>
Task<(int TotalProducts, int LowStockProducts, int OutOfStockProducts, decimal TotalValue)> GetWarehouseStatisticsAsync(
long warehouseId,
CancellationToken cancellationToken = default);
/// <summary>
/// گزارش محصولات پرفروش انبار
/// </summary>
Task<List<(long ProductId, string ProductName, int TotalSold, int CurrentStock)>> GetTopSellingProductsAsync(
long warehouseId,
DateTime fromDate,
DateTime toDate,
int count = 10,
CancellationToken cancellationToken = default);
#endregion
}