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
@@ -0,0 +1,289 @@
using MediatR;
using CMSMicroservice.Application.Common.Interfaces.Repositories;
using CMSMicroservice.Application.Features.InventoryItems.Commands;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.Features.InventoryItems.Handlers;
/// <summary>
/// Handler برای ایجاد آیتم موجودی جدید
/// </summary>
public class CreateInventoryItemCommandHandler : IRequestHandler<CreateInventoryItemCommand, long>
{
private readonly IInventoryItemRepository _repository;
private readonly IStockMovementRepository _stockMovementRepository;
public CreateInventoryItemCommandHandler(
IInventoryItemRepository repository,
IStockMovementRepository stockMovementRepository)
{
_repository = repository;
_stockMovementRepository = stockMovementRepository;
}
public async Task<long> Handle(CreateInventoryItemCommand request, CancellationToken cancellationToken)
{
// بررسی اینکه حداقل یکی از Product یا DiscountProduct تعریف شده باشد
if (request.ProductId == null && request.DiscountProductId == null)
{
throw new ArgumentException("Either ProductId or DiscountProductId must be provided");
}
// بررسی اینکه هر دو ProductId و DiscountProductId تعریف نشده باشند
if (request.ProductId != null && request.DiscountProductId != null)
{
throw new ArgumentException("Only one of ProductId or DiscountProductId can be provided");
}
// بررسی وجود آیتم موجودی قبلی برای همین محصول در همین انبار
InventoryItem? existingItem = null;
if (request.ProductId.HasValue)
{
existingItem = await _repository.GetByProductIdAsync(request.ProductId.Value, request.WarehouseId, cancellationToken);
}
else if (request.DiscountProductId.HasValue)
{
existingItem = await _repository.GetByDiscountProductIdAsync(request.DiscountProductId.Value, request.WarehouseId, cancellationToken);
}
if (existingItem != null)
{
throw new InvalidOperationException("Inventory item already exists for this product in this warehouse");
}
var productType = request.ProductId.HasValue ? ProductType.RegularProduct : ProductType.DiscountProduct;
var inventoryItem = new InventoryItem
{
ProductId = request.ProductId,
DiscountProductId = request.DiscountProductId,
ProductType = productType,
WarehouseId = request.WarehouseId,
Quantity = request.Quantity,
LowStockThreshold = request.MinQuantity,
MaxStockLevel = request.MaxQuantity,
ReservedQuantity = 0
};
var createdItem = await _repository.AddAsync(inventoryItem, cancellationToken);
// ثبت حرکت موجودی اولیه اگر موجودی اولیه بیشتر از صفر باشد
if (request.Quantity > 0)
{
var stockMovement = new StockMovement
{
InventoryItemId = createdItem.Id,
MovementType = StockMovementType.InitialStock,
Quantity = request.Quantity,
Note = "Initial stock creation",
ReferenceNumber = $"INIT-{createdItem.Id}"
};
await _stockMovementRepository.AddAsync(stockMovement, cancellationToken);
}
return createdItem.Id;
}
}
/// <summary>
/// Handler برای آپدیت آیتم موجودی
/// </summary>
public class UpdateInventoryItemCommandHandler : IRequestHandler<UpdateInventoryItemCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public UpdateInventoryItemCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(UpdateInventoryItemCommand request, CancellationToken cancellationToken)
{
var inventoryItem = await _repository.GetByIdAsync(request.Id, cancellationToken);
if (inventoryItem == null)
{
return false;
}
if (request.WarehouseId.HasValue && request.WarehouseId.Value != inventoryItem.WarehouseId)
{
inventoryItem.WarehouseId = request.WarehouseId.Value;
}
if (request.Quantity.HasValue)
{
inventoryItem.Quantity = request.Quantity.Value;
}
if (request.MinQuantity.HasValue)
{
inventoryItem.LowStockThreshold = request.MinQuantity.Value;
}
if (request.MaxQuantity.HasValue)
{
inventoryItem.MaxStockLevel = request.MaxQuantity.Value;
}
await _repository.UpdateAsync(inventoryItem, cancellationToken);
return true;
}
}
/// <summary>
/// Handler برای آپدیت موجودی
/// </summary>
public class UpdateInventoryQuantityCommandHandler : IRequestHandler<UpdateInventoryQuantityCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public UpdateInventoryQuantityCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(UpdateInventoryQuantityCommand request, CancellationToken cancellationToken)
{
var inventoryItem = await _repository.GetByIdAsync(request.Id, cancellationToken);
if (inventoryItem == null)
{
return false;
}
var quantityChange = request.NewQuantity - inventoryItem.Quantity;
var movementType = quantityChange >= 0 ? StockMovementType.AdjustmentPlus : StockMovementType.AdjustmentMinus;
var result = await _repository.UpdateQuantityAsync(
request.Id,
quantityChange,
movementType,
note: request.Note,
referenceNumber: request.ReferenceNumber,
performedByUserId: request.PerformedByUserId,
cancellationToken: cancellationToken);
return result;
}
}
/// <summary>
/// Handler برای رزرو موجودی
/// </summary>
public class ReserveInventoryCommandHandler : IRequestHandler<ReserveInventoryCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public ReserveInventoryCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(ReserveInventoryCommand request, CancellationToken cancellationToken)
{
return await _repository.ReserveQuantityAsync(
request.Id,
request.Quantity,
referenceNumber: request.ReferenceNumber,
orderId: request.OrderId,
discountOrderId: request.DiscountOrderId,
performedByUserId: request.PerformedByUserId,
cancellationToken: cancellationToken);
}
}
/// <summary>
/// Handler برای آزاد کردن موجودی رزرو شده
/// </summary>
public class ReleaseReservedInventoryCommandHandler : IRequestHandler<ReleaseReservedInventoryCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public ReleaseReservedInventoryCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(ReleaseReservedInventoryCommand request, CancellationToken cancellationToken)
{
return await _repository.ReleaseReservedQuantityAsync(
request.Id,
request.Quantity,
referenceNumber: request.ReferenceNumber,
orderId: request.OrderId,
discountOrderId: request.DiscountOrderId,
performedByUserId: request.PerformedByUserId,
cancellationToken: cancellationToken);
}
}
/// <summary>
/// Handler برای کم کردن موجودی
/// </summary>
public class ReduceInventoryCommandHandler : IRequestHandler<ReduceInventoryCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public ReduceInventoryCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(ReduceInventoryCommand request, CancellationToken cancellationToken)
{
return await _repository.UpdateQuantityAsync(
request.Id,
-request.Quantity,
StockMovementType.Sale,
referenceNumber: request.ReferenceNumber,
orderId: request.OrderId,
discountOrderId: request.DiscountOrderId,
performedByUserId: request.PerformedByUserId,
cancellationToken: cancellationToken);
}
}
/// <summary>
/// Handler برای اضافه کردن موجودی
/// </summary>
public class IncreaseInventoryCommandHandler : IRequestHandler<IncreaseInventoryCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public IncreaseInventoryCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(IncreaseInventoryCommand request, CancellationToken cancellationToken)
{
return await _repository.UpdateQuantityAsync(
request.Id,
request.Quantity,
StockMovementType.Restock,
note: request.Note,
referenceNumber: request.ReferenceNumber,
performedByUserId: request.PerformedByUserId,
cancellationToken: cancellationToken);
}
}
/// <summary>
/// Handler برای حذف آیتم موجودی
/// </summary>
public class DeleteInventoryItemCommandHandler : IRequestHandler<DeleteInventoryItemCommand, bool>
{
private readonly IInventoryItemRepository _repository;
public DeleteInventoryItemCommandHandler(IInventoryItemRepository repository)
{
_repository = repository;
}
public async Task<bool> Handle(DeleteInventoryItemCommand request, CancellationToken cancellationToken)
{
await _repository.DeleteAsync(request.Id, cancellationToken);
return true;
}
}