20261008-ide1.0发布 #1
2
db.sql
2
db.sql
@ -124,6 +124,7 @@ CREATE TABLE `t_order` (
|
||||
`ConsigneeCountryName` VARCHAR(64) NULL COMMENT '国家中文名称',
|
||||
`CargoAttribute` VARCHAR(32) NULL COMMENT '货物属性:general/electronics/sensitive',
|
||||
`ExportCustomsMode` VARCHAR(32) NULL COMMENT '出口报关方式',
|
||||
`ExportQualificationId` INT NULL COMMENT '出口资质备案 Id(t_customer_export_qualification.Id)',
|
||||
`PredeclareFailReason` VARCHAR(512) NULL COMMENT '预报失败原因',
|
||||
`AllocationPendingReason` VARCHAR(512) NULL COMMENT '待排货原因(排货或渠道买价未匹配)',
|
||||
`AllocationScheme` VARCHAR(32) NULL COMMENT '最近一次排货方案 costLowest/timeFastest/overallBest/designated',
|
||||
@ -204,6 +205,7 @@ CREATE TABLE `t_order` (
|
||||
KEY `idx_ord_source_ctime` (`OrderSource`, `CreateTime`),
|
||||
KEY `idx_order_pickup` (`PickupOrderId`),
|
||||
KEY `idx_order_inbound_method` (`InboundMethod`),
|
||||
KEY `idx_order_export_qualification` (`ExportQualificationId`),
|
||||
KEY `idx_ord_fulfillment_customer` (`FulfillmentType`, `CustomerId`, `OrderStatus`)
|
||||
) COMMENT='订单表';
|
||||
|
||||
|
||||
24
sql/migrate_order_export_qualification_id.sql
Normal file
24
sql/migrate_order_export_qualification_id.sql
Normal file
@ -0,0 +1,24 @@
|
||||
-- 存量库:t_order 增加出口资质备案 Id(9610Summary 选用)
|
||||
-- 可重复执行。
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
SET @exists := (
|
||||
SELECT COUNT(1)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order'
|
||||
AND COLUMN_NAME = 'ExportQualificationId'
|
||||
);
|
||||
|
||||
SET @sql := IF(
|
||||
@exists = 0,
|
||||
'ALTER TABLE `t_order`
|
||||
ADD COLUMN `ExportQualificationId` INT NULL COMMENT ''出口资质备案 Id(t_customer_export_qualification.Id)'' AFTER `ExportCustomsMode`,
|
||||
ADD KEY `idx_order_export_qualification` (`ExportQualificationId`)',
|
||||
'SELECT ''t_order.ExportQualificationId already exists'' AS info'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@ -48,18 +48,21 @@ public class CreateCustomerExportQualificationInput
|
||||
[StringLength(200)]
|
||||
public string DomesticConsignor { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
public string? DomesticConsignorCustomsCode { get; set; }
|
||||
public string DomesticConsignorCustomsCode { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(200)]
|
||||
public string ProducerName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
public string? ProducerCustomsCode { get; set; }
|
||||
public string ProducerCustomsCode { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string? CustomsPort { get; set; }
|
||||
public string CustomsPort { get; set; } = string.Empty;
|
||||
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
|
||||
@ -205,6 +205,11 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public string? ExportCustomsMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出口资质备案 Id。
|
||||
/// </summary>
|
||||
public int? ExportQualificationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预报失败原因(草稿订单)。
|
||||
/// </summary>
|
||||
|
||||
@ -45,6 +45,11 @@ public class OrderParcelCreateInput
|
||||
/// </summary>
|
||||
public string? ExportCustomsMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出口资质备案 Id(ExportCustomsMode=9610Summary 时必填)。
|
||||
/// </summary>
|
||||
public int? ExportQualificationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 仓库主键。
|
||||
/// </summary>
|
||||
|
||||
@ -86,10 +86,10 @@ public class CustomerExportQualificationService : ApplicationService
|
||||
CompanyName = input.CompanyName.Trim(),
|
||||
SocialCreditCode = input.SocialCreditCode.Trim(),
|
||||
DomesticConsignor = input.DomesticConsignor.Trim(),
|
||||
DomesticConsignorCustomsCode = TrimOrNull(input.DomesticConsignorCustomsCode),
|
||||
DomesticConsignorCustomsCode = input.DomesticConsignorCustomsCode.Trim(),
|
||||
ProducerName = input.ProducerName.Trim(),
|
||||
ProducerCustomsCode = TrimOrNull(input.ProducerCustomsCode),
|
||||
CustomsPort = TrimOrNull(input.CustomsPort),
|
||||
ProducerCustomsCode = input.ProducerCustomsCode.Trim(),
|
||||
CustomsPort = input.CustomsPort.Trim(),
|
||||
IsDefault = input.IsDefault ? (byte)1 : (byte)0,
|
||||
Remark = TrimOrNull(input.Remark)
|
||||
};
|
||||
@ -115,10 +115,10 @@ public class CustomerExportQualificationService : ApplicationService
|
||||
entity.CompanyName = input.CompanyName.Trim();
|
||||
entity.SocialCreditCode = input.SocialCreditCode.Trim();
|
||||
entity.DomesticConsignor = input.DomesticConsignor.Trim();
|
||||
entity.DomesticConsignorCustomsCode = TrimOrNull(input.DomesticConsignorCustomsCode);
|
||||
entity.DomesticConsignorCustomsCode = input.DomesticConsignorCustomsCode.Trim();
|
||||
entity.ProducerName = input.ProducerName.Trim();
|
||||
entity.ProducerCustomsCode = TrimOrNull(input.ProducerCustomsCode);
|
||||
entity.CustomsPort = TrimOrNull(input.CustomsPort);
|
||||
entity.ProducerCustomsCode = input.ProducerCustomsCode.Trim();
|
||||
entity.CustomsPort = input.CustomsPort.Trim();
|
||||
entity.IsDefault = input.IsDefault ? (byte)1 : (byte)0;
|
||||
entity.Remark = TrimOrNull(input.Remark);
|
||||
|
||||
@ -183,10 +183,25 @@ public class CustomerExportQualificationService : ApplicationService
|
||||
throw new BusinessException("境内发货人不能为空");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.DomesticConsignorCustomsCode))
|
||||
{
|
||||
throw new BusinessException("境内发货人海关代码不能为空");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.ProducerName))
|
||||
{
|
||||
throw new BusinessException("生产销售单位不能为空");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.ProducerCustomsCode))
|
||||
{
|
||||
throw new BusinessException("生产销售单位海关代码不能为空");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.CustomsPort))
|
||||
{
|
||||
throw new BusinessException("备案海关港口地不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
private static Expression<Func<CustomerExportQualification, bool>> BuildPredicate(
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
@ -13,15 +15,18 @@ public class Customs9610OrderWriter : IScopedDependency
|
||||
private readonly ICustoms9610OrderRepository _headerRepository;
|
||||
private readonly ICustoms9610OrderItemRepository _itemRepository;
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly ICustomerExportQualificationRepository _exportQualificationRepository;
|
||||
|
||||
public Customs9610OrderWriter(
|
||||
ICustoms9610OrderRepository headerRepository,
|
||||
ICustoms9610OrderItemRepository itemRepository,
|
||||
IOrderRepository orderRepository)
|
||||
IOrderRepository orderRepository,
|
||||
ICustomerExportQualificationRepository exportQualificationRepository)
|
||||
{
|
||||
_headerRepository = headerRepository;
|
||||
_itemRepository = itemRepository;
|
||||
_orderRepository = orderRepository;
|
||||
_exportQualificationRepository = exportQualificationRepository;
|
||||
}
|
||||
|
||||
public async Task UpsertFromOrderAsync(Order order, CancellationToken cancellationToken = default)
|
||||
@ -55,6 +60,7 @@ public class Customs9610OrderWriter : IScopedDependency
|
||||
var grossWeight = ResolveGrossWeight(weights);
|
||||
var serviceNo = order.ServiceNo?.Trim() ?? string.Empty;
|
||||
var clientNo = FirstNonEmpty(order.ClientNo, order.ServiceNo) ?? string.Empty;
|
||||
var qualification = await ResolveQualificationAsync(order, cancellationToken);
|
||||
|
||||
if (existing != null
|
||||
&& string.Equals(existing.Status, Customs9610StatusCodes.Declared, StringComparison.OrdinalIgnoreCase))
|
||||
@ -98,6 +104,8 @@ public class Customs9610OrderWriter : IScopedDependency
|
||||
header.PreRecordFlag = "1";
|
||||
}
|
||||
|
||||
ApplyQualification(header, qualification);
|
||||
|
||||
header.GrossWeight = grossWeight;
|
||||
header.Currency = FirstNonEmpty(order.DeclaredCurrency, header.Currency, "CNY")!;
|
||||
header.DestCountry = FirstNonEmpty(order.ConsigneeCountry, header.DestCountry) ?? string.Empty;
|
||||
@ -141,6 +149,50 @@ public class Customs9610OrderWriter : IScopedDependency
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CustomerExportQualification?> ResolveQualificationAsync(
|
||||
Order order,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (order.CustomerId is not > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (order.ExportQualificationId is > 0)
|
||||
{
|
||||
var owned = await _exportQualificationRepository.GetOwnedAsync(
|
||||
order.ExportQualificationId.Value,
|
||||
order.CustomerId.Value,
|
||||
cancellationToken);
|
||||
if (owned != null)
|
||||
{
|
||||
return owned;
|
||||
}
|
||||
}
|
||||
|
||||
return await _exportQualificationRepository.GetDefaultAsync(
|
||||
order.CustomerId.Value,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private static void ApplyQualification(Customs9610Order header, CustomerExportQualification? qualification)
|
||||
{
|
||||
if (qualification == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
header.EcomCorpCode = Truncate(
|
||||
FirstNonEmpty(qualification.DomesticConsignorCustomsCode, qualification.SocialCreditCode),
|
||||
18);
|
||||
header.EcomCorpName = Truncate(
|
||||
FirstNonEmpty(qualification.CompanyName, qualification.DomesticConsignor),
|
||||
100);
|
||||
header.ProducerCode = Truncate(qualification.ProducerCustomsCode, 18);
|
||||
header.ProducerName = Truncate(qualification.ProducerName, 100);
|
||||
header.ActualSellerCode = Truncate(qualification.SocialCreditCode, 18);
|
||||
}
|
||||
|
||||
private static decimal ResolveGrossWeight(IReadOnlyList<OrderWeight> weights)
|
||||
{
|
||||
var forecast = weights.FirstOrDefault(x =>
|
||||
|
||||
@ -200,6 +200,7 @@ public partial class OrderService
|
||||
: null,
|
||||
RequestId = order.RequestId,
|
||||
ExportCustomsMode = order.ExportCustomsMode,
|
||||
ExportQualificationId = order.ExportQualificationId,
|
||||
PredeclareFailReason = string.IsNullOrWhiteSpace(order.PredeclareFailReason)
|
||||
? null
|
||||
: order.PredeclareFailReason.Trim(),
|
||||
|
||||
@ -40,6 +40,8 @@ public partial class OrderService
|
||||
private static readonly ConcurrentDictionary<int, byte> InboundBillingInFlight = new();
|
||||
|
||||
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
||||
private ICustomerExportQualificationRepository ExportQualificationRepository =>
|
||||
LazyServiceProvider.GetRequiredService<ICustomerExportQualificationRepository>();
|
||||
|
||||
private IMemberLevelRepository MemberLevelRepository => LazyServiceProvider.GetRequiredService<IMemberLevelRepository>();
|
||||
|
||||
@ -409,6 +411,39 @@ public partial class OrderService
|
||||
throw new BusinessException("出口报关方式不合法");
|
||||
}
|
||||
|
||||
var is9610Summary = string.Equals(
|
||||
input.ExportCustomsMode,
|
||||
ExportCustomsModeCodes.Mode9610Summary,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
if (is9610Summary)
|
||||
{
|
||||
CustomerExportQualification? qualification = null;
|
||||
if (input.ExportQualificationId is > 0)
|
||||
{
|
||||
qualification = await ExportQualificationRepository.GetOwnedAsync(
|
||||
input.ExportQualificationId.Value,
|
||||
input.CustomerId!.Value);
|
||||
if (qualification == null)
|
||||
{
|
||||
throw new BusinessException("出口资质备案不存在或不属于当前客户");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qualification = await ExportQualificationRepository.GetDefaultAsync(input.CustomerId!.Value);
|
||||
if (qualification == null)
|
||||
{
|
||||
throw new BusinessException("9610汇总申报必须选择出口资质备案(或先维护默认备案)");
|
||||
}
|
||||
|
||||
input.ExportQualificationId = qualification.Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
input.ExportQualificationId = null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.DeclaredCurrency))
|
||||
{
|
||||
throw new BusinessException("申报币种不能为空");
|
||||
@ -1906,6 +1941,7 @@ public partial class OrderService
|
||||
ConsigneeCountryName = consigneeCountryName,
|
||||
CargoAttribute = input.CargoAttribute ?? CargoAttributeCodes.General,
|
||||
ExportCustomsMode = input.ExportCustomsMode,
|
||||
ExportQualificationId = input.ExportQualificationId,
|
||||
WarehouseId = input.WarehouseId,
|
||||
CustomerId = input.CustomerId,
|
||||
OrderSource = orderSource,
|
||||
|
||||
@ -110,10 +110,11 @@ public static class OrderTemplateFieldRegistry
|
||||
Add("warehouseId", "发货仓库", true, "select", "warehouse", "basic", "1", hint: "从下拉选择发货仓库 ID");
|
||||
Add("actualWeight", "实重(kg)", true, "number", example: "1.2", hint: "包裹实际重量,单位千克");
|
||||
Add("quantity", "包裹件数", true, "number", example: "1", hint: "一般为 1");
|
||||
Add("length", "长(cm)", false, "number", example: "10", hint: "选填,用于材积计费");
|
||||
Add("width", "宽(cm)", false, "number", example: "10", hint: "选填,用于材积计费");
|
||||
Add("height", "高(cm)", false, "number", example: "10", hint: "选填,用于材积计费");
|
||||
Add("exportCustomsMode", "出口报关", false, "select", "export_customs_mode", "basic", "9610Simple", hint: "不选则按简易申报");
|
||||
Add("length", "长(cm)", true, "number", example: "10", hint: "必填,用于材积计费");
|
||||
Add("width", "宽(cm)", true, "number", example: "10", hint: "必填,用于材积计费");
|
||||
Add("height", "高(cm)", true, "number", example: "10", hint: "必填,用于材积计费");
|
||||
Add("exportCustomsMode", "出口报关", false, "select", "export_customs_mode", "basic", "9610Simple", hint: "不选则按简易申报;填 9610Summary 时需有默认出口资质或填备案Id");
|
||||
Add("exportQualificationId", "出口资质备案Id", false, "number", example: "1", hint: "9610汇总申报时填写;留空则用客户默认备案");
|
||||
|
||||
Add("receiver.recipientsName", "收件人姓名", true, group: "receiver", example: "John Smith", hint: "必填");
|
||||
Add("receiver.phone", "收件人手机", true, group: "receiver", example: "13900139000", hint: "必填");
|
||||
@ -167,11 +168,12 @@ public static class OrderTemplateFieldRegistry
|
||||
Add("clientNo", "客户单号", true, example: "C202604230001", hint: "您自己的订单编号,同一客户不可重复");
|
||||
Add("productCode", "运输方式", true, "select", "sales_product", "basic", "PROD001", hint: "从下拉选择产品编码");
|
||||
Add("cargoAttribute", "货物属性", true, "select", "cargo_attribute", "basic", "general", hint: "普货选 general");
|
||||
Add("exportCustomsMode", "出口报关方式", false, "select", "export_customs_mode", "basic", "9610Simple", hint: "不选则简易申报");
|
||||
Add("exportCustomsMode", "出口报关方式", false, "select", "export_customs_mode", "basic", "9610Simple", hint: "不选则简易申报;填 9610Summary 时需有默认出口资质或填备案Id");
|
||||
Add("exportQualificationId", "出口资质备案Id", false, "number", example: "1", hint: "9610汇总申报时填写;留空则用客户默认备案");
|
||||
Add("warehouseId", "发货仓库", true, "select", "warehouse", "basic", "1", hint: "从下拉选择仓库 ID");
|
||||
Add("length", "长(cm)", false, "number", example: "10", hint: "材积计费用");
|
||||
Add("width", "宽(cm)", false, "number", example: "10", hint: "材积计费用");
|
||||
Add("height", "高(cm)", false, "number", example: "10", hint: "材积计费用");
|
||||
Add("length", "长(cm)", true, "number", example: "10", hint: "必填,材积计费用");
|
||||
Add("width", "宽(cm)", true, "number", example: "10", hint: "必填,材积计费用");
|
||||
Add("height", "高(cm)", true, "number", example: "10", hint: "必填,材积计费用");
|
||||
Add("actualWeight", "实重(kg)", true, "number", example: "1.2", hint: "必填");
|
||||
Add("isCod", "是否COD", false, "boolean", example: "false", hint: "货到付款填 true");
|
||||
Add("codCurrency", "COD币种", false, "select", "currency", example: "USD", hint: "COD 订单填写");
|
||||
|
||||
@ -20,6 +20,10 @@ public static class OrderTemplateHeaderMatcher
|
||||
["产品编码"] = "productCode",
|
||||
["货物属性"] = "cargoAttribute",
|
||||
["出口报关方式"] = "exportCustomsMode",
|
||||
["出口报关"] = "exportCustomsMode",
|
||||
["出口资质备案Id"] = "exportQualificationId",
|
||||
["出口资质备案"] = "exportQualificationId",
|
||||
["资质备案Id"] = "exportQualificationId",
|
||||
["仓库"] = "warehouseId",
|
||||
["仓库ID"] = "warehouseId",
|
||||
["长cm"] = "length",
|
||||
|
||||
@ -27,7 +27,7 @@ public static class OrderTemplateRowMapper
|
||||
|
||||
private static readonly HashSet<string> ForceNumberFields = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"warehouseId", "length", "width", "height", "actualWeight", "codSum", "declaredSum", "quantity",
|
||||
"warehouseId", "exportQualificationId", "length", "width", "height", "actualWeight", "codSum", "declaredSum", "quantity",
|
||||
"receiver.taxType", "receiver.certificateType", "items[0].unitPrice", "items[0].unitWeight",
|
||||
"items[0].quantity", "items[0].amount", "items[0].weight"
|
||||
};
|
||||
@ -75,9 +75,28 @@ public static class OrderTemplateRowMapper
|
||||
}
|
||||
|
||||
ApplyDerivedDeclarationFields(input);
|
||||
ApplyParcelDimensionDefaults(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
private static void ApplyParcelDimensionDefaults(OrderParcelCreateInput input)
|
||||
{
|
||||
if (!input.Length.HasValue || input.Length <= 0)
|
||||
{
|
||||
input.Length = 10;
|
||||
}
|
||||
|
||||
if (!input.Width.HasValue || input.Width <= 0)
|
||||
{
|
||||
input.Width = 10;
|
||||
}
|
||||
|
||||
if (!input.Height.HasValue || input.Height <= 0)
|
||||
{
|
||||
input.Height = 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyDerivedDeclarationFields(OrderParcelCreateInput input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.DeclaredCurrency))
|
||||
|
||||
@ -10,6 +10,10 @@ public interface ICustomerExportQualificationRepository : IRepository<CustomerEx
|
||||
int customerId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CustomerExportQualification?> GetDefaultAsync(
|
||||
int customerId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task ClearDefaultAsync(
|
||||
int customerId,
|
||||
int? excludeId = null,
|
||||
|
||||
@ -52,6 +52,12 @@ public class Order : OmsAuditedAggregateRoot
|
||||
|
||||
public string? ExportCustomsMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出口资质备案 Id(9610 汇总申报选用)。
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? ExportQualificationId { get; set; }
|
||||
|
||||
public string? PredeclareFailReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -18,6 +18,13 @@ public class CustomerExportQualificationRepository
|
||||
return FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<CustomerExportQualification?> GetDefaultAsync(
|
||||
int customerId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return FirstOrDefaultAsync(x => x.CustomerId == customerId && x.IsDefault == 1, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ClearDefaultAsync(
|
||||
int customerId,
|
||||
int? excludeId = null,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user