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;
///
/// 运单创建时按 ExportCustomsMode=9610Summary 写入/刷新 t_9610_order。
///
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 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 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];
}
}