feat(订单): 添加同步结果计数和完整地址信息

在订单服务中添加成功和失败计数返回
为订单DTO添加账单和收货地址全称字段
统一各服务的同步结果返回格式
优化地址格式化逻辑和映射处理
This commit is contained in:
tikkhun 2025-12-11 15:08:48 +08:00
parent 50317abff3
commit 87b4039a67
8 changed files with 412 additions and 75 deletions

View File

@ -60,86 +60,129 @@ export class ShopyyAdapter implements ISiteAdapter {
}
private mapOrder(item: any): UnifiedOrderDTO {
const billing = item.billing_address || {};
const shipping = item.shipping_address || {};
const billingObj = {
first_name: billing.first_name || item.firstname || '',
last_name: billing.last_name || item.lastname || '',
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
company: billing.company || '',
email: item.customer_email || item.email || '',
phone: billing.phone || item.telephone || '',
address_1: billing.address1 || item.payment_address || '',
address_2: billing.address2 || '',
city: billing.city || item.payment_city || '',
state: billing.province || item.payment_zone || '',
postcode: billing.zip || item.payment_postcode || '',
country: billing.country_name || billing.country_code || item.payment_country || ''
};
const shippingObj = {
first_name: shipping.first_name || item.firstname || '',
last_name: shipping.last_name || item.lastname || '',
fullname: shipping.name || '',
company: shipping.company || '',
address_1: shipping.address1 || item.shipping_address || '',
address_2: shipping.address2 || '',
city: shipping.city || item.shipping_city || '',
state: shipping.province || item.shipping_zone || '',
postcode: shipping.zip || item.shipping_postcode || '',
country: shipping.country_name || shipping.country_code || item.shipping_country || ''
};
const formatAddress = (addr: any) => {
return [
addr.fullname,
addr.company,
addr.address_1,
addr.address_2,
addr.city,
addr.state,
addr.postcode,
addr.country,
addr.phone
].filter(Boolean).join(', ');
};
return {
id: item.order_id,
number: item.order_sn,
status: item.order_status,
currency: item.currency,
total: item.total_amount,
customer_id: item.user_id,
customer_name: `${item.firstname} ${item.lastname}`.trim(),
email: item.email,
id: item.id || item.order_id,
number: item.order_number || item.order_sn,
status: String(item.status || item.order_status),
currency: item.currency_code || item.currency,
total: String(item.total_price || item.total_amount),
customer_id: item.customer_id || item.user_id,
customer_name: item.customer_name || `${item.firstname} ${item.lastname}`.trim(),
email: item.customer_email || item.email,
line_items: (item.products || []).map((p: any) => ({
id: p.id,
name: p.name,
name: p.product_title || p.name,
product_id: p.product_id,
quantity: p.quantity,
total: p.price,
sku: p.sku
total: String(p.price),
sku: p.sku || p.sku_code || ''
})),
sales: (item.products || []).map((p: any) => ({
id: p.id,
name: p.name,
name: p.product_title || p.name,
product_id: p.product_id,
productId: p.product_id,
quantity: p.quantity,
total: p.price,
sku: p.sku
total: String(p.price),
sku: p.sku || p.sku_code || ''
})),
billing: {
first_name: item.firstname,
last_name: item.lastname,
email: item.email,
phone: item.telephone,
address_1: item.payment_address,
city: item.payment_city,
state: item.payment_zone,
postcode: item.payment_postcode,
country: item.payment_country
},
shipping: {
first_name: item.firstname,
last_name: item.lastname,
address_1: item.shipping_address,
city: item.shipping_city,
state: item.shipping_zone,
postcode: item.shipping_postcode,
country: item.shipping_country
},
billing: billingObj,
shipping: shippingObj,
billing_full_address: formatAddress(billingObj),
shipping_full_address: formatAddress(shippingObj),
payment_method: item.payment_method,
date_created: item.date_added,
date_created: item.created_at ? new Date(item.created_at * 1000).toISOString() : item.date_added,
raw: item,
};
}
private mapCustomer(item: any): UnifiedCustomerDTO {
// 处理多地址结构
const addresses = item.addresses || [];
const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {});
// 尝试从地址列表中获取billing和shipping
// 如果没有明确区分,默认使用默认地址或第一个地址
const billing = defaultAddress;
const shipping = defaultAddress;
return {
id: item.customer_id,
first_name: item.firstname,
last_name: item.lastname,
fullname: item.customer_name,
email: item.customer_email,
phone: item.phone || '',
id: item.id || item.customer_id,
first_name: item.first_name || item.firstname || '',
last_name: item.last_name || item.lastname || '',
fullname: item.fullname || item.customer_name || `${item.first_name || item.firstname || ''} ${item.last_name || item.lastname || ''}`.trim(),
email: item.email || item.customer_email || '',
phone: item.contact || billing.phone || item.phone || '',
billing: {
first_name: item.first_name,
last_name: item.last_name,
fullname: item.name,
email: item.email,
phone: item.phone || '',
address_1: item.address_1 || '',
city: item.city || '',
state: item.zone || '',
postcode: item.postcode || '',
country: item.country || ''
first_name: billing.first_name || item.first_name || '',
last_name: billing.last_name || item.last_name || '',
fullname: billing.name || `${billing.first_name || ''} ${billing.last_name || ''}`.trim(),
company: billing.company || '',
email: item.email || '',
phone: billing.phone || item.contact || '',
address_1: billing.address1 || '',
address_2: billing.address2 || '',
city: billing.city || '',
state: billing.province || '',
postcode: billing.zip || '',
country: billing.country_name || billing.country_code || item.country?.country_name || ''
},
shipping: {
first_name: item.firstname,
last_name: item.lastname,
address_1: item.address_1 || '',
city: item.city || '',
state: item.zone || '',
postcode: item.postcode || '',
country: item.country || ''
first_name: shipping.first_name || item.first_name || '',
last_name: shipping.last_name || item.last_name || '',
fullname: shipping.name || `${shipping.first_name || ''} ${shipping.last_name || ''}`.trim(),
company: shipping.company || '',
address_1: shipping.address1 || '',
address_2: shipping.address2 || '',
city: shipping.city || '',
state: shipping.province || '',
postcode: shipping.zip || '',
country: shipping.country_name || shipping.country_code || item.country?.country_name || ''
},
raw: item,
};

