Implement Inventory Management Service with CRUD operations for warehouses and inventory items, stock operations, and bulk processing capabilities.
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m48s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m48s
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Interfaces.Repositories;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository implementation برای مدیریت انبارها
|
||||
/// </summary>
|
||||
public class WarehouseRepository : IWarehouseRepository
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public WarehouseRepository(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
#region Read Operations
|
||||
|
||||
public async Task<Warehouse?> GetByIdAsync(long id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Warehouses
|
||||
.Include(w => w.InventoryItems)
|
||||
.ThenInclude(i => i.Product)
|
||||
.Include(w => w.InventoryItems)
|
||||
.ThenInclude(i => i.DiscountProduct)
|
||||
.FirstOrDefaultAsync(w => w.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Warehouse?> GetByCodeAsync(string code, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Warehouses
|
||||
.Include(w => w.InventoryItems)
|
||||
.ThenInclude(i => i.Product)
|
||||
.Include(w => w.InventoryItems)
|
||||
.ThenInclude(i => i.DiscountProduct)
|
||||
.FirstOrDefaultAsync(w => w.Code == code, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Warehouse?> GetDefaultWarehouseAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Warehouses
|
||||
.Include(w => w.InventoryItems)
|
||||
.ThenInclude(i => i.Product)
|
||||
.Include(w => w.InventoryItems)
|
||||
.ThenInclude(i => i.DiscountProduct)
|
||||
.FirstOrDefaultAsync(w => w.IsDefault, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Warehouse>> GetActiveWarehousesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Warehouses
|
||||
.Where(w => w.IsActive)
|
||||
.OrderBy(w => w.Name)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Warehouse>> GetAllAsync(
|
||||
bool includeInactive = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _context.Warehouses.AsQueryable();
|
||||
|
||||
if (!includeInactive)
|
||||
{
|
||||
query = query.Where(w => w.IsActive);
|
||||
}
|
||||
|
||||
return await query
|
||||
.OrderBy(w => w.Name)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Warehouse>> SearchAsync(
|
||||
string? searchTerm = null,
|
||||
bool? isActive = null,
|
||||
int skip = 0,
|
||||
int take = 50,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _context.Warehouses.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var term = searchTerm.ToLower();
|
||||
query = query.Where(w =>
|
||||
w.Name.ToLower().Contains(term) ||
|
||||
w.Code.ToLower().Contains(term) ||
|
||||
(w.Address != null && w.Address.ToLower().Contains(term)));
|
||||
}
|
||||
|
||||
if (isActive.HasValue)
|
||||
{
|
||||
query = query.Where(w => w.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
return await query
|
||||
.OrderBy(w => w.Name)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync(
|
||||
string? searchTerm = null,
|
||||
bool? isActive = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _context.Warehouses.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var term = searchTerm.ToLower();
|
||||
query = query.Where(w =>
|
||||
w.Name.ToLower().Contains(term) ||
|
||||
w.Code.ToLower().Contains(term) ||
|
||||
(w.Address != null && w.Address.ToLower().Contains(term)));
|
||||
}
|
||||
|
||||
if (isActive.HasValue)
|
||||
{
|
||||
query = query.Where(w => w.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
return await query.CountAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByCodeAsync(string code, long? excludeId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _context.Warehouses.Where(w => w.Code == code);
|
||||
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
query = query.Where(w => w.Id != excludeId.Value);
|
||||
}
|
||||
|
||||
return await query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Operations
|
||||
|
||||
public async Task<Warehouse> AddAsync(Warehouse warehouse, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// اگر این انبار پیشفرض است، سایر انبارها را غیرپیشفرض کن
|
||||
if (warehouse.IsDefault)
|
||||
{
|
||||
await RemoveDefaultFromAllWarehousesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
_context.Warehouses.Add(warehouse);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return warehouse;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Warehouse warehouse, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// اگر این انبار پیشفرض شده، سایر انبارها را غیرپیشفرض کن
|
||||
if (warehouse.IsDefault)
|
||||
{
|
||||
await RemoveDefaultFromAllWarehousesAsync(warehouse.Id, cancellationToken);
|
||||
}
|
||||
|
||||
_context.Warehouses.Update(warehouse);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var warehouse = await _context.Warehouses.FindAsync(new object[] { id }, cancellationToken);
|
||||
if (warehouse != null)
|
||||
{
|
||||
warehouse.IsActive = false;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetActiveStatusAsync(long id, bool isActive, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var warehouse = await _context.Warehouses.FindAsync(new object[] { id }, cancellationToken);
|
||||
if (warehouse != null)
|
||||
{
|
||||
warehouse.IsActive = isActive;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetAsDefaultAsync(long id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// ابتدا همه انبارها را غیرپیشفرض کن
|
||||
await RemoveDefaultFromAllWarehousesAsync(cancellationToken);
|
||||
|
||||
// سپس انبار مورد نظر را پیشفرض کن
|
||||
var warehouse = await _context.Warehouses.FindAsync(new object[] { id }, cancellationToken);
|
||||
if (warehouse != null)
|
||||
{
|
||||
warehouse.IsDefault = true;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Analytics
|
||||
|
||||
public async Task<(int TotalProducts, int LowStockProducts, int OutOfStockProducts, decimal TotalValue)> GetWarehouseStatisticsAsync(
|
||||
long warehouseId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var inventoryItems = await _context.InventoryItems
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => i.WarehouseId == warehouseId)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var totalProducts = inventoryItems.Count;
|
||||
var lowStockProducts = inventoryItems.Count(i => i.Quantity <= i.LowStockThreshold && i.Quantity > 0);
|
||||
var outOfStockProducts = inventoryItems.Count(i => i.Quantity == 0);
|
||||
|
||||
// محاسبه ارزش کل بر اساس قیمت محصولات
|
||||
decimal totalValue = 0;
|
||||
foreach (var item in inventoryItems)
|
||||
{
|
||||
if (item.Product != null)
|
||||
{
|
||||
totalValue += item.Quantity * item.Product.Price;
|
||||
}
|
||||
else if (item.DiscountProduct != null)
|
||||
{
|
||||
totalValue += item.Quantity * item.DiscountProduct.Price;
|
||||
}
|
||||
}
|
||||
|
||||
return (totalProducts, lowStockProducts, outOfStockProducts, totalValue);
|
||||
}
|
||||
|
||||
public async Task<List<(long ProductId, string ProductName, int TotalSold, int CurrentStock)>> GetTopSellingProductsAsync(
|
||||
long warehouseId,
|
||||
DateTime fromDate,
|
||||
DateTime toDate,
|
||||
int count = 10,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// دریافت حرکات فروش برای این انبار در بازه زمانی مشخص
|
||||
var salesMovements = await _context.StockMovements
|
||||
.Include(m => m.InventoryItem)
|
||||
.ThenInclude(i => i.Product)
|
||||
.Include(m => m.InventoryItem)
|
||||
.ThenInclude(i => i.DiscountProduct)
|
||||
.Where(m => m.InventoryItem.WarehouseId == warehouseId &&
|
||||
m.MovementType == StockMovementType.Sale &&
|
||||
m.Created >= fromDate &&
|
||||
m.Created <= toDate)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// گروهبندی بر اساس محصول و محاسبه تعداد فروش
|
||||
var topProducts = salesMovements
|
||||
.GroupBy(m => m.InventoryItemId)
|
||||
.Select(g =>
|
||||
{
|
||||
var firstItem = g.First().InventoryItem;
|
||||
var productId = firstItem.ProductId ?? firstItem.DiscountProductId ?? 0;
|
||||
var productName = firstItem.Product?.Title ?? firstItem.DiscountProduct?.Title ?? "Unknown";
|
||||
var totalSold = g.Sum(m => m.Quantity);
|
||||
var currentStock = firstItem.Quantity;
|
||||
return (ProductId: productId, ProductName: productName, TotalSold: totalSold, CurrentStock: currentStock);
|
||||
})
|
||||
.OrderByDescending(x => x.TotalSold)
|
||||
.Take(count)
|
||||
.ToList();
|
||||
|
||||
return topProducts;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task RemoveDefaultFromAllWarehousesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var defaultWarehouses = await _context.Warehouses
|
||||
.Where(w => w.IsDefault)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var warehouse in defaultWarehouses)
|
||||
{
|
||||
warehouse.IsDefault = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveDefaultFromAllWarehousesAsync(long excludeId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var defaultWarehouses = await _context.Warehouses
|
||||
.Where(w => w.IsDefault && w.Id != excludeId)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var warehouse in defaultWarehouses)
|
||||
{
|
||||
warehouse.IsDefault = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user