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; /// /// Repository implementation برای مدیریت حرکات موجودی /// public class StockMovementRepository : IStockMovementRepository { private readonly IApplicationDbContext _context; public StockMovementRepository(IApplicationDbContext context) { _context = context; } #region Read Operations public async Task GetByIdAsync(long id, CancellationToken cancellationToken = default) { return await _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .FirstOrDefaultAsync(m => m.Id == id, cancellationToken); } public async Task> GetByInventoryItemIdAsync( long inventoryItemId, StockMovementType? movementType = null, DateTime? fromDate = null, DateTime? toDate = null, int skip = 0, int take = 100, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Include(m => m.InventoryItem) .Where(m => m.InventoryItemId == inventoryItemId); if (movementType.HasValue) { query = query.Where(m => m.MovementType == movementType.Value); } if (fromDate.HasValue) { query = query.Where(m => m.Created >= fromDate.Value); } if (toDate.HasValue) { query = query.Where(m => m.Created <= toDate.Value); } return await query .OrderByDescending(m => m.Created) .Skip(skip) .Take(take) .ToListAsync(cancellationToken); } public async Task> GetByOrderIdAsync(long orderId, CancellationToken cancellationToken = default) { return await _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .Where(m => m.OrderId == orderId) .OrderByDescending(m => m.Created) .ToListAsync(cancellationToken); } public async Task> GetByDiscountOrderIdAsync(long discountOrderId, CancellationToken cancellationToken = default) { return await _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .Where(m => m.DiscountOrderId == discountOrderId) .OrderByDescending(m => m.Created) .ToListAsync(cancellationToken); } public async Task> GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken = default) { return await _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .Where(m => m.ReferenceNumber == referenceNumber) .OrderByDescending(m => m.Created) .ToListAsync(cancellationToken); } public async Task> GetByMovementTypeAsync( StockMovementType movementType, DateTime? fromDate = null, DateTime? toDate = null, int skip = 0, int take = 100, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .Where(m => m.MovementType == movementType); if (fromDate.HasValue) { query = query.Where(m => m.Created >= fromDate.Value); } if (toDate.HasValue) { query = query.Where(m => m.Created <= toDate.Value); } return await query .OrderByDescending(m => m.Created) .Skip(skip) .Take(take) .ToListAsync(cancellationToken); } public async Task> GetRecentMovementsAsync( int count = 50, StockMovementType? movementType = null, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .AsQueryable(); if (movementType.HasValue) { query = query.Where(m => m.MovementType == movementType.Value); } return await query .OrderByDescending(m => m.Created) .Take(count) .ToListAsync(cancellationToken); } public async Task> SearchAsync( long? inventoryItemId = null, StockMovementType? movementType = null, DateTime? fromDate = null, DateTime? toDate = null, string? referenceNumber = null, long? orderId = null, long? discountOrderId = null, long? performedByUserId = null, int skip = 0, int take = 100, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .AsQueryable(); if (inventoryItemId.HasValue) { query = query.Where(m => m.InventoryItemId == inventoryItemId.Value); } if (movementType.HasValue) { query = query.Where(m => m.MovementType == movementType.Value); } if (fromDate.HasValue) { query = query.Where(m => m.Created >= fromDate.Value); } if (toDate.HasValue) { query = query.Where(m => m.Created <= toDate.Value); } if (!string.IsNullOrWhiteSpace(referenceNumber)) { query = query.Where(m => m.ReferenceNumber != null && m.ReferenceNumber.Contains(referenceNumber)); } if (orderId.HasValue) { query = query.Where(m => m.OrderId == orderId.Value); } if (discountOrderId.HasValue) { query = query.Where(m => m.DiscountOrderId == discountOrderId.Value); } if (performedByUserId.HasValue) { query = query.Where(m => m.PerformedByUserId == performedByUserId.Value); } return await query .OrderByDescending(m => m.Created) .Skip(skip) .Take(take) .ToListAsync(cancellationToken); } public async Task CountAsync( long? inventoryItemId = null, StockMovementType? movementType = null, DateTime? fromDate = null, DateTime? toDate = null, string? referenceNumber = null, long? orderId = null, long? discountOrderId = null, long? performedByUserId = null, CancellationToken cancellationToken = default) { var query = _context.StockMovements.AsQueryable(); if (inventoryItemId.HasValue) { query = query.Where(m => m.InventoryItemId == inventoryItemId.Value); } if (movementType.HasValue) { query = query.Where(m => m.MovementType == movementType.Value); } if (fromDate.HasValue) { query = query.Where(m => m.Created >= fromDate.Value); } if (toDate.HasValue) { query = query.Where(m => m.Created <= toDate.Value); } if (!string.IsNullOrWhiteSpace(referenceNumber)) { query = query.Where(m => m.ReferenceNumber != null && m.ReferenceNumber.Contains(referenceNumber)); } if (orderId.HasValue) { query = query.Where(m => m.OrderId == orderId.Value); } if (discountOrderId.HasValue) { query = query.Where(m => m.DiscountOrderId == discountOrderId.Value); } if (performedByUserId.HasValue) { query = query.Where(m => m.PerformedByUserId == performedByUserId.Value); } return await query.CountAsync(cancellationToken); } #endregion #region Write Operations public async Task AddAsync(StockMovement stockMovement, CancellationToken cancellationToken = default) { _context.StockMovements.Add(stockMovement); await _context.SaveChangesAsync(cancellationToken); return stockMovement; } public async Task DeleteAsync(long id, CancellationToken cancellationToken = default) { var stockMovement = await _context.StockMovements.FindAsync(new object[] { id }, cancellationToken); if (stockMovement != null) { _context.StockMovements.Remove(stockMovement); await _context.SaveChangesAsync(cancellationToken); } } public async Task> BulkAddAsync(List stockMovements, CancellationToken cancellationToken = default) { _context.StockMovements.AddRange(stockMovements); await _context.SaveChangesAsync(cancellationToken); return stockMovements; } #endregion #region Analytics & Reports public async Task> GetMovementSummaryAsync( DateTime fromDate, DateTime toDate, long? inventoryItemId = null, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Where(m => m.Created >= fromDate && m.Created <= toDate); if (inventoryItemId.HasValue) { query = query.Where(m => m.InventoryItemId == inventoryItemId.Value); } var movements = await query .GroupBy(m => m.MovementType) .Select(g => new { MovementType = g.Key, TotalQuantity = g.Sum(m => m.Quantity) }) .ToListAsync(cancellationToken); return movements.ToDictionary(x => x.MovementType, x => x.TotalQuantity); } public async Task> GetDailyMovementVolumeAsync( DateTime fromDate, DateTime toDate, long? inventoryItemId = null, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Where(m => m.Created >= fromDate && m.Created <= toDate); if (inventoryItemId.HasValue) { query = query.Where(m => m.InventoryItemId == inventoryItemId.Value); } var movements = await query.ToListAsync(cancellationToken); // نوع‌های ورودی (افزایش موجودی) var inboundTypes = new[] { StockMovementType.InitialStock, StockMovementType.Restock, StockMovementType.Return, StockMovementType.TransferIn, StockMovementType.AdjustmentPlus, StockMovementType.Released }; // نوع‌های خروجی (کاهش موجودی) var outboundTypes = new[] { StockMovementType.Sale, StockMovementType.Damaged, StockMovementType.Lost, StockMovementType.TransferOut, StockMovementType.AdjustmentMinus, StockMovementType.Reserved }; var dailyVolumes = movements .GroupBy(m => m.Created.Date) .Select(g => ( Date: g.Key, InboundQuantity: g.Where(m => inboundTypes.Contains(m.MovementType)).Sum(m => m.Quantity), OutboundQuantity: g.Where(m => outboundTypes.Contains(m.MovementType)).Sum(m => m.Quantity) )) .OrderBy(x => x.Date) .ToList(); return dailyVolumes; } public async Task> GetTopMovingProductsAsync( DateTime fromDate, DateTime toDate, int count = 10, StockMovementType? movementType = null, CancellationToken cancellationToken = default) { var query = _context.StockMovements .Include(m => m.InventoryItem) .ThenInclude(i => i.Product) .Include(m => m.InventoryItem) .ThenInclude(i => i.DiscountProduct) .Where(m => m.Created >= fromDate && m.Created <= toDate); if (movementType.HasValue) { query = query.Where(m => m.MovementType == movementType.Value); } var movements = await query.ToListAsync(cancellationToken); var topProducts = movements .GroupBy(m => m.InventoryItemId) .Select(g => { var firstItem = g.First().InventoryItem; var productName = firstItem.Product?.Title ?? firstItem.DiscountProduct?.Title ?? "Unknown"; return ( InventoryItemId: g.Key, ProductName: productName, MovementCount: g.Count(), TotalQuantityChange: g.Sum(m => m.Quantity) ); }) .OrderByDescending(x => x.MovementCount) .Take(count) .ToList(); return topProducts; } #endregion }