310 lines
11 KiB
C#
310 lines
11 KiB
C#
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
|
|
}
|