// EN: Main controller for Inventory operations. // VI: Controller chính cho các thao tác Inventory. using Asp.Versioning; using InventoryService.API.Application.Commands; using InventoryService.API.Application.DTOs; using InventoryService.API.Application.Queries; using MediatR; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; namespace InventoryService.API.Controllers; /// /// EN: Controller for inventory operations. /// VI: Controller cho các thao tác inventory. /// [ApiController] [ApiVersion("1.0")] [Route("api/v{version:apiVersion}/inventory")] [SwaggerTag("Inventory Management - Stock operations, reservations, and tracking")] public class InventoryController : ControllerBase { private readonly IMediator _mediator; private readonly ILogger _logger; public InventoryController( IMediator mediator, ILogger logger) { _mediator = mediator; _logger = logger; } /// /// EN: Get inventory by shop. /// VI: Lấy inventory theo shop. /// [HttpGet] [SwaggerOperation(Summary = "Get inventory by shop with pagination")] [SwaggerResponse(200, "Inventory items retrieved successfully")] [SwaggerResponse(400, "Invalid request")] public async Task>>> GetInventory( [FromQuery] Guid shopId, [FromQuery] int skip = 0, [FromQuery] int take = 50, CancellationToken ct = default) { if (shopId == Guid.Empty) return BadRequest(ApiResponse>.Fail("Shop ID is required")); var query = new GetInventoryByShopQuery(shopId, skip, take); var result = await _mediator.Send(query, ct); return Ok(ApiResponse>.Ok(result)); } /// /// EN: Get stock level for specific product and shop. /// VI: Lấy mức tồn kho cho product và shop cụ thể. /// [HttpGet("{productId:guid}")] [SwaggerOperation(Summary = "Get stock level by product ID and shop ID")] [SwaggerResponse(200, "Stock level retrieved successfully")] [SwaggerResponse(404, "Inventory item not found")] public async Task>> GetStockLevel( Guid productId, [FromQuery] Guid shopId, CancellationToken ct = default) { var query = new GetStockLevelQuery(productId, shopId); var result = await _mediator.Send(query, ct); if (result == null) return NotFound(ApiResponse.Fail("Inventory item not found")); return Ok(ApiResponse.Ok(result)); } /// /// EN: Stock in operation (add inventory). /// VI: Thao tác nhập kho (thêm inventory). /// [HttpPost("stock-in")] [SwaggerOperation(Summary = "Add stock to inventory")] [SwaggerResponse(200, "Stock added successfully")] [SwaggerResponse(400, "Invalid request")] public async Task>> StockIn( [FromBody] StockInRequest request, CancellationToken ct = default) { try { var command = new StockInCommand( request.ProductId, request.ShopId, request.Amount, request.Notes, request.ReferenceId); var inventoryItemId = await _mediator.Send(command, ct); return Ok(ApiResponse.Ok(inventoryItemId)); } catch (Exception ex) { _logger.LogError(ex, "Error performing stock in"); return BadRequest(ApiResponse.Fail(ex.Message)); } } /// /// EN: Stock out operation (remove inventory). /// VI: Thao tác xuất kho (giảm inventory). /// [HttpPost("stock-out")] [SwaggerOperation(Summary = "Remove stock from inventory")] [SwaggerResponse(200, "Stock removed successfully")] [SwaggerResponse(400, "Invalid request or insufficient stock")] [SwaggerResponse(404, "Inventory item not found")] public async Task>> StockOut( [FromBody] StockOutRequest request, CancellationToken ct = default) { try { var command = new StockOutCommand( request.ProductId, request.ShopId, request.Amount, request.Notes, request.ReferenceId); var result = await _mediator.Send(command, ct); if (!result) return NotFound(ApiResponse.Fail("Inventory item not found")); return Ok(ApiResponse.Ok(result)); } catch (Exception ex) { _logger.LogError(ex, "Error performing stock out"); return BadRequest(ApiResponse.Fail(ex.Message)); } } /// /// EN: Reserve stock for order. /// VI: Đặt trước stock cho order. /// [HttpPost("reserve")] [SwaggerOperation(Summary = "Reserve stock for order")] [SwaggerResponse(200, "Stock reserved successfully")] [SwaggerResponse(400, "Invalid request or insufficient available stock")] [SwaggerResponse(404, "Inventory item not found")] public async Task>> ReserveStock( [FromBody] ReserveStockRequest request, CancellationToken ct = default) { try { var command = new ReserveStockCommand( request.ProductId, request.ShopId, request.Amount, request.OrderId); var result = await _mediator.Send(command, ct); if (!result) return NotFound(ApiResponse.Fail("Inventory item not found")); return Ok(ApiResponse.Ok(result)); } catch (Exception ex) { _logger.LogError(ex, "Error reserving stock"); return BadRequest(ApiResponse.Fail(ex.Message)); } } /// /// EN: Release stock reservation. /// VI: Giải phóng đặt trước stock. /// [HttpPost("release")] [SwaggerOperation(Summary = "Release stock reservation")] [SwaggerResponse(200, "Reservation released successfully")] [SwaggerResponse(400, "Invalid request")] [SwaggerResponse(404, "Inventory item not found")] public async Task>> ReleaseReservation( [FromBody] ReleaseReservationRequest request, CancellationToken ct = default) { try { var command = new ReleaseReservationCommand( request.ProductId, request.ShopId, request.Amount, request.OrderId); var result = await _mediator.Send(command, ct); if (!result) return NotFound(ApiResponse.Fail("Inventory item not found")); return Ok(ApiResponse.Ok(result)); } catch (Exception ex) { _logger.LogError(ex, "Error releasing reservation"); return BadRequest(ApiResponse.Fail(ex.Message)); } } /// /// EN: Adjust stock (manual correction). /// VI: Điều chỉnh stock (sửa thủ công). /// [HttpPost("adjust")] [SwaggerOperation(Summary = "Manually adjust stock quantity")] [SwaggerResponse(200, "Stock adjusted successfully")] [SwaggerResponse(400, "Invalid request")] [SwaggerResponse(404, "Inventory item not found")] public async Task>> AdjustStock( [FromBody] AdjustStockRequest request, CancellationToken ct = default) { try { var command = new AdjustStockCommand( request.ProductId, request.ShopId, request.NewQuantity, request.Notes); var result = await _mediator.Send(command, ct); if (!result) return NotFound(ApiResponse.Fail("Inventory item not found")); return Ok(ApiResponse.Ok(result)); } catch (Exception ex) { _logger.LogError(ex, "Error adjusting stock"); return BadRequest(ApiResponse.Fail(ex.Message)); } } /// /// EN: Get transaction history by inventory item ID or shop ID. /// VI: Lấy lịch sử transactions theo inventory item ID hoặc shop ID. /// [HttpGet("transactions")] [SwaggerOperation(Summary = "Get transaction history by item or shop")] [SwaggerResponse(200, "Transactions retrieved successfully")] [SwaggerResponse(400, "Invalid request")] public async Task>>> GetTransactions( [FromQuery] Guid? inventoryItemId = null, [FromQuery] Guid? shopId = null, [FromQuery] int skip = 0, [FromQuery] int take = 50, CancellationToken ct = default) { if (shopId.HasValue && shopId.Value != Guid.Empty) { var shopQuery = new GetTransactionsByShopQuery(shopId.Value, skip, take); var shopResult = await _mediator.Send(shopQuery, ct); return Ok(ApiResponse>.Ok(shopResult)); } if (!inventoryItemId.HasValue || inventoryItemId.Value == Guid.Empty) return BadRequest(ApiResponse>.Fail("Either shopId or inventoryItemId is required")); var query = new GetTransactionsQuery(inventoryItemId.Value, skip, take); var result = await _mediator.Send(query, ct); return Ok(ApiResponse>.Ok(result)); } /// /// EN: Get low stock items. /// VI: Lấy các items stock thấp. /// [HttpGet("low-stock")] [SwaggerOperation(Summary = "Get items with stock at or below reorder level")] [SwaggerResponse(200, "Low stock items retrieved successfully")] public async Task>>> GetLowStockItems( [FromQuery] Guid? shopId = null, [FromQuery] int skip = 0, [FromQuery] int take = 50, CancellationToken ct = default) { var query = new GetLowStockItemsQuery(shopId, skip, take); var result = await _mediator.Send(query, ct); return Ok(ApiResponse>.Ok(result)); } }