All checks were successful
Build and Deploy / deploy (push) Successful in 1m5s
Co-authored-by: Cursor <cursoragent@cursor.com>
553 lines
22 KiB
C#
553 lines
22 KiB
C#
using System.Linq.Expressions;
|
||
using Fake.Application;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Oms.Application.Dtos.Shared;
|
||
using Oms.Application.Dtos.Warehouse;
|
||
using Oms.Application.Labeling;
|
||
using Oms.Application.Shared;
|
||
using Oms.Application.Storage;
|
||
using Oms.Domain.ChannelsAggregate.Consts;
|
||
using Oms.Domain.WarehouseAggregate.Consts;
|
||
using Oms.Domain.WarehouseAggregate.Entities;
|
||
|
||
namespace Oms.Application.Services.Warehouse;
|
||
|
||
public partial class WarehouseService
|
||
{
|
||
/// <summary>
|
||
/// 查询入库预报列表。
|
||
/// </summary>
|
||
/// <param name="input">入参:入库预报分页查询条件。</param>
|
||
/// <returns>入库预报分页结果。</returns>
|
||
[HttpPost("/warehouse/inbound/list")]
|
||
public async Task<OmsPageResult<WarehouseInboundOutput>> GetInboundListAsync(WarehouseInboundListInput input)
|
||
{
|
||
var customerId = await ResolveListCustomerIdAsync(input.CustomerId);
|
||
var (hasCustomerFilter, customerIdValue) = CustomerFilter(customerId);
|
||
var keyword = input.Keyword?.Trim();
|
||
var hasKeyword = !string.IsNullOrWhiteSpace(keyword);
|
||
// 前端 Tab「全部」会传 all / 空;需忽略,不能按字面 ReceiptStatus == "all" 过滤
|
||
var receiptStatus = NormalizeInboundReceiptStatusFilter(input.ReceiptStatus);
|
||
var hasReceiptStatusFilter = !string.IsNullOrWhiteSpace(receiptStatus);
|
||
var hasWarehouseFilter = input.WarehouseId.HasValue;
|
||
var warehouseIdValue = input.WarehouseId.GetValueOrDefault();
|
||
|
||
Expression<Func<CInboundNotice, bool>> predicate = hasReceiptStatusFilter
|
||
? x =>
|
||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
|
||
&& x.ReceiptStatus == receiptStatus
|
||
&& (!hasKeyword || x.NoticeNo.Contains(keyword!) || (x.MarkReference != null && x.MarkReference.Contains(keyword!)))
|
||
: x =>
|
||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
|
||
&& (!hasKeyword || x.NoticeNo.Contains(keyword!) || (x.MarkReference != null && x.MarkReference.Contains(keyword!)));
|
||
|
||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||
// 避免 Fake GetPagedListAsync(sorting) 丢失 Where:total 正确但 items 返回全量
|
||
var (entities, total) = await RepositoryPageHelper.GetPageByUpdateTimeDescAsync(
|
||
InboundNoticeRepository,
|
||
predicate,
|
||
pageIndex,
|
||
pageSize);
|
||
|
||
var noticeIds = entities.Select(x => x.Id).ToArray();
|
||
var boxes = noticeIds.Length == 0
|
||
? []
|
||
: await InboundBoxRepository.GetListAsync(x => noticeIds.Contains(x.InboundNoticeId));
|
||
var boxIds = boxes.Select(x => x.Id).ToArray();
|
||
var lines = boxIds.Length == 0
|
||
? []
|
||
: await InboundBoxLineRepository.GetListAsync(x => boxIds.Contains(x.InboundBoxId));
|
||
|
||
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToArray();
|
||
var customers = customerIds.Length == 0
|
||
? []
|
||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||
var customerMap = customers.ToDictionary(x => x.Id);
|
||
|
||
return new OmsPageResult<WarehouseInboundOutput>
|
||
{
|
||
Items = entities.Select(x =>
|
||
{
|
||
var output = MapInboundOutput(x, boxes, lines);
|
||
if (customerMap.TryGetValue(x.CustomerId, out var customer))
|
||
{
|
||
output.CustomerName = customer.CustomerName;
|
||
output.CustomerCode = customer.CustomerCode;
|
||
}
|
||
|
||
return output;
|
||
}).ToList(),
|
||
Total = total
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 规范化入库状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。
|
||
/// </summary>
|
||
private static string? NormalizeInboundReceiptStatusFilter(string? status)
|
||
{
|
||
var value = status?.Trim();
|
||
if (string.IsNullOrEmpty(value)
|
||
|| value.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (value.Equals(WarehouseReceiptStatusCodes.Forecasted, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return WarehouseReceiptStatusCodes.Forecasted;
|
||
}
|
||
|
||
if (value.Equals(WarehouseReceiptStatusCodes.Received, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return WarehouseReceiptStatusCodes.Received;
|
||
}
|
||
|
||
if (value.Equals(WarehouseReceiptStatusCodes.Completed, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return WarehouseReceiptStatusCodes.Completed;
|
||
}
|
||
|
||
return value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询入库预报详情。
|
||
/// </summary>
|
||
/// <param name="id">入库预报 ID。</param>
|
||
/// <param name="customerId">客户 ID(可选)。</param>
|
||
/// <returns>入库预报详情(含箱与明细)。</returns>
|
||
[HttpGet("/warehouse/inbound/detail")]
|
||
public async Task<WarehouseInboundDetailOutput> GetInboundDetailAsync(
|
||
[FromQuery] int id,
|
||
[FromQuery] int? customerId = null)
|
||
{
|
||
var resolvedCustomerId = await ResolveListCustomerIdAsync(customerId);
|
||
var notice = await GetInboundNoticeOrThrowAsync(resolvedCustomerId, id);
|
||
var boxes = await InboundBoxRepository.GetListAsync(x => x.InboundNoticeId == notice.Id);
|
||
boxes = boxes.OrderBy(x => x.BoxSeqNo).ThenBy(x => x.Id).ToList();
|
||
var boxIds = boxes.Select(x => x.Id).ToArray();
|
||
var lines = boxIds.Length == 0
|
||
? []
|
||
: await InboundBoxLineRepository.GetListAsync(x => boxIds.Contains(x.InboundBoxId));
|
||
|
||
var output = MapInboundOutput(notice, boxes, lines);
|
||
return new WarehouseInboundDetailOutput
|
||
{
|
||
Id = output.Id,
|
||
CustomerId = output.CustomerId,
|
||
WarehouseId = output.WarehouseId,
|
||
NoticeNo = output.NoticeNo,
|
||
MarkReference = output.MarkReference,
|
||
InboundType = output.InboundType,
|
||
ReceiptStatus = output.ReceiptStatus,
|
||
ExpectedArrivalTime = output.ExpectedArrivalTime,
|
||
ActualArrivalTime = output.ActualArrivalTime,
|
||
Remark = output.Remark,
|
||
BoxCount = output.BoxCount,
|
||
PlannedQty = output.PlannedQty,
|
||
CreateTime = output.CreateTime,
|
||
Boxes = boxes.Select(box => MapInboundBox(box, lines.Where(l => l.InboundBoxId == box.Id))).ToList()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存入库预报(新增或编辑)。编辑仅允许「预报」「待入库」状态;唛头(MarkReference)选填。
|
||
/// </summary>
|
||
/// <param name="input">入参:入库预报保存数据。</param>
|
||
/// <returns>保存后的入库预报详情。</returns>
|
||
[HttpPost("/warehouse/inbound/save")]
|
||
public async Task<WarehouseInboundDetailOutput> SaveInboundAsync(WarehouseInboundSaveInput input)
|
||
{
|
||
var customerId = await ResolveMutationCustomerIdAsync(input.CustomerId);
|
||
if (input.WarehouseId <= 0)
|
||
{
|
||
throw new BusinessException("仓库ID不能为空");
|
||
}
|
||
|
||
if (input.Boxes.Count == 0)
|
||
{
|
||
throw new BusinessException("请至少添加一个箱子");
|
||
}
|
||
|
||
CInboundNotice notice;
|
||
if (input.Id > 0)
|
||
{
|
||
notice = await GetInboundNoticeOrThrowAsync(customerId, input.Id);
|
||
if (notice.ReceiptStatus != WarehouseReceiptStatusCodes.Forecasted
|
||
&& notice.ReceiptStatus != WarehouseReceiptStatusCodes.Received)
|
||
{
|
||
throw new BusinessException("仅预报或待入库状态可编辑");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
notice = new CInboundNotice
|
||
{
|
||
CustomerId = customerId,
|
||
NoticeNo = WarehouseCustomerHelper.GenerateInboundNoticeNo(),
|
||
ReceiptStatus = WarehouseReceiptStatusCodes.Forecasted
|
||
};
|
||
}
|
||
|
||
notice.WarehouseId = input.WarehouseId;
|
||
notice.MarkReference = string.IsNullOrWhiteSpace(input.MarkReference) ? null : input.MarkReference.Trim();
|
||
notice.InboundType = string.IsNullOrWhiteSpace(input.InboundType)
|
||
? WarehouseInboundTypeCodes.Normal
|
||
: input.InboundType.Trim();
|
||
notice.ExpectedArrivalTime = input.ExpectedArrivalTime;
|
||
notice.Remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
|
||
|
||
if (input.Id > 0)
|
||
{
|
||
await InboundNoticeRepository.UpdateAsync(notice);
|
||
await ClearInboundBoxesAsync(notice.Id);
|
||
}
|
||
else
|
||
{
|
||
await InboundNoticeRepository.InsertAsync(notice);
|
||
}
|
||
|
||
foreach (var boxInput in input.Boxes)
|
||
{
|
||
var box = new CInboundBox
|
||
{
|
||
InboundNoticeId = notice.Id,
|
||
BoxSeqNo = string.IsNullOrWhiteSpace(boxInput.BoxSeqNo) ? "1" : boxInput.BoxSeqNo.Trim(),
|
||
MarkBoxNo = string.IsNullOrWhiteSpace(boxInput.MarkBoxNo) ? null : boxInput.MarkBoxNo.Trim(),
|
||
WeightG = boxInput.WeightG,
|
||
LengthCm = boxInput.LengthCm,
|
||
WidthCm = boxInput.WidthCm,
|
||
HeightCm = boxInput.HeightCm
|
||
};
|
||
await InboundBoxRepository.InsertAsync(box);
|
||
|
||
foreach (var lineInput in boxInput.Lines)
|
||
{
|
||
if (lineInput.SkuId <= 0 || lineInput.PlannedQty <= 0)
|
||
{
|
||
throw new BusinessException("箱内 SKU 行数据不完整");
|
||
}
|
||
|
||
var sku = await GetSkuOrThrowAsync(customerId, lineInput.SkuId);
|
||
var line = new CInboundBoxLine
|
||
{
|
||
InboundBoxId = box.Id,
|
||
SkuId = sku.Id,
|
||
SkuCode = string.IsNullOrWhiteSpace(lineInput.SkuCode) ? sku.SkuCode : lineInput.SkuCode.Trim(),
|
||
PlannedQty = lineInput.PlannedQty
|
||
};
|
||
await InboundBoxLineRepository.InsertAsync(line);
|
||
}
|
||
}
|
||
|
||
return await GetInboundDetailAsync(notice.Id, customerId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除入库预报。
|
||
/// </summary>
|
||
/// <param name="input">入参:入库预报 ID。</param>
|
||
[HttpPost("/warehouse/inbound/delete")]
|
||
public async Task DeleteInboundAsync(IdInput input)
|
||
{
|
||
var customerId = await ResolveListCustomerIdAsync(null);
|
||
var notice = await GetInboundNoticeOrThrowAsync(customerId, input.Id);
|
||
if (notice.ReceiptStatus != WarehouseReceiptStatusCodes.Forecasted)
|
||
{
|
||
throw new BusinessException("仅预报状态可删除");
|
||
}
|
||
|
||
await ClearInboundBoxesAsync(notice.Id);
|
||
await InboundNoticeRepository.DeleteAsync(notice);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认入库收货并上架。
|
||
/// </summary>
|
||
/// <param name="input">入参:收货箱与实收数量。</param>
|
||
[HttpPost("/warehouse/inbound/confirm-receive")]
|
||
public async Task ConfirmInboundReceiveAsync(WarehouseInboundConfirmReceiveInput input)
|
||
{
|
||
var customerId = await ResolveMutationCustomerIdAsync(input.CustomerId);
|
||
var notice = await GetInboundNoticeOrThrowAsync(customerId, input.InboundNoticeId);
|
||
if (notice.ReceiptStatus == WarehouseReceiptStatusCodes.Completed)
|
||
{
|
||
throw new BusinessException("入库单已完成收货");
|
||
}
|
||
|
||
if (input.Boxes.Count == 0)
|
||
{
|
||
throw new BusinessException("请提交收货箱数据");
|
||
}
|
||
|
||
var boxes = await InboundBoxRepository.GetListAsync(x => x.InboundNoticeId == notice.Id);
|
||
var boxMap = boxes.ToDictionary(x => x.Id);
|
||
var boxIds = boxes.Select(x => x.Id).ToArray();
|
||
var lines = boxIds.Length == 0
|
||
? []
|
||
: await InboundBoxLineRepository.GetListAsync(x => boxIds.Contains(x.InboundBoxId));
|
||
var lineMap = lines.ToDictionary(x => x.Id);
|
||
|
||
foreach (var boxInput in input.Boxes)
|
||
{
|
||
if (!boxMap.TryGetValue(boxInput.BoxId, out var box))
|
||
{
|
||
throw new BusinessException($"箱 {boxInput.BoxId} 不存在");
|
||
}
|
||
|
||
foreach (var lineInput in boxInput.Lines)
|
||
{
|
||
if (!lineMap.TryGetValue(lineInput.LineId, out var line) || line.InboundBoxId != box.Id)
|
||
{
|
||
throw new BusinessException($"箱内行 {lineInput.LineId} 不存在");
|
||
}
|
||
|
||
if (lineInput.ReceivedQty < 0)
|
||
{
|
||
throw new BusinessException("实收数量不能为负");
|
||
}
|
||
|
||
if (lineInput.ReceivedQty > 0 && lineInput.BinLocationId <= 0)
|
||
{
|
||
throw new BusinessException("上架库位不能为空");
|
||
}
|
||
|
||
var previousQty = line.ReceivedQty ?? 0;
|
||
var deltaQty = lineInput.ReceivedQty - previousQty;
|
||
line.ReceivedQty = lineInput.ReceivedQty;
|
||
line.BinLocationId = lineInput.ReceivedQty > 0 ? lineInput.BinLocationId : null;
|
||
await InboundBoxLineRepository.UpdateAsync(line);
|
||
|
||
if (deltaQty > 0)
|
||
{
|
||
await UpsertInventoryQtyAsync(
|
||
customerId,
|
||
notice.WarehouseId,
|
||
line.SkuId,
|
||
lineInput.BinLocationId,
|
||
deltaQty);
|
||
}
|
||
}
|
||
|
||
box.IsReceived = boxInput.Lines.All(x =>
|
||
{
|
||
if (!lineMap.TryGetValue(x.LineId, out var line))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return x.ReceivedQty >= line.PlannedQty;
|
||
});
|
||
await InboundBoxRepository.UpdateAsync(box);
|
||
}
|
||
|
||
notice.ReceiptStatus = WarehouseReceiptStatusCodes.Completed;
|
||
notice.ActualArrivalTime = DateTime.Now;
|
||
await InboundNoticeRepository.UpdateAsync(notice);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打印入库箱唛标签:按箱渲染后合并为一个多页 PDF,返回可打印 URL。
|
||
/// </summary>
|
||
/// <param name="input">入参:入库预报 ID(管理端可带 customerId)。</param>
|
||
/// <returns>printUrl / noticeNo / pageCount。</returns>
|
||
[HttpPost("/warehouse/inbound/print-labels")]
|
||
public async Task<WarehouseInboundPrintLabelsOutput> PrintInboundLabelsAsync(
|
||
WarehouseInboundPrintLabelsInput input)
|
||
{
|
||
if (input.Id <= 0)
|
||
{
|
||
throw new BusinessException("入库预报 ID 无效");
|
||
}
|
||
|
||
var customerId = await ResolveListCustomerIdAsync(input.CustomerId);
|
||
var notice = await GetInboundNoticeOrThrowAsync(customerId, input.Id);
|
||
var boxes = await InboundBoxRepository.GetListAsync(x => x.InboundNoticeId == notice.Id);
|
||
boxes = boxes.OrderBy(x => x.BoxSeqNo).ThenBy(x => x.Id).ToList();
|
||
if (boxes.Count == 0)
|
||
{
|
||
throw new BusinessException("该预报没有箱子,无法打印箱唛");
|
||
}
|
||
|
||
var boxType = LabelTemplateTypeCodes.Box;
|
||
var templates = await LabelTemplateRepository.GetListAsync(x => x.TemplateType == boxType);
|
||
var template = templates
|
||
.Where(x => !string.IsNullOrWhiteSpace(x.TemplateJson))
|
||
.OrderByDescending(x => x.UpdateTime)
|
||
.ThenByDescending(x => x.Id)
|
||
.FirstOrDefault();
|
||
if (template == null)
|
||
{
|
||
throw new BusinessException("未配置箱唛标签模板(类型 box),请先在标签设计中创建");
|
||
}
|
||
|
||
var boxIds = boxes.Select(x => x.Id).ToArray();
|
||
var lines = await InboundBoxLineRepository.GetListAsync(x => boxIds.Contains(x.InboundBoxId));
|
||
var skuIds = lines.Select(x => x.SkuId).Distinct().ToArray();
|
||
var skus = skuIds.Length == 0
|
||
? []
|
||
: await SkuRepository.GetListAsync(x => skuIds.Contains(x.Id));
|
||
var skuMap = skus.ToDictionary(x => x.Id);
|
||
|
||
var warehouse = await WarehouseAddressRepository.FirstOrDefaultAsync(x => x.Id == notice.WarehouseId);
|
||
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == notice.CustomerId);
|
||
var operatorName = CurrentUser?.UserName;
|
||
|
||
var pageCount = boxes.Count;
|
||
var printPages = new List<IReadOnlyDictionary<string, object?>>(pageCount);
|
||
for (var i = 0; i < boxes.Count; i++)
|
||
{
|
||
var box = boxes[i];
|
||
var boxLines = lines.Where(x => x.InboundBoxId == box.Id).ToList();
|
||
printPages.Add(InboundBoxLabelPrintDataBuilder.Build(
|
||
notice,
|
||
box,
|
||
boxLines,
|
||
skuMap,
|
||
warehouseName: warehouse?.WarehouseName,
|
||
customerName: customer?.CustomerName,
|
||
customerCode: customer?.CustomerCode,
|
||
operatorName: operatorName,
|
||
pageIndex: i + 1,
|
||
pageCount: pageCount));
|
||
}
|
||
|
||
var pdfBytes = await LabelPdfRenderer.RenderManyAsync(template.TemplateJson!, printPages);
|
||
var printUrl = await SaveInboundLabelPdfAsync(notice.NoticeNo, pdfBytes);
|
||
|
||
return new WarehouseInboundPrintLabelsOutput
|
||
{
|
||
PrintUrl = printUrl,
|
||
NoticeNo = notice.NoticeNo,
|
||
PageCount = pageCount
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询入库预报状态汇总。
|
||
/// </summary>
|
||
/// <param name="customerId">客户 ID(可选)。</param>
|
||
/// <param name="warehouseId">仓库 ID(可选)。</param>
|
||
/// <returns>各状态数量汇总。</returns>
|
||
[HttpGet("/warehouse/inbound/summary")]
|
||
public async Task<WarehouseInboundSummaryOutput> GetInboundSummaryAsync([FromQuery] int? customerId = null, [FromQuery] int? warehouseId = null)
|
||
{
|
||
var resolvedCustomerId = await ResolveListCustomerIdAsync(customerId);
|
||
var (hasCustomerFilter, customerIdValue) = CustomerFilter(resolvedCustomerId);
|
||
var hasWarehouseFilter = warehouseId.HasValue;
|
||
var warehouseIdValue = warehouseId.GetValueOrDefault();
|
||
var notices = await InboundNoticeRepository.GetListAsync(x =>
|
||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue));
|
||
|
||
return new WarehouseInboundSummaryOutput
|
||
{
|
||
Forecasted = notices.Count(x => x.ReceiptStatus == WarehouseReceiptStatusCodes.Forecasted),
|
||
Received = notices.Count(x => x.ReceiptStatus == WarehouseReceiptStatusCodes.Received),
|
||
Completed = notices.Count(x => x.ReceiptStatus == WarehouseReceiptStatusCodes.Completed)
|
||
};
|
||
}
|
||
|
||
private async Task<CInboundNotice> GetInboundNoticeOrThrowAsync(int? customerId, int id)
|
||
{
|
||
var entity = customerId.HasValue
|
||
? await InboundNoticeRepository.FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId.Value)
|
||
: await InboundNoticeRepository.FirstOrDefaultAsync(x => x.Id == id);
|
||
if (entity == null)
|
||
{
|
||
throw new BusinessException("入库预报不存在");
|
||
}
|
||
|
||
return entity;
|
||
}
|
||
|
||
private async Task<string> SaveInboundLabelPdfAsync(string noticeNo, byte[] pdfBytes)
|
||
{
|
||
var safeNo = string.IsNullOrWhiteSpace(noticeNo) ? "inbound" : noticeNo.Trim();
|
||
foreach (var c in Path.GetInvalidFileNameChars())
|
||
{
|
||
safeNo = safeNo.Replace(c, '_');
|
||
}
|
||
|
||
var fileName = $"{safeNo}-{DateTime.Now:yyyyMMddHHmmss}.pdf";
|
||
var relativeKey = $"uploads/prints/inbound-labels/{fileName}";
|
||
await using var stream = new MemoryStream(pdfBytes);
|
||
var key = await FileStorage.SaveObjectAsync(stream, relativeKey, "application/pdf");
|
||
return FileStorage.GetPublicUrl(key);
|
||
}
|
||
|
||
private async Task ClearInboundBoxesAsync(int noticeId)
|
||
{
|
||
var boxes = await InboundBoxRepository.GetListAsync(x => x.InboundNoticeId == noticeId);
|
||
if (boxes.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var boxIds = boxes.Select(x => x.Id).ToArray();
|
||
var lines = await InboundBoxLineRepository.GetListAsync(x => boxIds.Contains(x.InboundBoxId));
|
||
foreach (var line in lines)
|
||
{
|
||
await InboundBoxLineRepository.DeleteAsync(line);
|
||
}
|
||
|
||
foreach (var box in boxes)
|
||
{
|
||
await InboundBoxRepository.DeleteAsync(box);
|
||
}
|
||
}
|
||
|
||
private static WarehouseInboundOutput MapInboundOutput(
|
||
CInboundNotice notice,
|
||
IReadOnlyCollection<CInboundBox> boxes,
|
||
IReadOnlyCollection<CInboundBoxLine> lines)
|
||
{
|
||
var noticeBoxes = boxes.Where(x => x.InboundNoticeId == notice.Id).ToList();
|
||
var noticeBoxIds = noticeBoxes.Select(x => x.Id).ToHashSet();
|
||
var noticeLines = lines.Where(x => noticeBoxIds.Contains(x.InboundBoxId)).ToList();
|
||
|
||
return new WarehouseInboundOutput
|
||
{
|
||
Id = notice.Id,
|
||
CustomerId = notice.CustomerId,
|
||
WarehouseId = notice.WarehouseId,
|
||
NoticeNo = notice.NoticeNo,
|
||
MarkReference = notice.MarkReference,
|
||
InboundType = notice.InboundType,
|
||
ReceiptStatus = notice.ReceiptStatus,
|
||
ExpectedArrivalTime = notice.ExpectedArrivalTime,
|
||
ActualArrivalTime = notice.ActualArrivalTime,
|
||
Remark = notice.Remark,
|
||
BoxCount = noticeBoxes.Count,
|
||
PlannedQty = noticeLines.Sum(x => x.PlannedQty),
|
||
CreateTime = notice.CreateTime
|
||
};
|
||
}
|
||
|
||
private static WarehouseInboundBoxOutput MapInboundBox(CInboundBox box, IEnumerable<CInboundBoxLine> lines) =>
|
||
new()
|
||
{
|
||
Id = box.Id,
|
||
BoxSeqNo = box.BoxSeqNo,
|
||
MarkBoxNo = box.MarkBoxNo,
|
||
WeightG = box.WeightG,
|
||
LengthCm = box.LengthCm,
|
||
WidthCm = box.WidthCm,
|
||
HeightCm = box.HeightCm,
|
||
IsReceived = box.IsReceived,
|
||
Lines = lines.Select(line => new WarehouseInboundLineOutput
|
||
{
|
||
Id = line.Id,
|
||
SkuId = line.SkuId,
|
||
SkuCode = line.SkuCode,
|
||
PlannedQty = line.PlannedQty,
|
||
ReceivedQty = line.ReceivedQty,
|
||
BinLocationId = line.BinLocationId
|
||
}).ToList()
|
||
};
|
||
}
|