using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Shared;
using Oms.Application.Dtos.Warehouse;
using Oms.Application.Shared;
namespace Oms.Application.Services.Warehouse;
public partial class WarehouseService
{
///
/// 查询库存列表。
///
/// 入参:库存分页查询条件。
/// 按 SKU 汇总的库存分页结果。
[HttpPost("/warehouse/inventory/list")]
public async Task> GetInventoryListAsync(WarehouseInventoryListInput input)
{
var customerId = await ResolveCustomerIdAsync(input.CustomerId);
var inventories = await InventoryRepository.GetListAsync(x =>
x.CustomerId == customerId
&& x.Qty > 0
&& (!input.WarehouseId.HasValue || x.WarehouseId == input.WarehouseId.Value));
var skuIds = inventories.Select(x => x.SkuId).Distinct().ToArray();
var skus = skuIds.Length == 0
? []
: await SkuRepository.GetListAsync(x => x.CustomerId == customerId && skuIds.Contains(x.Id));
var skuMap = skus.ToDictionary(x => x.Id);
var keyword = input.Keyword?.Trim();
var grouped = inventories
.GroupBy(x => new { x.SkuId, x.WarehouseId })
.Select(g =>
{
skuMap.TryGetValue(g.Key.SkuId, out var sku);
return new WarehouseInventoryOutput
{
SkuId = g.Key.SkuId,
SkuCode = sku?.SkuCode ?? string.Empty,
SkuName = sku?.SkuName ?? string.Empty,
WarehouseId = g.Key.WarehouseId,
TotalQty = g.Sum(x => x.Qty),
BinCount = g.Count()
};
})
.Where(x =>
string.IsNullOrWhiteSpace(keyword)
|| x.SkuCode.Contains(keyword!, StringComparison.OrdinalIgnoreCase)
|| x.SkuName.Contains(keyword!, StringComparison.OrdinalIgnoreCase))
.OrderBy(x => x.SkuCode)
.ThenBy(x => x.WarehouseId)
.ToList();
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var skip = (pageIndex - 1) * pageSize;
return new OmsPageResult
{
Items = grouped.Skip(skip).Take(pageSize).ToList(),
Total = grouped.Count
};
}
///
/// 查询 SKU 在各库位的库存明细。
///
/// SKU ID。
/// 仓库 ID。
/// 客户 ID(可选)。
/// 库位库存明细列表。
[HttpGet("/warehouse/inventory/bin-detail")]
public async Task> GetInventoryBinDetailAsync(
[FromQuery] int skuId,
[FromQuery] int warehouseId,
[FromQuery] int? customerId = null)
{
if (skuId <= 0 || warehouseId <= 0)
{
throw new BusinessException("SKU 和仓库不能为空");
}
var resolvedCustomerId = await ResolveCustomerIdAsync(customerId);
await GetSkuOrThrowAsync(resolvedCustomerId, skuId);
var inventories = await InventoryRepository.GetListAsync(x =>
x.CustomerId == resolvedCustomerId
&& x.WarehouseId == warehouseId
&& x.SkuId == skuId
&& x.Qty > 0);
var binIds = inventories.Select(x => x.BinLocationId).Distinct().ToArray();
var bins = binIds.Length == 0
? []
: await BinLocationRepository.GetListAsync(x => binIds.Contains(x.Id));
var binMap = bins.ToDictionary(x => x.Id);
return new OmsItemsResult
{
Items = inventories
.OrderBy(x => x.BinLocationId)
.Select(x =>
{
binMap.TryGetValue(x.BinLocationId, out var bin);
return new WarehouseInventoryBinDetailOutput
{
Id = x.Id,
BinLocationId = x.BinLocationId,
BinCode = bin?.BinCode ?? string.Empty,
BinName = bin?.BinName,
Qty = x.Qty
};
})
.ToList()
};
}
///
/// 库存移库(库位间调拨)。
///
/// 入参:源库位、目标库位与数量。
[HttpPost("/warehouse/inventory/relocate")]
public async Task RelocateInventoryAsync(WarehouseInventoryRelocateInput input)
{
var customerId = await ResolveCustomerIdAsync(input.CustomerId);
if (input.SourceBinLocationId == input.TargetBinLocationId)
{
throw new BusinessException("源库位与目标库位不能相同");
}
await GetSkuOrThrowAsync(customerId, input.SkuId);
var source = await InventoryRepository.FirstOrDefaultAsync(x =>
x.CustomerId == customerId
&& x.WarehouseId == input.WarehouseId
&& x.SkuId == input.SkuId
&& x.BinLocationId == input.SourceBinLocationId);
if (source == null || source.Qty < input.Qty)
{
throw new BusinessException("源库位库存不足");
}
await UpsertInventoryQtyAsync(customerId, input.WarehouseId, input.SkuId, input.SourceBinLocationId, -input.Qty);
await UpsertInventoryQtyAsync(customerId, input.WarehouseId, input.SkuId, input.TargetBinLocationId, input.Qty);
}
}