1a502767ba
Adds GET /client/inventory/stock-movements, a unified read-only ledger flattening every GRN line, sale line and return across all products into one shape for the new Stock Tracking tab's table.
182 lines
7.9 KiB
C#
182 lines
7.9 KiB
C#
using Fitness_core_BE.DTOs.Inventory;
|
|
using Fitness_core_BE.Helpers;
|
|
using Fitness_core_BE.Interfaces.Services;
|
|
using Fitness_core_BE.Models.IOs;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using static Fitness_core_BE.Helpers.JwtMiddleware;
|
|
using static Fitness_core_BE.Helpers.ApiResponseHelper;
|
|
|
|
namespace Fitness_core_BE.Controllers
|
|
{
|
|
// Simple inventory: Products, Suppliers, GRN (stock in), Sales (stock out) and Returns
|
|
// (stock write-offs). All gated on the "inventory" nav permission.
|
|
[ApiController]
|
|
[Route("api/client/inventory")]
|
|
public class InventoryController(IInventoryService inventoryService) : ControllerBase
|
|
{
|
|
private readonly IInventoryService _inventoryService = inventoryService;
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("products")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetProducts([FromQuery] bool includeInactive = false)
|
|
=> Response(await _inventoryService.GetProducts(new { includeInactive }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("products/{id:guid}")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetProductById(Guid id)
|
|
=> Response(await _inventoryService.GetProductById(new { productId = id }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPost("products")]
|
|
public async Task<ActionResult<ApiResponse<object>>> CreateProduct([FromBody] CreateProductRequest request)
|
|
=> Response(await _inventoryService.CreateProduct(new
|
|
{
|
|
name = request.Name,
|
|
unitOfMeasure = request.UnitOfMeasure,
|
|
sellingPrice = request.SellingPrice
|
|
}, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPut("products/{id:guid}")]
|
|
public async Task<ActionResult<ApiResponse<object>>> UpdateProduct(Guid id, [FromBody] UpdateProductRequest request)
|
|
{
|
|
var payload = new Dictionary<string, object?> { ["productId"] = id };
|
|
if (request.Name != null) payload["name"] = request.Name;
|
|
if (request.UnitOfMeasure != null) payload["unitOfMeasure"] = request.UnitOfMeasure;
|
|
if (request.SellingPrice.HasValue) payload["sellingPrice"] = request.SellingPrice.Value;
|
|
if (request.IsActive.HasValue) payload["isActive"] = request.IsActive.Value;
|
|
|
|
return Response(await _inventoryService.UpdateProduct(payload, HttpContext));
|
|
}
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPost("products/{id:guid}/image")]
|
|
[RequestSizeLimit(6_000_000)]
|
|
[Consumes("multipart/form-data")]
|
|
public async Task<ActionResult<ApiResponse<object>>> UploadProductImage(Guid id, [FromForm] IFormFile image)
|
|
=> Response(await _inventoryService.UploadProductImage(id, image, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("suppliers")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetSuppliers([FromQuery] bool includeInactive = false)
|
|
=> Response(await _inventoryService.GetSuppliers(new { includeInactive }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPost("suppliers")]
|
|
public async Task<ActionResult<ApiResponse<object>>> CreateSupplier([FromBody] CreateSupplierRequest request)
|
|
=> Response(await _inventoryService.CreateSupplier(new
|
|
{
|
|
name = request.Name,
|
|
phone = request.Phone,
|
|
email = request.Email,
|
|
address = request.Address
|
|
}, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("grn")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetGrns()
|
|
=> Response(await _inventoryService.GetGrns(new { }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("grn/{id:guid}")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetGrnById(Guid id)
|
|
=> Response(await _inventoryService.GetGrnById(new { grnId = id }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPost("grn")]
|
|
public async Task<ActionResult<ApiResponse<object>>> CreateGrn([FromBody] CreateGrnRequest request)
|
|
=> Response(await _inventoryService.CreateGrn(new
|
|
{
|
|
supplierId = request.SupplierId,
|
|
receivedAt = request.ReceivedAt?.ToString("o"),
|
|
notes = request.Notes,
|
|
items = request.Items.Select(i => new { productId = i.ProductId, quantity = i.Quantity, unitCost = i.UnitCost })
|
|
}, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("sales")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetSales()
|
|
=> Response(await _inventoryService.GetSales(new { }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("sales/{id:guid}")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetSaleById(Guid id)
|
|
=> Response(await _inventoryService.GetSaleById(new { saleId = id }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPost("sales")]
|
|
public async Task<ActionResult<ApiResponse<object>>> CreateSale([FromBody] CreateSaleRequest request)
|
|
=> Response(await _inventoryService.CreateSale(new
|
|
{
|
|
soldAt = request.SoldAt?.ToString("o"),
|
|
paymentMethod = request.PaymentMethod,
|
|
customerId = request.CustomerId,
|
|
customerName = request.CustomerName,
|
|
items = request.Items.Select(i => new { productId = i.ProductId, quantity = i.Quantity, unitPrice = i.UnitPrice })
|
|
}, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("returns")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetStockReturns()
|
|
=> Response(await _inventoryService.GetStockReturns(new { }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("returns/{id:guid}")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetStockReturnById(Guid id)
|
|
=> Response(await _inventoryService.GetStockReturnById(new { stockReturnId = id }, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpPost("returns")]
|
|
public async Task<ActionResult<ApiResponse<object>>> CreateStockReturn([FromBody] CreateStockReturnRequest request)
|
|
=> Response(await _inventoryService.CreateStockReturn(new
|
|
{
|
|
productId = request.ProductId,
|
|
quantity = request.Quantity,
|
|
category = request.Category,
|
|
saleId = request.SaleId,
|
|
grnId = request.GrnId,
|
|
notes = request.Notes,
|
|
returnedAt = request.ReturnedAt?.ToString("o")
|
|
}, HttpContext));
|
|
|
|
[CustomMiddleware]
|
|
[TenantResolution]
|
|
[RequiresPermission("inventory")]
|
|
[HttpGet("stock-movements")]
|
|
public async Task<ActionResult<ApiResponse<object>>> GetStockMovements()
|
|
=> Response(await _inventoryService.GetStockMovements(new { }, HttpContext));
|
|
}
|
|
}
|