Compare commits

..

5 Commits
main ... main

Author SHA1 Message Date
tikkhun 3968fd8965 fix(woocommerce.adapter): 修正物流追踪日期格式转换问题 2026-01-23 18:36:03 +08:00
tikkhun c26918d4db feat(dto): 在 FulfillmentDTO 中添加物流产品代码字段 2026-01-23 18:34:37 +08:00
tikkhun 16d27179e7 feat(woocommerce): 重构订单物流追踪信息处理方式
使用元数据中的物流追踪信息替代原有接口
移除冗余的履行信息获取逻辑
2026-01-23 18:29:20 +08:00
tikkhun a556ab69bf feat(订单服务): 在订单统计查询中添加sku字段
在订单商品统计查询SQL中增加sku字段,以便按商品SKU进行分组统计和展示
2026-01-23 17:46:04 +08:00
tikkhun efbd318917 docs(order.entity): 更新total字段的ApiProperty描述 2026-01-23 17:36:02 +08:00
5 changed files with 64 additions and 70 deletions

View File

@ -31,11 +31,13 @@ import {
WooProductSearchParams,
WpMediaGetListParams,
WooFulfillment,
MetaDataFulfillment,
} from '../dto/woocommerce.dto';
import { Site } from '../entity/site.entity';
import { WPService } from '../service/wp.service';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
import { toArray, toNumber } from '../utils/trans.util';
import dayjs = require('dayjs');
export class WooCommerceAdapter implements ISiteAdapter {
// 构造函数接收站点配置与服务实例
@ -255,7 +257,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
}
mapMediaSearchParams(params: UnifiedSearchParamsDTO): Partial<WpMediaGetListParams> {
const page = params.page
const per_page = Number( params.per_page ?? 20);
const per_page = Number(params.per_page ?? 20);
return {
...params.where,
@ -293,12 +295,12 @@ export class WooCommerceAdapter implements ISiteAdapter {
throw new Error('Method not implemented.');
}
async updateMedia(where: {id: string | number}, data: any): Promise<any> {
async updateMedia(where: { id: string | number }, data: any): Promise<any> {
// 更新媒体信息
return await this.wpService.updateMedia(Number(this.site.id), Number(where.id), data);
}
async deleteMedia(where: {id: string | number}): Promise<boolean> {
async deleteMedia(where: { id: string | number }): Promise<boolean> {
// 删除媒体资源
await this.wpService.deleteMedia(Number(this.site.id), Number(where.id), true);
return true;
@ -348,7 +350,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
// 集合过滤参数
if (where.exclude) mapped.exclude = toArray(where.exclude);
if (where.ids || where.number || where.id || where.include) mapped.include = [...new Set([where.number,where.id,...toArray(where.ids),...toArray(where.include)])].filter(Boolean);
if (where.ids || where.number || where.id || where.include) mapped.include = [...new Set([where.number, where.id, ...toArray(where.ids), ...toArray(where.include)])].filter(Boolean);
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
@ -397,14 +399,17 @@ export class WooCommerceAdapter implements ISiteAdapter {
mapPlatformToUnifiedOrder(item: WooOrder): UnifiedOrderDTO {
// 将 WooCommerce 订单数据映射为统一订单DTO
// 包含账单地址与收货地址以及创建与更新时间
// 映射物流追踪信息,将后端格式转换为前端期望的格式
const fulfillments = (item.fulfillments || []).map((track) => ({
const metaFulfillments: MetaDataFulfillment[] = item.meta_data?.find?.(_meta => _meta.key === "_wc_shipment_tracking_items")?.value || []
const fulfillments = metaFulfillments.map((track) => {
return ({
tracking_id: track.tracking_id,
tracking_number: track.tracking_number,
tracking_product_code: track.tracking_product_code,
shipping_provider: track.tracking_provider,
date_created: track.data_sipped,
}));
date_created: dayjs(track.date_shipped).toString(),
})
});
return {
id: item.id,
@ -446,7 +451,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
}
// 订单操作方法
async getOrder(where: {id: string | number}): Promise<UnifiedOrderDTO> {
async getOrder(where: { id: string | number }): Promise<UnifiedOrderDTO> {
// 获取单个订单详情
const api = this.wpService.createApi(this.site, 'wc/v3');
const res = await api.get(`orders/${where.id}`);
@ -457,30 +462,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
const requestParams = this.mapOrderSearchParams(params);
const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
// 并行获取所有订单的履行信息
const ordersWithFulfillments = await Promise.all(
items.map(async (order: any) => {
try {
// 获取订单的履行信息
const fulfillments = await this.getOrderFulfillments(order.id);
// 将履行信息添加到订单对象中
return {
...order,
fulfillments: fulfillments || []
};
} catch (error) {
// 如果获取履行信息失败,仍然返回订单,只是履行信息为空数组
console.error(`获取订单 ${order.id} 的履行信息失败:`, error);
return {
...order,
fulfillments: []
};
}
})
);
return {
items: ordersWithFulfillments.map(this.mapPlatformToUnifiedOrder),
items: items.map(this.mapPlatformToUnifiedOrder),
total,
totalPages,
page,
@ -514,12 +497,12 @@ export class WooCommerceAdapter implements ISiteAdapter {
return this.mapPlatformToUnifiedOrder(res.data);
}
async updateOrder(where: {id: string | number}, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
async updateOrder(where: { id: string | number }, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
// 更新订单并返回布尔结果
return await this.wpService.updateOrder(this.site, String(where.id), data as any);
}
async deleteOrder(where: {id: string | number}): Promise<boolean> {
async deleteOrder(where: { id: string | number }): Promise<boolean> {
// 删除订单
const api = this.wpService.createApi(this.site, 'wc/v3');
await api.delete(`orders/${where.id}`, { force: true });
@ -690,8 +673,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
return mapped;
}
mapCreateProductParams(data: Partial<UnifiedProductDTO>):Partial<WooProduct> {
const {id,...mapped}= this.mapUnifiedToPlatformProduct(data);
mapCreateProductParams(data: Partial<UnifiedProductDTO>): Partial<WooProduct> {
const { id, ...mapped } = this.mapUnifiedToPlatformProduct(data);
// 创建不带 id
return mapped
}
@ -844,28 +827,28 @@ export class WooCommerceAdapter implements ISiteAdapter {
};
}
// 判断是否是这个站点的sku
isSiteSkuThisSite(sku: string,){
return sku.startsWith(this.site.skuPrefix+'-');
isSiteSkuThisSite(sku: string,) {
return sku.startsWith(this.site.skuPrefix + '-');
}
async getProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<UnifiedProductDTO>{
async getProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<UnifiedProductDTO> {
const { id, sku } = where;
if(id) return this.getProductById(id);
if(sku) return this.getProductBySku(sku);
if (id) return this.getProductById(id);
if (sku) return this.getProductBySku(sku);
throw new Error('必须提供id或sku参数');
}
async getProductBySku(sku: string){
async getProductBySku(sku: string) {
// const api = this.wpService.createApi(this.site, 'wc/v3');
// const res = await api.get(`products`,{
// sku
// });
// const product = res.data[0];
const res = await this.wpService.getProducts(this.site,{
const res = await this.wpService.getProducts(this.site, {
sku,
page:1,
per_page:1,
page: 1,
per_page: 1,
});
const product = res?.items?.[0];
if(!product) return null
if (!product) return null
return this.mapPlatformToUnifiedProduct(product);
}
// 产品操作方法
@ -976,7 +959,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
async updateProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>, data: Partial<UnifiedProductDTO>): Promise<boolean> {
// 更新产品并返回统一产品DTO
const product = await this.getProduct(where);
if(!product){
if (!product) {
throw new Error('产品不存在');
}
const updateData = this.mapUpdateProductParams(data);
@ -987,7 +970,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
async deleteProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<boolean> {
// 删除产品
const product = await this.getProduct(where);
if(!product){
if (!product) {
throw new Error('产品不存在');
}
const api = this.wpService.createApi(this.site, 'wc/v3');
@ -1415,7 +1398,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
}
// 获取单个 webhook 详情
async getWebhook(where: {id: string | number}): Promise<UnifiedWebhookDTO> {
async getWebhook(where: { id: string | number }): Promise<UnifiedWebhookDTO> {
try {
const result = await this.wpService.getWebhook(this.site, where.id);
return this.mapPlatformToUnifiedWebhook(result as WooWebhook);

View File

@ -811,7 +811,8 @@ export class FulfillmentDTO {
tracking_id?: string;
@ApiProperty({ description: '物流单号', required: false })
tracking_number?: string;
@ApiProperty({ description: "物流产品代码" , required: false})
tracking_product_code?: string;
@ApiProperty({ description: '物流公司', required: false })
shipping_provider?: string;

View File

@ -369,9 +369,19 @@ export interface WooOrder {
date_created_gmt?: string;
date_modified?: string;
date_modified_gmt?: string;
// 物流追踪信息
fulfillments?: WooFulfillment[];
}
export interface MetaDataFulfillment {
custom_tracking_link: string;
custom_tracking_provider: string;
date_shipped: number;
source: string;
status_shipped: string;
tracking_id: string;
tracking_number: string;
tracking_product_code: string;
tracking_provider: string;
user_id: number;
}
// 这个是一个插件的物流追踪信息
// 接口:
export interface WooFulfillment {

View File

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

View File

@ -1479,7 +1479,7 @@ export class OrderService {
}
let itemSql = `
SELECT os.productId, os.name, SUM(os.quantity) AS totalQuantity, COUNT(DISTINCT os.orderId) AS totalOrders
SELECT os.productId, os.name, os.sku, SUM(os.quantity) AS totalQuantity, COUNT(DISTINCT os.orderId) AS totalOrders
FROM order_sale os
INNER JOIN \`order\` o ON o.id = os.orderId
WHERE o.date_paid BETWEEN ? AND ?
@ -1501,7 +1501,7 @@ export class OrderService {
}
itemSql += nameCondition;
itemSql += `
GROUP BY os.productId, os.name
GROUP BY os.productId, os.name, os.sku
ORDER BY totalQuantity DESC
LIMIT ? OFFSET ?
`;