Feat/inventory session booking #5

Open
HarithaRandunu wants to merge 3 commits from feat/inventory-sessionBooking into dev
4 changed files with 91 additions and 0 deletions
Showing only changes of commit 1a502767ba - Show all commits
+7
View File
@@ -170,5 +170,12 @@ namespace Fitness_core_BE.Controllers
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));
}
}
+21
View File
@@ -85,6 +85,27 @@ namespace Fitness_core_BE.DTOs.Inventory
public List<SaleItemDto> Items { get; set; } = [];
}
/// <summary>One row in the Stock Tracking tab's unified ledger -- every GRN line, sale line
/// and return across every product, flattened into one shape for a single filterable table.
/// Not persisted; built on read from GRNs/Sales/Returns.</summary>
public class StockMovementDto
{
/// <summary>"GRN" | "Sale" | "Return".</summary>
public string Type { get; set; } = string.Empty;
public Guid ProductId { get; set; }
public string ProductName { get; set; } = string.Empty;
/// <summary>The GRN/Sale number this line belongs to, or the return's Category label.</summary>
public string Reference { get; set; } = string.Empty;
/// <summary>Only set for Type == "Return" -- the write-off/return category.</summary>
public string? Category { get; set; }
/// <summary>Signed: positive for a GRN or a CustomerReturn (stock in), negative for a
/// Sale or any other return category (stock out).</summary>
public decimal QuantityChange { get; set; }
/// <summary>This line's monetary value -- a GRN/Sale line's LineTotal, or a return's Amount.</summary>
public decimal Amount { get; set; }
public DateTime Date { get; set; }
}
public class StockReturnDto
{
public Guid StockReturnId { get; set; }
+2
View File
@@ -22,5 +22,7 @@ namespace Fitness_core_BE.Interfaces.Services
Task<object> CreateStockReturn(object payload, HttpContext httpContext);
Task<object> GetStockReturns(object payload, HttpContext httpContext);
Task<object> GetStockReturnById(object payload, HttpContext httpContext);
Task<object> GetStockMovements(object payload, HttpContext httpContext);
}
}
+61
View File
@@ -576,6 +576,67 @@ namespace Fitness_core_BE.Services
return MapToDto(stockReturn);
}
// --- Stock Tracking (unified ledger, new) ---
/// <summary>Every GRN line, sale line and return across every product, flattened into one
/// shape for the Stock Tracking tab's table. Built from the same three already-tenant-
/// scoped reads the GRN/Sales/Returns tabs use -- no new repository queries needed, and
/// the dataset is small enough (a single gym's "simple inventory") that filtering happens
/// client-side rather than via query params.</summary>
public async Task<object> GetStockMovements(object payload, HttpContext httpContext)
{
var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(httpContext));
var grns = await _inventoryRepository.GetGrns(clientId);
var sales = await _inventoryRepository.GetSales(clientId);
var returns = await _inventoryRepository.GetStockReturns(clientId);
var movements = new List<StockMovementDto>();
foreach (var g in grns)
foreach (var i in g.Items)
movements.Add(new StockMovementDto
{
Type = "GRN",
ProductId = i.FKProductId,
ProductName = i.Product?.Name ?? string.Empty,
Reference = g.GrnNumber,
QuantityChange = i.Quantity,
Amount = i.LineTotal,
Date = g.ReceivedAt
});
foreach (var s in sales)
foreach (var i in s.Items)
movements.Add(new StockMovementDto
{
Type = "Sale",
ProductId = i.FKProductId,
ProductName = i.Product?.Name ?? string.Empty,
Reference = s.SaleNumber,
QuantityChange = -i.Quantity,
Amount = i.LineTotal,
Date = s.SoldAt
});
foreach (var r in returns)
movements.Add(new StockMovementDto
{
Type = "Return",
ProductId = r.FKProductId,
ProductName = r.Product?.Name ?? string.Empty,
Reference = r.Sale?.SaleNumber ?? r.Grn?.GrnNumber ?? r.Category,
Category = r.Category,
// CustomerReturn is the one category that adds back to stock -- see
// StockReturn's doc comment.
QuantityChange = r.Category == "CustomerReturn" ? r.Quantity : -r.Quantity,
Amount = r.Amount,
Date = r.ReturnedAt
});
return movements.OrderByDescending(m => m.Date).ToList();
}
/// <summary>Staff optionally picking an existing registered member to attach to a sale
/// (search-and-pick, not free-typed) -- purely for identification, mirrors
/// BookingService.ResolveOptionalCustomerLinkAsync exactly.</summary>