All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
Co-authored-by: Cursor <cursoragent@cursor.com>
249 lines
8.9 KiB
C#
249 lines
8.9 KiB
C#
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;
|
|
|
|
namespace Oms.Application.Services.Orders;
|
|
|
|
/// <summary>
|
|
/// 运单创建时按 ExportCustomsMode=9610Summary 写入/刷新 t_9610_order。
|
|
/// </summary>
|
|
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,
|
|
ICustomerExportQualificationRepository exportQualificationRepository)
|
|
{
|
|
_headerRepository = headerRepository;
|
|
_itemRepository = itemRepository;
|
|
_orderRepository = orderRepository;
|
|
_exportQualificationRepository = exportQualificationRepository;
|
|
}
|
|
|
|
public async Task UpsertFromOrderAsync(
|
|
Order order,
|
|
string? platformCode = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (order.Id <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var isSummary = string.Equals(
|
|
order.ExportCustomsMode,
|
|
ExportCustomsModeCodes.Mode9610Summary,
|
|
StringComparison.OrdinalIgnoreCase);
|
|
|
|
var existing = await _headerRepository.GetByOrderIdAsync(order.Id, cancellationToken);
|
|
|
|
if (!isSummary)
|
|
{
|
|
if (existing != null
|
|
&& string.Equals(existing.Status, Customs9610StatusCodes.Pending, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
await _headerRepository.HardDeleteItemsByHeaderIdAsync(existing.Id, cancellationToken);
|
|
await _headerRepository.HardDeleteAsync(existing, cancellationToken);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
var declarations = await _orderRepository.GetDeclarationsByOrderIdsAsync([order.Id], cancellationToken);
|
|
var weights = await _orderRepository.GetWeightsByOrderIdsAsync([order.Id], cancellationToken);
|
|
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))
|
|
{
|
|
// 已申报:仅同步冗余检索字段,不覆盖申报内容
|
|
existing.ServiceNo = string.IsNullOrWhiteSpace(existing.ServiceNo) ? serviceNo : existing.ServiceNo;
|
|
existing.UpdateTime = DateTime.Now;
|
|
await _headerRepository.UpdateAsync(existing);
|
|
return;
|
|
}
|
|
|
|
Customs9610Order header;
|
|
if (existing == null)
|
|
{
|
|
header = new Customs9610Order
|
|
{
|
|
OrderId = order.Id,
|
|
CustomerId = order.CustomerId ?? 0,
|
|
Status = Customs9610StatusCodes.Pending,
|
|
BusinessType = Customs9610FormTypeCodes.SummaryB,
|
|
PreRecordFlag = "1",
|
|
IsSummarySimple = 0,
|
|
CreateTime = DateTime.Now,
|
|
};
|
|
}
|
|
else
|
|
{
|
|
header = existing;
|
|
header.IsDeleted = false;
|
|
}
|
|
|
|
header.ServiceNo = serviceNo;
|
|
header.CustomerId = order.CustomerId ?? header.CustomerId;
|
|
header.ClientNo = clientNo;
|
|
header.BusinessType = string.IsNullOrWhiteSpace(header.BusinessType)
|
|
? Customs9610FormTypeCodes.SummaryB
|
|
: header.BusinessType;
|
|
if (string.IsNullOrWhiteSpace(header.PreRecordFlag)
|
|
&& string.Equals(header.BusinessType, Customs9610FormTypeCodes.SummaryB, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
header.PreRecordFlag = "1";
|
|
}
|
|
|
|
ApplyQualification(header, qualification);
|
|
|
|
// 下单传入的电商平台优先;刷新时若未传则保留已有值
|
|
var normalizedPlatform = Truncate(FirstNonEmpty(platformCode, header.PlatformCode), 16);
|
|
if (!string.IsNullOrWhiteSpace(normalizedPlatform))
|
|
{
|
|
header.PlatformCode = normalizedPlatform;
|
|
}
|
|
|
|
header.GrossWeight = grossWeight;
|
|
header.Currency = FirstNonEmpty(order.DeclaredCurrency, header.Currency, "CNY")!;
|
|
header.DestCountry = FirstNonEmpty(order.ConsigneeCountry, header.DestCountry) ?? string.Empty;
|
|
header.UpdateTime = DateTime.Now;
|
|
|
|
if (existing == null)
|
|
{
|
|
header = await _headerRepository.InsertAsync(header);
|
|
}
|
|
else
|
|
{
|
|
await _headerRepository.UpdateAsync(header);
|
|
}
|
|
|
|
await _headerRepository.HardDeleteItemsByHeaderIdAsync(header.Id, cancellationToken);
|
|
|
|
var sort = 0;
|
|
foreach (var d in declarations.OrderBy(x => x.Id))
|
|
{
|
|
var itemName = FirstNonEmpty(d.CnName, d.EnName) ?? string.Empty;
|
|
var sku = FirstNonEmpty(d.Sku, $"ITEM{sort + 1}")!;
|
|
var declareElement = FirstNonEmpty(
|
|
d.DeclareElement,
|
|
JoinNonEmpty(" ", d.Model, d.Specifications, d.Attributes, d.Material));
|
|
await _itemRepository.InsertAsync(new Customs9610OrderItem
|
|
{
|
|
Customs9610OrderId = header.Id,
|
|
OrderDeclarationId = d.Id,
|
|
Sku = Truncate(sku, 32) ?? string.Empty,
|
|
CnName = Truncate(itemName, 250) ?? string.Empty,
|
|
ExHsCode = Truncate(FirstNonEmpty(d.ExHsCode, d.HsCode, d.ImHsCode), 10),
|
|
DeclareElement = Truncate(declareElement, 255),
|
|
UnitPrice = d.UnitPrice ?? 0,
|
|
Quantity = d.Quantity ?? 0,
|
|
Amount = d.Amount,
|
|
Unit = Truncate(d.Unit, 3),
|
|
Freight = null,
|
|
SortNo = sort++,
|
|
CreateTime = DateTime.Now,
|
|
});
|
|
}
|
|
}
|
|
|
|
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 =>
|
|
string.Equals(x.Type, OrderWeightTypeCodes.Forecast, StringComparison.OrdinalIgnoreCase));
|
|
var row = forecast ?? weights.FirstOrDefault();
|
|
if (row == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var w = row.ActualWeight ?? row.ChargeableWeight ?? 0;
|
|
return w > 0 ? w : 0;
|
|
}
|
|
|
|
private static string? FirstNonEmpty(params string?[] values)
|
|
{
|
|
foreach (var v in values)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(v))
|
|
{
|
|
return v.Trim();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? JoinNonEmpty(string sep, params string?[] values)
|
|
{
|
|
var parts = values.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x!.Trim()).ToList();
|
|
return parts.Count == 0 ? null : string.Join(sep, parts);
|
|
}
|
|
|
|
private static string? Truncate(string? value, int max)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return value.Length <= max ? value : value[..max];
|
|
}
|
|
}
|