Compare commits

...

13 Commits

Author SHA1 Message Date
tikkhun efbd318917 docs(order.entity): 更新total字段的ApiProperty描述 2026-01-23 17:36:02 +08:00
zhuotianyuan e8424afd91 refactor(订单服务): 替换console.log为logger并移除重复日志
移除重复的console.log调用,统一使用logger进行日志记录
清理调试日志并优化日志级别使用
2026-01-23 17:02:10 +08:00
zhuotianyuan 5f16801f98 refactor: 移除调试用的console.log语句 2026-01-23 16:33:14 +08:00
zhuotianyuan 22a13ce0b8 feat(logistics): 完善freightwaves运费试算接口实现
重构convertToFreightwavesRateTry方法,添加地址查询逻辑
移除freightwaves服务中无用的测试方法
修复订单同步日志格式和注释
2026-01-23 16:29:10 +08:00
zhuotianyuan 2f57dc0d8c fix(logistics): 修复shopyy订单发货逻辑并优化freightwaves集成
修复shopyy平台订单发货时fulfillment接口调用问题,调整请求数据结构
优化freightwaves服务集成,添加订单查询功能
移除冗余代码,清理注释
2026-01-22 19:36:02 +08:00
zhuotianyuan 926e531d4f refactor(dto): 移除重复的ShopyyGetAllOrdersParams类和冗余字段
删除api.dto.ts中重复定义的ShopyyGetAllOrdersParams类
移除shopyy.dto.ts中冗余的date_paid字段
2026-01-22 17:17:46 +08:00
zhuotianyuan 66a70f6209 fix(config): 更新本地数据库配置为远程服务器地址
将数据库连接配置从本地localhost改为远程服务器地址13.212.62.127,并调整端口为3306
同时启用数据库日志记录功能
2026-01-22 17:12:17 +08:00
zhuotianyuan 1eacee307d refactor(订单服务): 优化产品详情查询逻辑
使用 productService 的 getComponentDetailFromSiteSku 方法替代直接查询,简化代码并增加数量检查
2026-01-22 17:06:51 +08:00
zhuotianyuan bff03de8b0 feat(logistics): 添加freightwaves物流平台支持
新增freightwaves物流平台集成,包括运单创建、状态查询和费用试算功能
添加sync_tms.job定时任务用于同步freightwaves运单状态
扩展ShipmentBookDTO和ShipmentFeeBookDTO以支持多物流平台
重构物流服务以支持uniuni和freightwaves双平台
2026-01-22 16:58:19 +08:00
zhuotianyuan 86aa5f5790 fix: 修复测试方法调用和订单内容格式
修复测试文件中错误的方法调用,从testQueryOrder改为testCreateOrder
调整订单内容格式,移除SKU显示并简化格式
修正电话号码字段的类型断言问题
修复日期格式错误,从mm改为MM
更新API基础URL和端点路径
移除不必要的日志对象调用,改用console.log
2026-01-22 16:57:29 +08:00
zhuotianyuan 52fa7d651e feat: 添加产品图片URL字段并优化订单处理逻辑
添加产品实体中的图片URL字段
更新订单服务以支持更多查询参数和分页
修改数据库连接配置为生产环境
调整运费服务API基础URL
优化订单适配器中的字段映射逻辑
2026-01-22 16:57:29 +08:00
zhuotianyuan 0ea834218d fix(shopyy): 修复订单查询参数映射问题并添加时间范围支持
修正shopyy服务中获取所有订单的参数映射逻辑,添加支付时间范围支持
统一处理日期格式转换,确保参数正确传递
同时清理合并冲突标记和冗余代码
2026-01-22 16:55:53 +08:00
zhuotianyuan 86fd31ac12 feat(webhook): 添加对shoppy平台webhook的支持
- 在site.entity.ts中添加webhookUrl字段
- 在auth.middleware.ts中添加/shoppy路由到白名单
- 在webhook.controller.ts中实现shoppy平台webhook处理逻辑

fix(webhook): 更新webhook控制器中的密钥值

refactor(entity): 将可选字段明确标记为可选类型

feat(adapter): 公开映射方法以支持统一接口调用

