From efbd3189174e72ae993f7e4ddd9292fba86d8e65 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Fri, 23 Jan 2026 17:32:51 +0800 Subject: [PATCH 1/5] =?UTF-8?q?docs(order.entity):=20=E6=9B=B4=E6=96=B0tot?= =?UTF-8?q?al=E5=AD=97=E6=AE=B5=E7=9A=84ApiProperty=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entity/order.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity/order.entity.ts b/src/entity/order.entity.ts index d4f1cbb..8fe050c 100644 --- a/src/entity/order.entity.ts +++ b/src/entity/order.entity.ts @@ -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; -- 2.40.1 From a556ab69bfd56ce520f61c0831e65fb0240b5841 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Fri, 23 Jan 2026 17:46:04 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(=E8=AE=A2=E5=8D=95=E6=9C=8D=E5=8A=A1):?= =?UTF-8?q?=20=E5=9C=A8=E8=AE=A2=E5=8D=95=E7=BB=9F=E8=AE=A1=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E4=B8=AD=E6=B7=BB=E5=8A=A0sku=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在订单商品统计查询SQL中增加sku字段,以便按商品SKU进行分组统计和展示 --- src/service/order.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/service/order.service.ts b/src/service/order.service.ts index 321221c..5cde38a 100644 --- a/src/service/order.service.ts +++ b/src/service/order.service.ts @@ -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 ? `; -- 2.40.1 From 16d27179e77e4ed7fbca46b4b0900ad82fbd48ae Mon Sep 17 00:00:00 2001 From: tikkhun Date: Fri, 23 Jan 2026 18:29:20 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(woocommerce):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=89=A9=E6=B5=81=E8=BF=BD=E8=B8=AA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用元数据中的物流追踪信息替代原有接口 移除冗余的履行信息获取逻辑 --- src/adapter/woocommerce.adapter.ts | 111 ++++++++++++----------------- src/dto/woocommerce.dto.ts | 14 +++- 2 files changed, 59 insertions(+), 66 deletions(-) diff --git a/src/adapter/woocommerce.adapter.ts b/src/adapter/woocommerce.adapter.ts index f14d7d3..765034e 100644 --- a/src/adapter/woocommerce.adapter.ts +++ b/src/adapter/woocommerce.adapter.ts @@ -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 { // 构造函数接收站点配置与服务实例 @@ -201,7 +203,7 @@ export class WooCommerceAdapter implements ISiteAdapter { async updateCustomer(where: Partial>, data: Partial): Promise { const api = this.wpService.createApi(this.site, 'wc/v3'); let customerId: string | number; - + // 先根据条件获取客户ID if (where.id) { customerId = where.id; @@ -210,7 +212,7 @@ export class WooCommerceAdapter implements ISiteAdapter { const customer = await this.getCustomer(where); customerId = customer.id; } - + const res = await api.put(`customers/${customerId}`, data); return this.mapPlatformToUnifiedCustomer(res.data); } @@ -218,7 +220,7 @@ export class WooCommerceAdapter implements ISiteAdapter { async deleteCustomer(where: Partial>): Promise { const api = this.wpService.createApi(this.site, 'wc/v3'); let customerId: string | number; - + // 先根据条件获取客户ID if (where.id) { customerId = where.id; @@ -227,7 +229,7 @@ export class WooCommerceAdapter implements ISiteAdapter { const customer = await this.getCustomer(where); customerId = customer.id; } - + await api.delete(`customers/${customerId}`, { force: true }); return true; } @@ -255,7 +257,7 @@ export class WooCommerceAdapter implements ISiteAdapter { } mapMediaSearchParams(params: UnifiedSearchParamsDTO): Partial { 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 { + async updateMedia(where: { id: string | number }, data: any): Promise { // 更新媒体信息 return await this.wpService.updateMedia(Number(this.site.id), Number(where.id), data); } - async deleteMedia(where: {id: string | number}): Promise { + async deleteMedia(where: { id: string | number }): Promise { // 删除媒体资源 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) => ({ - tracking_id: track.tracking_id, - tracking_number: track.tracking_number, - shipping_provider: track.tracking_provider, - date_created: track.data_sipped, - })); + 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: dayjs(track.date_shipped), + }) + }); return { id: item.id, @@ -446,7 +451,7 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 订单操作方法 - async getOrder(where: {id: string | number}): Promise { + async getOrder(where: { id: string | number }): Promise { // 获取单个订单详情 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(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): Promise { + async updateOrder(where: { id: string | number }, data: Partial): Promise { // 更新订单并返回布尔结果 return await this.wpService.updateOrder(this.site, String(where.id), data as any); } - async deleteOrder(where: {id: string | number}): Promise { + async deleteOrder(where: { id: string | number }): Promise { // 删除订单 const api = this.wpService.createApi(this.site, 'wc/v3'); await api.delete(`orders/${where.id}`, { force: true }); @@ -606,7 +589,7 @@ export class WooCommerceAdapter implements ISiteAdapter { name: data.name, type: data.type, status: data.status, - sku: data.sku, + sku: data.sku, regular_price: data.regular_price, sale_price: data.sale_price, price: data.price, @@ -690,10 +673,10 @@ export class WooCommerceAdapter implements ISiteAdapter { return mapped; } - mapCreateProductParams(data: Partial):Partial { - const {id,...mapped}= this.mapUnifiedToPlatformProduct(data); + mapCreateProductParams(data: Partial): Partial { + const { id, ...mapped } = this.mapUnifiedToPlatformProduct(data); // 创建不带 id - return mapped + return mapped } mapUpdateProductParams(data: Partial): Partial { return this.mapUnifiedToPlatformProduct(data); @@ -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>): Promise{ + async getProduct(where: Partial>): Promise { 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>, data: Partial): Promise { // 更新产品并返回统一产品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>): Promise { // 删除产品 const product = await this.getProduct(where); - if(!product){ + if (!product) { throw new Error('产品不存在'); } const api = this.wpService.createApi(this.site, 'wc/v3'); @@ -1392,12 +1375,12 @@ export class WooCommerceAdapter implements ISiteAdapter { const result = await this.wpService.getWebhooks(this.site, params); return { - items: (result.items as WooWebhook[]).map(this.mapPlatformToUnifiedWebhook), - total: result.total, - page: Number(params.page || 1), - per_page: Number(params.per_page || 20), - totalPages: result.totalPages, - }; + items: (result.items as WooWebhook[]).map(this.mapPlatformToUnifiedWebhook), + total: result.total, + page: Number(params.page || 1), + per_page: Number(params.per_page || 20), + totalPages: result.totalPages, + }; } catch (error) { throw new Error(`Failed to get webhooks: ${error instanceof Error ? error.message : String(error)}`); } @@ -1415,7 +1398,7 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 获取单个 webhook 详情 - async getWebhook(where: {id: string | number}): Promise { + async getWebhook(where: { id: string | number }): Promise { try { const result = await this.wpService.getWebhook(this.site, where.id); return this.mapPlatformToUnifiedWebhook(result as WooWebhook); diff --git a/src/dto/woocommerce.dto.ts b/src/dto/woocommerce.dto.ts index c48b85f..8fe99b1 100644 --- a/src/dto/woocommerce.dto.ts +++ b/src/dto/woocommerce.dto.ts @@ -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 { -- 2.40.1 From c26918d4db3d3efcea02d800b7a5922a91f2aa65 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Fri, 23 Jan 2026 18:34:37 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(dto):=20=E5=9C=A8=20FulfillmentDTO=20?= =?UTF-8?q?=E4=B8=AD=E6=B7=BB=E5=8A=A0=E7=89=A9=E6=B5=81=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dto/site-api.dto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dto/site-api.dto.ts b/src/dto/site-api.dto.ts index a5b2fc7..6a72809 100644 --- a/src/dto/site-api.dto.ts +++ b/src/dto/site-api.dto.ts @@ -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; -- 2.40.1 From 3968fd8965f732c1e460601aa04155fe34f16f59 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Fri, 23 Jan 2026 18:36:03 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(woocommerce.adapter):=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E7=89=A9=E6=B5=81=E8=BF=BD=E8=B8=AA=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E8=BD=AC=E6=8D=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adapter/woocommerce.adapter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapter/woocommerce.adapter.ts b/src/adapter/woocommerce.adapter.ts index 765034e..c37ab3c 100644 --- a/src/adapter/woocommerce.adapter.ts +++ b/src/adapter/woocommerce.adapter.ts @@ -401,13 +401,13 @@ export class WooCommerceAdapter implements ISiteAdapter { // 包含账单地址与收货地址以及创建与更新时间 // 映射物流追踪信息,将后端格式转换为前端期望的格式 const metaFulfillments: MetaDataFulfillment[] = item.meta_data?.find?.(_meta => _meta.key === "_wc_shipment_tracking_items")?.value || [] - const fulfillments = metaFulfillments?.map?.((track) => { + 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: dayjs(track.date_shipped), + date_created: dayjs(track.date_shipped).toString(), }) }); -- 2.40.1