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]; } }