feat(订单): 添加订单配送方式、费用项和优惠券项支持
扩展订单DTO和适配器以支持配送方式、费用项和优惠券项数据 实现Shopyy平台getAllOrders方法并添加分页并发处理 优化订单状态自动更新逻辑,支持Shopyy平台状态映射
This commit is contained in:
parent
823967a268
commit
361b05117c
|
|
@ -222,6 +222,9 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
billing_full_address: formatAddress(billingObj),
|
billing_full_address: formatAddress(billingObj),
|
||||||
shipping_full_address: formatAddress(shippingObj),
|
shipping_full_address: formatAddress(shippingObj),
|
||||||
payment_method: item.payment_method,
|
payment_method: item.payment_method,
|
||||||
|
shipping_lines: item.shipping_lines,
|
||||||
|
fee_lines: item.fee_lines,
|
||||||
|
coupon_lines: item.coupon_lines,
|
||||||
refunds: [],
|
refunds: [],
|
||||||
date_created:
|
date_created:
|
||||||
typeof item.created_at === 'number'
|
typeof item.created_at === 'number'
|
||||||
|
|
@ -238,6 +241,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
||||||
// 处理多地址结构
|
// 处理多地址结构
|
||||||
const addresses = item.addresses || [];
|
const addresses = item.addresses || [];
|
||||||
|
|
@ -391,12 +396,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||||
// Shopyy getAllOrders 暂未实现
|
const data = await this.shopyyService.getAllOrders(this.site.id,params);
|
||||||
throw new Error('Shopyy getAllOrders 暂未实现');
|
return data.map(this.mapOrder.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||||||
const data = await this.shopyyService.getOrder(String(this.site.id), String(id));
|
const data = await this.shopyyService.getOrder(this.site.id, String(id));
|
||||||
return this.mapOrder(data);
|
return this.mapOrder(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,9 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
payment_method: item.payment_method_title,
|
payment_method: item.payment_method_title,
|
||||||
date_created: item.date_created,
|
date_created: item.date_created,
|
||||||
date_modified: item.date_modified,
|
date_modified: item.date_modified,
|
||||||
|
shipping_lines: item.shipping_lines,
|
||||||
|
fee_lines: item.fee_lines,
|
||||||
|
coupon_lines: item.coupon_lines,
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,61 @@ export interface ShopyyOrder {
|
||||||
updated_at?: number | string;
|
updated_at?: number | string;
|
||||||
date_updated?: string;
|
date_updated?: string;
|
||||||
last_modified?: string;
|
last_modified?: string;
|
||||||
|
|
||||||
|
// 配送方式
|
||||||
|
shipping_lines?: Array<UnifiedShippingLineDTO>;
|
||||||
|
// 费用项
|
||||||
|
fee_lines?: Array<UnifiedFeeLineDTO>;
|
||||||
|
// 优惠券项
|
||||||
|
coupon_lines?: Array<UnifiedCouponLineDTO>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class UnifiedShippingLineDTO {
|
||||||
|
// 配送方式DTO用于承载统一配送方式数据
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
method_title?: string;
|
||||||
|
|
||||||
|
method_id?: string;
|
||||||
|
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedFeeLineDTO {
|
||||||
|
// 费用项DTO用于承载统一费用项数据
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
tax_class?: string;
|
||||||
|
|
||||||
|
tax_status?: string;
|
||||||
|
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
meta_data?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedCouponLineDTO {
|
||||||
|
// 优惠券项DTO用于承载统一优惠券项数据
|
||||||
|
id?: string | number;
|
||||||
|
code?: string;
|
||||||
|
discount?: string;
|
||||||
|
discount_tax?: string;
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 客户类型
|
// 客户类型
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,86 @@ export class UnifiedOrderDTO {
|
||||||
|
|
||||||
@ApiProperty({ description: '原始数据', type: 'object', required: false })
|
@ApiProperty({ description: '原始数据', type: 'object', required: false })
|
||||||
raw?: Record<string, any>;
|
raw?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式', type: () => [UnifiedShippingLineDTO], required: false })
|
||||||
|
shipping_lines?: UnifiedShippingLineDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '费用项', type: () => [UnifiedFeeLineDTO], required: false })
|
||||||
|
fee_lines?: UnifiedFeeLineDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项', type: () => [UnifiedCouponLineDTO], required: false })
|
||||||
|
coupon_lines?: UnifiedCouponLineDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedShippingLineDTO {
|
||||||
|
// 配送方式DTO用于承载统一配送方式数据
|
||||||
|
@ApiProperty({ description: '配送方式ID' })
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式名称' })
|
||||||
|
method_title?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式实例ID' })
|
||||||
|
method_id?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式金额' })
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式税额' })
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式税额详情' })
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '配送方式元数据' })
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedFeeLineDTO {
|
||||||
|
// 费用项DTO用于承载统一费用项数据
|
||||||
|
@ApiProperty({ description: '费用项ID' })
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '费用项名称' })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税率类' })
|
||||||
|
tax_class?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税率状态' })
|
||||||
|
tax_status?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总金额' })
|
||||||
|
total?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总税额' })
|
||||||
|
total_tax?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '税额详情' })
|
||||||
|
taxes?: any[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '元数据' })
|
||||||
|
meta_data?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnifiedCouponLineDTO {
|
||||||
|
// 优惠券项DTO用于承载统一优惠券项数据
|
||||||
|
@ApiProperty({ description: '优惠券项ID' })
|
||||||
|
id?: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项代码' })
|
||||||
|
code?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项折扣' })
|
||||||
|
discount?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项税额' })
|
||||||
|
discount_tax?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '优惠券项元数据' })
|
||||||
|
meta_data?: any[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedCustomerDTO {
|
export class UnifiedCustomerDTO {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { ProductStockComponent } from './product_stock_component.entity';
|
||||||
import { ProductSiteSku } from './product_site_sku.entity';
|
import { ProductSiteSku } from './product_site_sku.entity';
|
||||||
import { Category } from './category.entity';
|
import { Category } from './category.entity';
|
||||||
|
|
||||||
@Entity('product_v2')
|
@Entity('product')
|
||||||
export class Product {
|
export class Product {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '1',
|
example: '1',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 't
|
||||||
import { Area } from './area.entity';
|
import { Area } from './area.entity';
|
||||||
import { StockPoint } from './stock_point.entity';
|
import { StockPoint } from './stock_point.entity';
|
||||||
|
|
||||||
@Entity('site_v2')
|
@Entity('site')
|
||||||
export class Site {
|
export class Site {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||||
import { UpdateStockDTO } from '../dto/stock.dto';
|
import { UpdateStockDTO } from '../dto/stock.dto';
|
||||||
import { StockService } from './stock.service';
|
import { StockService } from './stock.service';
|
||||||
import { OrderItemOriginal } from '../entity/order_item_original.entity';
|
import { OrderItemOriginal } from '../entity/order_item_original.entity';
|
||||||
|
import { SiteApiService } from './site-api.service';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
|
|
@ -91,12 +92,16 @@ export class OrderService {
|
||||||
@Inject()
|
@Inject()
|
||||||
siteService: SiteService;
|
siteService: SiteService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
siteApiService: SiteApiService;
|
||||||
|
|
||||||
async syncOrders(siteId: number, params: Record<string, any> = {}) {
|
async syncOrders(siteId: number, params: Record<string, any> = {}) {
|
||||||
// 调用 WooCommerce API 获取订单
|
// 调用 WooCommerce API 获取订单
|
||||||
const orders = await this.wpService.getOrders(siteId, params);
|
const result = await (await this.siteApiService.getAdapter(siteId)).getAllOrders(params);
|
||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let failureCount = 0;
|
let failureCount = 0;
|
||||||
for (const order of orders) {
|
for (const order of result) {
|
||||||
try {
|
try {
|
||||||
await this.syncSingleOrder(siteId, order);
|
await this.syncSingleOrder(siteId, order);
|
||||||
successCount++;
|
successCount++;
|
||||||
|
|
@ -129,6 +134,13 @@ export class OrderService {
|
||||||
[OrderStatus.RETURN_APPROVED]: OrderStatus.ON_HOLD, // 退款申请已通过转为 on-hold
|
[OrderStatus.RETURN_APPROVED]: OrderStatus.ON_HOLD, // 退款申请已通过转为 on-hold
|
||||||
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
|
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shopyyOrderAutoNextStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||||
|
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
||||||
|
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
||||||
|
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||||||
|
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
||||||
|
}
|
||||||
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
|
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
|
||||||
async autoUpdateOrderStatus(siteId: number, order: any) {
|
async autoUpdateOrderStatus(siteId: number, order: any) {
|
||||||
console.log('更新订单状态', order)
|
console.log('更新订单状态', order)
|
||||||
|
|
@ -170,6 +182,35 @@ export class OrderService {
|
||||||
if (existingOrder) {
|
if (existingOrder) {
|
||||||
await this.orderModel.update({ id: existingOrder.id }, { orderStatus: this.mapOrderStatus(order.status) });
|
await this.orderModel.update({ id: existingOrder.id }, { orderStatus: this.mapOrderStatus(order.status) });
|
||||||
}
|
}
|
||||||
|
const site = await this.siteService.get(siteId,true);
|
||||||
|
if(site.type === 'woocommerce'){
|
||||||
|
// 矫正数据库状态
|
||||||
|
await this.orderModel.update({ externalOrderId: order.id, siteId: siteId }, {
|
||||||
|
orderStatus: order.status,
|
||||||
|
})
|
||||||
|
}else if(site.type === 'shopyy'){
|
||||||
|
const originStatus = orderData.status;
|
||||||
|
orderData.status= this.shopyyOrderAutoNextStatusMap[originStatus];
|
||||||
|
// 根据currency_code获取对应货币符号
|
||||||
|
const currencySymbols: Record<string, string> = {
|
||||||
|
'EUR': '€',
|
||||||
|
'USD': '$',
|
||||||
|
'GBP': '£',
|
||||||
|
'JPY': '¥',
|
||||||
|
'AUD': 'A$',
|
||||||
|
'CAD': 'C$',
|
||||||
|
'CHF': 'CHF',
|
||||||
|
'CNY': '¥',
|
||||||
|
'HKD': 'HK$',
|
||||||
|
'NZD': 'NZ$',
|
||||||
|
'SGD': 'S$'
|
||||||
|
// 可以根据需要添加更多货币代码和符号
|
||||||
|
};
|
||||||
|
if (orderData.currency) {
|
||||||
|
const currencyCode = orderData.currency.toUpperCase();
|
||||||
|
orderData.currency_symbol = currencySymbols[currencyCode] || '$';
|
||||||
|
}
|
||||||
|
}
|
||||||
const externalOrderId = order.id;
|
const externalOrderId = order.id;
|
||||||
if (
|
if (
|
||||||
existingOrder &&
|
existingOrder &&
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { Site } from '../entity/site.entity';
|
||||||
import { UnifiedReviewDTO } from '../dto/site-api.dto';
|
import { UnifiedReviewDTO } from '../dto/site-api.dto';
|
||||||
import { ShopyyReview } from '../dto/shopyy.dto';
|
import { ShopyyReview } from '../dto/shopyy.dto';
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ShopYY平台服务实现
|
* ShopYY平台服务实现
|
||||||
*/
|
*/
|
||||||
|
|
@ -306,14 +305,66 @@ export class ShopyyService {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: response.data || [],
|
items: response.data?.list || [],
|
||||||
total: response.meta?.pagination?.total || 0,
|
total: response?.data?.paginate?.total || 0,
|
||||||
totalPages: response.meta?.pagination?.total_pages || 0,
|
totalPages: response?.data?.paginate?.pageTotal || 0,
|
||||||
page: response.meta?.pagination?.current_page || page,
|
page: response?.data?.paginate?.current || page,
|
||||||
per_page: response.meta?.pagination?.per_page || pageSize
|
per_page: response?.data?.paginate?.pagesize || pageSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAllOrders(site: any | number, params: Record<string, any> = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
|
||||||
|
const firstPage = await this.getOrders(site, 1, 100);
|
||||||
|
|
||||||
|
const { items: firstPageItems, totalPages} = firstPage;
|
||||||
|
|
||||||
|
// const { page = 1, per_page = 100 } = params;
|
||||||
|
// 如果只有一页数据,直接返回
|
||||||
|
if (totalPages <= 1) {
|
||||||
|
return firstPageItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制最大页数,避免过多的并发请求
|
||||||
|
const actualMaxPages = Math.min(totalPages, maxPages);
|
||||||
|
|
||||||
|
// 收集所有页面数据,从第二页开始
|
||||||
|
const allItems = [...firstPageItems];
|
||||||
|
let currentPage = 2;
|
||||||
|
|
||||||
|
// 使用并发限制,避免一次性发起过多请求
|
||||||
|
while (currentPage <= actualMaxPages) {
|
||||||
|
const batchPromises: Promise<any[]>[] = [];
|
||||||
|
const batchSize = Math.min(concurrencyLimit, actualMaxPages - currentPage + 1);
|
||||||
|
|
||||||
|
// 创建当前批次的并发请求
|
||||||
|
for (let i = 0; i < batchSize; i++) {
|
||||||
|
const page = currentPage + i;
|
||||||
|
const pagePromise = this.getOrders(site, page, 100)
|
||||||
|
.then(pageResult => pageResult.items)
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`获取第 ${page} 页数据失败:`, error);
|
||||||
|
return []; // 如果某页获取失败,返回空数组,不影响整体结果
|
||||||
|
});
|
||||||
|
|
||||||
|
batchPromises.push(pagePromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待当前批次完成
|
||||||
|
const batchResults = await Promise.all(batchPromises);
|
||||||
|
|
||||||
|
// 合并当前批次的数据
|
||||||
|
for (const pageItems of batchResults) {
|
||||||
|
allItems.push(...pageItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动到下一批次
|
||||||
|
currentPage += batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取ShopYY订单详情
|
* 获取ShopYY订单详情
|
||||||
* @param siteId 站点ID
|
* @param siteId 站点ID
|
||||||
|
|
@ -838,4 +889,5 @@ export class ShopyyService {
|
||||||
throw new Error(`删除ShopYY webhook失败: ${errorMessage}`);
|
throw new Error(`删除ShopYY webhook失败: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue