feat: Implement inventory and warehouse management features
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2m33s

- Add GetLowStockItemsResponseDto and LowStockItemDto for low stock item queries.
- Create CreateStockMovementCommand and its handler for managing stock movements.
- Implement CreateStockMovementCommandValidator for validating stock movement commands.
- Add GetStockMovementsQuery and its handler to retrieve stock movement records.
- Create GetStockMovementsResponseDto and StockMovementListDto for stock movement responses.
- Implement GetStockMovementsByInventoryItemQuery and its handler for fetching movements by inventory item.
- Add CreateWarehouseCommand and its handler for creating new warehouses.
- Implement CreateWarehouseCommandValidator for warehouse creation validation.
- Add DeleteWarehouseCommand and its handler for removing warehouses.
- Implement SetDefaultWarehouseCommand and its handler for setting a default warehouse.
- Create UpdateWarehouseCommand and its handler for updating warehouse details.
- Implement GetAllWarehousesQuery and its handler to retrieve all warehouses.
- Add GetWarehouseQuery and its handler for fetching a specific warehouse by ID.
- Implement SearchWarehousesQuery and its handler for searching warehouses based on criteria.
This commit is contained in:
masoodafar-web
2026-01-03 11:59:08 +03:30
parent 8c3d710253
commit dde4c68b2f
102 changed files with 2715 additions and 3721 deletions
@@ -0,0 +1,24 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem;
/// <summary>
/// Command برای ایجاد آیتم موجودی جدید
/// </summary>
public record CreateInventoryItemCommand : IRequest<CreateInventoryItemResponseDto>
{
/// <summary>شناسه محصول عادی</summary>
public long? ProductId { get; init; }
/// <summary>شناسه محصول تخفیفی</summary>
public long? DiscountProductId { get; init; }
/// <summary>شناسه انبار</summary>
public long WarehouseId { get; init; }
/// <summary>تعداد موجودی</summary>
public int Quantity { get; init; }
/// <summary>حداقل موجودی (هشدار)</summary>
public int MinQuantity { get; init; }
/// <summary>حداکثر موجودی</summary>
public int MaxQuantity { get; init; }
/// <summary>فعال؟</summary>
public bool IsActive { get; init; } = true;
}
@@ -0,0 +1,84 @@
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem;
public class CreateInventoryItemCommandHandler : IRequestHandler<CreateInventoryItemCommand, CreateInventoryItemResponseDto>
{
private readonly IApplicationDbContext _context;
public CreateInventoryItemCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<CreateInventoryItemResponseDto> 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");
}
// بررسی وجود آیتم موجودی قبلی برای همین محصول در همین انبار
bool existingItem;
if (request.ProductId.HasValue)
{
existingItem = await _context.InventoryItems
.AnyAsync(i => i.ProductId == request.ProductId.Value && i.WarehouseId == request.WarehouseId, cancellationToken);
}
else
{
existingItem = await _context.InventoryItems
.AnyAsync(i => i.DiscountProductId == request.DiscountProductId!.Value && i.WarehouseId == request.WarehouseId, cancellationToken);
}
if (existingItem)
{
throw new InvalidOperationException("Inventory item already exists for this product in this warehouse");
}
var productType = request.ProductId.HasValue ? ProductType.RegularProduct : ProductType.DiscountProduct;
var entity = new InventoryItem
{
ProductId = request.ProductId,
DiscountProductId = request.DiscountProductId,
ProductType = productType,
WarehouseId = request.WarehouseId,
Quantity = request.Quantity,
LowStockThreshold = request.MinQuantity,
MaxStockLevel = request.MaxQuantity,
ReservedQuantity = 0
};
await _context.InventoryItems.AddAsync(entity, cancellationToken);
// ثبت حرکت موجودی اولیه اگر موجودی اولیه بیشتر از صفر باشد
if (request.Quantity > 0)
{
var stockMovement = new StockMovement
{
InventoryItemId = entity.Id,
MovementType = StockMovementType.InitialStock,
Quantity = request.Quantity,
QuantityBefore = 0,
QuantityAfter = request.Quantity,
Note = "Initial stock creation",
ReferenceNumber = $"INIT-{entity.Id}"
};
await _context.StockMovements.AddAsync(stockMovement, cancellationToken);
}
await _context.SaveChangesAsync(cancellationToken);
return new CreateInventoryItemResponseDto { Id = entity.Id };
}
}
@@ -0,0 +1,27 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem;
public class CreateInventoryItemCommandValidator : AbstractValidator<CreateInventoryItemCommand>
{
public CreateInventoryItemCommandValidator()
{
RuleFor(x => x.WarehouseId)
.GreaterThan(0).WithMessage("شناسه انبار معتبر نیست");
RuleFor(x => x.Quantity)
.GreaterThanOrEqualTo(0).WithMessage("تعداد موجودی نمی‌تواند منفی باشد");
RuleFor(x => x.MinQuantity)
.GreaterThanOrEqualTo(0).WithMessage("حداقل موجودی نمی‌تواند منفی باشد");
RuleFor(x => x.MaxQuantity)
.GreaterThanOrEqualTo(0).WithMessage("حداکثر موجودی نمی‌تواند منفی باشد");
RuleFor(x => x)
.Must(x => x.ProductId.HasValue || x.DiscountProductId.HasValue)
.WithMessage("حداقل یکی از شناسه محصول یا شناسه محصول تخفیفی باید مشخص شود");
RuleFor(x => x)
.Must(x => !(x.ProductId.HasValue && x.DiscountProductId.HasValue))
.WithMessage("فقط یکی از شناسه محصول یا شناسه محصول تخفیفی باید مشخص شود");
}
}
@@ -0,0 +1,7 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem;
public class CreateInventoryItemResponseDto
{
/// <summary>شناسه آیتم موجودی ایجاد شده</summary>
public long Id { get; set; }
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
/// <summary>
/// Command برای حذف آیتم موجودی
/// </summary>
public record DeleteInventoryItemCommand(long Id) : IRequest<DeleteInventoryItemResponseDto>;
@@ -0,0 +1,35 @@
using CMSMicroservice.Application.Common.Exceptions;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
public class DeleteInventoryItemCommandHandler : IRequestHandler<DeleteInventoryItemCommand, DeleteInventoryItemResponseDto>
{
private readonly IApplicationDbContext _context;
public DeleteInventoryItemCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<DeleteInventoryItemResponseDto> Handle(DeleteInventoryItemCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
// بررسی اینکه موجودی رزرو نداشته باشد
if (item.ReservedQuantity > 0)
{
throw new InvalidOperationException("Cannot delete inventory item with reserved quantity");
}
_context.InventoryItems.Remove(item);
await _context.SaveChangesAsync(cancellationToken);
return new DeleteInventoryItemResponseDto { Success = true };
}
}
@@ -0,0 +1,10 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
public class DeleteInventoryItemCommandValidator : AbstractValidator<DeleteInventoryItemCommand>
{
public DeleteInventoryItemCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
}
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
public class DeleteInventoryItemResponseDto
{
public bool Success { get; set; }
}
@@ -0,0 +1,18 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
/// <summary>
/// Command برای اضافه کردن موجودی (خرید)
/// </summary>
public record IncreaseInventoryCommand : IRequest<IncreaseInventoryResponseDto>
{
/// <summary>شناسه آیتم موجودی</summary>
public long Id { get; init; }
/// <summary>تعداد افزایش</summary>
public int Quantity { get; init; }
/// <summary>شماره مرجع</summary>
public string? ReferenceNumber { get; init; }
/// <summary>شناسه کاربر انجام‌دهنده</summary>
public long? PerformedByUserId { get; init; }
/// <summary>یادداشت</summary>
public string? Note { get; init; }
}
@@ -0,0 +1,52 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
public class IncreaseInventoryCommandHandler : IRequestHandler<IncreaseInventoryCommand, IncreaseInventoryResponseDto>
{
private readonly IApplicationDbContext _context;
public IncreaseInventoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<IncreaseInventoryResponseDto> Handle(IncreaseInventoryCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
var previousQuantity = item.Quantity;
item.Quantity += request.Quantity;
item.LastRestockedAt = DateTime.UtcNow;
// ثبت حرکت موجودی
var stockMovement = new StockMovement
{
InventoryItemId = item.Id,
MovementType = StockMovementType.Restock,
Quantity = request.Quantity,
QuantityBefore = previousQuantity,
QuantityAfter = item.Quantity,
Note = request.Note ?? "Stock increased",
ReferenceNumber = request.ReferenceNumber ?? $"ADD-{DateTime.UtcNow:yyyyMMddHHmmss}",
PerformedByUserId = request.PerformedByUserId
};
await _context.StockMovements.AddAsync(stockMovement, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return new IncreaseInventoryResponseDto
{
Success = true,
NewQuantity = item.Quantity
};
}
}
@@ -0,0 +1,13 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
public class IncreaseInventoryCommandValidator : AbstractValidator<IncreaseInventoryCommand>
{
public IncreaseInventoryCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
RuleFor(x => x.Quantity)
.GreaterThan(0).WithMessage("تعداد افزایش باید بیشتر از صفر باشد");
}
}
@@ -0,0 +1,7 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
public class IncreaseInventoryResponseDto
{
public bool Success { get; set; }
public int NewQuantity { get; set; }
}
@@ -0,0 +1,22 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
/// <summary>
/// Command برای کم کردن موجودی (فروش)
/// </summary>
public record ReduceInventoryCommand : IRequest<ReduceInventoryResponseDto>
{
/// <summary>شناسه آیتم موجودی</summary>
public long Id { get; init; }
/// <summary>تعداد کاهش</summary>
public int Quantity { get; init; }
/// <summary>شناسه سفارش عادی</summary>
public long? OrderId { get; init; }
/// <summary>شناسه سفارش تخفیفی</summary>
public long? DiscountOrderId { get; init; }
/// <summary>شماره مرجع</summary>
public string? ReferenceNumber { get; init; }
/// <summary>شناسه کاربر انجام‌دهنده</summary>
public long? PerformedByUserId { get; init; }
/// <summary>آیا از موجودی رزرو شده کم شود؟</summary>
public bool FromReserved { get; init; } = true;
}
@@ -0,0 +1,72 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
public class ReduceInventoryCommandHandler : IRequestHandler<ReduceInventoryCommand, ReduceInventoryResponseDto>
{
private readonly IApplicationDbContext _context;
public ReduceInventoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<ReduceInventoryResponseDto> Handle(ReduceInventoryCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
var previousQuantity = item.Quantity;
if (request.FromReserved)
{
// کم کردن از موجودی رزرو شده
if (item.ReservedQuantity < request.Quantity)
{
throw new InvalidOperationException($"Insufficient reserved stock. Reserved: {item.ReservedQuantity}, Requested: {request.Quantity}");
}
item.ReservedQuantity -= request.Quantity;
item.Quantity -= request.Quantity;
}
else
{
// کم کردن مستقیم از موجودی
if (item.AvailableQuantity < request.Quantity)
{
throw new InvalidOperationException($"Insufficient available stock. Available: {item.AvailableQuantity}, Requested: {request.Quantity}");
}
item.Quantity -= request.Quantity;
}
item.LastSoldAt = DateTime.UtcNow;
// ثبت حرکت موجودی
var stockMovement = new StockMovement
{
InventoryItemId = item.Id,
MovementType = StockMovementType.Sale,
Quantity = request.Quantity,
QuantityBefore = previousQuantity,
QuantityAfter = item.Quantity,
Note = "Sale confirmed",
ReferenceNumber = request.ReferenceNumber ?? $"SALE-{DateTime.UtcNow:yyyyMMddHHmmss}",
OrderId = request.OrderId,
DiscountOrderId = request.DiscountOrderId,
PerformedByUserId = request.PerformedByUserId
};
await _context.StockMovements.AddAsync(stockMovement, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return new ReduceInventoryResponseDto { Success = true };
}
}
@@ -0,0 +1,13 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
public class ReduceInventoryCommandValidator : AbstractValidator<ReduceInventoryCommand>
{
public ReduceInventoryCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
RuleFor(x => x.Quantity)
.GreaterThan(0).WithMessage("تعداد کاهش باید بیشتر از صفر باشد");
}
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
public class ReduceInventoryResponseDto
{
public bool Success { get; set; }
}
@@ -0,0 +1,20 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
/// <summary>
/// Command برای آزاد کردن موجودی رزرو شده
/// </summary>
public record ReleaseReservedInventoryCommand : IRequest<ReleaseReservedInventoryResponseDto>
{
/// <summary>شناسه آیتم موجودی</summary>
public long Id { get; init; }
/// <summary>تعداد آزادسازی</summary>
public int Quantity { get; init; }
/// <summary>شناسه سفارش عادی</summary>
public long? OrderId { get; init; }
/// <summary>شناسه سفارش تخفیفی</summary>
public long? DiscountOrderId { get; init; }
/// <summary>شماره مرجع</summary>
public string? ReferenceNumber { get; init; }
/// <summary>شناسه کاربر انجام‌دهنده</summary>
public long? PerformedByUserId { get; init; }
}
@@ -0,0 +1,53 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
public class ReleaseReservedInventoryCommandHandler : IRequestHandler<ReleaseReservedInventoryCommand, ReleaseReservedInventoryResponseDto>
{
private readonly IApplicationDbContext _context;
public ReleaseReservedInventoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<ReleaseReservedInventoryResponseDto> Handle(ReleaseReservedInventoryCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
if (item.ReservedQuantity < request.Quantity)
{
throw new InvalidOperationException($"Cannot release more than reserved. Reserved: {item.ReservedQuantity}, Requested: {request.Quantity}");
}
item.ReservedQuantity -= request.Quantity;
// ثبت حرکت موجودی
var stockMovement = new StockMovement
{
InventoryItemId = item.Id,
MovementType = StockMovementType.Released,
Quantity = request.Quantity,
QuantityBefore = item.Quantity,
QuantityAfter = item.Quantity,
Note = "Reservation released",
ReferenceNumber = request.ReferenceNumber ?? $"REL-{DateTime.UtcNow:yyyyMMddHHmmss}",
OrderId = request.OrderId,
DiscountOrderId = request.DiscountOrderId,
PerformedByUserId = request.PerformedByUserId
};
await _context.StockMovements.AddAsync(stockMovement, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return new ReleaseReservedInventoryResponseDto { Success = true };
}
}
@@ -0,0 +1,13 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
public class ReleaseReservedInventoryCommandValidator : AbstractValidator<ReleaseReservedInventoryCommand>
{
public ReleaseReservedInventoryCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
RuleFor(x => x.Quantity)
.GreaterThan(0).WithMessage("تعداد آزادسازی باید بیشتر از صفر باشد");
}
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
public class ReleaseReservedInventoryResponseDto
{
public bool Success { get; set; }
}
@@ -0,0 +1,20 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory;
/// <summary>
/// Command برای رزرو کردن موجودی
/// </summary>
public record ReserveInventoryCommand : IRequest<ReserveInventoryResponseDto>
{
/// <summary>شناسه آیتم موجودی</summary>
public long Id { get; init; }
/// <summary>تعداد رزرو</summary>
public int Quantity { get; init; }
/// <summary>شناسه سفارش عادی</summary>
public long? OrderId { get; init; }
/// <summary>شناسه سفارش تخفیفی</summary>
public long? DiscountOrderId { get; init; }
/// <summary>شماره مرجع</summary>
public string? ReferenceNumber { get; init; }
/// <summary>شناسه کاربر انجام‌دهنده</summary>
public long? PerformedByUserId { get; init; }
}
@@ -0,0 +1,65 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory;
public class ReserveInventoryCommandHandler : IRequestHandler<ReserveInventoryCommand, ReserveInventoryResponseDto>
{
private readonly IApplicationDbContext _context;
public ReserveInventoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<ReserveInventoryResponseDto> Handle(ReserveInventoryCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
var availableQuantity = item.AvailableQuantity;
if (availableQuantity < request.Quantity)
{
return new ReserveInventoryResponseDto
{
Success = false,
Message = $"Insufficient stock. Available: {availableQuantity}, Requested: {request.Quantity}",
AvailableQuantity = availableQuantity
};
}
item.ReservedQuantity += request.Quantity;
// ثبت حرکت موجودی
var stockMovement = new StockMovement
{
InventoryItemId = item.Id,
MovementType = StockMovementType.Reserved,
Quantity = request.Quantity,
QuantityBefore = item.Quantity,
QuantityAfter = item.Quantity, // موجودی اصلی تغییر نمی‌کند، فقط رزرو می‌شود
Note = "Stock reserved",
ReferenceNumber = request.ReferenceNumber ?? $"RSV-{DateTime.UtcNow:yyyyMMddHHmmss}",
OrderId = request.OrderId,
DiscountOrderId = request.DiscountOrderId,
PerformedByUserId = request.PerformedByUserId
};
await _context.StockMovements.AddAsync(stockMovement, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return new ReserveInventoryResponseDto
{
Success = true,
Message = "Stock reserved successfully",
AvailableQuantity = item.AvailableQuantity
};
}
}
@@ -0,0 +1,13 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory;
public class ReserveInventoryCommandValidator : AbstractValidator<ReserveInventoryCommand>
{
public ReserveInventoryCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
RuleFor(x => x.Quantity)
.GreaterThan(0).WithMessage("تعداد رزرو باید بیشتر از صفر باشد");
}
}
@@ -0,0 +1,8 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory;
public class ReserveInventoryResponseDto
{
public bool Success { get; set; }
public string? Message { get; set; }
public int AvailableQuantity { get; set; }
}
@@ -0,0 +1,16 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem;
/// <summary>
/// Command برای آپدیت آیتم موجودی
/// </summary>
public record UpdateInventoryItemCommand : IRequest<UpdateInventoryItemResponseDto>
{
/// <summary>شناسه آیتم موجودی</summary>
public long Id { get; init; }
/// <summary>حداقل موجودی (LowStockThreshold)</summary>
public int? MinimumStock { get; init; }
/// <summary>حداکثر موجودی (MaxStockLevel)</summary>
public int? MaximumStock { get; init; }
/// <summary>نقطه سفارش مجدد</summary>
public int? ReorderPoint { get; init; }
}
@@ -0,0 +1,54 @@
using CMSMicroservice.Application.Common.Exceptions;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem;
public class UpdateInventoryItemCommandHandler : IRequestHandler<UpdateInventoryItemCommand, UpdateInventoryItemResponseDto>
{
private readonly IApplicationDbContext _context;
public UpdateInventoryItemCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<UpdateInventoryItemResponseDto> Handle(UpdateInventoryItemCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
if (request.MinimumStock.HasValue)
{
item.LowStockThreshold = request.MinimumStock.Value;
}
if (request.MaximumStock.HasValue)
{
item.MaxStockLevel = request.MaximumStock.Value;
}
if (request.ReorderPoint.HasValue)
{
item.ReorderPoint = request.ReorderPoint.Value;
}
await _context.SaveChangesAsync(cancellationToken);
return new UpdateInventoryItemResponseDto
{
Id = item.Id,
ProductId = item.ProductId ?? item.DiscountProductId ?? 0,
WarehouseId = item.WarehouseId,
Quantity = item.Quantity,
ReservedQuantity = item.ReservedQuantity,
AvailableQuantity = item.AvailableQuantity,
MinimumStock = item.LowStockThreshold,
MaximumStock = item.MaxStockLevel,
ReorderPoint = item.ReorderPoint
};
}
}
@@ -0,0 +1,22 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem;
public class UpdateInventoryItemCommandValidator : AbstractValidator<UpdateInventoryItemCommand>
{
public UpdateInventoryItemCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
RuleFor(x => x.MinimumStock)
.GreaterThanOrEqualTo(0).When(x => x.MinimumStock.HasValue)
.WithMessage("حداقل موجودی نمی‌تواند منفی باشد");
RuleFor(x => x.MaximumStock)
.GreaterThanOrEqualTo(0).When(x => x.MaximumStock.HasValue)
.WithMessage("حداکثر موجودی نمی‌تواند منفی باشد");
RuleFor(x => x.ReorderPoint)
.GreaterThanOrEqualTo(0).When(x => x.ReorderPoint.HasValue)
.WithMessage("نقطه سفارش مجدد نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,14 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem;
public class UpdateInventoryItemResponseDto
{
public long Id { get; set; }
public long ProductId { get; set; }
public long WarehouseId { get; set; }
public int Quantity { get; set; }
public int ReservedQuantity { get; set; }
public int AvailableQuantity { get; set; }
public int MinimumStock { get; set; }
public int MaximumStock { get; set; }
public int ReorderPoint { get; set; }
}
@@ -0,0 +1,18 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity;
/// <summary>
/// Command برای آپدیت کردن موجودی یک آیتم
/// </summary>
public record UpdateInventoryQuantityCommand : IRequest<UpdateInventoryQuantityResponseDto>
{
/// <summary>شناسه آیتم موجودی</summary>
public long Id { get; init; }
/// <summary>تعداد جدید موجودی</summary>
public int NewQuantity { get; init; }
/// <summary>شماره مرجع</summary>
public string? ReferenceNumber { get; init; }
/// <summary>شناسه کاربر انجام‌دهنده</summary>
public long? PerformedByUserId { get; init; }
/// <summary>یادداشت</summary>
public string? Note { get; init; }
}
@@ -0,0 +1,56 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity;
public class UpdateInventoryQuantityCommandHandler : IRequestHandler<UpdateInventoryQuantityCommand, UpdateInventoryQuantityResponseDto>
{
private readonly IApplicationDbContext _context;
public UpdateInventoryQuantityCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<UpdateInventoryQuantityResponseDto> Handle(UpdateInventoryQuantityCommand request, CancellationToken cancellationToken)
{
var item = await _context.InventoryItems
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
if (item == null)
{
throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id);
}
var previousQuantity = item.Quantity;
var difference = request.NewQuantity - previousQuantity;
item.Quantity = request.NewQuantity;
// ثبت حرکت موجودی
var movementType = difference > 0 ? StockMovementType.AdjustmentPlus : StockMovementType.AdjustmentMinus;
var stockMovement = new StockMovement
{
InventoryItemId = item.Id,
MovementType = movementType,
Quantity = Math.Abs(difference),
QuantityBefore = previousQuantity,
QuantityAfter = request.NewQuantity,
Note = request.Note ?? "Manual quantity adjustment",
ReferenceNumber = request.ReferenceNumber ?? $"ADJ-{DateTime.UtcNow:yyyyMMddHHmmss}",
PerformedByUserId = request.PerformedByUserId
};
await _context.StockMovements.AddAsync(stockMovement, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return new UpdateInventoryQuantityResponseDto
{
Success = true,
PreviousQuantity = previousQuantity,
NewQuantity = request.NewQuantity
};
}
}
@@ -0,0 +1,13 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity;
public class UpdateInventoryQuantityCommandValidator : AbstractValidator<UpdateInventoryQuantityCommand>
{
public UpdateInventoryQuantityCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
RuleFor(x => x.NewQuantity)
.GreaterThanOrEqualTo(0).WithMessage("تعداد موجودی نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,8 @@
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity;
public class UpdateInventoryQuantityResponseDto
{
public bool Success { get; set; }
public int PreviousQuantity { get; set; }
public int NewQuantity { get; set; }
}