admin/src/Oms.Application/Labeling/LabelPrintDataBuilder.cs
zzh b679151eb1 feat: Hiprint模板转QuestPDF异步出面单,并接通打印链路
支持模板预览/字段字典、产品绑定 LabelTemplateId、订单 Pending→Ready 生成 PDF,以及 order/print、批量与仓内 printUrl。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-18 14:52:39 +08:00

200 lines
8.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Globalization;
using Oms.Application.Dtos.Orders;
namespace Oms.Application.Labeling;
/// <summary>
/// 将订单出参映射为 hiprint PrintData(camelCase)。
/// </summary>
public static class LabelPrintDataBuilder
{
public static IReadOnlyDictionary<string, object?> Build(
OrderOutput order,
string? operatorName = null,
DateTime? printTime = null)
{
var receiver = order.Receiver;
var sender = order.Sender;
var weight = PickWeight(order.WeightList);
var data = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
{
["serviceNo"] = order.ServiceNo,
["clientNo"] = order.ClientNo,
["trackNo"] = order.TrackNo,
["requestId"] = order.RequestId,
["productCode"] = order.ProductCode,
["salesProductName"] = order.ProductName,
["channelCode"] = order.ChannelCode,
["channelName"] = order.ChannelName,
["warehouseName"] = order.Warehouse,
["customerCode"] = order.CustomerCode,
["customerName"] = order.CustomerName,
["recipientName"] = receiver?.RecipientsName,
["recipientCompany"] = receiver?.CompanyName,
["recipientPhone"] = FirstNonEmpty(receiver?.Phone, receiver?.TelePhone),
["recipientEmail"] = receiver?.Email,
["recipientZip"] = receiver?.ZipCode,
["recipientCountry"] = order.ConsigneeCountry,
["recipientCountryName"] = order.ConsigneeCountryName,
["recipientProvince"] = receiver?.Province,
["recipientCity"] = receiver?.City,
["recipientStreet1"] = receiver?.StreetAddress1,
["recipientStreet2"] = receiver?.StreetAddress2,
["recipientStreet3"] = receiver?.StreetAddress3,
["recipientAddressFull"] = BuildAddressFull(
receiver?.StreetAddress1,
receiver?.StreetAddress2,
receiver?.StreetAddress3,
receiver?.StreetNumber,
receiver?.City,
receiver?.Province,
receiver?.ZipCode,
order.ConsigneeCountryName ?? order.ConsigneeCountry),
["recipientTaxNo"] = receiver?.TaxNo,
["vatNo"] = receiver?.Vat,
["eori"] = receiver?.Eori,
["senderName"] = sender?.SenderName,
["senderCompany"] = sender?.Company,
["senderPhone"] = FirstNonEmpty(sender?.Phone, sender?.TelePhone),
["senderEmail"] = sender?.Email,
["senderZip"] = sender?.ZipCode,
["senderProvince"] = sender?.Province,
["senderCity"] = sender?.City,
["senderStreet1"] = sender?.StreetAddress1,
["senderStreet2"] = sender?.StreetAddress2,
["senderStreet3"] = sender?.StreetAddress3,
["senderAddressFull"] = BuildAddressFull(
sender?.StreetAddress1,
sender?.StreetAddress2,
sender?.StreetAddress3,
sender?.HouseNumber,
sender?.City,
sender?.Province,
sender?.ZipCode,
null),
["actualWeight"] = weight?.ActualWeight,
["volumetricWeight"] = weight?.VolumetricWeight,
["chargeableWeight"] = order.ChargeableWeight ?? weight?.ChargeableWeight,
["length"] = weight?.Length,
["width"] = weight?.Width,
["height"] = weight?.Height,
["weightUnit"] = "KG",
["dimUnit"] = "CM",
["printTime"] = (printTime ?? DateTime.Now).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
["operatorName"] = operatorName
};
data["items"] = order.Items.Select(x => new Dictionary<string, object?>
{
["skuCode"] = x.Sku,
["skuName"] = FirstNonEmpty(x.CnName, x.EnName),
["qty"] = x.Quantity,
["weight"] = x.Weight ?? x.UnitWeight,
["declareNameEn"] = x.EnName,
["declareValue"] = x.UnitPrice ?? x.Amount
}).ToList();
return data;
}
/// <summary>
/// 设计预览用的 Mock PrintData。
/// </summary>
public static IReadOnlyDictionary<string, object?> BuildMock() =>
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
{
["serviceNo"] = "S202607180001",
["clientNo"] = "C-10086",
["trackNo"] = "YT1234567890",
["requestId"] = "REQ-DEMO",
["productCode"] = "DEMO-PRODUCT",
["salesProductName"] = "专线-普货",
["channelCode"] = "CH-01",
["channelName"] = "Demo Channel",
["warehouseName"] = "SZ-01",
["customerCode"] = "AF0A1",
["customerName"] = "Demo Customer",
["recipientName"] = "John Lee",
["recipientCompany"] = "ACME Inc",
["recipientPhone"] = "+1-555-0100",
["recipientEmail"] = "john@example.com",
["recipientZip"] = "90001",
["recipientCountry"] = "US",
["recipientCountryName"] = "United States",
["recipientProvince"] = "CA",
["recipientCity"] = "Los Angeles",
["recipientStreet1"] = "100 Demo Street",
["recipientStreet2"] = "Suite 8",
["recipientStreet3"] = null,
["recipientAddressFull"] = "100 Demo Street, Suite 8, Los Angeles, CA 90001, United States",
["recipientTaxNo"] = null,
["vatNo"] = null,
["eori"] = null,
["senderName"] = "OMS Warehouse",
["senderCompany"] = "OMS Co",
["senderPhone"] = "+86-755-0000000",
["senderEmail"] = "ops@example.com",
["senderZip"] = "518000",
["senderProvince"] = "Guangdong",
["senderCity"] = "Shenzhen",
["senderStreet1"] = "Warehouse Rd 1",
["senderStreet2"] = null,
["senderStreet3"] = null,
["senderAddressFull"] = "Warehouse Rd 1, Shenzhen, Guangdong 518000",
["actualWeight"] = 1.25m,
["volumetricWeight"] = 1.10m,
["chargeableWeight"] = 1.25m,
["length"] = 30m,
["width"] = 20m,
["height"] = 10m,
["weightUnit"] = "KG",
["dimUnit"] = "CM",
["printTime"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
["operatorName"] = "preview",
["items"] = new List<Dictionary<string, object?>>
{
new()
{
["skuCode"] = "SKU001",
["skuName"] = "T-Shirt",
["qty"] = 2,
["weight"] = 0.2m,
["declareNameEn"] = "T-Shirt",
["declareValue"] = 9.9m
}
}
};
private static OrderWeightOutput? PickWeight(IReadOnlyList<OrderWeightOutput> list)
{
if (list.Count == 0)
{
return null;
}
return list.FirstOrDefault(x =>
string.Equals(x.Type, "receive", StringComparison.OrdinalIgnoreCase)
|| string.Equals(x.Type, "收货", StringComparison.OrdinalIgnoreCase))
?? list.FirstOrDefault(x =>
string.Equals(x.Type, "forecast", StringComparison.OrdinalIgnoreCase)
|| string.Equals(x.Type, "预报", StringComparison.OrdinalIgnoreCase))
?? list[0];
}
private static string? BuildAddressFull(params string?[] parts)
{
var values = parts
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!.Trim())
.ToArray();
return values.Length == 0 ? null : string.Join(", ", values);
}
private static string? FirstNonEmpty(params string?[] values) =>
values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
}