View File

@ -40,6 +40,22 @@ export class WooCommerceAdapter implements ISiteAdapter {
}
private mapOrder(item: any): UnifiedOrderDTO {
const formatAddress = (addr: any) => {
if (!addr) return '';
const name = addr.fullname || `${addr.first_name || ''} ${addr.last_name || ''}`.trim();
return [
name,
addr.company,
addr.address_1,
addr.address_2,
addr.city,
addr.state,
addr.postcode,
addr.country,
addr.phone
].filter(Boolean).join(', ');
};
return {
id: item.id,
number: item.number,
@ -59,6 +75,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
})),
billing: item.billing,
shipping: item.shipping,
billing_full_address: formatAddress(item.billing),
shipping_full_address: formatAddress(item.shipping),
payment_method: item.payment_method_title,
date_created: item.date_created,
raw: item,

View File

@ -0,0 +1,264 @@
import { ApiProperty } from '@midwayjs/swagger';
export class UnifiedPaginationDTO<T> {
@ApiProperty({ description: '列表数据' })
items: T[];
@ApiProperty({ description: '总数', example: 100 })
total: number;
@ApiProperty({ description: '当前页', example: 1 })
page: number;
@ApiProperty({ description: '每页数量', example: 20 })
per_page: number;
@ApiProperty({ description: '总页数', example: 5 })
totalPages: number;
}
export class UnifiedImageDTO {
@ApiProperty({ description: '图片ID' })
id: number | string;
@ApiProperty({ description: '图片URL' })
src: string;
@ApiProperty({ description: '图片名称' })
name?: string;
@ApiProperty({ description: '替代文本' })
alt?: string;
}
export class UnifiedProductDTO {
@ApiProperty({ description: '产品ID' })
id: string | number;
@ApiProperty({ description: '产品名称' })
name: string;
@ApiProperty({ description: '产品类型' })
type: string;
@ApiProperty({ description: '产品状态' })
status: string;
@ApiProperty({ description: '产品SKU' })
sku: string;
@ApiProperty({ description: '常规价格' })
regular_price: string;
@ApiProperty({ description: '销售价格' })
sale_price: string;
@ApiProperty({ description: '当前价格' })
price: string;
@ApiProperty({ description: '库存状态' })
stock_status: string;
@ApiProperty({ description: '库存数量' })
stock_quantity: number;
@ApiProperty({ description: '产品图片', type: [UnifiedImageDTO] })
images: UnifiedImageDTO[];
@ApiProperty({ description: '产品标签', type: 'json' })
tags?: string[];
@ApiProperty({ description: '产品属性', type: 'json' })
attributes: any[];
@ApiProperty({ description: '产品变体', type: 'json' })
variations?: any[];
@ApiProperty({ description: '创建时间' })
date_created: string;
@ApiProperty({ description: '更新时间' })
date_modified: string;
@ApiProperty({ description: '原始数据(保留备用)', type: 'json' })
raw?: any;
}
export class UnifiedOrderDTO {
@ApiProperty({ description: '订单ID' })
id: string | number;
@ApiProperty({ description: '订单号' })
number: string;
@ApiProperty({ description: '订单状态' })
status: string;
@ApiProperty({ description: '货币' })
currency: string;
@ApiProperty({ description: '总金额' })
total: string;
@ApiProperty({ description: '客户ID' })
customer_id: number;
@ApiProperty({ description: '客户姓名' })
customer_name: string;
@ApiProperty({ description: '客户邮箱' })
email: string;
@ApiProperty({ description: '订单项', type: 'json' })
line_items: any[];
@ApiProperty({ description: '销售项(兼容前端)', type: 'json' })
sales?: any[];
@ApiProperty({ description: '账单地址', type: 'json' })
billing: any;
@ApiProperty({ description: '收货地址', type: 'json' })
shipping: any;
@ApiProperty({ description: '账单地址全称' })
billing_full_address?: string;
@ApiProperty({ description: '收货地址全称' })
shipping_full_address?: string;
@ApiProperty({ description: '支付方式' })
payment_method: string;
@ApiProperty({ description: '创建时间' })
date_created: string;
@ApiProperty({ description: '原始数据', type: 'json' })
raw?: any;
}
export class UnifiedCustomerDTO {
@ApiProperty({ description: '客户ID' })
id: string | number;
@ApiProperty({ description: '邮箱' })
email: string;
@ApiProperty({ description: '名' })
first_name?: string;
@ApiProperty({ description: '姓' })
last_name?: string;
@ApiProperty({ description: '名字' })
fullname?: string;
@ApiProperty({ description: '用户名' })
username?: string;
@ApiProperty({ description: '电话' })
phone?: string;
@ApiProperty({ description: '账单地址', type: 'json' })
billing?: any;
@ApiProperty({ description: '收货地址', type: 'json' })
shipping?: any;
@ApiProperty({ description: '原始数据', type: 'json' })
raw?: any;
}
export class UnifiedSubscriptionDTO {
@ApiProperty({ description: '订阅ID' })
id: string | number;
@ApiProperty({ description: '订阅状态' })
status: string;
@ApiProperty({ description: '客户ID' })
customer_id: number;
@ApiProperty({ description: '计费周期' })
billing_period: string;
@ApiProperty({ description: '计费间隔' })
billing_interval: number;
@ApiProperty({ description: '开始时间' })
start_date: string;
@ApiProperty({ description: '下次支付时间' })
next_payment_date: string;
@ApiProperty({ description: '订单项', type: 'json' })
line_items: any[];
@ApiProperty({ description: '原始数据', type: 'json' })
raw?: any;
}
export class UnifiedMediaDTO {
@ApiProperty({ description: '媒体ID' })
id: number;
@ApiProperty({ description: '标题' })
title: string;
@ApiProperty({ description: '媒体类型' })
media_type: string;
@ApiProperty({ description: 'MIME类型' })
mime_type: string;
@ApiProperty({ description: '源URL' })
source_url: string;
@ApiProperty({ description: '创建时间' })
date_created: string;
}
export class UnifiedProductPaginationDTO extends UnifiedPaginationDTO<UnifiedProductDTO> {
@ApiProperty({ description: '列表数据', type: [UnifiedProductDTO] })
items: UnifiedProductDTO[];
}
export class UnifiedOrderPaginationDTO extends UnifiedPaginationDTO<UnifiedOrderDTO> {
@ApiProperty({ description: '列表数据', type: [UnifiedOrderDTO] })
items: UnifiedOrderDTO[];
}
export class UnifiedCustomerPaginationDTO extends UnifiedPaginationDTO<UnifiedCustomerDTO> {
@ApiProperty({ description: '列表数据', type: [UnifiedCustomerDTO] })
items: UnifiedCustomerDTO[];
}
export class UnifiedSubscriptionPaginationDTO extends UnifiedPaginationDTO<UnifiedSubscriptionDTO> {
@ApiProperty({ description: '列表数据', type: [UnifiedSubscriptionDTO] })
items: UnifiedSubscriptionDTO[];
}
export class UnifiedMediaPaginationDTO extends UnifiedPaginationDTO<UnifiedMediaDTO> {
@ApiProperty({ description: '列表数据', type: [UnifiedMediaDTO] })
items: UnifiedMediaDTO[];
}
export class UnifiedSearchParamsDTO {
@ApiProperty({ description: '页码', example: 1 })
page?: number;
@ApiProperty({ description: '每页数量', example: 20 })
per_page?: number;
@ApiProperty({ description: '搜索关键词' })
search?: string;
@ApiProperty({ description: '状态' })
status?: string;
@ApiProperty({ description: '排序字段' })
orderby?: string;
@ApiProperty({ description: '排序方式' })
order?: string;
}

