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); } }