/** 从订单详情组装 createOrderOne 标准请求体(ForwardWithSubscribe) */ function pick(raw: Record | undefined, ...keys: string[]) { if (!raw) return undefined for (const k of keys) { const v = raw[k] if (v !== undefined && v !== null && v !== '') return v } return undefined } function resolveCountryIso( countryRef: unknown, countryIdToCode: Map ): string | undefined { if (countryRef == null || countryRef === '') return undefined if (typeof countryRef === 'string' && countryRef.length === 2) return countryRef.toUpperCase() const n = Number(countryRef) if (Number.isFinite(n) && countryIdToCode.has(n)) return countryIdToCode.get(n) return typeof countryRef === 'string' ? countryRef : undefined } function mapItem(raw: Record) { const hs = pick(raw, 'hsCode', 'HsCode', 'exHsCode', 'ExHsCode', 'imHsCode', 'ImHsCode') return { cnName: pick(raw, 'cnName', 'CnName'), enName: pick(raw, 'enName', 'EnName'), sku: pick(raw, 'sku', 'Sku'), unit: pick(raw, 'unit', 'Unit'), unitPrice: pick(raw, 'unitPrice', 'UnitPrice'), unitWeight: pick(raw, 'unitWeight', 'UnitWeight'), quantity: pick(raw, 'quantity', 'Quantity'), amount: pick(raw, 'amount', 'Amount'), weight: pick(raw, 'weight', 'Weight'), imHsCode: pick(raw, 'imHsCode', 'ImHsCode') ?? hs, exHsCode: pick(raw, 'exHsCode', 'ExHsCode') ?? hs, declareElement: pick(raw, 'declareElement', 'DeclareElement'), productId: pick(raw, 'productId', 'ProductId'), nonStdProductId: pick(raw, 'nonStdProductId', 'NonStdProductId'), stdProductId: pick(raw, 'stdProductId', 'StdProductId'), skuImage: pick(raw, 'skuImage', 'SkuImage'), category: pick(raw, 'category', 'Category'), brand: pick(raw, 'brand', 'Brand'), brandType: pick(raw, 'brandType', 'BrandType'), isMagnetic: raw.isMagnetic === true || raw.IsMagnetic === true || raw.isMagnetic === 1, distribution: pick(raw, 'distribution', 'Distribution'), material: pick(raw, 'material', 'Material'), enMaterial: pick(raw, 'enMaterial', 'EnMaterial'), purpose: pick(raw, 'purpose', 'Purpose'), attributes: pick(raw, 'attributes', 'Attributes'), originCountry: pick(raw, 'originCountry', 'OriginCountry'), salesLink: pick(raw, 'salesLink', 'SalesLink'), model: pick(raw, 'model', 'Model'), specifications: pick(raw, 'specifications', 'Specifications'), comment: pick(raw, 'comment', 'Comment'), } } export function buildCountryIdToCodeMap(countries: any[]): Map { const map = new Map() for (const c of countries || []) { const id = Number(c.id ?? c.Id) const code = String(c.countryCode ?? c.CountryCode ?? '').trim() if (Number.isFinite(id) && code) map.set(id, code) } return map } export type BuildCreateOrderOneBodyOptions = { /** 服务渠道「预报渠道 code」,写入 productCode;不传则不带 productCode */ forecastCode?: string | null } /** * 组装 createOrderOne 请求体。 * - productCode:取渠道「预报渠道 code」(forecastCode),不是销售产品 code * - 不传 channelCode / requestId / exportCustomsMode 等 OMS 扩展字段 */ export function buildCreateOrderOneBody( order: Record, countryIdToCode: Map, options?: BuildCreateOrderOneBodyOptions ): Record { const weightList = (order.weightList ?? order.WeightList ?? []) as any[] const forecast = weightList.find((w) => String(w.type ?? w.Type ?? '').toLowerCase() === 'forecast') ?? weightList[0] ?? {} const receiverRaw = (order.receiver ?? order.Receiver ?? {}) as Record const senderRaw = (order.sender ?? order.Sender ?? {}) as Record const itemsRaw = (order.items ?? order.Items ?? []) as any[] const receiverCountry = pick(order, 'consigneeCountry', 'ConsigneeCountry') ?? resolveCountryIso( pick(receiverRaw, 'countryCode', 'CountryCode', 'country', 'Country'), countryIdToCode ) const senderCountry = resolveCountryIso( pick(senderRaw, 'countryCode', 'CountryCode', 'country', 'Country'), countryIdToCode ) const forecastCode = String(options?.forecastCode ?? '').trim() const body: Record = { clientNo: pick(order, 'clientNo', 'ClientNo'), trackNo: pick(order, 'trackNo', 'TrackNo'), serviceNo: pick(order, 'serviceNo', 'ServiceNo'), cargoAttribute: pick(order, 'cargoAttribute', 'CargoAttribute'), warehouseId: pick(order, 'warehouseId', 'WarehouseId'), customerId: pick(order, 'customerId', 'CustomerId'), length: pick(forecast, 'length', 'Length'), width: pick(forecast, 'width', 'Width'), height: pick(forecast, 'height', 'Height'), actualWeight: pick(forecast, 'actualWeight', 'ActualWeight'), volumetricWeight: pick(forecast, 'volumetricWeight', 'VolumetricWeight'), chargeableWeight: pick(forecast, 'chargeableWeight', 'ChargeableWeight'), cubic: pick(forecast, 'cubic', 'Cubic'), isCod: order.isCod === true || order.IsCod === true || order.isCod === 1 || order.IsCod === 1, codSum: pick(order, 'codSum', 'CodSum') != null ? String(pick(order, 'codSum', 'CodSum')) : undefined, codCurrency: pick(order, 'codCurrency', 'CodCurrency'), declaredCurrency: pick(order, 'declaredCurrency', 'DeclaredCurrency'), declaredSum: pick(order, 'declaredSum', 'DeclaredSum') != null ? String(pick(order, 'declaredSum', 'DeclaredSum')) : undefined, quantity: pick(order, 'quantity', 'Quantity') != null ? String(pick(order, 'quantity', 'Quantity')) : undefined, receiver: { recipientsName: pick(receiverRaw, 'recipientsName', 'RecipientsName'), companyName: pick(receiverRaw, 'companyName', 'CompanyName'), phone: pick(receiverRaw, 'phone', 'Phone'), telePhone: pick(receiverRaw, 'telePhone', 'TelePhone'), email: pick(receiverRaw, 'email', 'Email'), countryCode: receiverCountry, province: pick(receiverRaw, 'province', 'Province'), city: pick(receiverRaw, 'city', 'City'), streetNumber: pick(receiverRaw, 'streetNumber', 'StreetNumber'), streetAddress1: pick(receiverRaw, 'streetAddress1', 'StreetAddress1'), streetAddress2: pick(receiverRaw, 'streetAddress2', 'StreetAddress2'), streetAddress3: pick(receiverRaw, 'streetAddress3', 'StreetAddress3'), zipCode: pick(receiverRaw, 'zipCode', 'ZipCode'), taxType: pick(receiverRaw, 'taxType', 'TaxType'), taxNo: pick(receiverRaw, 'taxNo', 'TaxNo'), certificateType: pick(receiverRaw, 'certificateType', 'CertificateType'), certificateNo: pick(receiverRaw, 'certificateNo', 'CertificateNo'), eori: pick(receiverRaw, 'eori', 'Eori'), vat: pick(receiverRaw, 'vat', 'Vat'), }, sender: { senderName: pick(senderRaw, 'senderName', 'SenderName'), company: pick(senderRaw, 'company', 'Company'), phone: pick(senderRaw, 'phone', 'Phone'), telePhone: pick(senderRaw, 'telePhone', 'TelePhone'), email: pick(senderRaw, 'email', 'Email'), countryCode: senderCountry, province: pick(senderRaw, 'province', 'Province'), city: pick(senderRaw, 'city', 'City'), zipCode: pick(senderRaw, 'zipCode', 'ZipCode'), streetAddress1: pick(senderRaw, 'streetAddress1', 'StreetAddress1'), streetAddress2: pick(senderRaw, 'streetAddress2', 'StreetAddress2'), streetAddress3: pick(senderRaw, 'streetAddress3', 'StreetAddress3'), houseNumber: pick(senderRaw, 'houseNumber', 'HouseNumber'), }, items: itemsRaw.map((row) => mapItem(row)), } if (forecastCode) { body.productCode = forecastCode } return body }