feat: 新增 /order/print/batch 同步批量返回面单地址
All checks were successful
Build and Deploy / deploy (push) Successful in 1m44s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-09-12 18:56:28 +08:00
parent 0fdc9e29f4
commit 61d1502148
5 changed files with 174 additions and 0 deletions

View File

@ -1,3 +1,5 @@
using System.ComponentModel.DataAnnotations;
namespace Oms.Application.Dtos.Orders;
/// <summary>
@ -20,3 +22,37 @@ public class OrderPrintOutput
/// </summary>
public string? LabelGenerateError { get; set; }
}
/// <summary>
/// 批量获取面单打印地址入参。
/// </summary>
public class OrderPrintBatchInput
{
[Required]
[MinLength(1)]
public List<int> OrderIds { get; set; } = [];
}
/// <summary>
/// 批量获取面单打印地址单条结果。
/// </summary>
public class OrderPrintBatchItemOutput
{
public int OrderId { get; set; }
public bool Success { get; set; }
public string PrintUrl { get; set; } = string.Empty;
public string? LabelGenerateStatus { get; set; }
public string? Error { get; set; }
}
/// <summary>
/// 批量获取面单打印地址出参。
/// </summary>
public class OrderPrintBatchOutput
{
public List<OrderPrintBatchItemOutput> Items { get; set; } = [];
}

View File

@ -34,6 +34,37 @@ public sealed class OrderFileManager(
return string.IsNullOrWhiteSpace(file?.FileUrl) ? null : fileStorage.GetPublicUrl(file.FileUrl.Trim());
}
/// <summary>
/// 批量取系统面单公开地址(已存在才返回)。
/// </summary>
public async Task<Dictionary<int, string>> GetSystemLabelUrlsAsync(
IReadOnlyCollection<int> orderIds,
CancellationToken cancellationToken = default)
{
var result = new Dictionary<int, string>();
var ids = orderIds?.Where(x => x > 0).Distinct().ToList() ?? [];
if (ids.Count == 0)
{
return result;
}
var files = await orderFileRepository.GetByOrderIdsAndTypeAsync(
ids,
OrderFileTypeCodes.SystemLabel,
cancellationToken);
foreach (var file in files)
{
if (string.IsNullOrWhiteSpace(file.FileUrl) || result.ContainsKey(file.OrderId))
{
continue;
}
result[file.OrderId] = fileStorage.GetPublicUrl(file.FileUrl.Trim());
}
return result;
}
public async Task<OrderFileGroupedOutput> GetGroupedAsync(
int orderId,
bool customerView,

View File

@ -317,6 +317,89 @@ public partial class OrderService : ApplicationService
};
}
/// <summary>
/// 批量获取打印面单地址(同步返回,避免前端逐单请求)。
/// </summary>
/// <param name="input">入参:订单 ID 列表。</param>
/// <returns>各订单打印结果。</returns>
[HttpPost("/order/print/batch")]
public async Task<OrderPrintBatchOutput> PrintBatchAsync([FromBody] OrderPrintBatchInput input)
{
var orderIds = (input.OrderIds ?? []).Where(x => x > 0).Distinct().ToList();
if (orderIds.Count == 0)
{
throw new BusinessException("请选择要打印的订单");
}
const int maxBatchSize = 100;
if (orderIds.Count > maxBatchSize)
{
throw new BusinessException($"单次最多获取 {maxBatchSize} 张面单");
}
var output = new OrderPrintBatchOutput
{
Items = new List<OrderPrintBatchItemOutput>(orderIds.Count)
};
// 已有系统面单一律批量查出,避免 N 次附件查询
var existingUrls = await OrderFileManager.GetSystemLabelUrlsAsync(orderIds);
var pendingIds = new List<int>();
foreach (var orderId in orderIds)
{
if (existingUrls.TryGetValue(orderId, out var url) && !string.IsNullOrWhiteSpace(url))
{
output.Items.Add(new OrderPrintBatchItemOutput
{
OrderId = orderId,
Success = true,
PrintUrl = url,
LabelGenerateStatus = LabelGenerateStatusCodes.Ready
});
}
else
{
pendingIds.Add(orderId);
}
}
// 未就绪的走单票打印逻辑(含即时补生成),单条失败不影响其它
foreach (var orderId in pendingIds)
{
try
{
var print = await PrintAsync(new OrderActionInput { Id = orderId });
var ok = !string.IsNullOrWhiteSpace(print.PrintUrl);
output.Items.Add(new OrderPrintBatchItemOutput
{
OrderId = orderId,
Success = ok,
PrintUrl = print.PrintUrl ?? string.Empty,
LabelGenerateStatus = print.LabelGenerateStatus,
Error = ok ? null : (print.LabelGenerateError ?? "未获取到打印文件")
});
}
catch (Exception ex)
{
output.Items.Add(new OrderPrintBatchItemOutput
{
OrderId = orderId,
Success = false,
Error = ex is BusinessException be ? be.Message : ex.Message
});
}
}
// 保持与入参顺序一致
var byId = output.Items.ToDictionary(x => x.OrderId);
output.Items = orderIds
.Select(id => byId.TryGetValue(id, out var item)
? item
: new OrderPrintBatchItemOutput { OrderId = id, Success = false, Error = "未处理" })
.ToList();
return output;
}
/// <summary>
/// 导出订单。
/// </summary>

View File

@ -17,6 +17,14 @@ public interface IOrderFileRepository : IRepository<OrderFile>
string fileType,
CancellationToken cancellationToken = default);
/// <summary>
/// 按订单 ID 集合 + 附件类型批量查询。
/// </summary>
Task<List<OrderFile>> GetByOrderIdsAndTypeAsync(
IReadOnlyCollection<int> orderIds,
string fileType,
CancellationToken cancellationToken = default);
Task SoftDeleteByOrderAndTypeAsync(
int orderId,
string fileType,

View File

@ -31,6 +31,22 @@ public class OrderFileRepository
x => x.OrderId == orderId && x.FileType == fileType,
cancellationToken: cancellationToken);
public Task<List<OrderFile>> GetByOrderIdsAndTypeAsync(
IReadOnlyCollection<int> orderIds,
string fileType,
CancellationToken cancellationToken = default)
{
var ids = orderIds?.Where(x => x > 0).Distinct().ToList() ?? [];
if (ids.Count == 0)
{
return Task.FromResult(new List<OrderFile>());
}
return GetListAsync(
x => ids.Contains(x.OrderId) && x.FileType == fileType,
cancellationToken: cancellationToken);
}
public async Task SoftDeleteByOrderAndTypeAsync(
int orderId,
string fileType,