feat(inventory): фильтр остатков по группе товаров с вложенными
GET /api/inventory/stock?groupId= — остатки по группе включая все вложенные подгруппы (через денормализованный ProductGroup.Path). Комбинируется с storeId/search/сортировкой. Нужно мобильному «FM Склад» для навигации по категориям. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5173eaa07c
commit
7231c83d23
|
|
@ -24,6 +24,7 @@ public record StockRow(
|
||||||
public async Task<ActionResult<PagedResult<StockRow>>> GetStock(
|
public async Task<ActionResult<PagedResult<StockRow>>> GetStock(
|
||||||
[FromQuery] Guid? storeId,
|
[FromQuery] Guid? storeId,
|
||||||
[FromQuery] Guid? productId,
|
[FromQuery] Guid? productId,
|
||||||
|
[FromQuery] Guid? groupId,
|
||||||
[FromQuery] string? search,
|
[FromQuery] string? search,
|
||||||
[FromQuery] bool includeZero = false,
|
[FromQuery] bool includeZero = false,
|
||||||
[FromQuery] int page = 1,
|
[FromQuery] int page = 1,
|
||||||
|
|
@ -40,6 +41,11 @@ public record StockRow(
|
||||||
|
|
||||||
if (storeId.HasValue) q = q.Where(x => x.s.StoreId == storeId.Value);
|
if (storeId.HasValue) q = q.Where(x => x.s.StoreId == storeId.Value);
|
||||||
if (productId.HasValue) q = q.Where(x => x.s.ProductId == productId.Value);
|
if (productId.HasValue) q = q.Where(x => x.s.ProductId == productId.Value);
|
||||||
|
if (groupId.HasValue)
|
||||||
|
{
|
||||||
|
var groupIds = await DescendantGroupIdsAsync(groupId.Value, ct);
|
||||||
|
q = q.Where(x => groupIds.Contains(x.p.ProductGroupId));
|
||||||
|
}
|
||||||
if (!includeZero) q = q.Where(x => x.s.Quantity != 0);
|
if (!includeZero) q = q.Where(x => x.s.Quantity != 0);
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
{
|
{
|
||||||
|
|
@ -111,6 +117,19 @@ public record StockRow(
|
||||||
public record StockExportRow(string Name, string? Article, string Unit, string Store,
|
public record StockExportRow(string Name, string? Article, string Unit, string Store,
|
||||||
decimal Quantity, decimal Reserved, decimal Available);
|
decimal Quantity, decimal Reserved, decimal Available);
|
||||||
|
|
||||||
|
/// <summary>Id группы + всех её вложенных подгрупп (по денормализованному
|
||||||
|
/// Path). Пустой список, если группа не найдена → фильтр вернёт пусто.</summary>
|
||||||
|
private async Task<List<Guid>> DescendantGroupIdsAsync(Guid groupId, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var path = await _db.ProductGroups.Where(g => g.Id == groupId)
|
||||||
|
.Select(g => g.Path).FirstOrDefaultAsync(ct);
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
return await _db.ProductGroups.Where(g => g.Id == groupId).Select(g => g.Id).ToListAsync(ct);
|
||||||
|
return await _db.ProductGroups
|
||||||
|
.Where(g => g.Path == path || g.Path.StartsWith(path + "/"))
|
||||||
|
.Select(g => g.Id).ToListAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
public record MovementRow(
|
public record MovementRow(
|
||||||
Guid Id, DateTime OccurredAt,
|
Guid Id, DateTime OccurredAt,
|
||||||
Guid ProductId, string ProductName, string? Article,
|
Guid ProductId, string ProductName, string? Article,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue