feat(logistics): 完善freightwaves运费试算接口实现

重构convertToFreightwavesRateTry方法,添加地址查询逻辑
移除freightwaves服务中无用的测试方法
修复订单同步日志格式和注释
This commit is contained in:
zhuotianyuan 2026-01-23 16:29:10 +08:00
parent 2f57dc0d8c
commit 22a13ce0b8
5 changed files with 80 additions and 341 deletions

View File

@ -34,7 +34,7 @@ export class ShipmentFeeBookDTO {
@ApiProperty() @ApiProperty()
sender: string; sender: string;
@ApiProperty() @ApiProperty()
startPhone: string; startPhone: string|any;
@ApiProperty() @ApiProperty()
startPostalCode: string; startPostalCode: string;
@ApiProperty() @ApiProperty()

View File

@ -47,6 +47,11 @@ export class ShippingAddress {
@Expose() @Expose()
phone_number_country: string; phone_number_country: string;
@ApiProperty()
@Column()
@Expose()
email: string;
@ApiProperty({ @ApiProperty({
example: '2022-12-12 11:11:11', example: '2022-12-12 11:11:11',
description: '创建时间', description: '创建时间',

View File

@ -314,250 +314,10 @@ export class FreightwavesService {
...params, ...params,
partner: this.config.partner, partner: this.config.partner,
}; };
const response = await this.sendRequest<RefundOrderResponseData>('/shipService/order/refundOrder', requestData); const response = await this.sendRequest<RefundOrderResponseData>('/shipService/order/refundOrder', requestData);
return response.data; return response.data;
} }
/**
*
* 使createOrder方法
*/
async testCreateOrder() {
try {
// 设置必要的配置
this.setConfig({
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'http://tms.freightwaves.ca:8901/',
partner: '25072621035200000060'
});
// 准备测试数据
const testParams: Omit<CreateOrderRequest, 'partner'> = {
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<RateTryRequest, 'partner'> = {
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<QueryOrderRequest, 'partner'> = {
// 选择其中一个参数进行测试
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可能未定义的情况 * logger可能未定义的情况
* @param message * @param message

View File

@ -279,8 +279,7 @@ export class LogisticsService {
shipmentRepo.remove(shipment); shipmentRepo.remove(shipment);
const res = await this.uniExpressService.deleteShipment(shipment.return_tracking_number); await this.uniExpressService.deleteShipment(shipment.return_tracking_number);
console.log('res', res.data); // todo
await orderRepo.save(order); await orderRepo.save(order);
@ -310,7 +309,6 @@ export class LogisticsService {
console.log('同步到woocommerce失败', error); console.log('同步到woocommerce失败', error);
return true; return true;
} }
return true; return true;
} catch { } catch {
throw new Error('删除运单失败'); throw new Error('删除运单失败');
@ -330,14 +328,12 @@ export class LogisticsService {
if (data.shipmentPlatform === 'uniuni') { if (data.shipmentPlatform === 'uniuni') {
resShipmentFee = await this.uniExpressService.getRates(reqBody); resShipmentFee = await this.uniExpressService.getRates(reqBody);
} else if (data.shipmentPlatform === 'freightwaves') { } else if (data.shipmentPlatform === 'freightwaves') {
const fre_reqBody = await this.convertToFreightwavesRateTry(data);
resShipmentFee = await this.freightwavesService.rateTry(fre_reqBody);
// resShipmentFee = await this.freightwavesService.rateTry(reqBody);
} else { } else {
throw new Error('不支持的运单平台'); throw new Error('不支持的运单平台');
} }
if (resShipmentFee.status !== 'SUCCESS') { if (resShipmentFee.status !== 'SUCCESS') {
throw new Error(resShipmentFee.ret_msg); throw new Error(resShipmentFee.ret_msg);
} }
@ -362,10 +358,9 @@ export class LogisticsService {
let resShipmentOrder; let resShipmentOrder;
try { try {
resShipmentOrder = await this.mepShipment(data, order); resShipmentOrder = await this.mepShipment(data, order);
// 记录物流信息,并将订单状态转到完成 // 记录物流信息,并将订单状态转到完成,uniuni状态为SUCCESStms.freightwaves状态为00000200
if (resShipmentOrder.status === 'SUCCESS' || resShipmentOrder.code === '00000200') { if (resShipmentOrder.status === 'SUCCESS' || resShipmentOrder.code === '00000200') {
order.orderStatus = ErpOrderStatus.COMPLETED; order.orderStatus = ErpOrderStatus.COMPLETED;
} else { } else {
@ -443,25 +438,23 @@ export class LogisticsService {
shipmentId = shipment.id; shipmentId = shipment.id;
} }
if (order.status !== OrderStatus.COMPLETED) { if (order.status !== OrderStatus.COMPLETED) {
// shopyy未提供更新订单接口暂不更新订单状态
// await this.shopyyService.updateOrder(site, order.externalOrderId, { // await this.shopyyService.updateOrder(site, order.externalOrderId, {
// status: OrderStatus.COMPLETED, // status: OrderStatus.COMPLETED,
// }); // });
order.status = OrderStatus.COMPLETED; order.status = OrderStatus.COMPLETED;
} }
} }
order.orderStatus = ErpOrderStatus.COMPLETED; order.orderStatus = ErpOrderStatus.COMPLETED;
await orderRepo.save(order); await orderRepo.save(order);
}).catch(error => { }).catch(error => {
transactionError = error transactionError = error
throw new Error(`请求错误:${error}`);
}); });
if (transactionError !== undefined) { if (transactionError !== undefined) {
console.log('err', transactionError);
throw transactionError; throw transactionError;
} }
// 更新产品发货信息 // 更新产品发货信息
this.orderService.updateOrderSales(order.id, sales); this.orderService.updateOrderSales(order.id, sales);
@ -733,7 +726,6 @@ export class LogisticsService {
'order_id': order.externalOrderId // todo: 需要获取订单的externalOrderId 'order_id': order.externalOrderId // todo: 需要获取订单的externalOrderId
} }
}; };
// 添加运单 // 添加运单
resShipmentOrder = await this.uniExpressService.createShipment(reqBody); resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
} }
@ -742,7 +734,7 @@ export class LogisticsService {
// 根据TMS系统对接说明文档格式化参数 // 根据TMS系统对接说明文档格式化参数
const reqBody: any = { const reqBody: any = {
shipCompany: 'UPSYYZ7000NEW', shipCompany: 'UPSYYZ7000NEW',
partnerOrderNumber: order.siteId + '-1-' + order.externalOrderId, partnerOrderNumber: order.siteId + '-' + order.externalOrderId,
warehouseId: '25072621030107400060', warehouseId: '25072621030107400060',
shipper: { shipper: {
name: data.details.origin.contact_name, // 姓名 name: data.details.origin.contact_name, // 姓名
@ -806,10 +798,9 @@ export class LogisticsService {
}; };
resShipmentOrder = await this.freightwavesService.createOrder(reqBody); // 创建订单 resShipmentOrder = await this.freightwavesService.createOrder(reqBody); // 创建订单
//tms只返回了物流订单号需要查询一次来获取完整的物流信息
const queryRes = await this.freightwavesService.queryOrder({ shipOrderId: resShipmentOrder.shipOrderId }); // 查询订单 const queryRes = await this.freightwavesService.queryOrder({ shipOrderId: resShipmentOrder.shipOrderId }); // 查询订单
console.log('queryRes:', queryRes); // 打印查询结果
resShipmentOrder.push(queryRes); resShipmentOrder.push(queryRes);
} }
return resShipmentOrder; return resShipmentOrder;
@ -824,41 +815,49 @@ export class LogisticsService {
* @param data ShipmentFeeBookDTO数据 * @param data ShipmentFeeBookDTO数据
* @returns RateTryRequest格式的数据 * @returns RateTryRequest格式的数据
*/ */
convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Omit<RateTryRequest, 'partner'> { async convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Promise<Omit<RateTryRequest, 'partner'>> {
const shipments = await this.shippingAddressModel.findOne({
where: {
id: data.address_id,
},
})
const address = shipments?.address;
// 转换为RateTryRequest格式 // 转换为RateTryRequest格式
return { const r = {
shipCompany: 'UPSYYZ7000NEW', // 必填但ShipmentFeeBookDTO中缺少 shipCompany: 'UPSYYZ7000NEW', // 必填但ShipmentFeeBookDTO中缺少
partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成 partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成
warehouseId: '25072621030107400060', // 可选使用stockPointId转换 warehouseId: '25072621030107400060', // 可选使用stockPointId转换
shipper: { shipper: {
name: data.sender, // 必填 name: data.sender, // 必填
phone: data.startPhone, // 必填 phone: data.startPhone.phone, // 必填
company: '', // 必填但ShipmentFeeBookDTO中缺少 company: address.country, // 必填但ShipmentFeeBookDTO中缺少
countryCode: data.shipperCountryCode, // 必填 countryCode: data.shipperCountryCode, // 必填
city: '', // 必填但ShipmentFeeBookDTO中缺少 city: address.city || '', // 必填但ShipmentFeeBookDTO中缺少
state: '', // 必填但ShipmentFeeBookDTO中缺少 state: address.region || '', // 必填但ShipmentFeeBookDTO中缺少
address1: data.pickupAddress, // 必填 address1: address.address_line_1, // 必填
address2: '', // 必填但ShipmentFeeBookDTO中缺少 address2: address.address_line_1 || '', // 必填但ShipmentFeeBookDTO中缺少
postCode: data.startPostalCode, // 必填 postCode: data.startPostalCode, // 必填
countryName: '', // 必填但ShipmentFeeBookDTO中缺少 countryName: address.country || '', // 必填但ShipmentFeeBookDTO中缺少
cityName: '', // 必填但ShipmentFeeBookDTO中缺少 cityName: address.city || '', // 必填但ShipmentFeeBookDTO中缺少
stateName: '', // 必填但ShipmentFeeBookDTO中缺少 stateName: address.region || '', // 必填但ShipmentFeeBookDTO中缺少
companyName: '', // 必填但ShipmentFeeBookDTO中缺少 companyName: address.country || '', // 必填但ShipmentFeeBookDTO中缺少
}, },
reciver: { reciver: {
name: data.receiver, // 必填 name: data.receiver, // 必填
phone: data.receiverPhone, // 必填 phone: data.receiverPhone, // 必填
company: '', // 必填但ShipmentFeeBookDTO中缺少 company: address.country,// 必填但ShipmentFeeBookDTO中缺少
countryCode: data.country, // 必填使用country代替countryCode countryCode: data.country, // 必填使用country代替countryCode
city: data.city, // 必填 city: data.city, // 必填
state: data.province, // 必填使用province代替state state: data.province, // 必填使用province代替state
address1: data.deliveryAddress, // 必填 address1: data.deliveryAddress, // 必填
address2: '', // 必填但ShipmentFeeBookDTO中缺少 address2: data.deliveryAddress, // 必填但ShipmentFeeBookDTO中缺少
postCode: data.postalCode, // 必填 postCode: data.postalCode, // 必填
countryName: '', // 必填但ShipmentFeeBookDTO中缺少 countryName: address.country, // 必填但ShipmentFeeBookDTO中缺少
cityName: data.city, // 必填使用city代替cityName cityName: data.city || '', // 必填使用city代替cityName
stateName: data.province, // 必填使用province代替stateName stateName: data.province || '', // 必填使用province代替stateName
companyName: '', // 必填但ShipmentFeeBookDTO中缺少 companyName: address.country || '', // 必填但ShipmentFeeBookDTO中缺少
}, },
packages: [ packages: [
{ {
@ -866,9 +865,9 @@ export class LogisticsService {
length: data.length, // 必填 length: data.length, // 必填
width: data.width, // 必填 width: data.width, // 必填
height: data.height, // 必填 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, // 必填 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中缺少使用默认值 currency: 'CAD', // 必填但ShipmentFeeBookDTO中缺少使用默认值
description: 'Package', // 必填但ShipmentFeeBookDTO中缺少使用默认值 description: 'Package', // 必填但ShipmentFeeBookDTO中缺少使用默认值
@ -876,31 +875,6 @@ export class LogisticsService {
], ],
signService: 0, // 可选,默认不使用签名服务 signService: 0, // 可选,默认不使用签名服务
}; };
} return r as any;
/**
* 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
];
} }
} }

View File

@ -141,8 +141,8 @@ export class OrderService {
updated: 0, updated: 0,
errors: [] errors: []
}; };
console.log('开始进入循环同步订单', result.length, '个订单') console.log('开始进入循环同步订单', result.length, '个订单')
console.log('开始进入循环同步订单', result.length, '个订单') console.log('开始进入循环同步订单', result.length, '个订单')
// 遍历每个订单进行同步 // 遍历每个订单进行同步
for (const order of result) { for (const order of result) {
try { try {
@ -166,7 +166,7 @@ export class OrderService {
} else { } else {
syncResult.created++; syncResult.created++;
} }
// console.log('updated', syncResult.updated, 'created:', syncResult.created) // console.log('updated', syncResult.updated, 'created:', syncResult.created)
} catch (error) { } catch (error) {
// 记录错误但不中断整个同步过程 // 记录错误但不中断整个同步过程
syncResult.errors.push({ syncResult.errors.push({
@ -176,7 +176,7 @@ export class OrderService {
syncResult.processed++; syncResult.processed++;
} }
} }
console.log('同步完成', syncResult.updated, 'created:', syncResult.created) console.log('同步完成', syncResult.updated, 'created:', syncResult.created)
this.logger.debug('syncOrders result', syncResult) this.logger.debug('syncOrders result', syncResult)
return syncResult; return syncResult;
@ -337,26 +337,26 @@ export class OrderService {
// 自动更新订单状态(如果需要) // 自动更新订单状态(如果需要)
await this.autoUpdateOrderStatus(siteId, order); await this.autoUpdateOrderStatus(siteId, order);
if(existingOrder){ if (existingOrder) {
// 矫正数据库中的订单数据 // 矫正数据库中的订单数据
const updateData: any = { status: order.status }; const updateData: any = { status: order.status };
if (this.canUpdateErpStatus(existingOrder.orderStatus)) { if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
updateData.orderStatus = this.mapOrderStatus(order.status as any); updateData.orderStatus = this.mapOrderStatus(order.status as any);
} }
// 更新订单主数据 // 更新订单主数据
await this.orderModel.update({ externalOrderId: String(order.id), siteId: siteId }, updateData); await this.orderModel.update({ externalOrderId: String(order.id), siteId: siteId }, updateData);
// 更新 fulfillments 数据 // 更新 fulfillments 数据
await this.saveOrderFulfillments({ await this.saveOrderFulfillments({
siteId, siteId,
orderId: existingOrder.id, orderId: existingOrder.id,
externalOrderId:order.id, externalOrderId: order.id,
fulfillments: fulfillments, fulfillments: fulfillments,
}); });
} }
const externalOrderId = String(order.id); const externalOrderId = String(order.id);
// 这里的 saveOrder 已经包括了创建订单和更新订单 // 这里的 saveOrder 已经包括了创建订单和更新订单
let orderRecord: Order = await this.saveOrder(siteId, orderData); let orderRecord: Order = await this.saveOrder(siteId, orderData);
// 如果订单从未完成变为完成状态,则更新库存 // 如果订单从未完成变为完成状态,则更新库存
if ( if (
orderRecord && orderRecord &&
orderRecord.orderStatus !== ErpOrderStatus.COMPLETED && orderRecord.orderStatus !== ErpOrderStatus.COMPLETED &&
@ -377,7 +377,7 @@ export class OrderService {
await this.saveOrderRefunds({ await this.saveOrderRefunds({
siteId, siteId,
orderId, orderId,
externalOrderId , externalOrderId,
refunds, refunds,
}); });
// 保存费用信息 // 保存费用信息
@ -732,11 +732,11 @@ export class OrderService {
} }
if (!orderItem.sku) return; if (!orderItem.sku) return;
// 从数据库查询产品,关联查询组件 // 从数据库查询产品,关联查询组件
const productDetail = await this.productService.getComponentDetailFromSiteSku({ sku: orderItem.sku, name: orderItem.name }); const productDetail = await this.productService.getComponentDetailFromSiteSku({ sku: orderItem.sku, name: orderItem.name });
if (!productDetail || !productDetail.quantity) return; if (!productDetail || !productDetail.quantity) return;
const {product, quantity} = productDetail const { product, quantity } = productDetail
const componentDetails: { product: Product, quantity: number }[] = product.components?.length > 0 ? await Promise.all(product.components.map(async comp => { const componentDetails: { product: Product, quantity: number }[] = product.components?.length > 0 ? await Promise.all(product.components.map(async comp => {
return { return {
product: await this.productModel.findOne({ product: await this.productModel.findOne({
@ -770,7 +770,7 @@ export class OrderService {
}); });
return orderSale return orderSale
}).filter(v => v !== null) }).filter(v => v !== null)
console.log("orderSales",orderSales) console.log("orderSales", orderSales)
if (orderSales.length > 0) { if (orderSales.length > 0) {
await this.orderSaleModel.save(orderSales); await this.orderSaleModel.save(orderSales);
} }