using CMSMicroservice.Protobuf.Protos.Inventory; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; using CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; using CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; using CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; using CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse; using CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses; using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; using CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; using CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; using CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; using CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem; using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct; using CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems; using CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems; using CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements; using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem; using CMSMicroservice.Application.Common.Interfaces; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using MediatR; using Microsoft.EntityFrameworkCore; using System.Linq; namespace CMSMicroservice.WebApi.Services; public class InventoryService : InventoryContract.InventoryContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; private readonly IMediator _mediator; private readonly IApplicationDbContext _context; public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator, IApplicationDbContext context) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _mediator = mediator; _context = context; } // ========== Warehouse Management ========== public override async Task CreateWarehouse(CreateWarehouseRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task UpdateWarehouse(UpdateWarehouseRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task DeleteWarehouse(DeleteWarehouseRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetWarehouse(GetWarehouseRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetAllWarehouses(GetAllWarehousesRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task SetDefaultWarehouse(SetDefaultWarehouseRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } // ========== Inventory Item Management ========== public override async Task GetInventoryItem(GetInventoryItemRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetInventoryByProduct(GetInventoryByProductRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetAllInventoryItems(GetAllInventoryItemsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetLowStockItems(GetLowStockItemsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task UpdateInventorySettings(UpdateInventorySettingsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } // ========== Stock Operations ========== public override async Task AddStock(AddStockRequest request, ServerCallContext context) { // Lookup InventoryItem by ProductId + ProductType var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) { throw new RpcException(new Status(StatusCode.NotFound, $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}")); } // Execute IncreaseInventoryCommand var response = await _mediator.Send( new IncreaseInventoryCommand { Id = inventoryItem.Id, Quantity = request.Quantity, ReferenceNumber = request.ReferenceNumber }, context.CancellationToken); return new AddStockResponse { InventoryItemId = inventoryItem.Id, NewQuantity = response.NewQuantity }; } public override async Task AdjustStock(AdjustStockRequest request, ServerCallContext context) { // Lookup InventoryItem var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) { throw new RpcException(new Status(StatusCode.NotFound, $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}")); } // Determine if increase or decrease var difference = request.NewQuantity - inventoryItem.Quantity; if (difference > 0) { await _mediator.Send( new IncreaseInventoryCommand { Id = inventoryItem.Id, Quantity = difference, ReferenceNumber = request.ReferenceNumber }, context.CancellationToken); return new AdjustStockResponse { PreviousQuantity = inventoryItem.Quantity, NewQuantity = request.NewQuantity, Difference = difference }; } else if (difference < 0) { await _mediator.Send( new ReduceInventoryCommand { Id = inventoryItem.Id, Quantity = Math.Abs(difference), FromReserved = false, ReferenceNumber = request.ReferenceNumber, MovementType = Domain.Enums.StockMovementType.AdjustmentMinus, Note = "Stock adjustment (decrease)" }, context.CancellationToken); return new AdjustStockResponse { PreviousQuantity = inventoryItem.Quantity, NewQuantity = request.NewQuantity, Difference = difference }; } else { return new AdjustStockResponse { PreviousQuantity = inventoryItem.Quantity, NewQuantity = inventoryItem.Quantity, Difference = 0 }; } } public override async Task ReserveStock(ReserveStockRequest request, ServerCallContext context) { var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) return new ReserveStockResponse { Success = false, Message = "آیتم موجودی یافت نشد", AvailableQuantity = 0 }; var available = inventoryItem.Quantity - inventoryItem.ReservedQuantity; if (available < request.Quantity) return new ReserveStockResponse { Success = false, Message = $"موجودی کافی نیست. موجود: {available}", AvailableQuantity = available }; await _mediator.Send( new ReserveInventoryCommand { Id = inventoryItem.Id, Quantity = request.Quantity, ReferenceNumber = request.OrderId != null ? $"ORDER-{request.OrderId.Value}" : $"RESERVE-{DateTime.UtcNow.Ticks}" }, context.CancellationToken); return new ReserveStockResponse { Success = true, Message = "رزرو با موفقیت انجام شد", AvailableQuantity = available - request.Quantity }; } public override async Task ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context) { var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد")); await _mediator.Send( new ReleaseReservedInventoryCommand { Id = inventoryItem.Id, Quantity = request.Quantity, ReferenceNumber = request.OrderId != null ? $"RELEASE-ORDER-{request.OrderId.Value}" : $"RELEASE-{DateTime.UtcNow.Ticks}" }, context.CancellationToken); return new Empty(); } public override async Task ConfirmSale(ConfirmSaleRequest request, ServerCallContext context) { var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد")); await _mediator.Send( new ReduceInventoryCommand { Id = inventoryItem.Id, Quantity = request.Quantity, FromReserved = request.FromReservation, ReferenceNumber = request.OrderId != null ? $"SALE-ORDER-{request.OrderId.Value}" : $"SALE-{DateTime.UtcNow.Ticks}" }, context.CancellationToken); return new Empty(); } public override async Task ProcessReturn(ProcessReturnRequest request, ServerCallContext context) { var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد")); var result = await _mediator.Send( new IncreaseInventoryCommand { Id = inventoryItem.Id, Quantity = request.Quantity, ReferenceNumber = request.OrderId != null ? $"RETURN-ORDER-{request.OrderId.Value}" : $"RETURN-{DateTime.UtcNow.Ticks}" }, context.CancellationToken); return new ProcessReturnResponse { NewQuantity = result.NewQuantity }; } public override async Task RecordLoss(RecordLossRequest request, ServerCallContext context) { // Lookup InventoryItem by ProductId + ProductType var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = request.ProductId, ProductType = (Domain.Enums.ProductType)request.ProductType }, context.CancellationToken); if (inventoryItem == null) { throw new RpcException(new Status(StatusCode.NotFound, $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}")); } // Execute ReduceInventoryCommand to record the loss await _mediator.Send( new ReduceInventoryCommand { Id = inventoryItem.Id, Quantity = request.Quantity, FromReserved = false, ReferenceNumber = request.ReferenceNumber ?? $"LOSS-{DateTime.UtcNow:yyyyMMddHHmmss}", MovementType = (Domain.Enums.StockMovementType)request.LossType, Note = string.IsNullOrWhiteSpace(request.Reason) ? null : request.Reason }, context.CancellationToken); return new Empty(); } // ========== Bulk Operations ========== public override async Task BulkAddStock(BulkAddStockRequest request, ServerCallContext context) { var response = new BulkAddStockResponse(); foreach (var item in request.Items) { try { var inventoryItem = await _mediator.Send( new GetInventoryByProductQuery { ProductId = item.ProductId, ProductType = (Domain.Enums.ProductType)item.ProductType }, context.CancellationToken); if (inventoryItem == null) { response.FailedCount++; response.Errors.Add($"ProductId={item.ProductId}: آیتم موجودی یافت نشد"); continue; } await _mediator.Send( new IncreaseInventoryCommand { Id = inventoryItem.Id, Quantity = item.Quantity, ReferenceNumber = request.ReferenceNumber ?? $"BULK-{DateTime.UtcNow.Ticks}" }, context.CancellationToken); response.SuccessCount++; } catch (Exception ex) { response.FailedCount++; response.Errors.Add($"ProductId={item.ProductId}: {ex.Message}"); } } return response; } // ========== Stock Movements ========== public override async Task GetStockMovements(GetStockMovementsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetStockMovementsByInventoryItem(GetStockMovementsByInventoryItemRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } // ========== Reports ========== public override async Task GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context) { var query = _context.InventoryItems .Include(i => i.Product) .Include(i => i.DiscountProduct) .Where(i => !i.IsDeleted); if (request.WarehouseId != null) query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); var items = await query.ToListAsync(context.CancellationToken); var regularProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.RegularProduct).ToList(); var discountProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.DiscountProduct).ToList(); long totalStockValue = items.Sum(i => { long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0; return (long)i.Quantity * unitPrice; }); return new GetInventorySummaryResponse { TotalProducts = regularProducts.Count, TotalDiscountProducts = discountProducts.Count, TotalQuantity = items.Sum(i => i.Quantity), TotalReserved = items.Sum(i => i.ReservedQuantity), LowStockCount = items.Count(i => i.Quantity <= i.LowStockThreshold && i.Quantity > 0), OutOfStockCount = items.Count(i => i.Quantity == 0), TotalStockValue = totalStockValue }; } public override async Task GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context) { var query = _context.InventoryItems .Include(i => i.Product) .Include(i => i.DiscountProduct) .Where(i => !i.IsDeleted); if (request.WarehouseId != null) query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); if (request.ProductType != ProductType.Unspecified) query = query.Where(i => i.ProductType == (Domain.Enums.ProductType)request.ProductType); var dbItems = await query.ToListAsync(context.CancellationToken); var items = dbItems.Select(i => { long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0; string title = i.Product?.Title ?? i.DiscountProduct?.Title ?? string.Empty; return new StockValueItem { ProductId = i.ProductId ?? i.DiscountProductId ?? 0, ProductTitle = title, ProductType = (ProductType)i.ProductType, Quantity = i.Quantity, UnitPrice = unitPrice, TotalValue = (long)i.Quantity * unitPrice }; }).ToList(); return new GetStockValueReportResponse { Items = { items }, TotalValue = items.Sum(i => i.TotalValue), TotalItems = items.Count }; } }