zksu
/
API
forked from yoone/API
1
0
Fork 0

feat(logistics): 添加freightwaves物流平台支持

新增freightwaves物流平台集成,包括运单创建、状态查询和费用试算功能
添加sync_tms.job定时任务用于同步freightwaves运单状态
扩展ShipmentBookDTO和ShipmentFeeBookDTO以支持多物流平台
重构物流服务以支持uniuni和freightwaves双平台
This commit is contained in:
zhuotianyuan 2026-01-22 16:24:57 +08:00
parent 86aa5f5790
commit bff03de8b0
8 changed files with 486 additions and 76 deletions

View File

@ -19,9 +19,16 @@ export class ShipmentBookDTO {
@ApiProperty({ type: 'number', isArray: true }) @ApiProperty({ type: 'number', isArray: true })
@Rule(RuleType.array<number>().default([])) @Rule(RuleType.array<number>().default([]))
orderIds?: number[]; orderIds?: number[];
@ApiProperty()
@Rule(RuleType.string())
shipmentPlatform: string;
} }
export class ShipmentFeeBookDTO { export class ShipmentFeeBookDTO {
@ApiProperty()
shipmentPlatform: string;
@ApiProperty() @ApiProperty()
stockPointId: number; stockPointId: number;
@ApiProperty() @ApiProperty()
@ -63,6 +70,8 @@ export class ShipmentFeeBookDTO {
weight: number; weight: number;
@ApiProperty() @ApiProperty()
weightUom: string; weightUom: string;
@ApiProperty()
address_id: number;
} }
export class PaymentMethodDTO { export class PaymentMethodDTO {

View File

@ -54,9 +54,9 @@ export class Shipment {
tracking_provider?: string; tracking_provider?: string;
@ApiProperty() @ApiProperty()
@Column() @Column({ nullable: true })
@Expose() @Expose()
unique_id: string; unique_id?: string;
@Column({ nullable: true }) @Column({ nullable: true })
@Expose() @Expose()

40
src/job/sync_tms.job.ts Normal file
View File

@ -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<Shipment>
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}`);
}
}

View File

@ -67,7 +67,7 @@ interface Declaration {
} }
// 费用试算请求接口 // 费用试算请求接口
interface RateTryRequest { export interface RateTryRequest {
shipCompany: string; shipCompany: string;
partnerOrderNumber: string; partnerOrderNumber: string;
warehouseId?: string; warehouseId?: string;
@ -118,8 +118,8 @@ interface RateTryResponseData {
// 创建订单响应数据接口 // 创建订单响应数据接口
interface CreateOrderResponseData { interface CreateOrderResponseData {
partnerOrderNumber: string; msg: string;
shipOrderId: string; data: any;
} }
// 查询订单响应数据接口 // 查询订单响应数据接口
@ -140,10 +140,10 @@ interface QueryOrderResponseData {
} }
// 修改订单响应数据接口 // 修改订单响应数据接口
interface ModifyOrderResponseData extends CreateOrderResponseData {} interface ModifyOrderResponseData extends CreateOrderResponseData { }
// 订单退款响应数据接口 // 订单退款响应数据接口
interface RefundOrderResponseData {} interface RefundOrderResponseData { }
@Provide() @Provide()
export class FreightwavesService { export class FreightwavesService {
@ -152,8 +152,8 @@ export class FreightwavesService {
// 默认配置 // 默认配置
private config: FreightwavesConfig = { private config: FreightwavesConfig = {
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'https://tms.freightwaves.ca', apiBaseUrl: 'http://tms.freightwaves.ca:8901/',
partner: '25072621035200000060', partner: '25072621035200000060'
}; };
// 初始化配置 // 初始化配置
@ -180,19 +180,19 @@ export class FreightwavesService {
}; };
// 记录请求前的详细信息 // 记录请求前的详细信息
console.log(`Sending request to: ${this.config.apiBaseUrl}${url}`,JSON.stringify({ console.log(`Sending request to: ${this.config.apiBaseUrl}${url}`, JSON.stringify({
headers, headers,
data data
})) }))
console.log('Request data:', `${this.config.apiBaseUrl}${url}`, data,headers); console.log('Request data:', `${this.config.apiBaseUrl}${url}`, data, headers);
// 发送请求 - 临时禁用SSL证书验证以解决UNABLE_TO_VERIFY_LEAF_SIGNATURE错误 // 发送请求 - 临时禁用SSL证书验证以解决UNABLE_TO_VERIFY_LEAF_SIGNATURE错误
const response = await axios.post<FreightwavesResponse<T>>( const response = await axios.post<FreightwavesResponse<T>>(
`${this.config.apiBaseUrl}${url}`, `${this.config.apiBaseUrl}${url}`,
data, data,
{ {
headers, headers,
httpsAgent: new (require('https').Agent)({ httpsAgent: new (require('https').Agent)({
rejectUnauthorized: false rejectUnauthorized: false
}) })
} }
); );
@ -267,8 +267,8 @@ export class FreightwavesService {
partner: this.config.partner, partner: this.config.partner,
}; };
const response = await this.sendRequest<CreateOrderResponseData>('shipService/order/rateTry', requestData); const response = await this.sendRequest<CreateOrderResponseData>('shipService/order/createOrder', requestData);
return response.data; return response;
} }
/** /**
@ -283,6 +283,9 @@ export class FreightwavesService {
}; };
const response = await this.sendRequest<QueryOrderResponseData>('/shipService/order/queryOrder', requestData); const response = await this.sendRequest<QueryOrderResponseData>('/shipService/order/queryOrder', requestData);
if (response.code !== '00000200') {
throw new Error(response.msg);
}
return response.data; return response.data;
} }
@ -331,9 +334,9 @@ export class FreightwavesService {
// 准备测试数据 // 准备测试数据
const testParams: Omit<CreateOrderRequest, 'partner'> = { const testParams: Omit<CreateOrderRequest, 'partner'> = {
shipCompany: '', shipCompany: 'UPSYYZ7000NEW',
partnerOrderNumber: `test-order-${Date.now()}`, partnerOrderNumber: `test-order-${Date.now()}`,
warehouseId: '25072621035200000060', warehouseId: '25072621030107400060',
shipper: { shipper: {
name: 'John Doe', name: 'John Doe',
phone: '123-456-7890', phone: '123-456-7890',
@ -397,12 +400,12 @@ export class FreightwavesService {
// 调用创建订单方法 // 调用创建订单方法
this.log('开始测试创建订单...'); this.log('开始测试创建订单...');
this.log('测试参数:', testParams); this.log('测试参数:', testParams);
// 注意:在实际环境中取消注释以下行来执行真实请求 // 注意:在实际环境中取消注释以下行来执行真实请求
const result = await this.createOrder(testParams); const result = await this.createOrder(testParams);
this.log('创建订单成功:', result); this.log('创建订单成功:', result);
// 返回模拟结果 // 返回模拟结果
return { return {
partnerOrderNumber: testParams.partnerOrderNumber, partnerOrderNumber: testParams.partnerOrderNumber,
@ -414,6 +417,95 @@ export class FreightwavesService {
} }
} }
/**
*
* @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 * @returns
@ -423,7 +515,7 @@ export class FreightwavesService {
// 设置必要的配置 // 设置必要的配置
this.setConfig({ this.setConfig({
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt', appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'http://freightwaves.ca:8901/shipService/order/rateTry', apiBaseUrl: 'http://freightwaves.ca:8901',
partner: '25072621035200000060' partner: '25072621035200000060'
}); });
@ -437,13 +529,13 @@ export class FreightwavesService {
// 调用查询订单方法 // 调用查询订单方法
this.log('开始测试查询订单...'); this.log('开始测试查询订单...');
this.log('测试参数:', testParams); this.log('测试参数:', testParams);
// 注意:在实际环境中取消注释以下行来执行真实请求 // 注意:在实际环境中取消注释以下行来执行真实请求
const result = await this.queryOrder(testParams); const result = await this.queryOrder(testParams);
this.log('查询订单成功:', result); this.log('查询订单成功:', result);
this.log('测试完成:查询订单方法调用成功(模拟)'); this.log('测试完成:查询订单方法调用成功(模拟)');
// 返回模拟结果 // 返回模拟结果
return { return {
thirdOrderId: 'thirdOrder-123456789', thirdOrderId: 'thirdOrder-123456789',

View File

@ -27,6 +27,7 @@ import { CanadaPostService } from './canadaPost.service';
import { OrderItem } from '../entity/order_item.entity'; import { OrderItem } from '../entity/order_item.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { UniExpressService } from './uni_express.service'; import { UniExpressService } from './uni_express.service';
import { FreightwavesService, RateTryRequest } from './freightwaves.service';
import { StockPoint } from '../entity/stock_point.entity'; import { StockPoint } from '../entity/stock_point.entity';
import { OrderService } from './order.service'; import { OrderService } from './order.service';
import { convertKeysFromCamelToSnake } from '../utils/object-transform.util'; import { convertKeysFromCamelToSnake } from '../utils/object-transform.util';
@ -73,6 +74,9 @@ export class LogisticsService {
@Inject() @Inject()
uniExpressService: UniExpressService; uniExpressService: UniExpressService;
@Inject()
freightwavesService: FreightwavesService;
@Inject() @Inject()
wpService: WPService; wpService: WPService;
@ -126,8 +130,8 @@ export class LogisticsService {
const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number); const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number);
console.log('updateShipmentState data:', data); console.log('updateShipmentState data:', data);
// huo // huo
if(data.status === 'FAIL'){ if (data.status === 'FAIL') {
throw new Error('获取运单状态失败,原因为'+ data.ret_msg) throw new Error('获取运单状态失败,原因为' + data.ret_msg)
} }
shipment.state = data.data[0].state; shipment.state = data.data[0].state;
if (shipment.state in [203, 215, 216, 230]) { // todo,写常数 if (shipment.state in [203, 215, 216, 230]) { // todo,写常数
@ -141,6 +145,30 @@ 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) { async updateShipmentStateById(id: number) {
const shipment: Shipment = await this.shipmentModel.findOneBy({ id: id }); const shipment: Shipment = await this.shipmentModel.findOneBy({ id: id });
return this.updateShipmentState(shipment); return this.updateShipmentState(shipment);
@ -294,7 +322,18 @@ export class LogisticsService {
currency: 'CAD', currency: 'CAD',
// item_description: data.sales, // todo: 货品信息 // 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') {
// resShipmentFee = await this.freightwavesService.rateTry(reqBody);
} else {
throw new Error('不支持的运单平台');
}
if (resShipmentFee.status !== 'SUCCESS') { if (resShipmentFee.status !== 'SUCCESS') {
throw new Error(resShipmentFee.ret_msg); throw new Error(resShipmentFee.ret_msg);
} }
@ -319,40 +358,49 @@ export class LogisticsService {
let resShipmentOrder; let resShipmentOrder;
try { try {
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId }); //const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId });
const reqBody = { // const reqBody = {
sender: data.details.origin.contact_name, // sender: data.details.origin.contact_name,
start_phone: data.details.origin.phone_number, // start_phone: data.details.origin.phone_number,
start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''), // start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''),
pickup_address: data.details.origin.address.address_line_1, // pickup_address: data.details.origin.address.address_line_1,
pickup_warehouse: stock_point.upStreamStockPointId, // pickup_warehouse: stock_point.upStreamStockPointId,
shipper_country_code: data.details.origin.address.country, // shipper_country_code: data.details.origin.address.country,
receiver: data.details.destination.contact_name, // receiver: data.details.destination.contact_name,
city: data.details.destination.address.city, // city: data.details.destination.address.city,
province: data.details.destination.address.region, // province: data.details.destination.address.region,
country: data.details.destination.address.country, // country: data.details.destination.address.country,
postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''), // postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''),
delivery_address: data.details.destination.address.address_line_1, // delivery_address: data.details.destination.address.address_line_1,
receiver_phone: data.details.destination.phone_number.number, // receiver_phone: data.details.destination.phone_number.number,
receiver_email: data.details.destination.email_addresses, // receiver_email: data.details.destination.email_addresses,
// item_description: data.sales, // todo: 货品信息 // // item_description: data.sales, // todo: 货品信息
length: data.details.packaging_properties.packages[0].measurements.cuboid.l, // length: data.details.packaging_properties.packages[0].measurements.cuboid.l,
width: data.details.packaging_properties.packages[0].measurements.cuboid.w, // width: data.details.packaging_properties.packages[0].measurements.cuboid.w,
height: data.details.packaging_properties.packages[0].measurements.cuboid.h, // height: data.details.packaging_properties.packages[0].measurements.cuboid.h,
dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit, // dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit,
weight: data.details.packaging_properties.packages[0].measurements.weight.value, // weight: data.details.packaging_properties.packages[0].measurements.weight.value,
weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit, // weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit,
currency: 'CAD', // currency: 'CAD',
custom_field: { // custom_field: {
'order_id': order.externalOrderId // 'order_id': order.externalOrderId
} // }
} // }
// 添加运单 resShipmentOrder = await this.mepShipment(data, order);
resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
// if (data.shipmentPlatform === 'uniuni') {
// // 添加运单
// resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
// }
// if (data.shipmentPlatform === 'freightwaves') {
// // 添加运单
// resShipmentOrder = await this.freightcomService.createShipment(reqBody);
// }
// 记录物流信息,并将订单状态转到完成 // 记录物流信息,并将订单状态转到完成
if (resShipmentOrder.status === 'SUCCESS') { if (resShipmentOrder.status === 'SUCCESS' || resShipmentOrder.code === '00000200') {
order.orderStatus = ErpOrderStatus.COMPLETED; order.orderStatus = ErpOrderStatus.COMPLETED;
} else { } else {
throw new Error('运单生成失败'); throw new Error('运单生成失败');
@ -363,12 +411,24 @@ export class LogisticsService {
await dataSource.transaction(async manager => { await dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order); const orderRepo = manager.getRepository(Order);
const shipmentRepo = manager.getRepository(Shipment); const shipmentRepo = manager.getRepository(Shipment);
const tracking_provider = 'UniUni'; // todo: id未确定,后写进常数 const tracking_provider = data.shipmentPlatform; // todo: id未确定,后写进常数
// 同步物流信息到woocommerce // 同步物流信息到woocommerce
const site = await this.siteService.get(Number(order.siteId), true); const site = await this.siteService.get(Number(order.siteId), true);
let co: any;
let unique_id: any;
let state: any;
if (data.shipmentPlatform === 'uniuni') {
co = resShipmentOrder.data.tno;
unique_id = resShipmentOrder.data.uni_order_sn;
state = resShipmentOrder.data.uni_status_code;
} else {
co = resShipmentOrder.data?.shipOrderId;
unique_id = resShipmentOrder.data?.shipOrderId;
state = ErpOrderStatus.COMPLETED;
}
const res = await this.wpService.createFulfillment(site, order.externalOrderId, { const res = await this.wpService.createFulfillment(site, order.externalOrderId, {
tracking_number: resShipmentOrder.data.tno, tracking_number: co,
tracking_provider: tracking_provider, tracking_provider: tracking_provider,
}); });
@ -376,10 +436,10 @@ export class LogisticsService {
const shipment = await shipmentRepo.save({ const shipment = await shipmentRepo.save({
tracking_provider: tracking_provider, tracking_provider: tracking_provider,
tracking_id: res.data.tracking_id, tracking_id: res.data.tracking_id,
unique_id: resShipmentOrder.data.uni_order_sn, unique_id: unique_id,
stockPointId: String(data.stockPointId), // todo stockPointId: String(data.stockPointId), // todo
state: resShipmentOrder.data.uni_status_code, state: state,
return_tracking_number: resShipmentOrder.data.tno, return_tracking_number: co,
fee: data.details.shipmentFee, fee: data.details.shipmentFee,
order: order order: order
}); });
@ -388,12 +448,15 @@ export class LogisticsService {
} }
// 同步订单状态到woocommerce // 同步订单状态到woocommerce
if (order.status !== OrderStatus.COMPLETED) { if (order.source_type != "shopyy") {
await this.wpService.updateOrder(site, order.externalOrderId, { if (order.status !== OrderStatus.COMPLETED) {
status: OrderStatus.COMPLETED, await this.wpService.updateOrder(site, order.externalOrderId, {
}); 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);
@ -642,4 +705,208 @@ export class LogisticsService {
return { items, total, current, pageSize }; return { items, total, current, pageSize };
} }
async mepShipment(data: ShipmentBookDTO, order: Order) {
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': order.externalOrderId // todo: 需要获取订单的externalOrderId
}
};
// 添加运单
resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
}
if (data.shipmentPlatform === 'freightwaves') {
// 根据TMS系统对接说明文档格式化参数
const reqBody: any = {
shipCompany: 'UPSYYZ7000NEW',
partnerOrderNumber: order.siteId + '-1-' + order.externalOrderId,
warehouseId: '25072621030107400060',
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: '', // 详细地址2Address类型中没有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: '', // 详细地址2Address类型中没有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: 'site:' + order.siteId + ' orderId:' + order.externalOrderId // 包裹描述(确保是字符串类型)
}
],
signService: 0
// signService: 0, // 签名服务 0不使用, 1使用
// 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); // 创建订单
}
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, 'partner'> {
// 转换为RateTryRequest格式
return {
shipCompany: 'UPSYYZ7000NEW', // 必填但ShipmentFeeBookDTO中缺少
partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成
warehouseId: '25072621030107400060', // 可选使用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
];
}
} }

View File

@ -732,10 +732,12 @@ export class OrderService {
} }
if (!orderItem.sku) return; if (!orderItem.sku) return;
// 从数据库查询产品,关联查询组件 // 从数据库查询产品,关联查询组件
const productDetail = await this.productService.getComponentDetailFromSiteSku({ sku: orderItem.sku, name: orderItem.name }); const product = await this.productModel.findOne({
where: { siteSkus: Like(`%${orderItem.sku}%`) },
if (!productDetail || !productDetail.quantity) return; relations: ['components','attributes','attributes.dict'],
const {product, quantity} = productDetail });
if (!product) return;
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({

View File

@ -481,7 +481,7 @@ export class ShopyyService {
shipping_method: data.shipping_method shipping_method: data.shipping_method
}; };
const response = await this.request(site, `orders/${orderId}/shipments`, 'POST', fulfillmentData); const response = await this.request(site, `orders/${orderId}/fulfillments`, 'POST', fulfillmentData);
return response.data; return response.data;
} }

View File

@ -9,7 +9,7 @@ async function testFreightwavesService() {
// Call the test method // Call the test method
console.log('Starting test for createOrder method...'); console.log('Starting test for createOrder method...');
const result = await service.testCreateOrder(); const result = await service.testQueryOrder();
console.log('Test completed successfully!'); console.log('Test completed successfully!');
console.log('Result:', result); console.log('Result:', result);