feat: Implement inventory and warehouse management features
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2m33s
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:
+24
@@ -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;
|
||||
}
|
||||
+84
@@ -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 };
|
||||
}
|
||||
}
|
||||
+27
@@ -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("فقط یکی از شناسه محصول یا شناسه محصول تخفیفی باید مشخص شود");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem;
|
||||
|
||||
public class CreateInventoryItemResponseDto
|
||||
{
|
||||
/// <summary>شناسه آیتم موجودی ایجاد شده</summary>
|
||||
public long Id { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای حذف آیتم موجودی
|
||||
/// </summary>
|
||||
public record DeleteInventoryItemCommand(long Id) : IRequest<DeleteInventoryItemResponseDto>;
|
||||
+35
@@ -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 };
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
|
||||
|
||||
public class DeleteInventoryItemCommandValidator : AbstractValidator<DeleteInventoryItemCommand>
|
||||
{
|
||||
public DeleteInventoryItemCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست");
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem;
|
||||
|
||||
public class DeleteInventoryItemResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
+18
@@ -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; }
|
||||
}
|
||||
+52
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
+13
@@ -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("تعداد افزایش باید بیشتر از صفر باشد");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
|
||||
|
||||
public class IncreaseInventoryResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public int NewQuantity { get; set; }
|
||||
}
|
||||
+22
@@ -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;
|
||||
}
|
||||
+72
@@ -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 };
|
||||
}
|
||||
}
|
||||
+13
@@ -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("تعداد کاهش باید بیشتر از صفر باشد");
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
|
||||
|
||||
public class ReduceInventoryResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
+20
@@ -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; }
|
||||
}
|
||||
+53
@@ -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 };
|
||||
}
|
||||
}
|
||||
+13
@@ -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("تعداد آزادسازی باید بیشتر از صفر باشد");
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
|
||||
|
||||
public class ReleaseReservedInventoryResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
+20
@@ -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; }
|
||||
}
|
||||
+65
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
+13
@@ -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("تعداد رزرو باید بیشتر از صفر باشد");
|
||||
}
|
||||
}
|
||||
+8
@@ -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; }
|
||||
}
|
||||
+16
@@ -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; }
|
||||
}
|
||||
+54
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
+22
@@ -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("نقطه سفارش مجدد نمیتواند منفی باشد");
|
||||
}
|
||||
}
|
||||
+14
@@ -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; }
|
||||
}
|
||||
+18
@@ -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; }
|
||||
}
|
||||
+56
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
+13
@@ -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("تعداد موجودی نمیتواند منفی باشد");
|
||||
}
|
||||
}
|
||||
+8
@@ -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; }
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای جستجوی آیتم های موجودی
|
||||
/// </summary>
|
||||
public record GetAllInventoryItemsQuery : IRequest<GetAllInventoryItemsResponseDto>
|
||||
{
|
||||
public long? WarehouseId { get; init; }
|
||||
public long? ProductId { get; init; }
|
||||
public long? DiscountProductId { get; init; }
|
||||
public ProductType? ProductType { get; init; }
|
||||
public string? SearchTerm { get; init; }
|
||||
public bool? IsActive { get; init; }
|
||||
public bool? IsLowStock { get; init; }
|
||||
public bool? IsOutOfStock { get; init; }
|
||||
public int Skip { get; init; } = 0;
|
||||
public int Take { get; init; } = 50;
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems;
|
||||
|
||||
public class GetAllInventoryItemsQueryHandler : IRequestHandler<GetAllInventoryItemsQuery, GetAllInventoryItemsResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllInventoryItemsQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetAllInventoryItemsResponseDto> Handle(GetAllInventoryItemsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.InventoryItems
|
||||
.AsNoTracking()
|
||||
.Include(i => i.Warehouse)
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.AsQueryable();
|
||||
|
||||
// فیلترها
|
||||
if (request.WarehouseId.HasValue)
|
||||
{
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
}
|
||||
|
||||
if (request.ProductId.HasValue)
|
||||
{
|
||||
query = query.Where(i => i.ProductId == request.ProductId.Value);
|
||||
}
|
||||
|
||||
if (request.DiscountProductId.HasValue)
|
||||
{
|
||||
query = query.Where(i => i.DiscountProductId == request.DiscountProductId.Value);
|
||||
}
|
||||
|
||||
if (request.ProductType.HasValue)
|
||||
{
|
||||
query = query.Where(i => i.ProductType == request.ProductType.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.SearchTerm))
|
||||
{
|
||||
query = query.Where(i =>
|
||||
(i.Product != null && i.Product.Title.Contains(request.SearchTerm)) ||
|
||||
(i.DiscountProduct != null && i.DiscountProduct.Title.Contains(request.SearchTerm)));
|
||||
}
|
||||
|
||||
if (request.IsActive.HasValue)
|
||||
{
|
||||
// فعلاً بدون فیلتر IsActive چون entity این فیلد رو نداره
|
||||
}
|
||||
|
||||
if (request.IsLowStock == true)
|
||||
{
|
||||
query = query.Where(i => i.Quantity <= i.LowStockThreshold);
|
||||
}
|
||||
|
||||
if (request.IsOutOfStock == true)
|
||||
{
|
||||
query = query.Where(i => i.AvailableQuantity <= 0);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var items = await query
|
||||
.OrderByDescending(i => i.Created)
|
||||
.Skip(request.Skip)
|
||||
.Take(request.Take)
|
||||
.Select(i => new InventoryItemListDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ProductId = i.ProductId,
|
||||
DiscountProductId = i.DiscountProductId,
|
||||
ProductType = i.ProductType,
|
||||
WarehouseId = i.WarehouseId,
|
||||
WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null,
|
||||
ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null),
|
||||
ProductPrice = i.Product != null ? i.Product.Price : (i.DiscountProduct != null ? i.DiscountProduct.Price : 0),
|
||||
Quantity = i.Quantity,
|
||||
ReservedQuantity = i.ReservedQuantity,
|
||||
AvailableQuantity = i.AvailableQuantity,
|
||||
LowStockThreshold = i.LowStockThreshold,
|
||||
MaxStockLevel = i.MaxStockLevel,
|
||||
LastRestockedAt = i.LastRestockedAt,
|
||||
LastSoldAt = i.LastSoldAt,
|
||||
Created = i.Created
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetAllInventoryItemsResponseDto
|
||||
{
|
||||
Items = items,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems;
|
||||
|
||||
public class GetAllInventoryItemsResponseDto
|
||||
{
|
||||
public List<InventoryItemListDto> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class InventoryItemListDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? ProductId { get; set; }
|
||||
public long? DiscountProductId { get; set; }
|
||||
public ProductType ProductType { get; set; }
|
||||
public long WarehouseId { get; set; }
|
||||
public string? WarehouseName { get; set; }
|
||||
public string? ProductTitle { get; set; }
|
||||
public decimal? ProductPrice { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public int ReservedQuantity { get; set; }
|
||||
public int AvailableQuantity { get; set; }
|
||||
public int LowStockThreshold { get; set; }
|
||||
public int MaxStockLevel { get; set; }
|
||||
public DateTime? LastRestockedAt { get; set; }
|
||||
public DateTime? LastSoldAt { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت آیتم موجودی با ProductId یا DiscountProductId
|
||||
/// </summary>
|
||||
public record GetInventoryByProductQuery : IRequest<GetInventoryByProductResponseDto?>
|
||||
{
|
||||
/// <summary>شناسه محصول</summary>
|
||||
public long ProductId { get; init; }
|
||||
/// <summary>نوع محصول</summary>
|
||||
public ProductType ProductType { get; init; }
|
||||
/// <summary>شناسه انبار (اختیاری)</summary>
|
||||
public long? WarehouseId { get; init; }
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct;
|
||||
|
||||
public class GetInventoryByProductQueryHandler : IRequestHandler<GetInventoryByProductQuery, GetInventoryByProductResponseDto?>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetInventoryByProductQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetInventoryByProductResponseDto?> Handle(GetInventoryByProductQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.InventoryItems
|
||||
.AsNoTracking()
|
||||
.Include(i => i.Warehouse)
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.AsQueryable();
|
||||
|
||||
// فیلتر بر اساس نوع محصول
|
||||
if (request.ProductType == ProductType.RegularProduct)
|
||||
{
|
||||
query = query.Where(i => i.ProductId == request.ProductId);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = query.Where(i => i.DiscountProductId == request.ProductId);
|
||||
}
|
||||
|
||||
// فیلتر بر اساس انبار (اختیاری)
|
||||
if (request.WarehouseId.HasValue)
|
||||
{
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
}
|
||||
|
||||
var item = await query
|
||||
.Select(i => new GetInventoryByProductResponseDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ProductId = i.ProductId,
|
||||
DiscountProductId = i.DiscountProductId,
|
||||
ProductType = i.ProductType,
|
||||
WarehouseId = i.WarehouseId,
|
||||
WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null,
|
||||
ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null),
|
||||
ProductPrice = i.Product != null ? i.Product.Price : (i.DiscountProduct != null ? i.DiscountProduct.Price : 0),
|
||||
Quantity = i.Quantity,
|
||||
ReservedQuantity = i.ReservedQuantity,
|
||||
AvailableQuantity = i.AvailableQuantity,
|
||||
LowStockThreshold = i.LowStockThreshold,
|
||||
MaxStockLevel = i.MaxStockLevel,
|
||||
LastRestockedAt = i.LastRestockedAt,
|
||||
LastSoldAt = i.LastSoldAt,
|
||||
Created = i.Created
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct;
|
||||
|
||||
public class GetInventoryByProductResponseDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? ProductId { get; set; }
|
||||
public long? DiscountProductId { get; set; }
|
||||
public ProductType ProductType { get; set; }
|
||||
public long WarehouseId { get; set; }
|
||||
public string? WarehouseName { get; set; }
|
||||
public string? ProductTitle { get; set; }
|
||||
public decimal? ProductPrice { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public int ReservedQuantity { get; set; }
|
||||
public int AvailableQuantity { get; set; }
|
||||
public int LowStockThreshold { get; set; }
|
||||
public int MaxStockLevel { get; set; }
|
||||
public DateTime? LastRestockedAt { get; set; }
|
||||
public DateTime? LastSoldAt { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت آیتم موجودی با شناسه
|
||||
/// </summary>
|
||||
public record GetInventoryItemQuery(long Id) : IRequest<GetInventoryItemResponseDto?>;
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem;
|
||||
|
||||
public class GetInventoryItemQueryHandler : IRequestHandler<GetInventoryItemQuery, GetInventoryItemResponseDto?>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetInventoryItemQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetInventoryItemResponseDto?> Handle(GetInventoryItemQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var item = await _context.InventoryItems
|
||||
.AsNoTracking()
|
||||
.Include(i => i.Warehouse)
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => i.Id == request.Id)
|
||||
.Select(i => new GetInventoryItemResponseDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ProductId = i.ProductId,
|
||||
DiscountProductId = i.DiscountProductId,
|
||||
ProductType = i.ProductType,
|
||||
WarehouseId = i.WarehouseId,
|
||||
WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null,
|
||||
ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null),
|
||||
ProductPrice = i.Product != null ? i.Product.Price : (i.DiscountProduct != null ? i.DiscountProduct.Price : 0),
|
||||
Quantity = i.Quantity,
|
||||
ReservedQuantity = i.ReservedQuantity,
|
||||
AvailableQuantity = i.AvailableQuantity,
|
||||
LowStockThreshold = i.LowStockThreshold,
|
||||
MaxStockLevel = i.MaxStockLevel,
|
||||
LastRestockedAt = i.LastRestockedAt,
|
||||
LastSoldAt = i.LastSoldAt,
|
||||
Created = i.Created
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem;
|
||||
|
||||
public class GetInventoryItemResponseDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? ProductId { get; set; }
|
||||
public long? DiscountProductId { get; set; }
|
||||
public ProductType ProductType { get; set; }
|
||||
public long WarehouseId { get; set; }
|
||||
public string? WarehouseName { get; set; }
|
||||
public string? ProductTitle { get; set; }
|
||||
public decimal? ProductPrice { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public int ReservedQuantity { get; set; }
|
||||
public int AvailableQuantity { get; set; }
|
||||
public int LowStockThreshold { get; set; }
|
||||
public int MaxStockLevel { get; set; }
|
||||
public DateTime? LastRestockedAt { get; set; }
|
||||
public DateTime? LastSoldAt { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت آیتم های کم موجود
|
||||
/// </summary>
|
||||
public record GetLowStockItemsQuery : IRequest<GetLowStockItemsResponseDto>
|
||||
{
|
||||
public long? WarehouseId { get; init; }
|
||||
public int Count { get; init; } = 50;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems;
|
||||
|
||||
public class GetLowStockItemsQueryHandler : IRequestHandler<GetLowStockItemsQuery, GetLowStockItemsResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetLowStockItemsQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetLowStockItemsResponseDto> Handle(GetLowStockItemsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.InventoryItems
|
||||
.AsNoTracking()
|
||||
.Include(i => i.Warehouse)
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => i.Quantity <= i.LowStockThreshold);
|
||||
|
||||
if (request.WarehouseId.HasValue)
|
||||
{
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var items = await query
|
||||
.OrderBy(i => i.AvailableQuantity)
|
||||
.Take(request.Count)
|
||||
.Select(i => new LowStockItemDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ProductId = i.ProductId,
|
||||
DiscountProductId = i.DiscountProductId,
|
||||
ProductType = i.ProductType,
|
||||
WarehouseId = i.WarehouseId,
|
||||
WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null,
|
||||
ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null),
|
||||
Quantity = i.Quantity,
|
||||
ReservedQuantity = i.ReservedQuantity,
|
||||
AvailableQuantity = i.AvailableQuantity,
|
||||
LowStockThreshold = i.LowStockThreshold
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetLowStockItemsResponseDto
|
||||
{
|
||||
Items = items,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems;
|
||||
|
||||
public class GetLowStockItemsResponseDto
|
||||
{
|
||||
public List<LowStockItemDto> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class LowStockItemDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? ProductId { get; set; }
|
||||
public long? DiscountProductId { get; set; }
|
||||
public ProductType ProductType { get; set; }
|
||||
public long WarehouseId { get; set; }
|
||||
public string? WarehouseName { get; set; }
|
||||
public string? ProductTitle { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public int ReservedQuantity { get; set; }
|
||||
public int AvailableQuantity { get; set; }
|
||||
public int LowStockThreshold { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user