feat(order): 强化小包下单规则并暴露预报失败原因
客户单号必填且同客户唯一,运单号统一 NE 前缀自动生成;模板导入不再接受跟踪号/运单号;列表与导出返回预报失败原因字段。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7d5bc2408f
commit
d8752959e7
11
sql/migrate_t_order_predeclare_fail_reason.sql
Normal file
11
sql/migrate_t_order_predeclare_fail_reason.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- t_order 预报失败原因(可重复执行)
|
||||
-- 在 OMS 业务库执行:mysql -h<host> -u<user> -p<db> < migrate_t_order_predeclare_fail_reason.sql
|
||||
|
||||
SET @db = DATABASE();
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'PredeclareFailReason');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `PredeclareFailReason` VARCHAR(512) NULL COMMENT ''预报失败原因'' AFTER `IsRemote`',
|
||||
'SELECT ''skip PredeclareFailReason''');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@ -105,6 +105,11 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public string? ExportCustomsMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预报失败原因(草稿订单)。
|
||||
/// </summary>
|
||||
public string? PredeclareFailReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 申报币种。
|
||||
/// </summary>
|
||||
|
||||
@ -121,6 +121,9 @@ public partial class OrderService
|
||||
CustomerId = order.CustomerId,
|
||||
RequestId = order.RequestId,
|
||||
ExportCustomsMode = order.ExportCustomsMode,
|
||||
PredeclareFailReason = string.IsNullOrWhiteSpace(order.PredeclareFailReason)
|
||||
? null
|
||||
: order.PredeclareFailReason.Trim(),
|
||||
DeclaredCurrency = order.DeclaredCurrency,
|
||||
DeclaredSum = order.DeclaredSum,
|
||||
Quantity = order.Quantity,
|
||||
@ -188,6 +191,7 @@ public partial class OrderService
|
||||
x.CustomerId,
|
||||
x.Status,
|
||||
x.OrderStatus,
|
||||
x.PredeclareFailReason,
|
||||
x.ReceiveTime,
|
||||
x.ShipTime,
|
||||
x.PayTime,
|
||||
|
||||
@ -27,7 +27,19 @@ public partial class OrderService
|
||||
|
||||
if (input.ClientNo != null)
|
||||
{
|
||||
order.ClientNo = string.IsNullOrWhiteSpace(input.ClientNo) ? null : input.ClientNo.Trim();
|
||||
var newClientNo = string.IsNullOrWhiteSpace(input.ClientNo) ? null : input.ClientNo.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(newClientNo)
|
||||
&& !string.Equals(newClientNo, order.ClientNo, StringComparison.OrdinalIgnoreCase)
|
||||
&& order.CustomerId.HasValue)
|
||||
{
|
||||
var duplicated = await OrderRepository.GetByCustomerAndClientNoAsync(order.CustomerId.Value, newClientNo);
|
||||
if (duplicated != null && duplicated.Id != order.Id)
|
||||
{
|
||||
throw new BusinessException("客户单号已存在");
|
||||
}
|
||||
}
|
||||
|
||||
order.ClientNo = newClientNo;
|
||||
}
|
||||
|
||||
if (input.TrackNo != null)
|
||||
|
||||
@ -78,7 +78,7 @@ public partial class OrderService
|
||||
throw new BusinessException("收件地址命中黑名单区域");
|
||||
}
|
||||
|
||||
var serviceNo = await ResolveServiceNoAsync(input.ServiceNo);
|
||||
var serviceNo = await ResolveServiceNoAsync();
|
||||
var order = BuildDraftOrderEntity(input, product, country.CountryCode, country.ChineseName, snapshotBinding, serviceNo);
|
||||
order = await OrderRepository.InsertAsync(order);
|
||||
|
||||
@ -165,16 +165,36 @@ public partial class OrderService
|
||||
}
|
||||
}
|
||||
|
||||
private const string ParcelServiceNoPrefix = "NE";
|
||||
|
||||
private static void NormalizeParcelCreateInput(OrderParcelCreateInput input)
|
||||
{
|
||||
input.TrackNo = string.IsNullOrWhiteSpace(input.TrackNo) ? null : input.TrackNo.Trim();
|
||||
input.ServiceNo = string.IsNullOrWhiteSpace(input.ServiceNo) ? null : input.ServiceNo.Trim();
|
||||
input.TrackNo = null;
|
||||
input.ServiceNo = null;
|
||||
input.ClientNo = string.IsNullOrWhiteSpace(input.ClientNo) ? null : input.ClientNo.Trim();
|
||||
input.RequestId = string.IsNullOrWhiteSpace(input.RequestId) ? null : input.RequestId.Trim();
|
||||
}
|
||||
|
||||
private async Task ValidateParcelCreateInputAsync(OrderParcelCreateInput input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.ClientNo))
|
||||
{
|
||||
throw new BusinessException("客户单号不能为空");
|
||||
}
|
||||
|
||||
if (!input.CustomerId.HasValue || input.CustomerId <= 0)
|
||||
{
|
||||
throw new BusinessException("客户ID不能为空");
|
||||
}
|
||||
|
||||
var duplicatedClientNo = await OrderRepository.GetByCustomerAndClientNoAsync(
|
||||
input.CustomerId.Value,
|
||||
input.ClientNo);
|
||||
if (duplicatedClientNo != null)
|
||||
{
|
||||
throw new BusinessException("客户单号已存在");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.ProductCode))
|
||||
{
|
||||
throw new BusinessException("产品代码不能为空");
|
||||
@ -185,11 +205,6 @@ public partial class OrderService
|
||||
throw new BusinessException("仓库ID不能为空");
|
||||
}
|
||||
|
||||
if (!input.CustomerId.HasValue || input.CustomerId <= 0)
|
||||
{
|
||||
throw new BusinessException("客户ID不能为空");
|
||||
}
|
||||
|
||||
if (!input.Length.HasValue || !input.Width.HasValue || !input.Height.HasValue || !input.ActualWeight.HasValue
|
||||
|| input.Length <= 0 || input.Width <= 0 || input.Height <= 0 || input.ActualWeight <= 0)
|
||||
{
|
||||
@ -1185,22 +1200,11 @@ public partial class OrderService
|
||||
await OrderRepository.UpdateAsync(order);
|
||||
}
|
||||
|
||||
private async Task<string> ResolveServiceNoAsync(string? serviceNo)
|
||||
private async Task<string> ResolveServiceNoAsync()
|
||||
{
|
||||
var normalized = serviceNo?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
if (await OrderRepository.HasServiceNoAsync(normalized))
|
||||
{
|
||||
throw new BusinessException("运单号已存在");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var generated = $"LP{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
|
||||
var generated = $"{ParcelServiceNoPrefix}{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
|
||||
if (!await OrderRepository.HasServiceNoAsync(generated))
|
||||
{
|
||||
return generated;
|
||||
|
||||
@ -70,8 +70,6 @@ public static class OrderTemplateFieldRegistry
|
||||
|
||||
Add("requestId", "幂等请求ID", false);
|
||||
Add("clientNo", "客户单号", true, example: "C202604230001");
|
||||
Add("trackNo", "跟踪号", false);
|
||||
Add("serviceNo", "运单号", false);
|
||||
Add("productCode", "运输方式", true, "select", "sales_product", "basic", "PROD001");
|
||||
Add("cargoAttribute", "货物属性", true, "select", "cargo_attribute", "basic", "general");
|
||||
Add("exportCustomsMode", "出口报关方式", false, "select", "export_customs_mode", "basic", "9610Simple");
|
||||
|
||||
@ -15,8 +15,6 @@ public static class OrderTemplateHeaderMatcher
|
||||
["客户单号"] = "clientNo",
|
||||
["客户订单号"] = "clientNo",
|
||||
["外部单号"] = "clientNo",
|
||||
["跟踪号"] = "trackNo",
|
||||
["运单号"] = "serviceNo",
|
||||
["运输方式"] = "productCode",
|
||||
["产品代码"] = "productCode",
|
||||
["产品编码"] = "productCode",
|
||||
|
||||
@ -11,7 +11,7 @@ public static class OrderTemplateRowMapper
|
||||
{
|
||||
private static readonly HashSet<string> ForceStringFields = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"requestId", "clientNo", "trackNo", "serviceNo", "productCode", "cargoAttribute", "exportCustomsMode",
|
||||
"requestId", "clientNo", "productCode", "cargoAttribute", "exportCustomsMode",
|
||||
"codCurrency", "declaredCurrency", "receiver.recipientsName", "receiver.companyName", "receiver.phone",
|
||||
"receiver.telePhone", "receiver.email", "receiver.countryCode", "receiver.province", "receiver.city",
|
||||
"receiver.streetNumber", "receiver.streetAddress1", "receiver.streetAddress2", "receiver.streetAddress3",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user