将各适配器中的私有映射方法改为公开,并在接口中定义统一方法签名
修改webhook控制器以使用适配器映射方法处理订单数据

feat: 添加订单支付日期字段并支持国家筛选

- 在ShopyyOrder接口中添加date_paid字段
- 在OrderStatisticsParams中添加country数组字段用于国家筛选
- 修改统计服务以支持按国家筛选订单数据
- 更新数据库配置和同步设置
- 优化订单服务中的类型定义和查询条件

refactor(webhook): 移除未使用的shoppy webhook处理逻辑

fix(订单服务): 修复订单内容括号处理并添加同步日志

添加订单同步过程的调试日志
修复订单内容中括号内容的处理逻辑
修正控制器方法名拼写错误
2026-01-22 16:52:48 +08:00
11 changed files with 450 additions and 304 deletions

View File

@ -244,7 +244,7 @@ export class ShopyyAdapter implements ISiteAdapter {
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(), fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
company: billing.company || '', company: billing.company || '',
email: item.customer_email || item.email || '', email: item.customer_email || item.email || '',
phone: billing.phone || (item as any).telephone || '', phone: billing.phone || item.telephone || '',
address_1: billing.address1 || item.payment_address || '', address_1: billing.address1 || item.payment_address || '',
address_2: billing.address2 || '', address_2: billing.address2 || '',
city: billing.city || item.payment_city || '', city: billing.city || item.payment_city || '',
@ -275,7 +275,7 @@ export class ShopyyAdapter implements ISiteAdapter {
state: shipping.province || item.shipping_zone || '', state: shipping.province || item.shipping_zone || '',
postcode: shipping.zip || item.shipping_postcode || '', postcode: shipping.zip || item.shipping_postcode || '',
method_title: item.payment_method || '', method_title: item.payment_method || '',
phone: shipping.phone || (item as any).telephone || '', phone: shipping.phone || item.telephone || '',
country: country:
shipping.country_name || shipping.country_name ||
shipping.country_code || shipping.country_code ||
@ -393,6 +393,7 @@ export class ShopyyAdapter implements ISiteAdapter {
tracking_number: f.tracking_number || '', tracking_number: f.tracking_number || '',
shipping_provider: f.tracking_company || '', shipping_provider: f.tracking_company || '',
shipping_method: f.tracking_company || '', shipping_method: f.tracking_company || '',
date_created: typeof f.created_at === 'number' date_created: typeof f.created_at === 'number'
? new Date(f.created_at * 1000).toISOString() ? new Date(f.created_at * 1000).toISOString()
: f.created_at || '', : f.created_at || '',

View File

@ -19,15 +19,22 @@ 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()
sender: string; sender: string;
@ApiProperty() @ApiProperty()
startPhone: string; startPhone: string|any;
@ApiProperty() @ApiProperty()
startPostalCode: string; startPostalCode: string;
@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

@ -967,8 +967,6 @@ export interface ShopyyOrder {
}>; }>;
// 支付时间 // 支付时间
pay_at?: number | null; pay_at?: number | null;
// 支付时间(备用)
date_paid?: number | string;
// 系统支付ID // 系统支付ID
sys_payment_id?: number; sys_payment_id?: number;
@ -1211,6 +1209,7 @@ export interface ShopyyOrder {
// 时间戳信息 // 时间戳信息
// ======================================== // ========================================
// 订单创建时间 // 订单创建时间
date_paid?: number | string;
created_at?: number | string; created_at?: number | string;
// 订单添加时间 // 订单添加时间
date_added?: string; date_added?: string;

View File

@ -106,7 +106,7 @@ export class Order {
@Expose() @Expose()
cart_tax: number; cart_tax: number;
@ApiProperty() @ApiProperty({ type: "总金额"})
@Column('decimal', { precision: 10, scale: 2, default: 0 }) @Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose() @Expose()
total: number; total: number;

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()

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: '创建时间',

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'
}; };
// 初始化配置 // 初始化配置
@ -172,19 +172,13 @@ export class FreightwavesService {
private async sendRequest<T>(url: string, data: any): Promise<FreightwavesResponse<T>> { private async sendRequest<T>(url: string, data: any): Promise<FreightwavesResponse<T>> {
try { try {
// 设置请求头 - 使用太平洋时间 (America/Los_Angeles) // 设置请求头 - 使用太平洋时间 (America/Los_Angeles)
const date = dayjs().tz('America/Los_Angeles').format('YYYY-mm-dd HH:mm:ss'); const date = dayjs().tz('America/Los_Angeles').format('YYYY-MM-DD HH:mm:ss');
const headers = { const headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'requestDate': date, 'requestDate': date,
'signature': this.generateSignature(data, date), 'signature': this.generateSignature(data, date),
}; };
// 记录请求前的详细信息
this.log(`Sending request to: ${this.config.apiBaseUrl}${url}`, {
headers,
data
});
// 发送请求 - 临时禁用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}`,
@ -267,8 +261,8 @@ export class FreightwavesService {
partner: this.config.partner, partner: this.config.partner,
}; };
const response = await this.sendRequest<CreateOrderResponseData>('/shipService/order/createOrder?apipost_id=0422aa', requestData); const response = await this.sendRequest<CreateOrderResponseData>('/shipService/order/createOrder', requestData);
return response.data; return response;
} }
/** /**
@ -283,6 +277,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;
} }
@ -311,161 +308,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: 'https://tms.freightwaves.ca',
partner: '25072621035200000060'
});
// 准备测试数据
const testParams: Omit<CreateOrderRequest, 'partner'> = {
shipCompany: 'DHL',
partnerOrderNumber: `test-order-${Date.now()}`,
warehouseId: '25072621035200000060',
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 testQueryOrder() {
try {
// 设置必要的配置
this.setConfig({
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'https://tms.freightwaves.ca',
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

@ -27,10 +27,12 @@ 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';
import { SiteService } from './site.service'; import { SiteService } from './site.service';
import { ShopyyService } from './shopyy.service';
@Provide() @Provide()
export class LogisticsService { export class LogisticsService {
@ -73,9 +75,15 @@ export class LogisticsService {
@Inject() @Inject()
uniExpressService: UniExpressService; uniExpressService: UniExpressService;
@Inject()
freightwavesService: FreightwavesService;
@Inject() @Inject()
wpService: WPService; wpService: WPService;
@Inject()
shopyyService: ShopyyService;
@Inject() @Inject()
orderService: OrderService; orderService: OrderService;
@ -126,8 +134,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 +149,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);
@ -247,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);
@ -278,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('删除运单失败');
@ -294,7 +324,16 @@ 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') {
const fre_reqBody = await this.convertToFreightwavesRateTry(data);
resShipmentFee = await this.freightwavesService.rateTry(fre_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,10 @@ export class LogisticsService {
let resShipmentOrder; let resShipmentOrder;
try { try {
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId }); resShipmentOrder = await this.mepShipment(data, order);
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
}
}
// 添加运单 // 记录物流信息,并将订单状态转到完成,uniuni状态为SUCCESStms.freightwaves状态为00000200
resShipmentOrder = await this.uniExpressService.createShipment(reqBody); if (resShipmentOrder.status === 'SUCCESS' || resShipmentOrder.code === '00000200') {
// 记录物流信息,并将订单状态转到完成
if (resShipmentOrder.status === 'SUCCESS') {
order.orderStatus = ErpOrderStatus.COMPLETED; order.orderStatus = ErpOrderStatus.COMPLETED;
} else { } else {
throw new Error('运单生成失败'); throw new Error('运单生成失败');
@ -363,12 +372,27 @@ 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;
}
// 同步订单状态到woocommerce
if (order.source_type != "shopyy") {
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,36 +400,61 @@ 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
}); });
order.shipmentId = shipment.id; order.shipmentId = shipment.id;
shipmentId = shipment.id; shipmentId = shipment.id;
} }
// 同步订单状态到woocommerce
if (order.status !== OrderStatus.COMPLETED) { if (order.status !== OrderStatus.COMPLETED) {
await this.wpService.updateOrder(site, order.externalOrderId, { await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.COMPLETED, status: OrderStatus.COMPLETED,
}); });
order.status = OrderStatus.COMPLETED; 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; 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);
@ -642,4 +691,190 @@ 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 + '-' + 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
// 非跨境订单不需要declaration
// declaration: {
// "boxNo": "", //箱子编号
// "sku": "", //SKU
// "cnname": "", //中文名称
// "enname": "", //英文名称
// "declaredPrice": 1, //申报单价
// "declaredQty": 1, //申报数量
// "material": "", //材质
// "intendedUse": "", //用途
// "cweight": 1, //产品单重
// "hsCode": "", //海关编码
// "battery": "" //电池描述
// }
};
resShipmentOrder = await this.freightwavesService.createOrder(reqBody); // 创建订单
//tms只返回了物流订单号需要查询一次来获取完整的物流信息
const queryRes = await this.freightwavesService.queryOrder({ shipOrderId: resShipmentOrder.shipOrderId }); // 查询订单
resShipmentOrder.push(queryRes);
}
return resShipmentOrder;
} catch (error) {
console.log('物流订单处理失败:', error); // 使用console.log代替this.log
throw error;
}
}
/**
* ShipmentFeeBookDTO转换为freightwaves的RateTryRequest格式
* @param data ShipmentFeeBookDTO数据
* @returns RateTryRequest格式的数据
*/
async convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Promise<Omit<RateTryRequest, 'partner'>> {
const shipments = await this.shippingAddressModel.findOne({
where: {
id: data.address_id,
},
})
const address = shipments?.address;
// 转换为RateTryRequest格式
const r = {
shipCompany: 'UPSYYZ7000NEW', // 必填但ShipmentFeeBookDTO中缺少
partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成
warehouseId: '25072621030107400060', // 可选使用stockPointId转换
shipper: {
name: data.sender, // 必填
phone: data.startPhone.phone, // 必填
company: address.country, // 必填但ShipmentFeeBookDTO中缺少
countryCode: data.shipperCountryCode, // 必填
city: address.city || '', // 必填但ShipmentFeeBookDTO中缺少
state: address.region || '', // 必填但ShipmentFeeBookDTO中缺少
address1: address.address_line_1, // 必填
address2: address.address_line_1 || '', // 必填但ShipmentFeeBookDTO中缺少
postCode: data.startPostalCode, // 必填
countryName: address.country || '', // 必填但ShipmentFeeBookDTO中缺少
cityName: address.city || '', // 必填但ShipmentFeeBookDTO中缺少
stateName: address.region || '', // 必填但ShipmentFeeBookDTO中缺少
companyName: address.country || '', // 必填但ShipmentFeeBookDTO中缺少
},
reciver: {
name: data.receiver, // 必填
phone: data.receiverPhone, // 必填
company: address.country,// 必填但ShipmentFeeBookDTO中缺少
countryCode: data.country, // 必填使用country代替countryCode
city: data.city, // 必填
state: data.province, // 必填使用province代替state
address1: data.deliveryAddress, // 必填
address2: data.deliveryAddress, // 必填但ShipmentFeeBookDTO中缺少
postCode: data.postalCode, // 必填
countryName: address.country, // 必填但ShipmentFeeBookDTO中缺少
cityName: data.city || '', // 必填使用city代替cityName
stateName: data.province || '', // 必填使用province代替stateName
companyName: address.country || '', // 必填但ShipmentFeeBookDTO中缺少
},
packages: [
{
dimensions: {
length: data.length, // 必填
width: data.width, // 必填
height: data.height, // 必填
lengthUnit: (data.dimensionUom === 'IN' ? 'IN' : 'CM') as 'IN' | 'CM', // 必填,转换为有效的单位
weight: data.weight, // 必填
weightUnit: (data.weightUom === 'LBS' ? 'LB' : 'KG') as 'LB' | 'KG', // 必填,转换为有效的单位
},
currency: 'CAD', // 必填但ShipmentFeeBookDTO中缺少使用默认值
description: 'Package', // 必填但ShipmentFeeBookDTO中缺少使用默认值
},
],
signService: 0, // 可选,默认不使用签名服务
};
return r as any;
}
} }

View File

@ -141,7 +141,7 @@ export class OrderService {
updated: 0, updated: 0,
errors: [] errors: []
}; };
console.log('开始进入循环同步订单', result.length, '个订单') this.logger.info('开始进入循环同步订单', result.length, '个订单')
// 遍历每个订单进行同步 // 遍历每个订单进行同步
for (const order of result) { for (const order of result) {
try { try {
@ -150,7 +150,7 @@ export class OrderService {
where: { externalOrderId: String(order.id), siteId: siteId }, where: { externalOrderId: String(order.id), siteId: siteId },
}); });
if (!existingOrder) { if (!existingOrder) {
console.log("数据库中不存在", order.id, '订单状态:', order.status) this.logger.debug("数据库中不存在", order.id, '订单状态:', order.status)
} }
// 同步单个订单 // 同步单个订单
await this.syncSingleOrder(siteId, order); await this.syncSingleOrder(siteId, order);
@ -175,9 +175,7 @@ export class OrderService {
syncResult.processed++; syncResult.processed++;
} }
} }
console.log('同步完成', syncResult.updated, 'created:', syncResult.created) this.logger.info('同步完成', syncResult.updated, 'created:', syncResult.created)
this.logger.debug('syncOrders result', syncResult)
return syncResult; return syncResult;
} }
@ -215,7 +213,7 @@ export class OrderService {
where: { externalOrderId: String(order.id), siteId: siteId }, where: { externalOrderId: String(order.id), siteId: siteId },
}); });
if (!existingOrder) { if (!existingOrder) {
console.log("数据库不存在", siteId, "订单:", order.id, '订单状态:' + order.status) this.logger.debug("数据库不存在", siteId, "订单:", order.id, '订单状态:' + order.status)
} }
// 同步单个订单 // 同步单个订单
await this.syncSingleOrder(siteId, order, true); await this.syncSingleOrder(siteId, order, true);
@ -329,13 +327,30 @@ export class OrderService {
this.logger.debug('订单状态为 AUTO_DRAFT,跳过处理', siteId, order.id) this.logger.debug('订单状态为 AUTO_DRAFT,跳过处理', siteId, order.id)
return; return;
} }
// 这里其实不用过滤不可编辑的行为,而是应在 save 中做判断 // 检查数据库中是否已存在该订单
// if(!order.is_editable && !forceUpdate){ const existingOrder = await this.orderModel.findOne({
// this.logger.debug('订单不可编辑,跳过处理', siteId, order.id) where: { externalOrderId: String(order.id), siteId: siteId },
// return; });
// } // 自动更新订单状态(如果需要)
// 自动转换远程订单的状态(如果需要)
await this.autoUpdateOrderStatus(siteId, order); await this.autoUpdateOrderStatus(siteId, order);
if (existingOrder) {
// 矫正数据库中的订单数据
const updateData: any = { status: order.status };
if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
updateData.orderStatus = this.mapOrderStatus(order.status as any);
}
// 更新订单主数据
await this.orderModel.update({ externalOrderId: String(order.id), siteId: siteId }, updateData);
// 更新 fulfillments 数据
await this.saveOrderFulfillments({
siteId,
orderId: existingOrder.id,
externalOrderId: order.id,
fulfillments: fulfillments,
});
}
const externalOrderId = String(order.id);
// 这里的 saveOrder 已经包括了创建订单和更新订单 // 这里的 saveOrder 已经包括了创建订单和更新订单
let orderRecord: Order = await this.saveOrder(siteId, orderData); let orderRecord: Order = await this.saveOrder(siteId, orderData);
// 如果订单从未完成变为完成状态,则更新库存 // 如果订单从未完成变为完成状态,则更新库存
@ -347,7 +362,6 @@ export class OrderService {
await this.updateStock(orderRecord); await this.updateStock(orderRecord);
// 不再直接返回,继续执行后续的更新操作 // 不再直接返回,继续执行后续的更新操作
} }
const externalOrderId = String(order.id);
const orderId = orderRecord.id; const orderId = orderRecord.id;
// 保存订单项 // 保存订单项
await this.saveOrderItems({ await this.saveOrderItems({
@ -360,7 +374,7 @@ export class OrderService {
await this.saveOrderRefunds({ await this.saveOrderRefunds({
siteId, siteId,
orderId, orderId,
externalOrderId , externalOrderId,
refunds, refunds,
}); });
// 保存费用信息 // 保存费用信息
@ -714,11 +728,12 @@ export class OrderService {
await this.orderSaleModel.delete(currentOrderSale.map(v => v.id)); await this.orderSaleModel.delete(currentOrderSale.map(v => v.id));
} }
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({
@ -752,7 +767,6 @@ export class OrderService {
}); });
return orderSale return orderSale
}).filter(v => v !== null) }).filter(v => v !== null)
console.log("orderSales",orderSales)
if (orderSales.length > 0) { if (orderSales.length > 0) {
await this.orderSaleModel.save(orderSales); await this.orderSaleModel.save(orderSales);
} }
@ -1544,7 +1558,6 @@ export class OrderService {
GROUP BY os.productId GROUP BY os.productId
`; `;
console.log('------3.5-----', pcSql, pcParams, exceptPackage);
const pcResults = await this.orderSaleModel.query(pcSql, pcParams); const pcResults = await this.orderSaleModel.query(pcSql, pcParams);
const pcMap = new Map<number, any>(); const pcMap = new Map<number, any>();
@ -2512,7 +2525,7 @@ export class OrderService {
const boxCount = items.reduce((total, item) => total + item.quantity, 0); const boxCount = items.reduce((total, item) => total + item.quantity, 0);
// 构建订单内容 // 构建订单内容
const orderContent = items.map(item => `${item.name} (${item.sku || ''}) x ${item.quantity}`).join('; '); const orderContent = items.map(item => `${item.name} x ${item.quantity}`).join('; ');
// 构建姓名地址 // 构建姓名地址
const shipping = order.shipping; const shipping = order.shipping;
@ -2544,7 +2557,7 @@ export class OrderService {
'姓名地址': nameAddress, '姓名地址': nameAddress,
'邮箱': order.customer_email || '', '邮箱': order.customer_email || '',
'号码': phone, '号码': phone,
'订单内容': this.removeLastParenthesesContent(orderContent), '订单内容': orderContent,
'盒数': boxCount, '盒数': boxCount,
'换盒数': exchangeBoxCount, '换盒数': exchangeBoxCount,
'换货内容': exchangeContent, '换货内容': exchangeContent,
@ -2623,13 +2636,9 @@ export class OrderService {
if (!fs.existsSync(downloadsDir)) { if (!fs.existsSync(downloadsDir)) {
fs.mkdirSync(downloadsDir, { recursive: true }); fs.mkdirSync(downloadsDir, { recursive: true });
} }
const filePath = path.join(downloadsDir, fileName); const filePath = path.join(downloadsDir, fileName);
// 写入文件 // 写入文件
fs.writeFileSync(filePath, csvContent, 'utf8'); fs.writeFileSync(filePath, csvContent, 'utf8');
console.log(`数据已成功导出至 ${filePath}`);
return filePath; return filePath;
} }
@ -2640,7 +2649,6 @@ export class OrderService {
return csvContent; return csvContent;
} catch (error) { } catch (error) {
console.error('导出CSV时出错:', error);
throw new Error(`导出CSV文件失败: ${error.message}`); throw new Error(`导出CSV文件失败: ${error.message}`);
} }
} }

