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:
+18
@@ -0,0 +1,18 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای ایجاد انبار جدید
|
||||
/// </summary>
|
||||
public record CreateWarehouseCommand : IRequest<CreateWarehouseResponseDto>
|
||||
{
|
||||
/// <summary>نام انبار</summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
/// <summary>کد انبار</summary>
|
||||
public string Code { get; init; } = string.Empty;
|
||||
/// <summary>آدرس انبار</summary>
|
||||
public string? Address { get; init; }
|
||||
/// <summary>فعال؟</summary>
|
||||
public bool IsActive { get; init; } = true;
|
||||
/// <summary>پیشفرض؟</summary>
|
||||
public bool IsDefault { get; init; } = false;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse;
|
||||
|
||||
public class CreateWarehouseCommandHandler : IRequestHandler<CreateWarehouseCommand, CreateWarehouseResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public CreateWarehouseCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<CreateWarehouseResponseDto> Handle(CreateWarehouseCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// بررسی تکراری نبودن کد
|
||||
var codeExists = await _context.Warehouses
|
||||
.AnyAsync(w => w.Code == request.Code, cancellationToken);
|
||||
|
||||
if (codeExists)
|
||||
{
|
||||
throw new InvalidOperationException($"Warehouse with code '{request.Code}' already exists");
|
||||
}
|
||||
|
||||
var entity = new Warehouse
|
||||
{
|
||||
Name = request.Name,
|
||||
Code = request.Code,
|
||||
Address = request.Address,
|
||||
IsActive = request.IsActive,
|
||||
IsDefault = request.IsDefault
|
||||
};
|
||||
|
||||
await _context.Warehouses.AddAsync(entity, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new CreateWarehouseResponseDto { Id = entity.Id };
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse;
|
||||
|
||||
public class CreateWarehouseCommandValidator : AbstractValidator<CreateWarehouseCommand>
|
||||
{
|
||||
public CreateWarehouseCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Name)
|
||||
.NotEmpty().WithMessage("نام انبار الزامی است")
|
||||
.MaximumLength(100).WithMessage("نام انبار نباید بیش از 100 کاراکتر باشد");
|
||||
|
||||
RuleFor(x => x.Code)
|
||||
.NotEmpty().WithMessage("کد انبار الزامی است")
|
||||
.MaximumLength(50).WithMessage("کد انبار نباید بیش از 50 کاراکتر باشد");
|
||||
|
||||
RuleFor(x => x.Address)
|
||||
.MaximumLength(500).WithMessage("آدرس نباید بیش از 500 کاراکتر باشد");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse;
|
||||
|
||||
public class CreateWarehouseResponseDto
|
||||
{
|
||||
/// <summary>شناسه انبار ایجاد شده</summary>
|
||||
public long Id { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای حذف انبار
|
||||
/// </summary>
|
||||
public record DeleteWarehouseCommand(long Id) : IRequest<DeleteWarehouseResponseDto>;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse;
|
||||
|
||||
public class DeleteWarehouseCommandHandler : IRequestHandler<DeleteWarehouseCommand, DeleteWarehouseResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public DeleteWarehouseCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<DeleteWarehouseResponseDto> Handle(DeleteWarehouseCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var warehouse = await _context.Warehouses
|
||||
.FirstOrDefaultAsync(w => w.Id == request.Id, cancellationToken);
|
||||
|
||||
if (warehouse == null)
|
||||
{
|
||||
throw new NotFoundException(nameof(Domain.Entities.Warehouse), request.Id);
|
||||
}
|
||||
|
||||
// بررسی اینکه انبار دارای موجودی نباشد
|
||||
var hasInventory = await _context.InventoryItems
|
||||
.AnyAsync(i => i.WarehouseId == request.Id, cancellationToken);
|
||||
|
||||
if (hasInventory)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot delete warehouse with existing inventory items");
|
||||
}
|
||||
|
||||
_context.Warehouses.Remove(warehouse);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new DeleteWarehouseResponseDto { Success = true };
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse;
|
||||
|
||||
public class DeleteWarehouseCommandValidator : AbstractValidator<DeleteWarehouseCommand>
|
||||
{
|
||||
public DeleteWarehouseCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0).WithMessage("شناسه انبار معتبر نیست");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse;
|
||||
|
||||
public class DeleteWarehouseResponseDto
|
||||
{
|
||||
/// <summary>آیا عملیات موفق بود؟</summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای تعیین انبار پیشفرض
|
||||
/// </summary>
|
||||
public record SetDefaultWarehouseCommand(long Id) : IRequest<SetDefaultWarehouseResponseDto>;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse;
|
||||
|
||||
public class SetDefaultWarehouseCommandHandler : IRequestHandler<SetDefaultWarehouseCommand, SetDefaultWarehouseResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public SetDefaultWarehouseCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<SetDefaultWarehouseResponseDto> Handle(SetDefaultWarehouseCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var warehouse = await _context.Warehouses
|
||||
.FirstOrDefaultAsync(w => w.Id == request.Id, cancellationToken);
|
||||
|
||||
if (warehouse == null)
|
||||
{
|
||||
throw new NotFoundException(nameof(Domain.Entities.Warehouse), request.Id);
|
||||
}
|
||||
|
||||
// ریست کردن همه انبارهای دیگر
|
||||
var otherWarehouses = await _context.Warehouses
|
||||
.Where(w => w.IsDefault && w.Id != request.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var other in otherWarehouses)
|
||||
{
|
||||
other.IsDefault = false;
|
||||
}
|
||||
|
||||
// تنظیم این انبار به عنوان پیشفرض
|
||||
warehouse.IsDefault = true;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new SetDefaultWarehouseResponseDto { Success = true };
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse;
|
||||
|
||||
public class SetDefaultWarehouseCommandValidator : AbstractValidator<SetDefaultWarehouseCommand>
|
||||
{
|
||||
public SetDefaultWarehouseCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0).WithMessage("شناسه انبار معتبر نیست");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse;
|
||||
|
||||
public class SetDefaultWarehouseResponseDto
|
||||
{
|
||||
/// <summary>آیا عملیات موفق بود؟</summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای آپدیت انبار
|
||||
/// </summary>
|
||||
public record UpdateWarehouseCommand : IRequest<UpdateWarehouseResponseDto>
|
||||
{
|
||||
/// <summary>شناسه انبار</summary>
|
||||
public long Id { get; init; }
|
||||
/// <summary>نام انبار</summary>
|
||||
public string? Name { get; init; }
|
||||
/// <summary>کد انبار</summary>
|
||||
public string? Code { get; init; }
|
||||
/// <summary>آدرس انبار</summary>
|
||||
public string? Address { get; init; }
|
||||
/// <summary>فعال؟</summary>
|
||||
public bool? IsActive { get; init; }
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse;
|
||||
|
||||
public class UpdateWarehouseCommandHandler : IRequestHandler<UpdateWarehouseCommand, UpdateWarehouseResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public UpdateWarehouseCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<UpdateWarehouseResponseDto> Handle(UpdateWarehouseCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var warehouse = await _context.Warehouses
|
||||
.FirstOrDefaultAsync(w => w.Id == request.Id, cancellationToken);
|
||||
|
||||
if (warehouse == null)
|
||||
{
|
||||
throw new NotFoundException(nameof(Domain.Entities.Warehouse), request.Id);
|
||||
}
|
||||
|
||||
// بررسی تکراری نبودن کد جدید
|
||||
if (!string.IsNullOrEmpty(request.Code) && request.Code != warehouse.Code)
|
||||
{
|
||||
var codeExists = await _context.Warehouses
|
||||
.AnyAsync(w => w.Code == request.Code && w.Id != request.Id, cancellationToken);
|
||||
|
||||
if (codeExists)
|
||||
{
|
||||
throw new InvalidOperationException($"Warehouse with code '{request.Code}' already exists");
|
||||
}
|
||||
warehouse.Code = request.Code;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Name))
|
||||
{
|
||||
warehouse.Name = request.Name;
|
||||
}
|
||||
|
||||
if (request.Address != null)
|
||||
{
|
||||
warehouse.Address = request.Address;
|
||||
}
|
||||
|
||||
if (request.IsActive.HasValue)
|
||||
{
|
||||
warehouse.IsActive = request.IsActive.Value;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new UpdateWarehouseResponseDto { Success = true };
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse;
|
||||
|
||||
public class UpdateWarehouseCommandValidator : AbstractValidator<UpdateWarehouseCommand>
|
||||
{
|
||||
public UpdateWarehouseCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0).WithMessage("شناسه انبار معتبر نیست");
|
||||
|
||||
RuleFor(x => x.Name)
|
||||
.MaximumLength(100).WithMessage("نام انبار نباید بیش از 100 کاراکتر باشد");
|
||||
|
||||
RuleFor(x => x.Code)
|
||||
.MaximumLength(50).WithMessage("کد انبار نباید بیش از 50 کاراکتر باشد");
|
||||
|
||||
RuleFor(x => x.Address)
|
||||
.MaximumLength(500).WithMessage("آدرس نباید بیش از 500 کاراکتر باشد");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse;
|
||||
|
||||
public class UpdateWarehouseResponseDto
|
||||
{
|
||||
/// <summary>آیا عملیات موفق بود؟</summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای گرفتن لیست تمام انبارها
|
||||
/// </summary>
|
||||
public record GetAllWarehousesQuery : IRequest<GetAllWarehousesResponseDto>;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses;
|
||||
|
||||
public class GetAllWarehousesQueryHandler : IRequestHandler<GetAllWarehousesQuery, GetAllWarehousesResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllWarehousesQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetAllWarehousesResponseDto> Handle(GetAllWarehousesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var warehouses = await _context.Warehouses
|
||||
.AsNoTracking()
|
||||
.OrderBy(w => w.Name)
|
||||
.Select(w => new WarehouseItemDto
|
||||
{
|
||||
Id = w.Id,
|
||||
Name = w.Name,
|
||||
Code = w.Code,
|
||||
Address = w.Address,
|
||||
IsDefault = w.IsDefault,
|
||||
IsActive = w.IsActive
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetAllWarehousesResponseDto
|
||||
{
|
||||
Warehouses = warehouses,
|
||||
TotalCount = warehouses.Count
|
||||
};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses;
|
||||
|
||||
public class GetAllWarehousesResponseDto
|
||||
{
|
||||
public List<WarehouseItemDto> Warehouses { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class WarehouseItemDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string? Address { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای گرفتن انبار با شناسه
|
||||
/// </summary>
|
||||
public record GetWarehouseQuery(long Id) : IRequest<GetWarehouseResponseDto?>;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse;
|
||||
|
||||
public class GetWarehouseQueryHandler : IRequestHandler<GetWarehouseQuery, GetWarehouseResponseDto?>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetWarehouseQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetWarehouseResponseDto?> Handle(GetWarehouseQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var warehouse = await _context.Warehouses
|
||||
.AsNoTracking()
|
||||
.Where(w => w.Id == request.Id)
|
||||
.Select(w => new GetWarehouseResponseDto
|
||||
{
|
||||
Id = w.Id,
|
||||
Name = w.Name,
|
||||
Code = w.Code,
|
||||
Address = w.Address,
|
||||
IsDefault = w.IsDefault,
|
||||
IsActive = w.IsActive
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return warehouse;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse;
|
||||
|
||||
public class GetWarehouseResponseDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string? Address { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.SearchWarehouses;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای جستجوی انبارها
|
||||
/// </summary>
|
||||
public record SearchWarehousesQuery : IRequest<SearchWarehousesResponseDto>
|
||||
{
|
||||
public string? SearchTerm { get; init; }
|
||||
public bool? IsActive { get; init; }
|
||||
public int Skip { get; init; } = 0;
|
||||
public int Take { get; init; } = 50;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.SearchWarehouses;
|
||||
|
||||
public class SearchWarehousesQueryHandler : IRequestHandler<SearchWarehousesQuery, SearchWarehousesResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public SearchWarehousesQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<SearchWarehousesResponseDto> Handle(SearchWarehousesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.Warehouses.AsNoTracking();
|
||||
|
||||
// فیلتر بر اساس جستجو
|
||||
if (!string.IsNullOrEmpty(request.SearchTerm))
|
||||
{
|
||||
query = query.Where(w =>
|
||||
w.Name.Contains(request.SearchTerm) ||
|
||||
w.Code.Contains(request.SearchTerm) ||
|
||||
(w.Address != null && w.Address.Contains(request.SearchTerm)));
|
||||
}
|
||||
|
||||
// فیلتر بر اساس فعال بودن
|
||||
if (request.IsActive.HasValue)
|
||||
{
|
||||
query = query.Where(w => w.IsActive == request.IsActive.Value);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var warehouses = await query
|
||||
.OrderBy(w => w.Name)
|
||||
.Skip(request.Skip)
|
||||
.Take(request.Take)
|
||||
.Select(w => new WarehouseSearchItemDto
|
||||
{
|
||||
Id = w.Id,
|
||||
Name = w.Name,
|
||||
Code = w.Code,
|
||||
Address = w.Address,
|
||||
IsDefault = w.IsDefault,
|
||||
IsActive = w.IsActive
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new SearchWarehousesResponseDto
|
||||
{
|
||||
Warehouses = warehouses,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
namespace CMSMicroservice.Application.WarehouseCQ.Queries.SearchWarehouses;
|
||||
|
||||
public class SearchWarehousesResponseDto
|
||||
{
|
||||
public List<WarehouseSearchItemDto> Warehouses { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class WarehouseSearchItemDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string? Address { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user