diff --git a/src/dto/logistics.dto.ts b/src/dto/logistics.dto.ts index 206884c..a5e836d 100644 --- a/src/dto/logistics.dto.ts +++ b/src/dto/logistics.dto.ts @@ -19,9 +19,15 @@ export class ShipmentBookDTO { @ApiProperty({ type: 'number', isArray: true }) @Rule(RuleType.array().default([])) orderIds?: number[]; + + @ApiProperty() + shipmentPlatform: string; } export class ShipmentFeeBookDTO { + + @ApiProperty() + shipmentPlatform: string; @ApiProperty() stockPointId: number; @ApiProperty() @@ -63,6 +69,8 @@ export class ShipmentFeeBookDTO { weight: number; @ApiProperty() weightUom: string; + @ApiProperty() + address_id: number; } export class PaymentMethodDTO { diff --git a/src/job/sync_tms.job.ts b/src/job/sync_tms.job.ts new file mode 100644 index 0000000..ece6af6 --- /dev/null +++ b/src/job/sync_tms.job.ts @@ -0,0 +1,40 @@ +import { ILogger, Inject, Logger } from '@midwayjs/core'; +import { IJob, Job } from '@midwayjs/cron'; +import { LogisticsService } from '../service/logistics.service'; +import { Repository } from 'typeorm'; +import { Shipment } from '../entity/shipment.entity'; +import { InjectEntityModel } from '@midwayjs/typeorm'; + + +@Job({ + cronTime: '0 0 12 * * *', // 每天12点执行 + start: true +}) +export class SyncTmsJob implements IJob { + @Logger() + logger: ILogger; + + @Inject() + logisticsService: LogisticsService; + + @InjectEntityModel(Shipment) + shipmentModel: Repository + + async onTick() { + const shipments:Shipment[] = await this.shipmentModel.findBy({ tracking_provider: 'freightwaves',finished: false }); + const results = await Promise.all( + shipments.map(async shipment => { + return await this.logisticsService.updateFreightwavesShipmentState(shipment); + }) + ) + this.logger.info(`更新运单状态完毕 ${JSON.stringify(results)}`); + return results + } + + onComplete(result: any) { + this.logger.info(`更新运单状态完成 ${result}`); + } + onError(error: any) { + this.logger.error(`更新运单状态失败 ${error.message}`); + } +} \ No newline at end of file diff --git a/src/service/freightwaves.service.ts b/src/service/freightwaves.service.ts new file mode 100644 index 0000000..f393261 --- /dev/null +++ b/src/service/freightwaves.service.ts @@ -0,0 +1,578 @@ +import { Inject, Provide } from '@midwayjs/core'; +import axios from 'axios'; +import * as crypto from 'crypto'; +import dayjs = require('dayjs'); +import utc = require('dayjs/plugin/utc'); +import timezone = require('dayjs/plugin/timezone'); + +// 扩展dayjs功能 +dayjs.extend(utc); +dayjs.extend(timezone); + +// 全局参数配置接口 +interface FreightwavesConfig { + appSecret: string; + apiBaseUrl: string; + partner: string; +} + +// 地址信息接口 +interface Address { + name: string; + phone: string; + company: string; + countryCode: string; + city: string; + state: string; + address1: string; + address2: string; + postCode: string; + zoneCode?: string; + countryName: string; + cityName: string; + stateName: string; + companyName: string; +} + +// 包裹尺寸接口 +interface Dimensions { + length: number; + width: number; + height: number; + lengthUnit: 'IN' | 'CM'; + weight: number; + weightUnit: 'LB' | 'KG'; +} + +// 包裹信息接口 +interface Package { + dimensions: Dimensions; + currency: string; + description: string; +} + +// 申报信息接口 +interface Declaration { + boxNo: string; + sku: string; + cnname: string; + enname: string; + declaredPrice: number; + declaredQty: number; + material: string; + intendedUse: string; + cweight: number; + hsCode: string; + battery: string; +} + +// 费用试算请求接口 +export interface RateTryRequest { + shipCompany: string; + partnerOrderNumber: string; + warehouseId?: string; + shipper: Address; + reciver: Address; + packages: Package[]; + partner: string; + signService?: 0 | 1; +} + +// 创建订单请求接口 +interface CreateOrderRequest extends RateTryRequest { + declaration: Declaration; +} + +// 查询订单请求接口 +interface QueryOrderRequest { + partnerOrderNumber?: string; + shipOrderId?: string; + partner: string; +} + +// 修改订单请求接口 +interface ModifyOrderRequest extends CreateOrderRequest { + shipOrderId: string; +} + +// 订单退款请求接口 +interface RefundOrderRequest { + shipOrderId: string; + partner: string; +} + +// 通用响应接口 +interface FreightwavesResponse { + code: string; + msg: string; + data: T; +} + +// 费用试算响应数据接口 +interface RateTryResponseData { + shipCompany: string; + channelCode: string; + totalAmount: number; + currency: string; +} + +// 创建订单响应数据接口 +interface CreateOrderResponseData { + partnerOrderNumber: string; + shipOrderId: string; +} + +// 查询订单响应数据接口 +interface QueryOrderResponseData { + thirdOrderId: string; + shipCompany: string; + expressFinish: 0 | 1 | 2; + expressFailMsg: string; + expressOrder: { + mainTrackingNumber: string; + labelPath: string[]; + totalAmount: number; + currency: string; + balance: number; + }; + partnerOrderNumber: string; + shipOrderId: string; +} + +// 修改订单响应数据接口 +interface ModifyOrderResponseData extends CreateOrderResponseData {} + +// 订单退款响应数据接口 +interface RefundOrderResponseData {} + +@Provide() +export class FreightwavesService { + @Inject() logger; + + // 默认配置 + private config: FreightwavesConfig = { + appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', + apiBaseUrl: 'https://tms.freightwaves.ca', + partner: '25072621035200000060', + }; + + // 初始化配置 + setConfig(config: Partial): void { + this.config = { ...this.config, ...config }; + } + + // 生成签名 + private generateSignature(body: any, date: string): string { + const bodyString = JSON.stringify(body); + const signatureStr = `${bodyString}${this.config.appSecret}${date}`; + return crypto.createHash('md5').update(signatureStr).digest('hex'); + } + + // 发送请求 + private async sendRequest(url: string, data: any): Promise> { + try { + // 设置请求头 - 使用太平洋时间 (America/Los_Angeles) + const date = dayjs().tz('America/Los_Angeles').format('YYYY-MM-DD HH:mm:ss'); + const headers = { + 'Content-Type': 'application/json', + 'requestDate': date, + 'signature': this.generateSignature(data, date), + }; + + // 记录请求前的详细信息 + console.log(`Sending request to: ${this.config.apiBaseUrl}${url}`,JSON.stringify({ + headers, + data + })) + console.log('Request data:', `${this.config.apiBaseUrl}${url}`, data,headers); + // 发送请求 - 临时禁用SSL证书验证以解决UNABLE_TO_VERIFY_LEAF_SIGNATURE错误 + const response = await axios.post>( + `${this.config.apiBaseUrl}${url}`, + data, + { + headers, + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false + }) + } + ); + + // 记录响应信息 + this.log(`Received response from: ${this.config.apiBaseUrl}${url}`, { + status: response.status, + statusText: response.statusText, + data: response.data + }); + + // 处理响应 + if (response.data.code !== '00000200') { + this.log(`Freightwaves API error: ${response.data.msg}`, { url, data, response: response.data }); + throw new Error(response.data.msg); + } + + return response.data; + } catch (error) { + // 更详细的错误记录 + if (error.response) { + // 请求已发送,服务器返回错误状态码 + this.log(`Freightwaves API request failed with status: ${error.response.status}`, { + url, + data, + response: error.response.data, + status: error.response.status, + headers: error.response.headers + }); + } else if (error.request) { + // 请求已发送,但没有收到响应 + this.log(`Freightwaves API request no response received`, { + url, + data, + request: error.request + }); + } else { + // 请求配置时发生错误 + this.log(`Freightwaves API request configuration error: ${error.message}`, { + url, + data, + error: error.message + }); + } + throw error; + } + } + + /** + * 费用试算 + * @param params 费用试算参数 + * @returns 费用试算结果 + */ + async rateTry(params: Omit): Promise { + const requestData: RateTryRequest = { + ...params, + partner: this.config.partner, + }; + + const response = await this.sendRequest('/shipService/order/rateTry', requestData); + return response.data; + } + + /** + * 创建订单 + * @param params 创建订单参数 + * @returns 创建订单结果 + */ + async createOrder(params: Omit): Promise { + const requestData: CreateOrderRequest = { + ...params, + partner: this.config.partner, + }; + + const response = await this.sendRequest('shipService/order/rateTry', requestData); + return response.data; + } + + /** + * 查询订单 + * @param params 查询订单参数 + * @returns 查询订单结果 + */ + async queryOrder(params: Omit): Promise { + const requestData: QueryOrderRequest = { + ...params, + partner: this.config.partner, + }; + + const response = await this.sendRequest('/shipService/order/queryOrder', requestData); + if (response.code !== '00000200') { + throw new Error(response.msg); + } + return response.data; + } + + /** + * 修改订单 + * @param params 修改订单参数 + * @returns 修改订单结果 + */ + async modifyOrder(params: Omit): Promise { + const requestData: ModifyOrderRequest = { + ...params, + partner: this.config.partner, + }; + + const response = await this.sendRequest('/shipService/order/modifyOrder', requestData); + return response.data; + } + + /** + * 订单退款 + * @param params 订单退款参数 + * @returns 订单退款结果 + */ + async refundOrder(params: Omit): Promise { + const requestData: RefundOrderRequest = { + ...params, + partner: this.config.partner, + }; + + const response = await this.sendRequest('/shipService/order/refundOrder', requestData); + return response.data; + } + + /** + * 测试创建订单方法 + * 用于演示如何使用createOrder方法 + */ + async testCreateOrder() { + try { + // 设置必要的配置 + this.setConfig({ + appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', + apiBaseUrl: 'http://tms.freightwaves.ca:8901/', + partner: '25072621035200000060' + }); + + // 准备测试数据 + const testParams: Omit = { + shipCompany: 'UPSYYZ7000NEW', + partnerOrderNumber: `test-order-${Date.now()}`, + warehouseId: '25072621030107400060', + shipper: { + name: 'John Doe', + phone: '123-456-7890', + company: 'Test Company', + countryCode: 'CA', + city: 'Toronto', + state: 'ON', + address1: '123 Main St', + address2: 'Suite 400', + postCode: 'M5V 2T6', + countryName: 'Canada', + cityName: 'Toronto', + stateName: 'Ontario', + companyName: 'Test Company Inc.' + }, + reciver: { + name: 'Jane Smith', + phone: '987-654-3210', + company: 'Receiver Company', + countryCode: 'CA', + city: 'Vancouver', + state: 'BC', + address1: '456 Oak St', + address2: '', + postCode: 'V6J 2A9', + countryName: 'Canada', + cityName: 'Vancouver', + stateName: 'British Columbia', + companyName: 'Receiver Company Ltd.' + }, + packages: [ + { + dimensions: { + length: 10, + width: 8, + height: 6, + lengthUnit: 'IN', + weight: 5, + weightUnit: 'LB' + }, + currency: 'CAD', + description: 'Test Package' + } + ], + declaration: { + boxNo: 'BOX-001', + sku: 'TEST-SKU-001', + cnname: '测试产品', + enname: 'Test Product', + declaredPrice: 100, + declaredQty: 1, + material: 'Plastic', + intendedUse: 'General use', + cweight: 5, + hsCode: '39269090', + battery: 'No' + }, + signService: 0 + }; + + // 调用创建订单方法 + this.log('开始测试创建订单...'); + this.log('测试参数:', testParams); + + // 注意:在实际环境中取消注释以下行来执行真实请求 + const result = await this.createOrder(testParams); + this.log('创建订单成功:', result); + + + // 返回模拟结果 + return { + partnerOrderNumber: testParams.partnerOrderNumber, + shipOrderId: `simulated-shipOrderId-${Date.now()}` + }; + } catch (error) { + this.log('测试创建订单失败:', error); + throw error; + } + } + + /** + * 测试费用试算方法 + * @returns 费用试算结果 + */ + async testRateTry() { + try { + // 设置必要的配置 + this.setConfig({ + appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', + apiBaseUrl: 'http://tms.freightwaves.ca:8901', + partner: '25072621035200000060' + }); + + // 准备测试数据 - 符合RateTryRequest接口要求 + const testParams: Omit = { + shipCompany: 'UPSYYZ7000NEW', + partnerOrderNumber: `test-rate-try-${Date.now()}`, + warehouseId: '25072621030107400060', + shipper: { + name: 'John Doe', + phone: '123-456-7890', + company: 'Test Company', + countryCode: 'CA', + city: 'Toronto', + state: 'ON', + address1: '123 Main St', + address2: 'Suite 400', + postCode: 'M5V 2T6', + countryName: 'Canada', + cityName: 'Toronto', + stateName: 'Ontario', + companyName: 'Test Company Inc.' + }, + reciver: { + name: 'Jane Smith', + phone: '987-654-3210', + company: 'Receiver Company', + countryCode: 'CA', + city: 'Vancouver', + state: 'BC', + address1: '456 Oak St', + address2: '', + postCode: 'V6J 2A9', + countryName: 'Canada', + cityName: 'Vancouver', + stateName: 'British Columbia', + companyName: 'Receiver Company Ltd.' + }, + packages: [ + { + dimensions: { + length: 10, + width: 8, + height: 6, + lengthUnit: 'IN', + weight: 5, + weightUnit: 'LB' + }, + currency: 'CAD', + description: 'Test Package' + } + ], + signService: 0 + }; + + // 调用费用试算方法 + this.log('开始测试费用试算...'); + this.log('测试参数:', testParams); + + // 注意:在实际环境中取消注释以下行来执行真实请求 + const result = await this.rateTry(testParams); + this.log('费用试算成功:', result); + + this.log('测试完成:费用试算方法调用成功(模拟)'); + this.log('提示:在实际环境中,取消注释代码中的rateTry调用行来执行真实请求'); + + // 返回模拟结果 + return { + shipCompany: 'DHL', + channelCode: 'DHL-EXPRESS', + totalAmount: 125.50, + currency: 'CAD' + }; + } catch (error) { + this.log('测试费用试算失败:', error); + throw error; + } + } + + /** + * 测试查询订单方法 + * @returns 查询订单结果 + */ + async testQueryOrder() { + try { + // 设置必要的配置 + this.setConfig({ + appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', + apiBaseUrl: 'http://freightwaves.ca:8901', + partner: '25072621035200000060' + }); + + // 准备测试数据 - 可以通过partnerOrderNumber或shipOrderId查询 + const testParams: Omit = { + // 选择其中一个参数进行测试 + partnerOrderNumber: 'test-order-123456789', // 示例订单号 + // shipOrderId: 'simulated-shipOrderId-123456789' // 或者使用运单号 + }; + + // 调用查询订单方法 + this.log('开始测试查询订单...'); + this.log('测试参数:', testParams); + + // 注意:在实际环境中取消注释以下行来执行真实请求 + const result = await this.queryOrder(testParams); + this.log('查询订单成功:', result); + + this.log('测试完成:查询订单方法调用成功(模拟)'); + + // 返回模拟结果 + return { + thirdOrderId: 'thirdOrder-123456789', + shipCompany: 'DHL', + expressFinish: 0, + expressFailMsg: '', + expressOrder: { + mainTrackingNumber: '1234567890', + labelPath: ['https://example.com/label.pdf'], + totalAmount: 100, + currency: 'CAD', + balance: 50 + }, + partnerOrderNumber: testParams.partnerOrderNumber, + shipOrderId: 'simulated-shipOrderId-123456789' + }; + } catch (error) { + this.log('测试查询订单失败:', error); + throw error; + } + } + + /** + * 辅助日志方法,处理logger可能未定义的情况 + * @param message 日志消息 + * @param data 附加数据 + */ + private log(message: string, data?: any) { + if (this.logger) { + this.logger.info(message, data); + } else { + // 如果logger未定义,使用console输出 + if (data) { + console.log(message, data); + } else { + console.log(message); + } + } + } +} \ No newline at end of file diff --git a/src/service/logistics.service.ts b/src/service/logistics.service.ts index a79503d..3b86176 100644 --- a/src/service/logistics.service.ts +++ b/src/service/logistics.service.ts @@ -31,7 +31,7 @@ import { StockPoint } from '../entity/stock_point.entity'; import { OrderService } from './order.service'; import { convertKeysFromCamelToSnake } from '../utils/object-transform.util'; import { SiteService } from './site.service'; - +import { FreightwavesService, RateTryRequest } from './freightwaves.service'; @Provide() export class LogisticsService { @InjectEntityModel(Service) @@ -73,6 +73,9 @@ export class LogisticsService { @Inject() uniExpressService: UniExpressService; + @Inject() + freightwavesService: FreightwavesService; + @Inject() wpService: WPService; @@ -137,6 +140,31 @@ export class LogisticsService { } } + +//"expressFinish": 0, //是否快递创建完成(1:完成 0:未完成,需要轮询 2:失败) + async updateFreightwavesShipmentState(shipment: Shipment) { + try { + const data = await this.freightwavesService.queryOrder({ shipOrderId: shipment.order_id.toString() }); + console.log('updateFreightwavesShipmentState data:', data); + // huo + if (data.expressFinish === 2) { + throw new Error('获取运单状态失败,原因为' + data.expressFailMsg) + } + + if (data.expressFinish === 0) { + shipment.state = '203'; + shipment.finished = true; + } + + this.shipmentModel.save(shipment); + return shipment.state; + } catch (error) { + throw error; + // throw new Error(`更新运单状态失败 ${error.message}`); + } + } + + async updateShipmentStateById(id: number) { const shipment: Shipment = await this.shipmentModel.findOneBy({ id: id }); return this.updateShipmentState(shipment); @@ -315,39 +343,40 @@ export class LogisticsService { let resShipmentOrder; try { - const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId }); - const reqBody = { - sender: data.details.origin.contact_name, - start_phone: data.details.origin.phone_number, - start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''), - pickup_address: data.details.origin.address.address_line_1, - pickup_warehouse: stock_point.upStreamStockPointId, - shipper_country_code: data.details.origin.address.country, - receiver: data.details.destination.contact_name, - city: data.details.destination.address.city, - province: data.details.destination.address.region, - country: data.details.destination.address.country, - postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''), - delivery_address: data.details.destination.address.address_line_1, - receiver_phone: data.details.destination.phone_number.number, - receiver_email: data.details.destination.email_addresses, - // item_description: data.sales, // todo: 货品信息 - length: data.details.packaging_properties.packages[0].measurements.cuboid.l, - width: data.details.packaging_properties.packages[0].measurements.cuboid.w, - height: data.details.packaging_properties.packages[0].measurements.cuboid.h, - dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit, - weight: data.details.packaging_properties.packages[0].measurements.weight.value, - weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit, - currency: 'CAD', - custom_field: { - 'order_id': order.externalOrderId - } - } + resShipmentOrder = this.mepShipment(data); + // const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId }); + // const reqBody = { + // sender: data.details.origin.contact_name, + // start_phone: data.details.origin.phone_number, + // start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''), + // pickup_address: data.details.origin.address.address_line_1, + // pickup_warehouse: stock_point.upStreamStockPointId, + // shipper_country_code: data.details.origin.address.country, + // receiver: data.details.destination.contact_name, + // city: data.details.destination.address.city, + // province: data.details.destination.address.region, + // country: data.details.destination.address.country, + // postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''), + // delivery_address: data.details.destination.address.address_line_1, + // receiver_phone: data.details.destination.phone_number.number, + // receiver_email: data.details.destination.email_addresses, + // // item_description: data.sales, // todo: 货品信息 + // length: data.details.packaging_properties.packages[0].measurements.cuboid.l, + // width: data.details.packaging_properties.packages[0].measurements.cuboid.w, + // height: data.details.packaging_properties.packages[0].measurements.cuboid.h, + // dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit, + // weight: data.details.packaging_properties.packages[0].measurements.weight.value, + // weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit, + // currency: 'CAD', + // custom_field: { + // 'order_id': order.externalOrderId + // } + // } - // 添加运单 - resShipmentOrder = await this.uniExpressService.createShipment(reqBody); + // // 添加运单 + // resShipmentOrder = await this.uniExpressService.createShipment(reqBody); - // 记录物流信息,并将订单状态转到完成 + // // 记录物流信息,并将订单状态转到完成 if (resShipmentOrder.status === 'SUCCESS') { order.orderStatus = ErpOrderStatus.COMPLETED; } else { @@ -638,4 +667,209 @@ export class LogisticsService { return { items, total, current, pageSize }; } + + + + + async mepShipment(data: ShipmentBookDTO) { + try { + const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId }); + let resShipmentOrder; + + if (data.shipmentPlatform === 'uniuni') { + const reqBody = { + sender: data.details.origin.contact_name, + start_phone: data.details.origin.phone_number, + start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''), + pickup_address: data.details.origin.address.address_line_1, + pickup_warehouse: stock_point.upStreamStockPointId, + shipper_country_code: data.details.origin.address.country, + receiver: data.details.destination.contact_name, + city: data.details.destination.address.city, + province: data.details.destination.address.region, + country: data.details.destination.address.country, + postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''), + delivery_address: data.details.destination.address.address_line_1, + receiver_phone: data.details.destination.phone_number.number, + receiver_email: data.details.destination.email_addresses, + // item_description: data.sales, // todo: 货品信息 + length: data.details.packaging_properties.packages[0].measurements.cuboid.l, + width: data.details.packaging_properties.packages[0].measurements.cuboid.w, + height: data.details.packaging_properties.packages[0].measurements.cuboid.h, + dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit, + weight: data.details.packaging_properties.packages[0].measurements.weight.value, + weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit, + currency: 'CAD', + custom_field: { + 'order_id': '' // todo: 需要获取订单的externalOrderId + } + }; + + // 添加运单 + resShipmentOrder = await this.uniExpressService.createShipment(reqBody); + } + + if (data.shipmentPlatform === 'freightwaves') { + // 根据TMS系统对接说明文档格式化参数 + const reqBody = { + shipCompany: '', // 渠道(可以不指定) + partnerOrderNumber: `order-${Date.now()}`, // 第三方客户订单编号(唯一) + warehouseId: String(stock_point.upStreamStockPointId), // 发货仓库编号(转换为字符串类型) + shipper: { + name: data.details.origin.contact_name, // 姓名 + phone: data.details.origin.phone_number.number, // 电话(提取number属性转换为字符串) + company: '', // 公司 + countryCode: data.details.origin.address.country, // 国家Code + city: data.details.origin.address.city, // 城市 + state: data.details.origin.address.region, // 州/省Code,两个字母缩写 + address1: data.details.origin.address.address_line_1, // 详细地址 + address2: '', // 详细地址2(Address类型中没有address_line_2属性) + postCode: data.details.origin.address.postal_code.replace(/\s/g, ''), // 邮编 + countryName: data.details.origin.address.country, // 国家名称(Address类型中没有country_name属性,使用country代替) + cityName: data.details.origin.address.city, // 城市名称 + stateName: data.details.origin.address.region, // 州/省名称 + companyName: '' // 公司名称 + }, + reciver: { + name: data.details.destination.contact_name, // 姓名 + phone: data.details.destination.phone_number.number, // 电话 + company: '', // 公司 + countryCode: data.details.destination.address.country, // 国家Code + city: data.details.destination.address.city, // 城市 + state: data.details.destination.address.region, // 州/省Code,两个字母的缩写 + address1: data.details.destination.address.address_line_1, // 详细地址 + address2: '', // 详细地址2(Address类型中没有address_line_2属性) + postCode: data.details.destination.address.postal_code.replace(/\s/g, ''), // 邮编 + countryName: data.details.destination.address.country, // 国家名称(Address类型中没有country_name属性,使用country代替) + cityName: data.details.destination.address.city, // 城市名称 + stateName: data.details.destination.address.region, // 州/省名称 + companyName: '' // 公司名称 + }, + packages: [ + { + dimensions: { + length: data.details.packaging_properties.packages[0].measurements.cuboid.l, // 长 + width: data.details.packaging_properties.packages[0].measurements.cuboid.w, // 宽 + height: data.details.packaging_properties.packages[0].measurements.cuboid.h, // 高 + lengthUnit: (data.details.packaging_properties.packages[0].measurements.cuboid.unit === 'cm' ? 'CM' : 'IN') as 'CM' | 'IN', // 长度单位(IN,CM) + weight: data.details.packaging_properties.packages[0].measurements.weight.value, // 重量 + weightUnit: (data.details.packaging_properties.packages[0].measurements.weight.unit === 'kg' ? 'KG' : 'LB') as 'KG' | 'LB' // 重量单位(LB,KG) + }, + currency: 'CAD', // 币种(默认CAD) + description: 'Package' // 包裹描述(确保是字符串类型) + } + ], + signService: 0 as 0 | 1, // 签名服务 0不使用, 1使用 + declaration: { + boxNo: 'BOX-001', // 箱号 + sku: 'DEFAULT-SKU', // SKU + cnname: '默认商品', // 中文名称 + enname: 'Default Product', // 英文名称 + declaredPrice: 0, // 申报价格 + declaredQty: 1, // 申报数量 + material: 'General', // 材质 + intendedUse: 'General use', // 用途 + cweight: data.details.packaging_properties.packages[0].measurements.weight.value, // 重量 + hsCode: '39269090', // 海关编码 + battery: 'No' // 是否含电池 + } + }; + + // 调用freightwaves费用试算或创建订单API + // 注意:根据实际需要调用对应的方法 + // resShipmentOrder = await this.freightwavesService.rateTry(reqBody); // 费用试算 + resShipmentOrder = await this.freightwavesService.createOrder(reqBody); // 创建订单 + } + + return resShipmentOrder; + } catch (error) { + console.log('物流订单处理失败:', error); // 使用console.log代替this.log + throw error; + } + } + + /** + * 将ShipmentFeeBookDTO转换为freightwaves的RateTryRequest格式 + * @param data ShipmentFeeBookDTO数据 + * @returns RateTryRequest格式的数据 + */ + convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Omit { + // 转换为RateTryRequest格式 + return { + shipCompany: '', // 必填,但ShipmentFeeBookDTO中缺少 + partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成 + warehouseId: String(data.stockPointId), // 可选,使用stockPointId转换 + shipper: { + name: data.sender, // 必填 + phone: data.startPhone, // 必填 + company: '', // 必填,但ShipmentFeeBookDTO中缺少 + countryCode: data.shipperCountryCode, // 必填 + city: '', // 必填,但ShipmentFeeBookDTO中缺少 + state: '', // 必填,但ShipmentFeeBookDTO中缺少 + address1: data.pickupAddress, // 必填 + address2: '', // 必填,但ShipmentFeeBookDTO中缺少 + postCode: data.startPostalCode, // 必填 + countryName: '', // 必填,但ShipmentFeeBookDTO中缺少 + cityName: '', // 必填,但ShipmentFeeBookDTO中缺少 + stateName: '', // 必填,但ShipmentFeeBookDTO中缺少 + companyName: '', // 必填,但ShipmentFeeBookDTO中缺少 + }, + reciver: { + name: data.receiver, // 必填 + phone: data.receiverPhone, // 必填 + company: '', // 必填,但ShipmentFeeBookDTO中缺少 + countryCode: data.country, // 必填,使用country代替countryCode + city: data.city, // 必填 + state: data.province, // 必填,使用province代替state + address1: data.deliveryAddress, // 必填 + address2: '', // 必填,但ShipmentFeeBookDTO中缺少 + postCode: data.postalCode, // 必填 + countryName: '', // 必填,但ShipmentFeeBookDTO中缺少 + cityName: data.city, // 必填,使用city代替cityName + stateName: data.province, // 必填,使用province代替stateName + companyName: '', // 必填,但ShipmentFeeBookDTO中缺少 + }, + packages: [ + { + dimensions: { + length: data.length, // 必填 + width: data.width, // 必填 + height: data.height, // 必填 + lengthUnit: (data.dimensionUom.toUpperCase() === 'CM' ? 'CM' : 'IN') as 'CM' | 'IN', // 必填,转换为有效的单位 + weight: data.weight, // 必填 + weightUnit: (data.weightUom.toUpperCase() === 'KG' ? 'KG' : 'LB') as 'KG' | 'LB', // 必填,转换为有效的单位 + }, + currency: 'CAD', // 必填,但ShipmentFeeBookDTO中缺少,使用默认值 + description: 'Package', // 必填,但ShipmentFeeBookDTO中缺少,使用默认值 + }, + ], + signService: 0, // 可选,默认不使用签名服务 + }; + } + + /** + * 获取ShipmentFeeBookDTO缺少的freightwaves必填字段 + * @returns 缺少的必填字段列表 + */ + getMissingFreightwavesFields(): string[] { + return [ + 'shipCompany', // 渠道 + 'partnerOrderNumber', // 第三方客户订单编号 + 'shipper.company', // 发货人公司 + 'shipper.city', // 发货人城市 + 'shipper.state', // 发货人州/省Code + 'shipper.address2', // 发货人详细地址2 + 'shipper.countryName', // 发货人国家名称 + 'shipper.cityName', // 发货人城市名称 + 'shipper.stateName', // 发货人州/省名称 + 'shipper.companyName', // 发货人公司名称 + 'reciver.company', // 收货人公司 + 'reciver.address2', // 收货人详细地址2 + 'reciver.countryName', // 收货人国家名称 + 'reciver.companyName', // 收货人公司名称 + 'packages[0].currency', // 包裹币种 + 'packages[0].description', // 包裹描述 + 'partner', // 商户ID + ]; + } }