feat: Implement a new Inventory Service API, add admin budget and frequency controllers to Ads Serving, and introduce product and category controllers to Catalog Service.
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for inventory operations.
|
||||
/// VI: Controller cho các thao tác inventory.
|
||||
/// </summary>
|
||||
[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<InventoryController> _logger;
|
||||
|
||||
public InventoryController(
|
||||
IMediator mediator,
|
||||
ILogger<InventoryController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get inventory by shop.
|
||||
/// VI: Lấy inventory theo shop.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[SwaggerOperation(Summary = "Get inventory by shop with pagination")]
|
||||
[SwaggerResponse(200, "Inventory items retrieved successfully")]
|
||||
[SwaggerResponse(400, "Invalid request")]
|
||||
public async Task<ActionResult<ApiResponse<PagedResult<InventoryItemDto>>>> GetInventory(
|
||||
[FromQuery] Guid shopId,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 50,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (shopId == Guid.Empty)
|
||||
return BadRequest(ApiResponse<PagedResult<InventoryItemDto>>.Fail("Shop ID is required"));
|
||||
|
||||
var query = new GetInventoryByShopQuery(shopId, skip, take);
|
||||
var result = await _mediator.Send(query, ct);
|
||||
|
||||
return Ok(ApiResponse<PagedResult<InventoryItemDto>>.Ok(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get stock level for specific product and shop.
|
||||
/// VI: Lấy mức tồn kho cho product và shop cụ thể.
|
||||
/// </summary>
|
||||
[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<ActionResult<ApiResponse<InventoryItemDto>>> 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<InventoryItemDto>.Fail("Inventory item not found"));
|
||||
|
||||
return Ok(ApiResponse<InventoryItemDto>.Ok(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Stock in operation (add inventory).
|
||||
/// VI: Thao tác nhập kho (thêm inventory).
|
||||
/// </summary>
|
||||
[HttpPost("stock-in")]
|
||||
[SwaggerOperation(Summary = "Add stock to inventory")]
|
||||
[SwaggerResponse(200, "Stock added successfully")]
|
||||
[SwaggerResponse(400, "Invalid request")]
|
||||
public async Task<ActionResult<ApiResponse<Guid>>> 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<Guid>.Ok(inventoryItemId));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error performing stock in");
|
||||
return BadRequest(ApiResponse<Guid>.Fail(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Stock out operation (remove inventory).
|
||||
/// VI: Thao tác xuất kho (giảm inventory).
|
||||
/// </summary>
|
||||
[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<ActionResult<ApiResponse<bool>>> 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<bool>.Fail("Inventory item not found"));
|
||||
|
||||
return Ok(ApiResponse<bool>.Ok(result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error performing stock out");
|
||||
return BadRequest(ApiResponse<bool>.Fail(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Reserve stock for order.
|
||||
/// VI: Đặt trước stock cho order.
|
||||
/// </summary>
|
||||
[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<ActionResult<ApiResponse<bool>>> 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<bool>.Fail("Inventory item not found"));
|
||||
|
||||
return Ok(ApiResponse<bool>.Ok(result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error reserving stock");
|
||||
return BadRequest(ApiResponse<bool>.Fail(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Release stock reservation.
|
||||
/// VI: Giải phóng đặt trước stock.
|
||||
/// </summary>
|
||||
[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<ActionResult<ApiResponse<bool>>> 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<bool>.Fail("Inventory item not found"));
|
||||
|
||||
return Ok(ApiResponse<bool>.Ok(result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error releasing reservation");
|
||||
return BadRequest(ApiResponse<bool>.Fail(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Adjust stock (manual correction).
|
||||
/// VI: Điều chỉnh stock (sửa thủ công).
|
||||
/// </summary>
|
||||
[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<ActionResult<ApiResponse<bool>>> 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<bool>.Fail("Inventory item not found"));
|
||||
|
||||
return Ok(ApiResponse<bool>.Ok(result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adjusting stock");
|
||||
return BadRequest(ApiResponse<bool>.Fail(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get transaction history for inventory item.
|
||||
/// VI: Lấy lịch sử transactions cho inventory item.
|
||||
/// </summary>
|
||||
[HttpGet("transactions")]
|
||||
[SwaggerOperation(Summary = "Get transaction history")]
|
||||
[SwaggerResponse(200, "Transactions retrieved successfully")]
|
||||
[SwaggerResponse(400, "Invalid request")]
|
||||
public async Task<ActionResult<ApiResponse<PagedResult<InventoryTransactionDto>>>> GetTransactions(
|
||||
[FromQuery] Guid inventoryItemId,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 50,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (inventoryItemId == Guid.Empty)
|
||||
return BadRequest(ApiResponse<PagedResult<InventoryTransactionDto>>.Fail("Inventory item ID is required"));
|
||||
|
||||
var query = new GetTransactionsQuery(inventoryItemId, skip, take);
|
||||
var result = await _mediator.Send(query, ct);
|
||||
|
||||
return Ok(ApiResponse<PagedResult<InventoryTransactionDto>>.Ok(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get low stock items.
|
||||
/// VI: Lấy các items stock thấp.
|
||||
/// </summary>
|
||||
[HttpGet("low-stock")]
|
||||
[SwaggerOperation(Summary = "Get items with stock at or below reorder level")]
|
||||
[SwaggerResponse(200, "Low stock items retrieved successfully")]
|
||||
public async Task<ActionResult<ApiResponse<PagedResult<InventoryItemDto>>>> 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<PagedResult<InventoryItemDto>>.Ok(result));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user