View File

@ -121,6 +121,12 @@ export class UnifiedOrderDTO {
@ApiProperty({ description: '收货地址', type: 'json' })
shipping: any;
@ApiProperty({ description: '账单地址全称' })
billing_full_address?: string;
@ApiProperty({ description: '收货地址全称' })
shipping_full_address?: string;
@ApiProperty({ description: '支付方式' })
payment_method: string;

View File

@ -117,6 +117,8 @@ export class OrderService {
}
return {
success: failureCount === 0,
successCount,
failureCount,
message: `同步完成: 成功 ${successCount}, 失败 ${failureCount}`,
};
}

View File

@ -31,7 +31,7 @@ export class ShopyyService implements IPlatformService {
* @returns
*/
private buildHeaders(site: Site): Record<string, string> {
if(!site?.token){
if (!site?.token) {
throw new Error(`获取站点${site?.name}数据,但失败,因为未设置站点令牌配置`)
}
return {
@ -52,7 +52,7 @@ export class ShopyyService implements IPlatformService {
private async request(site: any, endpoint: string, method: string = 'GET', data: any = null, params: any = null): Promise<any> {
const url = this.buildURL(site.apiUrl, endpoint);
const headers = this.buildHeaders(site);
const config: AxiosRequestConfig = {
url,
method,
@ -60,7 +60,7 @@ export class ShopyyService implements IPlatformService {
params,
data
};
try {
const response = await axios(config);
return response.data;
@ -81,11 +81,11 @@ export class ShopyyService implements IPlatformService {
limit: params.per_page || 20
};
const response = await this.request(site, endpoint, 'GET', null, requestParams);
if(response?.code !== 0){
if (response?.code !== 0) {
throw new Error(response?.msg)
}
return {
items: response.data.list || [],
items: (response.data.list || []) as T,
total: response.data?.paginate?.total || 0,
totalPages: response.data?.paginate?.pageTotal || 0,
page: response.data?.paginate?.current || requestParams.page,
@ -106,7 +106,7 @@ export class ShopyyService implements IPlatformService {
page,
page_size: pageSize
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
@ -142,7 +142,7 @@ export class ShopyyService implements IPlatformService {
page,
page_size: pageSize
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
@ -175,13 +175,13 @@ export class ShopyyService implements IPlatformService {
async getOrders(site: any | number, page: number = 1, pageSize: number = 100): Promise<any> {
// 如果传入的是站点ID则获取站点配置
const siteConfig = typeof site === 'number' ? await this.siteService.get(site) : site;
// ShopYY API: GET /orders
const response = await this.request(siteConfig, 'orders', 'GET', null, {
page,
page_size: pageSize
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
@ -199,7 +199,7 @@ export class ShopyyService implements IPlatformService {
*/
async getOrder(siteId: string, orderId: string): Promise<any> {
const site = await this.siteService.get(Number(siteId));
// ShopYY API: GET /orders/{id}
const response = await this.request(site, `orders/${orderId}`, 'GET');
return response.data;
@ -247,7 +247,7 @@ export class ShopyyService implements IPlatformService {
// ShopYY产品状态映射
const shopyyStatus = status === 'publish' ? 1 : 0;
const shopyyStockStatus = stockStatus === 'instock' ? 1 : 0;
try {
await this.request(site, `products/${productId}`, 'PUT', {
status: shopyyStatus,
@ -312,7 +312,7 @@ export class ShopyyService implements IPlatformService {
carrier_name: data.carrier_name,
shipping_method: data.shipping_method
};
const response = await this.request(site, `orders/${orderId}/shipments`, 'POST', shipmentData);
return response.data;
}
@ -349,7 +349,7 @@ export class ShopyyService implements IPlatformService {
page,
page_size: pageSize
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
@ -425,8 +425,8 @@ export class ShopyyService implements IPlatformService {
*/
async fetchCustomersPaged(site: any, params: any): Promise<any> {
// ShopYY API: GET /customers
const { items, total, totalPages, page, per_page } =
await this.fetchResourcePaged<any>(site, 'customers', params);
const { items, total, totalPages, page, per_page } =
await this.fetchResourcePaged<any>(site, 'customers/list', params);
return {
items,
total,

View File

@ -35,11 +35,13 @@ export class SubscriptionService {
}
return {
success: failureCount === 0,
successCount,
failureCount,
message: `同步完成: 成功 ${successCount}, 失败 ${failureCount}`,
};
} catch (error) {
console.error('同步订阅失败:', error);
return { success: false, message: `同步失败: ${error.message}` };
return { success: false, successCount: 0, failureCount: 0, message: `同步失败: ${error.message}` };
}
}

View File

@ -606,11 +606,13 @@ export class WpProductService {
}
return {
success: failureCount === 0,
successCount,
failureCount,
message: `同步完成: 成功 ${successCount}, 失败 ${failureCount}`,
};
} catch (error) {
console.error('同步站点产品失败:', error);
return { success: false, message: `同步失败: ${error.message}` };
return { success: false, successCount: 0, failureCount: 0, message: `同步失败: ${error.message}` };
}
}