View File

@ -10,14 +10,14 @@ import { Site } from '../entity/site.entity';
import { UnifiedReviewDTO } from '../dto/site-api.dto'; import { UnifiedReviewDTO } from '../dto/site-api.dto';
import { ShopyyGetOneOrderResult, ShopyyReview } from '../dto/shopyy.dto'; import { ShopyyGetOneOrderResult, ShopyyReview } from '../dto/shopyy.dto';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
import { UnifiedSearchParamsDTO,ShopyyGetAllOrdersParams } from '../dto/api.dto'; import { UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto';
/** /**
* ShopYY平台服务实现 * ShopYY平台服务实现
*/ */
@Provide() @Provide()
export class ShopyyService { export class ShopyyService {
@Inject() @Inject()
logger:ILogger; logger: ILogger;
/** /**
* ShopYY评论列表 * ShopYY评论列表
* @param site * @param site
@ -184,9 +184,9 @@ export class ShopyyService {
*/ */
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) { public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
const response = await this.request(site, endpoint, 'GET', null, params); const response = await this.request(site, endpoint, 'GET', null, params);
return this.mapPageResponse<T>(response,params); return this.mapPageResponse<T>(response, params);
} }
mapPageResponse<T>(response:any,query: Record<string, any>){ mapPageResponse<T>(response: any, query: Record<string, any>) {
if (response?.code !== 0) { if (response?.code !== 0) {
throw new Error(response?.msg) throw new Error(response?.msg)
} }
@ -272,7 +272,7 @@ export class ShopyyService {
const response = await this.request(site, `products/${productId}/variations/${variationId}`, 'GET'); const response = await this.request(site, `products/${productId}/variations/${variationId}`, 'GET');
return response.data; return response.data;
} }
mapOrderSearchParams(params: UnifiedSearchParamsDTO){ mapOrderSearchParams(params: UnifiedSearchParamsDTO) {
const { after, before, ...restParams } = params; const { after, before, ...restParams } = params;
return { return {
...restParams, ...restParams,
@ -311,7 +311,7 @@ export class ShopyyService {
async getAllOrders(site: any | number, params: ShopyyGetAllOrdersParams = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> { async getAllOrders(site: any | number, params: ShopyyGetAllOrdersParams = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
const firstPage = await this.getOrders(site, 1, 100, params); const firstPage = await this.getOrders(site, 1, 100, params);
const { items: firstPageItems, totalPages} = firstPage; const { items: firstPageItems, totalPages } = firstPage;
// 如果只有一页数据,直接返回 // 如果只有一页数据,直接返回
if (totalPages <= 1) { if (totalPages <= 1) {
@ -475,13 +475,16 @@ export class ShopyyService {
async createFulfillment(site: Site, orderId: string, data: any): Promise<any> { async createFulfillment(site: Site, orderId: string, data: any): Promise<any> {
// ShopYY API: POST /orders/{id}/shipments // ShopYY API: POST /orders/{id}/shipments
const fulfillmentData = { const fulfillmentData = {
data: [{
order_number: orderId,
tracking_company: data.tracking_company,
tracking_number: data.tracking_number, tracking_number: data.tracking_number,
carrier_code: data.carrier_code, carrier_code: data.carrier_code,
carrier_name: data.carrier_name, note: "note",
shipping_method: data.shipping_method mode: ""
}]
}; };
const response = await this.request(site, `orders/fulfillments`, 'POST', fulfillmentData);
const response = await this.request(site, `orders/${orderId}/shipments`, 'POST', fulfillmentData);
return response.data; return response.data;
} }
@ -494,7 +497,7 @@ export class ShopyyService {
*/ */
async deleteFulfillment(site: any, orderId: string, fulfillmentId: string): Promise<boolean> { async deleteFulfillment(site: any, orderId: string, fulfillmentId: string): Promise<boolean> {
try { try {
// ShopYY API: DELETE /orders/{order_id}/shipments/{fulfillment_id} // ShopYY API: DELETE /orders/fulfillments/{fulfillment_id}
await this.request(site, `orders/${orderId}/fulfillments/${fulfillmentId}`, 'DELETE'); await this.request(site, `orders/${orderId}/fulfillments/${fulfillmentId}`, 'DELETE');
return true; return true;
} catch (error) { } catch (error) {
@ -647,7 +650,7 @@ export class ShopyyService {
const result = response.data; const result = response.data;
// 转换 ShopYY 批量操作结果为统一格式 // 转换 ShopYY 批量操作结果为统一格式
const errors: Array<{identifier: string, error: string}> = []; const errors: Array<{ identifier: string, error: string }> = [];
// 假设 ShopYY 返回格式与 WooCommerce 类似: { create: [...], update: [...], delete: [...] } // 假设 ShopYY 返回格式与 WooCommerce 类似: { create: [...], update: [...], delete: [...] }
// 错误信息可能在每个项目的 error 字段中 // 错误信息可能在每个项目的 error 字段中