From 3cd36892f62f4379602f67e048885fc262d7eef2 Mon Sep 17 00:00:00 2001 From: zhuotianyuan Date: Mon, 26 Jan 2026 16:38:14 +0800 Subject: [PATCH] =?UTF-8?q?refactor(logistics):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=89=A9=E6=B5=81=E6=9C=8D=E5=8A=A1=E9=80=BB=E8=BE=91=E5=B9=B6?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构物流服务相关代码,移除冗余的测试方法,优化运费计算和订单创建流程 调整freightwaves服务配置,移除调试日志,统一API路径格式 完善物流信息同步逻辑,支持多平台物流服务集成 --- src/dto/logistics.dto.ts | 2 +- src/service/freightwaves.service.ts | 250 +--------------------------- src/service/logistics.service.ts | 238 +++++++++++++------------- 3 files changed, 120 insertions(+), 370 deletions(-) diff --git a/src/dto/logistics.dto.ts b/src/dto/logistics.dto.ts index fa57e60..b49b72c 100644 --- a/src/dto/logistics.dto.ts +++ b/src/dto/logistics.dto.ts @@ -34,7 +34,7 @@ export class ShipmentFeeBookDTO { @ApiProperty() sender: string; @ApiProperty() - startPhone: string; + startPhone: string|any; @ApiProperty() startPostalCode: string; @ApiProperty() diff --git a/src/service/freightwaves.service.ts b/src/service/freightwaves.service.ts index be7deee..f2bbe0a 100644 --- a/src/service/freightwaves.service.ts +++ b/src/service/freightwaves.service.ts @@ -152,7 +152,7 @@ export class FreightwavesService { // 默认配置 private config: FreightwavesConfig = { appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', - apiBaseUrl: 'http://tms.freightwaves.ca:8901/', + apiBaseUrl: 'http://tms.freightwaves.ca:8901', partner: '25072621035200000060' }; @@ -179,12 +179,6 @@ export class FreightwavesService { '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}`, @@ -267,7 +261,7 @@ export class FreightwavesService { partner: this.config.partner, }; - const response = await this.sendRequest('shipService/order/createOrder', requestData); + const response = await this.sendRequest('/shipService/order/createOrder', requestData); return response; } @@ -314,250 +308,10 @@ export class FreightwavesService { ...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 日志消息 diff --git a/src/service/logistics.service.ts b/src/service/logistics.service.ts index 997bd17..8630deb 100644 --- a/src/service/logistics.service.ts +++ b/src/service/logistics.service.ts @@ -32,7 +32,6 @@ 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) serviceModel: Repository; @@ -318,7 +317,16 @@ export class LogisticsService { currency: 'CAD', // item_description: data.sales, // todo: 货品信息 } - const resShipmentFee = await this.uniExpressService.getRates(reqBody); + let resShipmentFee: any; + if (data.shipmentPlatform === 'uniuni') { + resShipmentFee = await this.uniExpressService.getRates(reqBody); + } else if (data.shipmentPlatform === 'freightwaves') { + const fre_reqBody = await this.convertToFreightwavesRateTry(data); + resShipmentFee = await this.freightwavesService.rateTry(fre_reqBody); + } else { + throw new Error('不支持的运单平台'); + } + if (resShipmentFee.status !== 'SUCCESS') { throw new Error(resShipmentFee.ret_msg); } @@ -327,7 +335,7 @@ export class LogisticsService { throw e; } } - + async createShipment(orderId: number, data: ShipmentBookDTO, userId: number) { const order = await this.orderModel.findOneBy({ id: orderId }); const { sales } = data; @@ -343,48 +351,9 @@ 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 = await this.mepShipment(data, order); - // if (data.shipmentPlatform === 'uniuni') { - // // 添加运单 - // resShipmentOrder = await this.uniExpressService.createShipment(reqBody); - // } - - // if (data.shipmentPlatform === 'freightwaves') { - // // 添加运单 - // resShipmentOrder = await this.freightcomService.createShipment(reqBody); - // } - - // 记录物流信息,并将订单状态转到完成 + // 记录物流信息,并将订单状态转到完成,uniuni状态为SUCCESS,tms.freightwaves状态为00000200 if (resShipmentOrder.status === 'SUCCESS' || resShipmentOrder.code === '00000200') { order.orderStatus = ErpOrderStatus.COMPLETED; } else { @@ -398,6 +367,8 @@ export class LogisticsService { const shipmentRepo = manager.getRepository(Shipment); const tracking_provider = data.shipmentPlatform; // todo: id未确定,后写进常数 + // 同步物流信息到woocommerce + const site = await this.siteService.get(Number(order.siteId), true); let co: any; let unique_id: any; let state: any; @@ -410,30 +381,28 @@ export class LogisticsService { unique_id = resShipmentOrder.data?.shipOrderId; state = ErpOrderStatus.COMPLETED; } - // 同步物流信息到woocommerce - const site = await this.siteService.get(Number(order.siteId), true); - const res = await this.wpService.createFulfillment(site, order.externalOrderId, { - tracking_number: co, - tracking_provider: tracking_provider, - }); - - if (order.orderStatus === ErpOrderStatus.COMPLETED) { - const shipment = await shipmentRepo.save({ - tracking_provider: tracking_provider, - tracking_id: res.data.tracking_id, - unique_id: unique_id, - stockPointId: String(data.stockPointId), // todo - state: state, - return_tracking_number: co, - fee: data.details.shipmentFee, - order: order - }); - order.shipmentId = shipment.id; - shipmentId = shipment.id; - } // 同步订单状态到woocommerce if (order.source_type != "shopyy") { + const res = await this.wpService.createFulfillment(site, order.externalOrderId, { + tracking_number: co, + tracking_provider: tracking_provider, + }); + + if (order.orderStatus === ErpOrderStatus.COMPLETED) { + const shipment = await shipmentRepo.save({ + tracking_provider: tracking_provider, + tracking_id: res.data.tracking_id, + unique_id: unique_id, + stockPointId: String(data.stockPointId), // todo + state: state, + return_tracking_number: co, + fee: data.details.shipmentFee, + order: order + }); + order.shipmentId = shipment.id; + shipmentId = shipment.id; + } if (order.status !== OrderStatus.COMPLETED) { await this.wpService.updateOrder(site, order.externalOrderId, { status: OrderStatus.COMPLETED, @@ -441,19 +410,44 @@ export class LogisticsService { order.status = OrderStatus.COMPLETED; } } - + // if (order.source_type === "shopyy") { + // const res = await this.shopyyService.createFulfillment(site, order.externalOrderId, { + // tracking_number: co, + // tracking_company: resShipmentOrder.shipCompany, + // carrier_code: resShipmentOrder.shipperOrderId, + // }); + // if (order.orderStatus === ErpOrderStatus.COMPLETED) { + // const shipment = await shipmentRepo.save({ + // tracking_provider: tracking_provider, + // tracking_id: res.data.tracking_id, + // unique_id: unique_id, + // stockPointId: String(data.stockPointId), // todo + // state: state, + // return_tracking_number: co, + // fee: data.details.shipmentFee, + // order: order + // }); + // order.shipmentId = shipment.id; + // shipmentId = shipment.id; + // } + // if (order.status !== OrderStatus.COMPLETED) { + // // shopyy未提供更新订单接口,暂不更新订单状态 + // // await this.shopyyService.updateOrder(site, order.externalOrderId, { + // // status: OrderStatus.COMPLETED, + // // }); + // order.status = OrderStatus.COMPLETED; + // } + // } order.orderStatus = ErpOrderStatus.COMPLETED; - await orderRepo.save(order); }).catch(error => { transactionError = error + throw new Error(`请求错误:${error}`); }); if (transactionError !== undefined) { - console.log('err', transactionError); throw transactionError; } - // 更新产品发货信息 this.orderService.updateOrderSales(order.id, sales); @@ -726,7 +720,6 @@ export class LogisticsService { 'order_id': order.externalOrderId // todo: 需要获取订单的externalOrderId } }; - // 添加运单 resShipmentOrder = await this.uniExpressService.createShipment(reqBody); } @@ -778,16 +771,30 @@ export class LogisticsService { weightUnit: (data.details.packaging_properties.packages[0].measurements.weight.unit === 'kg' ? 'KG' : 'LB') as 'KG' | 'LB' // 重量单位(LB,KG) }, currency: 'CAD', // 币种(默认CAD) - description: '订单编号:' + order.externalOrderId // 包裹描述(确保是字符串类型) + description: 'site:' + order.siteId + ' orderId:' + order.externalOrderId // 包裹描述(确保是字符串类型) } ], signService: 0 + // 非跨境订单不需要declaration + // declaration: { + // "boxNo": "", //箱子编号 + // "sku": "", //SKU + // "cnname": "", //中文名称 + // "enname": "", //英文名称 + // "declaredPrice": 1, //申报单价 + // "declaredQty": 1, //申报数量 + // "material": "", //材质 + // "intendedUse": "", //用途 + // "cweight": 1, //产品单重 + // "hsCode": "", //海关编码 + // "battery": "" //电池描述 + // } }; - // 调用freightwaves费用试算或创建订单API - // 注意:根据实际需要调用对应的方法 - // resShipmentOrder = await this.freightwavesService.rateTry(reqBody); // 费用试算 resShipmentOrder = await this.freightwavesService.createOrder(reqBody); // 创建订单 + //tms只返回了物流订单号,需要查询一次来获取完整的物流信息 + const queryRes = await this.freightwavesService.queryOrder({ shipOrderId: resShipmentOrder.shipOrderId }); // 查询订单 + resShipmentOrder.push(queryRes); } return resShipmentOrder; @@ -797,46 +804,60 @@ export class LogisticsService { } } + /** * 将ShipmentFeeBookDTO转换为freightwaves的RateTryRequest格式 * @param data ShipmentFeeBookDTO数据 * @returns RateTryRequest格式的数据 */ - convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Omit { + /** + * 将ShipmentFeeBookDTO转换为freightwaves的RateTryRequest格式 + * @param data ShipmentFeeBookDTO数据 + * @returns RateTryRequest格式的数据 + */ + async convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Promise> { + + const shipments = await this.shippingAddressModel.findOne({ + where: { + id: data.address_id, + }, + }) + + const address = shipments?.address; // 转换为RateTryRequest格式 - return { - shipCompany: '', // 必填,但ShipmentFeeBookDTO中缺少 + const r = { + shipCompany: 'UPSYYZ7000NEW', // 必填,但ShipmentFeeBookDTO中缺少 partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成 - warehouseId: String(data.stockPointId), // 可选,使用stockPointId转换 + warehouseId: '25072621030107400060', // 可选,使用stockPointId转换 shipper: { name: data.sender, // 必填 - phone: data.startPhone, // 必填 - company: '', // 必填,但ShipmentFeeBookDTO中缺少 + phone: data.startPhone.phone, // 必填 + company: address.country, // 必填,但ShipmentFeeBookDTO中缺少 countryCode: data.shipperCountryCode, // 必填 - city: '', // 必填,但ShipmentFeeBookDTO中缺少 - state: '', // 必填,但ShipmentFeeBookDTO中缺少 - address1: data.pickupAddress, // 必填 - address2: '', // 必填,但ShipmentFeeBookDTO中缺少 + city: address.city || '', // 必填,但ShipmentFeeBookDTO中缺少 + state: address.region || '', // 必填,但ShipmentFeeBookDTO中缺少 + address1: address.address_line_1, // 必填 + address2: address.address_line_1 || '', // 必填,但ShipmentFeeBookDTO中缺少 postCode: data.startPostalCode, // 必填 - countryName: '', // 必填,但ShipmentFeeBookDTO中缺少 - cityName: '', // 必填,但ShipmentFeeBookDTO中缺少 - stateName: '', // 必填,但ShipmentFeeBookDTO中缺少 - companyName: '', // 必填,但ShipmentFeeBookDTO中缺少 + countryName: address.country || '', // 必填,但ShipmentFeeBookDTO中缺少 + cityName: address.city || '', // 必填,但ShipmentFeeBookDTO中缺少 + stateName: address.region || '', // 必填,但ShipmentFeeBookDTO中缺少 + companyName: address.country || '', // 必填,但ShipmentFeeBookDTO中缺少 }, reciver: { name: data.receiver, // 必填 phone: data.receiverPhone, // 必填 - company: '', // 必填,但ShipmentFeeBookDTO中缺少 + company: address.country,// 必填,但ShipmentFeeBookDTO中缺少 countryCode: data.country, // 必填,使用country代替countryCode city: data.city, // 必填 state: data.province, // 必填,使用province代替state address1: data.deliveryAddress, // 必填 - address2: '', // 必填,但ShipmentFeeBookDTO中缺少 + address2: data.deliveryAddress, // 必填,但ShipmentFeeBookDTO中缺少 postCode: data.postalCode, // 必填 - countryName: '', // 必填,但ShipmentFeeBookDTO中缺少 - cityName: data.city, // 必填,使用city代替cityName - stateName: data.province, // 必填,使用province代替stateName - companyName: '', // 必填,但ShipmentFeeBookDTO中缺少 + countryName: address.country, // 必填,但ShipmentFeeBookDTO中缺少 + cityName: data.city || '', // 必填,使用city代替cityName + stateName: data.province || '', // 必填,使用province代替stateName + companyName: address.country || '', // 必填,但ShipmentFeeBookDTO中缺少 }, packages: [ { @@ -844,9 +865,9 @@ export class LogisticsService { length: data.length, // 必填 width: data.width, // 必填 height: data.height, // 必填 - lengthUnit: (data.dimensionUom.toUpperCase() === 'CM' ? 'CM' : 'IN') as 'CM' | 'IN', // 必填,转换为有效的单位 + lengthUnit: (data.dimensionUom === 'IN' ? 'IN' : 'CM') as 'IN' | 'CM', // 必填,转换为有效的单位 weight: data.weight, // 必填 - weightUnit: (data.weightUom.toUpperCase() === 'KG' ? 'KG' : 'LB') as 'KG' | 'LB', // 必填,转换为有效的单位 + weightUnit: (data.weightUom === 'LBS' ? 'LB' : 'KG') as 'LB' | 'KG', // 必填,转换为有效的单位 }, currency: 'CAD', // 必填,但ShipmentFeeBookDTO中缺少,使用默认值 description: 'Package', // 必填,但ShipmentFeeBookDTO中缺少,使用默认值 @@ -854,31 +875,6 @@ export class LogisticsService { ], 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 - ]; + return r as any; } }