diff --git a/src/adapter/shopyy.adapter.ts b/src/adapter/shopyy.adapter.ts index c8e5df5..e457284 100644 --- a/src/adapter/shopyy.adapter.ts +++ b/src/adapter/shopyy.adapter.ts @@ -21,6 +21,10 @@ import { CreateReviewDTO, CreateVariationDTO, UpdateReviewDTO + FulfillmentDTO, + CreateReviewDTO, + CreateVariationDTO, + UpdateReviewDTO } from '../dto/site-api.dto'; import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto'; import { @@ -38,6 +42,7 @@ import { OrderStatus, } from '../enums/base.enum'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; +import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; export class ShopyyAdapter implements ISiteAdapter { shopyyFinancialStatusMap= { '200': '待支付', @@ -56,53 +61,53 @@ export class ShopyyAdapter implements ISiteAdapter { this.mapPlatformToUnifiedOrder = this.mapPlatformToUnifiedOrder.bind(this); this.mapPlatformToUnifiedMedia = this.mapPlatformToUnifiedMedia.bind(this); // this.mapPlatformToUnifiedSubscription = this.mapPlatformToUnifiedSubscription.bind(this); + this.mapPlatformToUnifiedCustomer = this.mapPlatformToUnifiedCustomer.bind(this); + this.mapPlatformToUnifiedProduct = this.mapPlatformToUnifiedProduct.bind(this); + this.mapPlatformToUnifiedVariation = this.mapPlatformToUnifiedVariation.bind(this); + this.mapPlatformToUnifiedOrder = this.mapPlatformToUnifiedOrder.bind(this); + this.mapPlatformToUnifiedMedia = this.mapPlatformToUnifiedMedia.bind(this); + // this.mapPlatformToUnifiedSubscription = this.mapPlatformToUnifiedSubscription.bind(this); } - private mapMedia(item: any): UnifiedMediaDTO { - // 映射媒体项目 - return { - id: item.id, - date_created: item.created_at, - date_modified: item.updated_at, - source_url: item.src, - title: item.alt || '', - media_type: '', // Shopyy API未提供,暂时留空 - mime_type: '', // Shopyy API未提供,暂时留空 + // ========== 客户映射方法 ========== + mapPlatformToUnifiedCustomer(item: ShopyyCustomer): UnifiedCustomerDTO { + // 处理多地址结构 + const addresses = item.addresses || []; + const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {}); + + // 尝试从地址列表中获取billing和shipping + // 如果没有明确区分,默认使用默认地址或第一个地址 + const billingAddress = defaultAddress; + const shippingAddress = defaultAddress; + + const billing = { + first_name: billingAddress.first_name || item.first_name || '', + last_name: billingAddress.last_name || item.last_name || '', + fullname: billingAddress.name || `${billingAddress.first_name || item.first_name || ''} ${billingAddress.last_name || item.last_name || ''}`.trim(), + company: billingAddress.company || '', + email: item.email || '', + phone: billingAddress.phone || item.contact || '', + address_1: billingAddress.address1 || '', + address_2: billingAddress.address2 || '', + city: billingAddress.city || '', + state: billingAddress.province || '', + postcode: billingAddress.zip || '', + country: billingAddress.country_name || billingAddress.country_code || item.country?.country_name || '' }; - } - private mapMediaSearchParams(params: UnifiedSearchParamsDTO): any { - return this.mapSearchParams(params) - } + const shipping = { + first_name: shippingAddress.first_name || item.first_name || '', + last_name: shippingAddress.last_name || item.last_name || '', + fullname: shippingAddress.name || `${shippingAddress.first_name || item.first_name || ''} ${shippingAddress.last_name || item.last_name || ''}`.trim(), + company: shippingAddress.company || '', + address_1: shippingAddress.address1 || '', + address_2: shippingAddress.address2 || '', + city: shippingAddress.city || '', + state: shippingAddress.province || '', + postcode: shippingAddress.zip || '', + country: shippingAddress.country_name || shippingAddress.country_code || item.country?.country_name || '' + }; - /** - * 通用搜索参数转换方法,处理 where 和 orderBy 的转换 - * 将统一的搜索参数转换为 ShopYY API 所需的参数格式 - */ - private mapSearchParams(params: UnifiedSearchParamsDTO): any { - // 处理分页参数 - const page = Number(params.page || 1); - const limit = Number(params.per_page ?? 20); - - // 处理 where 条件 - const query: any = { - ...(params.where || {}), - page, - limit, - } - if(params.orderBy){ - const [field, dir] = Object.entries(params.orderBy)[0]; - query.order_by = dir === 'desc' ? 'desc' : 'asc'; - query.order_field = field - } - return query; - } - - private mapProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO { - // 映射产品状态 - function mapProductStatus(status: number) { - return status === 1 ? 'publish' : 'draft'; - } return { id: item.id || item.customer_id, orders: Number(item.orders_count ?? item.order_count ?? item.orders ?? 0), @@ -130,11 +135,8 @@ export class ShopyyAdapter implements ISiteAdapter { return data } - async getCustomer(where: {id?: string | number,email?: string,phone?: string}): Promise { - if(!where.id && !where.email && !where.phone){ - throw new Error('必须传入 id 或 email 或 phone') - } - const customer = await this.shopyyService.getCustomer(this.site, where.id); + async getCustomer(id: string | number): Promise { + const customer = await this.shopyyService.getCustomer(this.site, id); return this.mapPlatformToUnifiedCustomer(customer); } @@ -160,13 +162,13 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedCustomer(createdCustomer); } - async updateCustomer(where: {id: string | number}, data: Partial): Promise { - const updatedCustomer = await this.shopyyService.updateCustomer(this.site, where.id, data); + async updateCustomer(id: string | number, data: Partial): Promise { + const updatedCustomer = await this.shopyyService.updateCustomer(this.site, id, data); return this.mapPlatformToUnifiedCustomer(updatedCustomer); } - async deleteCustomer(where: {id: string | number}): Promise { - return await this.shopyyService.deleteCustomer(this.site, where.id); + async deleteCustomer(id: string | number): Promise { + return await this.shopyyService.deleteCustomer(this.site, id); } batchProcessCustomers?(data: BatchOperationDTO): Promise { @@ -219,13 +221,13 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedMedia(createdMedia); } - async updateMedia(where: {id: string | number}, data: any): Promise { - const updatedMedia = await this.shopyyService.updateMedia(this.site, where.id, data); + async updateMedia(id: string | number, data: any): Promise { + const updatedMedia = await this.shopyyService.updateMedia(this.site, id, data); return this.mapPlatformToUnifiedMedia(updatedMedia); } - async deleteMedia(where: {id: string | number}): Promise { - return await this.shopyyService.deleteMedia(this.site, where.id); + async deleteMedia(id: string | number): Promise { + return await this.shopyyService.deleteMedia(this.site, id); } mapMediaSearchParams(params: UnifiedSearchParamsDTO): any { @@ -402,186 +404,148 @@ export class ShopyyAdapter implements ISiteAdapter { raw: item, }; } - shopyyFulfillmentStatusMap = { - // 未发货 - '300': OrderFulfillmentStatus.PENDING, - // 部分发货 - '310': OrderFulfillmentStatus.PARTIALLY_FULFILLED, - // 已发货 - '320': OrderFulfillmentStatus.FULFILLED, - // 已取消 - '330': OrderFulfillmentStatus.CANCELLED, - // 确认发货 + + mapUnifiedToPlatformOrder(data: Partial) { + return data } - private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO { - // 处理多地址结构 - const addresses = item.addresses || []; - const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {}); - - // 尝试从地址列表中获取billing和shipping - // 如果没有明确区分,默认使用默认地址或第一个地址 - const billingAddress = defaultAddress; - const shippingAddress = defaultAddress; - - const billing = { - first_name: billingAddress.first_name || item.first_name || '', - last_name: billingAddress.last_name || item.last_name || '', - fullname: billingAddress.name || `${billingAddress.first_name || item.first_name || ''} ${billingAddress.last_name || item.last_name || ''}`.trim(), - company: billingAddress.company || '', - email: item.email || '', - phone: billingAddress.phone || item.contact || '', - address_1: billingAddress.address1 || '', - address_2: billingAddress.address2 || '', - city: billingAddress.city || '', - state: billingAddress.province || '', - postcode: billingAddress.zip || '', - country: billingAddress.country_name || billingAddress.country_code || item.country?.country_name || '' - }; - - const shipping = { - first_name: shippingAddress.first_name || item.first_name || '', - last_name: shippingAddress.last_name || item.last_name || '', - fullname: shippingAddress.name || `${shippingAddress.first_name || item.first_name || ''} ${shippingAddress.last_name || item.last_name || ''}`.trim(), - company: shippingAddress.company || '', - address_1: shippingAddress.address1 || '', - address_2: shippingAddress.address2 || '', - city: shippingAddress.city || '', - state: shippingAddress.province || '', - postcode: shippingAddress.zip || '', - country: shippingAddress.country_name || shippingAddress.country_code || item.country?.country_name || '' - }; - - return { - id: item.id || item.customer_id, - orders: Number(item.orders_count ?? item.order_count ?? item.orders ?? 0), - total_spend: Number(item.total_spent ?? item.total_spend_amount ?? item.total_spend_money ?? 0), - 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, - shipping, - date_created: - typeof item.created_at === 'number' - ? new Date(item.created_at * 1000).toISOString() - : (typeof item.created_at === 'string' ? item.created_at : item.date_added || ''), - date_modified: - typeof item.updated_at === 'number' - ? new Date(item.updated_at * 1000).toISOString() - : (typeof item.updated_at === 'string' ? item.updated_at : item.date_updated || ''), - raw: item, - }; - } - mapProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery { - return this.mapSearchParams(query) + mapCreateOrderParams(data: Partial): any { + return data } - async getProducts( - params: UnifiedSearchParamsDTO - ): Promise> { - // 转换搜索参数 - const requestParams = this.mapProductQuery(params); - const response = await this.shopyyService.fetchResourcePaged( - this.site, - 'products/list', - requestParams - ); - const { items = [], total, totalPages, page, per_page } = response; - const finalItems = items.map((item) => ({ - ...item, - permalink: `${this.site.websiteUrl}/products/${item.handle}`, - })).map(this.mapProduct.bind(this)) - return { - items: finalItems as UnifiedProductDTO[], - total, - totalPages, - page, - per_page, - }; - } + mapUpdateOrderParams(data: Partial): any { + // 构建 ShopYY 订单更新参数(仅包含传入的字段) + const params: any = {}; - async getAllProducts(params?: UnifiedSearchParamsDTO): Promise { - // Shopyy getAllProducts 暂未实现 - throw new Error('Shopyy getAllProducts 暂未实现'); - } + // 仅当字段存在时才添加到更新参数中 + if (data.status !== undefined) { + // 映射订单状态 + const statusMap = { + [OrderStatus.PENDING]: 100, // pending -> 100 未完成 + [OrderStatus.PROCESSING]: 110, // processing -> 110 待处理 + [OrderStatus.COMPLETED]: 180, // completed -> 180 已完成 + [OrderStatus.CANCEL]: 190 // cancel -> 190 取消 + }; + params.status = statusMap[data.status] || 100; + } - async getProduct(id: string | number): Promise { - // 使用ShopyyService获取单个产品 - const product = await this.shopyyService.getProduct(this.site, id); - return this.mapProduct(product); - } + if (data.payment_method !== undefined) { + params.payment_method = data.payment_method; + } - async createProduct(data: Partial): Promise { - const res = await this.shopyyService.createProduct(this.site, data); - return this.mapProduct(res); - } - async updateProduct(id: string | number, data: Partial): Promise { - // Shopyy update returns boolean? - // shopyyService.updateProduct returns boolean. - // So I can't return the updated product. - // I have to fetch it again or return empty/input. - // Since getProduct is missing, I'll return input data as UnifiedProductDTO (mock). - await this.shopyyService.updateProduct(this.site, String(id), data); - return true; - } + if (data.billing) { + // 更新客户信息 + if (data.billing.first_name !== undefined) { + params.firstname = data.billing.first_name; + } + if (data.billing.last_name !== undefined) { + params.lastname = data.billing.last_name; + } + if (data.billing.email !== undefined) { + params.email = data.billing.email; + } + if (data.billing.phone !== undefined) { + params.phone = data.billing.phone; + } - async updateVariation(productId: string | number, variationId: string | number, data: any): Promise { - await this.shopyyService.updateVariation(this.site, String(productId), String(variationId), data); - return { ...data, id: variationId }; - } - - async getOrderNotes(orderId: string | number): Promise { - return await this.shopyyService.getOrderNotes(this.site, orderId); - } - - async createOrderNote(orderId: string | number, data: any): Promise { - return await this.shopyyService.createOrderNote(this.site, orderId, data); - } - - async deleteProduct(id: string | number): Promise { - // Use batch delete - await this.shopyyService.batchProcessProducts(this.site, { delete: [id] }); - return true; - } - - async batchProcessProducts( - data: { create?: any[]; update?: any[]; delete?: Array } - ): Promise { - return await this.shopyyService.batchProcessProducts(this.site, data); - } - /** - * 将统一的订单查询参数转换为 ShopYY 订单查询参数 - * 包含状态映射等特殊处理 - */ - private mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial { - // 首先使用通用参数转换 - const baseParams = this.mapSearchParams(params); - - // 订单状态映射 - const statusMap = { - 'pending': '100', // 100 未完成 - 'processing': '110', // 110 待处理 - 'completed': "180", // 180 已完成(确认收货) - 'cancelled': '190', // 190 取消 - }; - - // 如果有状态参数,进行特殊映射 - if (baseParams.status) { - const unifiedStatus = baseParams.status - if (statusMap[unifiedStatus]) { - baseParams.status = statusMap[unifiedStatus]; + // 更新账单地址 + params.billing_address = params.billing_address || {}; + if (data.billing.first_name !== undefined) { + params.billing_address.first_name = data.billing.first_name; + } + if (data.billing.last_name !== undefined) { + params.billing_address.last_name = data.billing.last_name; + } + if (data.billing.company !== undefined) { + params.billing_address.company = data.billing.company; + } + if (data.billing.address_1 !== undefined) { + params.billing_address.address1 = data.billing.address_1; + } + if (data.billing.address_2 !== undefined) { + params.billing_address.address2 = data.billing.address_2; + } + if (data.billing.city !== undefined) { + params.billing_address.city = data.billing.city; + } + if (data.billing.state !== undefined) { + params.billing_address.province = data.billing.state; + } + if (data.billing.postcode !== undefined) { + params.billing_address.zip = data.billing.postcode; + } + if (data.billing.country !== undefined) { + params.billing_address.country_code = data.billing.country; } } - - // 处理ID参数 - if (baseParams.id) { - baseParams.ids = baseParams.id; - delete baseParams.id; + + if (data.shipping) { + // 更新送货地址 + params.shipping_address = params.shipping_address || {}; + if (data.shipping.first_name !== undefined) { + params.shipping_address.first_name = data.shipping.first_name; + } + if (data.shipping.last_name !== undefined) { + params.shipping_address.last_name = data.shipping.last_name; + } + if (data.shipping.company !== undefined) { + params.shipping_address.company = data.shipping.company; + } + if (data.shipping.address_1 !== undefined) { + params.shipping_address.address1 = data.shipping.address_1; + } + if (data.shipping.address_2 !== undefined) { + params.shipping_address.address2 = data.shipping.address_2; + } + if (data.shipping.city !== undefined) { + params.shipping_address.city = data.shipping.city; + } + if (data.shipping.state !== undefined) { + params.shipping_address.province = data.shipping.state; + } + if (data.shipping.postcode !== undefined) { + params.shipping_address.zip = data.shipping.postcode; + } + if (data.shipping.country !== undefined) { + params.shipping_address.country_code = data.shipping.country; + } + if (data.shipping.phone !== undefined) { + params.shipping_address.phone = data.shipping.phone; + } } - - return baseParams; + + // 更新订单项 + if (data.line_items && data.line_items.length > 0) { + params.products = data.line_items.map((item: UnifiedOrderLineItemDTO) => ({ + product_id: item.product_id, + quantity: item.quantity, + // price: item.price || '0.00', + sku: item.sku || '', + })); + } + + // 更新物流信息 + if (data.shipping_lines && data.shipping_lines.length > 0) { + const shippingLine = data.shipping_lines[0]; + if (shippingLine.method_title !== undefined) { + params.shipping_method = shippingLine.method_title; + } + if (shippingLine.total !== undefined) { + params.shipping_price = shippingLine.total; + } + } + + // // 更新备注信息 + // if (data.note !== undefined) { + // params.note = data.note; + // } + + return params; + } + + async getOrder(id: string | number): Promise { + const data = await this.shopyyService.getOrder(this.site.id, String(id)); + return this.mapPlatformToUnifiedOrder(data); } async getOrders( @@ -602,6 +566,7 @@ export class ShopyyAdapter implements ISiteAdapter { normalizedParams ); return { + items: items.map(this.mapPlatformToUnifiedOrder.bind(this)), items: items.map(this.mapPlatformToUnifiedOrder.bind(this)), total, totalPages, @@ -610,17 +575,6 @@ export class ShopyyAdapter implements ISiteAdapter { }; } - async countOrders(where: Record): Promise { - // 使用最小分页只获取总数 - const searchParams = { - where, - page: 1, - per_page: 1, - } - const data = await this.getOrders(searchParams); - return data.total || 0; - } - async getAllOrders(params?: UnifiedSearchParamsDTO): Promise { const data = await this.shopyyService.getAllOrders(this.site.id, params); return data.map(this.mapPlatformToUnifiedOrder.bind(this)); @@ -642,12 +596,16 @@ export class ShopyyAdapter implements ISiteAdapter { const requestParams = this.mapCreateOrderParams(data); const createdOrder = await this.shopyyService.createOrder(this.site, requestParams); return this.mapPlatformToUnifiedOrder(createdOrder); + // 使用映射方法转换参数 + const requestParams = this.mapCreateOrderParams(data); + const createdOrder = await this.shopyyService.createOrder(this.site, requestParams); + return this.mapPlatformToUnifiedOrder(createdOrder); } - async updateOrder(where: {id: string | number}, data: Partial): Promise { + async updateOrder(id: string | number, data: Partial): Promise { // 使用映射方法转换参数 const requestParams = this.mapUpdateOrderParams(data); - return await this.shopyyService.updateOrder(this.site, String(where.id), requestParams); + return await this.shopyyService.updateOrder(this.site, String(id), requestParams); } async deleteOrder(where: {id: string | number}): Promise { @@ -658,6 +616,12 @@ export class ShopyyAdapter implements ISiteAdapter { return await this.shopyyService.getOrderNotes(this.site, orderId); } + async createOrderNote(orderId: string | number, data: any): Promise { + return await this.shopyyService.createOrderNote(this.site, orderId, data); + async getOrderNotes(orderId: string | number): Promise { + return await this.shopyyService.getOrderNotes(this.site, orderId); + } + async createOrderNote(orderId: string | number, data: any): Promise { return await this.shopyyService.createOrderNote(this.site, orderId, data); } @@ -684,6 +648,7 @@ export class ShopyyAdapter implements ISiteAdapter { }; } catch (error) { throw new Error(`履行失败: ${error.message}`); + throw new Error(`履行失败: ${error.message}`); } } @@ -925,36 +890,39 @@ export class ShopyyAdapter implements ISiteAdapter { return params; } - async getProduct(where: {id?: string | number, sku?: string}): Promise { - if(!where.id && !where.sku){ - throw new Error('必须传入 id 或 sku') - } - if (where.id) { - // 使用ShopyyService获取单个产品 - const product = await this.shopyyService.getProduct(this.site, where.id); - return this.mapPlatformToUnifiedProduct(product); - } else if (where.sku) { - // 通过sku获取产品 - return this.getProductBySku(where.sku); - } + async getProduct(id: string | number): Promise { + // 使用ShopyyService获取单个产品 + const product = await this.shopyyService.getProduct(this.site, id); + return this.mapPlatformToUnifiedProduct(product); } + async getProducts( async getProducts( params: UnifiedSearchParamsDTO + ): Promise> { + // 转换搜索参数 + const requestParams = this.mapProductQuery(params); + const response = await this.shopyyService.fetchResourcePaged( ): Promise> { // 转换搜索参数 const requestParams = this.mapProductQuery(params); const response = await this.shopyyService.fetchResourcePaged( this.site, - 'media', // Shopyy的媒体API端点可能需要调整 - requestParams + 'products/list', + requestParams ); const { items = [], total, totalPages, page, per_page } = response; const finalItems = items.map((item) => ({ ...item, permalink: `${this.site.websiteUrl}/products/${item.handle}`, })).map(this.mapPlatformToUnifiedProduct.bind(this)) + const { items = [], total, totalPages, page, per_page } = response; + const finalItems = items.map((item) => ({ + ...item, + permalink: `${this.site.websiteUrl}/products/${item.handle}`, + })).map(this.mapPlatformToUnifiedProduct.bind(this)) return { + items: finalItems as UnifiedProductDTO[], items: finalItems as UnifiedProductDTO[], total, totalPages, @@ -963,11 +931,19 @@ export class ShopyyAdapter implements ISiteAdapter { }; } + async getAllProducts(params?: UnifiedSearchParamsDTO): Promise { + // Shopyy getAllProducts 暂未实现 + throw new Error('Shopyy getAllProducts 暂未实现'); async getAllProducts(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllProducts 暂未实现 throw new Error('Shopyy getAllProducts 暂未实现'); } + async createProduct(data: Partial): Promise { + // 使用映射方法转换参数 + const requestParams = this.mapCreateProductParams(data); + const res = await this.shopyyService.createProduct(this.site, requestParams); + return this.mapPlatformToUnifiedProduct(res); async createProduct(data: Partial): Promise { // 使用映射方法转换参数 const requestParams = this.mapCreateProductParams(data); @@ -975,50 +951,24 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedProduct(res); } - async updateProduct(where: {id?: string | number, sku?: string}, data: Partial): Promise { - let productId: string; - if (where.id) { - productId = String(where.id); - } else if (where.sku) { - // 通过sku获取产品ID - const product = await this.getProductBySku(where.sku); - productId = String(product.id); - } else { - throw new Error('必须提供id或sku参数'); - } + async updateProduct(id: string | number, data: Partial): Promise { + // Shopyy update returns boolean? + // shopyyService.updateProduct returns boolean. + // So I can't return the updated product. + // I have to fetch it again or return empty/input. + // Since getProduct is missing, I'll return input data as UnifiedProductDTO (mock). // 使用映射方法转换参数 const requestParams = this.mapUpdateProductParams(data); - await this.shopyyService.updateProduct(this.site, productId, requestParams); + await this.shopyyService.updateProduct(this.site, String(id), requestParams); return true; } - async deleteProduct(where: {id?: string | number, sku?: string}): Promise { - let productId: string | number; - if (where.id) { - productId = where.id; - } else if (where.sku) { - // 通过sku获取产品ID - const product = await this.getProductBySku(where.sku); - productId = product.id; - } else { - throw new Error('必须提供id或sku参数'); - } + async deleteProduct(id: string | number): Promise { // Use batch delete - await this.shopyyService.batchProcessProducts(this.site, { delete: [productId] }); + await this.shopyyService.batchProcessProducts(this.site, { delete: [id] }); return true; } - // 通过sku获取产品详情的私有方法 - private async getProductBySku(sku: string): Promise { - // 使用Shopyy API的搜索功能通过sku查询产品 - const response = await this.shopyyService.getProducts(this.site, 1, 100); - const product = response.items.find((item: any) => item.sku === sku); - if (!product) { - throw new Error(`未找到sku为${sku}的产品`); - } - return this.mapPlatformToUnifiedProduct(product); - } - async batchProcessProducts( data: { create?: any[]; update?: any[]; delete?: Array } ): Promise { @@ -1030,6 +980,9 @@ export class ShopyyAdapter implements ISiteAdapter { } // ========== 评论映射方法 ========== + mapPlatformToUnifiedReview(data: any): UnifiedReviewDTO { + return data + } mapUnifiedToPlatformReview(data: Partial) { return data @@ -1067,19 +1020,19 @@ export class ShopyyAdapter implements ISiteAdapter { async createReview(data: any): Promise { const createdReview = await this.shopyyService.createReview(this.site, data); - return this.mapPlatformToUnifiedReview(createdReview); + return this.mapReview(createdReview); } - async updateReview(where: {id: string | number}, data: any): Promise { - const updatedReview = await this.shopyyService.updateReview(this.site, where.id, data); - return this.mapPlatformToUnifiedReview(updatedReview); + async updateReview(id: string | number, data: any): Promise { + const updatedReview = await this.shopyyService.updateReview(this.site, id, data); + return this.mapReview(updatedReview); } - async deleteReview(where: {id: string | number}): Promise { - return await this.shopyyService.deleteReview(this.site, where.id); + async deleteReview(id: string | number): Promise { + return await this.shopyyService.deleteReview(this.site, id); } - mapPlatformToUnifiedReview(review: any): UnifiedReviewDTO { + mapReview(review: any): UnifiedReviewDTO { // 将ShopYY评论数据映射到统一评论DTO格式 return { id: review.id || review.review_id, @@ -1096,7 +1049,6 @@ export class ShopyyAdapter implements ISiteAdapter { }; } - mapReviewSearchParams(params: UnifiedSearchParamsDTO): any { mapReviewSearchParams(params: UnifiedSearchParamsDTO): any { const { search, page, per_page, where } = params; const shopyyParams: any = { @@ -1122,19 +1074,36 @@ export class ShopyyAdapter implements ISiteAdapter { mapUnifiedToPlatformSubscription(data: Partial) { return data + // ========== 订阅映射方法 ========== + mapPlatformToUnifiedSubscription(data: any): UnifiedSubscriptionDTO { + return data } + mapUnifiedToPlatformSubscription(data: Partial) { + return data + } + + async getSubscriptions( + params: UnifiedSearchParamsDTO + ): Promise> { + throw new Error('Shopyy does not support subscriptions.'); async getSubscriptions( params: UnifiedSearchParamsDTO ): Promise> { throw new Error('Shopyy does not support subscriptions.'); } + async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise { + // Shopyy getAllSubscriptions 暂未实现 + throw new Error('Shopyy getAllSubscriptions 暂未实现'); async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllSubscriptions 暂未实现 throw new Error('Shopyy getAllSubscriptions 暂未实现'); } + // ========== 产品变体映射方法 ========== + mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO { + // 映射变体 // ========== 产品变体映射方法 ========== mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO { // 映射变体 @@ -1218,7 +1187,9 @@ export class ShopyyAdapter implements ISiteAdapter { } // ========== Webhook映射方法 ========== - + mapPlatformToUnifiedWebhook(data: any): UnifiedWebhookDTO { + return data + } mapUnifiedToPlatformWebhook(data: Partial) { return data @@ -1232,9 +1203,9 @@ export class ShopyyAdapter implements ISiteAdapter { return data } - async getWebhook(where: {id: string | number}): Promise { - const webhook = await this.shopyyService.getWebhook(this.site, where.id); - return this.mapPlatformToUnifiedWebhook(webhook); + async getWebhook(id: string | number): Promise { + const webhook = await this.shopyyService.getWebhook(this.site, id); + return this.mapWebhook(webhook); } async getWebhooks(params: UnifiedSearchParamsDTO): Promise { @@ -1267,7 +1238,7 @@ export class ShopyyAdapter implements ISiteAdapter { return await this.shopyyService.deleteWebhook(this.site, where.id); } - mapPlatformToUnifiedWebhook(item: ShopyyWebhook): UnifiedWebhookDTO { + mapWebhook(item: ShopyyWebhook): UnifiedWebhookDTO { return { id: item.id, name: item.webhook_name || `Webhook-${item.id}`, @@ -1297,6 +1268,19 @@ export class ShopyyAdapter implements ISiteAdapter { return links; } + // ========== 辅助方法 ========== + /** + * 通用搜索参数转换方法,处理 where 和 orderBy 的转换 + * 将统一的搜索参数转换为 ShopYY API 所需的参数格式 + */ + mapSearchParams(params: UnifiedSearchParamsDTO): any { + // 处理分页参数 + const page = Number(params.page || 1); + const limit = Number(params.per_page ?? 20); + + // 处理 where 条件 + const query: any = { + ...(params.where || {}), // ========== 辅助方法 ========== /** * 通用搜索参数转换方法,处理 where 和 orderBy 的转换 @@ -1318,6 +1302,14 @@ export class ShopyyAdapter implements ISiteAdapter { query.order_by = dir === 'desc' ? 'desc' : 'asc'; query.order_field = field } + return query; + limit, + } + if(params.orderBy){ + const [field, dir] = Object.entries(params.orderBy)[0]; + query.order_by = dir === 'desc' ? 'desc' : 'asc'; + query.order_field = field + } return query; } @@ -1340,6 +1332,35 @@ export class ShopyyAdapter implements ISiteAdapter { [190]: OrderStatus.CANCEL // 190 取消 转为 cancelled } + shopyyFulfillmentStatusMap = { + // 未发货 + '300': OrderFulfillmentStatus.PENDING, + // 部分发货 + '310': OrderFulfillmentStatus.PARTIALLY_FULFILLED, + // 已发货 + '320': OrderFulfillmentStatus.FULFILLED, + // 已取消 + '330': OrderFulfillmentStatus.CANCELLED, + // 确认发货 + // 映射产品状态: publish -> 1, draft -> 0 + mapStatus = (status: string) => { + return status === 'publish' ? 1 : 0; + }; + + // 映射库存状态: instock -> 1, outofstock -> 0 + mapStockStatus = (stockStatus: string) => { + return stockStatus === 'instock' ? 1 : 0; + }; + + shopyyOrderStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消; + [100]: OrderStatus.PENDING, // 100 未完成 转为 pending + [110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing + // 已发货 + + [180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed + [190]: OrderStatus.CANCEL // 190 取消 转为 cancelled + } + shopyyFulfillmentStatusMap = { // 未发货 '300': OrderFulfillmentStatus.PENDING, diff --git a/src/adapter/woocommerce.adapter.ts b/src/adapter/woocommerce.adapter.ts index 9986a38..7950610 100644 --- a/src/adapter/woocommerce.adapter.ts +++ b/src/adapter/woocommerce.adapter.ts @@ -17,6 +17,8 @@ import { UnifiedVariationPaginationDTO, CreateReviewDTO, UpdateReviewDTO, + CreateReviewDTO, + UpdateReviewDTO, } from '../dto/site-api.dto'; import { UnifiedPaginationDTO, UnifiedSearchParamsDTO } from '../dto/api.dto'; import { @@ -32,6 +34,7 @@ import { import { Site } from '../entity/site.entity'; import { WPService } from '../service/wp.service'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; +import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; export class WooCommerceAdapter implements ISiteAdapter { // 构造函数接收站点配置与服务实例 @@ -43,9 +46,6 @@ export class WooCommerceAdapter implements ISiteAdapter { this.mapPlatformToUnifiedOrder = this.mapPlatformToUnifiedOrder.bind(this); this.mapPlatformToUnifiedWebhook = this.mapPlatformToUnifiedWebhook.bind(this); } - mapUnifiedToPlatformCustomer(data: Partial) { - return data - } batchProcessProducts?(data: BatchOperationDTO): Promise { throw new Error('Method not implemented.'); } @@ -57,8 +57,14 @@ export class WooCommerceAdapter implements ISiteAdapter { } // ========== 客户映射方法 ========== + mapPlatformToUnifiedCustomer(data: any): UnifiedCustomerDTO { + return data; + } + mapUnifiedToPlatformCustomer(data: Partial) { + return data; + } - mapPlatformToUnifiedCustomer(item: WooCustomer): UnifiedCustomerDTO { + mapCustomer(item: WooCustomer): UnifiedCustomerDTO { // 将 WooCommerce 客户数据映射为统一客户DTO // 包含基础信息地址信息与时间信息 return { @@ -76,6 +82,20 @@ export class WooCommerceAdapter implements ISiteAdapter { date_created: item.date_created, date_modified: item.date_modified, raw: item, + id: item.id, + avatar: item.avatar_url, + email: item.email, + orders: Number(item.orders ?? 0), + total_spend: Number(item.total_spent ?? 0), + first_name: item.first_name, + last_name: item.last_name, + username: item.username, + phone: item.billing?.phone || item.shipping?.phone, + billing: item.billing, + shipping: item.shipping, + date_created: item.date_created, + date_modified: item.date_modified, + raw: item, }; } @@ -133,10 +153,10 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 客户操作方法 - async getCustomer(where: {id: string | number}): Promise { + async getCustomer(id: string | number): Promise { const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.get(`customers/${where.id}`); - return this.mapPlatformToUnifiedCustomer(res.data); + const res = await api.get(`customers/${id}`); + return this.mapCustomer(res.data); } async getCustomers(params: UnifiedSearchParamsDTO): Promise> { @@ -147,7 +167,7 @@ export class WooCommerceAdapter implements ISiteAdapter { requestParams ); return { - items: items.map((i: any) => this.mapPlatformToUnifiedCustomer(i)), + items: items.map((i: any) => this.mapCustomer(i)), total, totalPages, page, @@ -164,33 +184,36 @@ export class WooCommerceAdapter implements ISiteAdapter { const requestParams = this.mapCustomerSearchParams(params || {}); const customers = await this.wpService.sdkGetAll(api, 'customers', requestParams); - return customers.map((customer: any) => this.mapPlatformToUnifiedCustomer(customer)); + return customers.map((customer: any) => this.mapCustomer(customer)); } async createCustomer(data: Partial): Promise { const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const res = await api.post('customers', data); - return this.mapPlatformToUnifiedCustomer(res.data); + return this.mapCustomer(res.data); } - async updateCustomer(where: {id: string | number}, data: Partial): Promise { + async updateCustomer(id: string | number, data: Partial): Promise { const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.put(`customers/${where.id}`, data); - return this.mapPlatformToUnifiedCustomer(res.data); + const res = await api.put(`customers/${id}`, data); + return this.mapCustomer(res.data); } - async deleteCustomer(where: {id: string | number}): Promise { + async deleteCustomer(id: string | number): Promise { const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - await api.delete(`customers/${where.id}`, { force: true }); + await api.delete(`customers/${id}`, { force: true }); return true; } // ========== 媒体映射方法 ========== + mapPlatformToUnifiedMedia(data: any): UnifiedMediaDTO { + return data; + } mapUnifiedToPlatformMedia(data: Partial) { return data; } - mapPlatformToUnifiedMedia(item: WpMedia): UnifiedMediaDTO { + mapMedia(item: WpMedia): UnifiedMediaDTO { // 将 WordPress 媒体数据映射为统一媒体DTO // 兼容不同字段命名的时间信息 return { @@ -215,7 +238,7 @@ export class WooCommerceAdapter implements ISiteAdapter { params ); return { - items: items.map(this.mapPlatformToUnifiedMedia.bind(this)), + items: items.map(this.mapMedia.bind(this)), total, totalPages, page, @@ -227,21 +250,21 @@ export class WooCommerceAdapter implements ISiteAdapter { // 使用sdkGetAll获取所有媒体数据,不受分页限制 const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const media = await this.wpService.sdkGetAll(api, 'media', params); - return media.map((mediaItem: any) => this.mapPlatformToUnifiedMedia(mediaItem)); + return media.map((mediaItem: any) => this.mapMedia(mediaItem)); } createMedia(file: any): Promise { throw new Error('Method not implemented.'); } - async updateMedia(where: {id: string | number}, data: any): Promise { + async updateMedia(id: string | number, data: any): Promise { // 更新媒体信息 - return await this.wpService.updateMedia(Number(this.site.id), Number(where.id), data); + return await this.wpService.updateMedia(Number(this.site.id), Number(id), data); } - async deleteMedia(where: {id: string | number}): Promise { + async deleteMedia(id: string | number): Promise { // 删除媒体资源 - await this.wpService.deleteMedia(Number(this.site.id), Number(where.id), true); + await this.wpService.deleteMedia(Number(this.site.id), Number(id), true); return true; } @@ -252,6 +275,9 @@ export class WooCommerceAdapter implements ISiteAdapter { } // ========== 订单映射方法 ========== + mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO { + return data; + } mapUnifiedToPlatformOrder(data: Partial) { return data; } @@ -263,7 +289,6 @@ export class WooCommerceAdapter implements ISiteAdapter { return data; } - mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial { mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial { // 计算分页参数 const page = Number(params.page ?? 1); @@ -348,7 +373,7 @@ export class WooCommerceAdapter implements ISiteAdapter { ].filter(Boolean).join(', '); } - mapPlatformToUnifiedOrder(item: WooOrder): UnifiedOrderDTO { + mapOrder(item: WooOrder): UnifiedOrderDTO { // 将 WooCommerce 订单数据映射为统一订单DTO // 包含账单地址与收货地址以及创建与更新时间 @@ -402,11 +427,11 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 订单操作方法 - async getOrder(where: {id: string | number}): Promise { + async getOrder(id: string | number): Promise { // 获取单个订单详情 const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.get(`orders/${where.id}`); - return this.mapPlatformToUnifiedOrder(res.data); + const res = await api.get(`orders/${id}`); + return this.mapOrder(res.data); } async getOrders(params: UnifiedSearchParamsDTO): Promise> { @@ -436,7 +461,7 @@ export class WooCommerceAdapter implements ISiteAdapter { ); return { - items: ordersWithFulfillments.map(this.mapPlatformToUnifiedOrder), + items: ordersWithFulfillments.map(this.mapOrder), total, totalPages, page, @@ -448,7 +473,7 @@ export class WooCommerceAdapter implements ISiteAdapter { // 使用sdkGetAll获取所有订单数据,不受分页限制 const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const orders = await this.wpService.sdkGetAll(api, 'orders', params); - return orders.map((order: any) => this.mapPlatformToUnifiedOrder(order)); + return orders.map((order: any) => this.mapOrder(order)); } async countOrders(where: Record): Promise { @@ -467,18 +492,18 @@ export class WooCommerceAdapter implements ISiteAdapter { // 创建订单并返回统一订单DTO const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const res = await api.post('orders', data); - return this.mapPlatformToUnifiedOrder(res.data); + return this.mapOrder(res.data); } - async updateOrder(where: {id: string | number}, data: Partial): Promise { + async updateOrder(id: string | number, data: Partial): Promise { // 更新订单并返回布尔结果 - return await this.wpService.updateOrder(this.site, String(where.id), data as any); + return await this.wpService.updateOrder(this.site, String(id), data as any); } - async deleteOrder(where: {id: string | number}): Promise { + async deleteOrder(id: string | number): Promise { // 删除订单 const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - await api.delete(`orders/${where.id}`, { force: true }); + await api.delete(`orders/${id}`, { force: true }); return true; } @@ -583,6 +608,9 @@ export class WooCommerceAdapter implements ISiteAdapter { } // ========== 产品映射方法 ========== + mapPlatformToUnifiedProduct(data: any): UnifiedProductDTO { + return data; + } mapUnifiedToPlatformProduct(data: Partial) { return data; } @@ -612,6 +640,40 @@ export class WooCommerceAdapter implements ISiteAdapter { return String(value).split(',').map(v => v.trim()).filter(Boolean); }; + if (where.search_fields ?? where.searchFields) mapped.search_fields = toArray(where.search_fields ?? where.searchFields); + if (where.after ?? where.date_created_after ?? where.created_after) mapped.after = String(where.after ?? where.date_created_after ?? where.created_after); + if (where.before ?? where.date_created_before ?? where.created_before) mapped.before = String(where.before ?? where.date_created_before ?? where.created_before); + if (where.modified_after ?? where.date_modified_after) mapped.modified_after = String(where.modified_after ?? where.date_modified_after); + if (where.modified_before ?? where.date_modified_before) mapped.modified_before = String(where.modified_before ?? where.date_modified_before); + if (where.dates_are_gmt ?? where.datesAreGmt) mapped.dates_are_gmt = Boolean(where.dates_are_gmt ?? where.datesAreGmt); + if (where.exclude ?? where.exclude_ids ?? where.excludedIds) mapped.exclude = toArray(where.exclude ?? where.exclude_ids ?? where.excludedIds); + if (where.include ?? where.ids) mapped.include = toArray(where.include ?? where.ids); + if (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); + if (where.slug) mapped.slug = String(where.slug); + if (!mapped.status && (where.status || where.include_status || where.exclude_status || where.includeStatus || where.excludeStatus)) { + if (where.include_status ?? where.includeStatus) mapped.include_status = String(where.include_status ?? where.includeStatus); + if (where.exclude_status ?? where.excludeStatus) mapped.exclude_status = String(where.exclude_status ?? where.excludeStatus); + if (where.status) mapped.status = String(where.status); + } + if (where.type) mapped.type = String(where.type); + if (where.include_types ?? where.includeTypes) mapped.include_types = String(where.include_types ?? where.includeTypes); + if (where.exclude_types ?? where.excludeTypes) mapped.exclude_types = String(where.exclude_types ?? where.excludeTypes); + if (where.sku) mapped.sku = String(where.sku); + if (where.featured ?? where.isFeatured) mapped.featured = Boolean(where.featured ?? where.isFeatured); + if (where.category ?? where.categoryId) mapped.category = String(where.category ?? where.categoryId); + if (where.tag ?? where.tagId) mapped.tag = String(where.tag ?? where.tagId); + if (where.shipping_class ?? where.shippingClass) mapped.shipping_class = String(where.shipping_class ?? where.shippingClass); + if (where.attribute ?? where.attributeName) mapped.attribute = String(where.attribute ?? where.attributeName); + if (where.attribute_term ?? where.attributeTermId ?? where.attributeTerm) mapped.attribute_term = String(where.attribute_term ?? where.attributeTermId ?? where.attributeTerm); + if (where.tax_class ?? where.taxClass) mapped.tax_class = String(where.tax_class ?? where.taxClass); + if (where.on_sale ?? where.onSale) mapped.on_sale = Boolean(where.on_sale ?? where.onSale); + if (where.min_price ?? where.minPrice) mapped.min_price = String(where.min_price ?? where.minPrice); + if (where.max_price ?? where.maxPrice) mapped.max_price = String(where.max_price ?? where.maxPrice); + if (where.stock_status ?? where.stockStatus) mapped.stock_status = String(where.stock_status ?? where.stockStatus); + if (where.virtual !== undefined) mapped.virtual = Boolean(where.virtual); + if (where.downloadable !== undefined) mapped.downloadable = Boolean(where.downloadable); if (where.search_fields ?? where.searchFields) mapped.search_fields = toArray(where.search_fields ?? where.searchFields); if (where.after ?? where.date_created_after ?? where.created_after) mapped.after = String(where.after ?? where.date_created_after ?? where.created_after); if (where.before ?? where.date_created_before ?? where.created_before) mapped.before = String(where.before ?? where.date_created_before ?? where.created_before); @@ -650,7 +712,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return mapped; } - mapPlatformToUnifiedProduct(item: WooProduct): UnifiedProductDTO { + mapProduct(item: WooProduct): UnifiedProductDTO { // 将 WooCommerce 产品数据映射为统一产品DTO // 保留常用字段与时间信息以便前端统一展示 // https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties @@ -738,22 +800,11 @@ export class WooCommerceAdapter implements ISiteAdapter { raw: item, }; } - async getProduct({id, sku}: {id?: string, sku?:string}){ - if(id) return this.getProductById(id); - if(sku) return this.getProductBySku(sku) - return this.getProductById(id || sku || ''); - } - async getProductBySku(sku: string){ - const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.get(`products?sku=${sku}`); - const product = res.data[0]; - return this.mapPlatformToUnifiedProduct(product); - } + // 产品操作方法 - async getProductById(id: string | number): Promise { + async getProduct(id: string | number): Promise { // 获取单个产品详情并映射为统一产品DTO const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.get(`products/${id}`); const product = res.data; @@ -773,9 +824,10 @@ export class WooCommerceAdapter implements ISiteAdapter { } } - return this.mapPlatformToUnifiedProduct(product); + return this.mapProduct(product); } + async getProducts(params: UnifiedSearchParamsDTO): Promise> { async getProducts(params: UnifiedSearchParamsDTO): Promise> { // 获取产品列表并使用统一分页结构返回 const requestParams = this.mapProductSearchParams(params); @@ -784,6 +836,11 @@ export class WooCommerceAdapter implements ISiteAdapter { 'products', requestParams ); + const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged( + this.site, + 'products', + requestParams + ); // 对于类型为 variable 的产品,需要加载完整的变体数据 const productsWithVariations = await Promise.all( @@ -844,7 +901,7 @@ export class WooCommerceAdapter implements ISiteAdapter { }) ); - return productsWithVariations.map((product: any) => this.mapPlatformToUnifiedProduct(product)); + return productsWithVariations.map((product: any) => this.mapProduct(product)); } async createProduct(data: Partial): Promise { @@ -855,17 +912,7 @@ export class WooCommerceAdapter implements ISiteAdapter { async updateProduct(where: {id?: string | number, sku?: string}, data: Partial): Promise { // 更新产品并返回统一产品DTO - let productId: string; - if (where.id) { - productId = String(where.id); - } else if (where.sku) { - // 通过sku获取产品ID - const product = await this.getProductBySku(where.sku); - productId = String(product.id); - } else { - throw new Error('必须提供id或sku参数'); - } - const res = await this.wpService.updateProduct(this.site, productId, data as any); + const res = await this.wpService.updateProduct(this.site, String(id), data as any); return res; } @@ -890,87 +937,12 @@ export class WooCommerceAdapter implements ISiteAdapter { } } - async batchProcessProducts( - data: { create?: any[]; update?: any[]; delete?: Array } - ): Promise { - // 批量处理产品增删改 - return await this.wpService.batchProcessProducts(this.site, data); + // ========== 评论映射方法 ========== + mapPlatformToUnifiedReview(data: any): UnifiedReviewDTO { + return data; } - - async getOrders( - params: UnifiedSearchParamsDTO - ): Promise> { - 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.mapOrder), - total, - totalPages, - page, - per_page, - }; - } - - async countOrders(where: Record): Promise { - // 使用最小分页只获取总数 - const searchParams: UnifiedSearchParamsDTO = { - where, - page: 1, - per_page: 1, - }; - const requestParams = this.mapOrderSearchParams(searchParams); - const { total } = await this.wpService.fetchResourcePaged(this.site, 'orders', requestParams); - return total || 0; - } - - async getOrder(id: string | number): Promise { - // 获取单个订单详情 - const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.get(`orders/${id}`); - return this.mapOrder(res.data); - } - - async getAllOrders(params?: UnifiedSearchParamsDTO): Promise { - // 使用sdkGetAll获取所有订单数据,不受分页限制 - const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const orders = await this.wpService.sdkGetAll(api, 'orders', params); - return orders.map((order: any) => this.mapOrder(order)); - } - - async createOrder(data: Partial): Promise { - // 创建订单并返回统一订单DTO - const api = (this.wpService as any).createApi(this.site, 'wc/v3'); - const res = await api.post('orders', data); - return this.mapOrder(res.data); - } - - async updateOrder(id: string | number, data: Partial): Promise { - // 更新订单并返回布尔结果 - return await this.wpService.updateOrder(this.site, String(id), data as any); + mapUnifiedToPlatformReview(data: Partial) { + return data; } mapCreateReviewParams(data: CreateReviewDTO) { @@ -980,7 +952,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return data; } - mapPlatformToUnifiedReview(item: any): UnifiedReviewDTO & { raw: any } { + mapReview(item: any): UnifiedReviewDTO & { raw: any } { // 将 WooCommerce 评论数据映射为统一评论DTO return { id: item.id, @@ -995,6 +967,8 @@ export class WooCommerceAdapter implements ISiteAdapter { }; } + // 评论操作方法 + async getReviews(params: UnifiedSearchParamsDTO): Promise { // 评论操作方法 async getReviews(params: UnifiedSearchParamsDTO): Promise { // 获取评论列表并使用统一分页结构返回 @@ -1004,6 +978,11 @@ export class WooCommerceAdapter implements ISiteAdapter { 'products/reviews', requestParams ); + const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged( + this.site, + 'products/reviews', + requestParams + ); return { items: items.map(this.mapPlatformToUnifiedReview.bind(this)), total, @@ -1030,16 +1009,19 @@ export class WooCommerceAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedReview(res); } - async deleteReview(where: {id: number}): Promise { - return await this.wpService.deleteReview(this.site, where.id); + async deleteReview(id: number): Promise { + return await this.wpService.deleteReview(this.site, id); } // ========== 订阅映射方法 ========== + mapPlatformToUnifiedSubscription(data: any): UnifiedSubscriptionDTO { + return data; + } mapUnifiedToPlatformSubscription(data: Partial) { return data; } - mapPlatformToUnifiedSubscription(item: WooSubscription): UnifiedSubscriptionDTO { + mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO { // 将 WooCommerce 订阅数据映射为统一订阅DTO // 若缺少创建时间则回退为开始时间 return { @@ -1049,14 +1031,26 @@ export class WooCommerceAdapter implements ISiteAdapter { billing_period: item.billing_period, billing_interval: item.billing_interval, date_created: item.date_created ?? item.start_date, + status: item.status, + customer_id: item.customer_id, + billing_period: item.billing_period, + billing_interval: item.billing_interval, + date_created: item.date_created ?? item.start_date, date_modified: item.date_modified, start_date: item.start_date, next_payment_date: item.next_payment_date, line_items: item.line_items, + start_date: item.start_date, + next_payment_date: item.next_payment_date, + line_items: item.line_items, raw: item, }; } + // 订阅操作方法 + async getSubscriptions(params: UnifiedSearchParamsDTO): Promise> { + // 获取订阅列表并映射为统一订阅DTO集合 + // 订阅操作方法 async getSubscriptions(params: UnifiedSearchParamsDTO): Promise> { // 获取订阅列表并映射为统一订阅DTO集合 @@ -1064,9 +1058,11 @@ export class WooCommerceAdapter implements ISiteAdapter { this.site, 'subscriptions', params + 'subscriptions', + params ); return { - items: items.map(this.mapPlatformToUnifiedSubscription), + items: items.map(this.mapSubscription), total, totalPages, page, @@ -1075,13 +1071,21 @@ export class WooCommerceAdapter implements ISiteAdapter { }; } + async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise { + // 使用sdkGetAll获取所有订阅数据,不受分页限制 async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise { // 使用sdkGetAll获取所有订阅数据,不受分页限制 const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const subscriptions = await this.wpService.sdkGetAll(api, 'subscriptions', params); - return subscriptions.map((subscription: any) => this.mapPlatformToUnifiedSubscription(subscription)); + return subscriptions.map((subscription: any) => this.mapSubscription(subscription)); } + // ========== 变体映射方法 ========== + mapPlatformToUnifiedVariation(data: any): UnifiedProductVariationDTO { + return data; + } + mapUnifiedToPlatformVariation(data: Partial) { + return data; // ========== 变体映射方法 ========== mapPlatformToUnifiedVariation(data: any): UnifiedProductVariationDTO { return data; @@ -1091,7 +1095,6 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 映射 WooCommerce 变体到统一格式 - mapVariation(variation: any, productName?: string): UnifiedProductVariationDTO { mapVariation(variation: any, productName?: string): UnifiedProductVariationDTO { // 将变体属性转换为统一格式 const mappedAttributes = variation.attributes && Array.isArray(variation.attributes) @@ -1141,6 +1144,7 @@ export class WooCommerceAdapter implements ISiteAdapter { }; } + // 变体操作方法 // 变体操作方法 // 获取产品变体列表 async getVariations(productId: string | number, params: UnifiedSearchParamsDTO): Promise { @@ -1316,4 +1320,145 @@ export class WooCommerceAdapter implements ISiteAdapter { throw new Error(`删除产品变体失败: ${error instanceof Error ? error.message : String(error)}`); } } + + // ========== 网络钩子映射方法 ========== + mapPlatformToUnifiedWebhook(data: any): UnifiedWebhookDTO { + return data; + } + mapUnifiedToPlatformWebhook(data: Partial) { + return data; + } + + mapCreateWebhookParams(data: CreateWebhookDTO) { + return data; + } + mapUpdateWebhookParams(data: UpdateWebhookDTO) { + return data; + } + + // 映射 WooCommerce webhook 到统一格式 + mapWebhook(webhook: WooWebhook): UnifiedWebhookDTO { + return { + id: webhook.id.toString(), + name: webhook.name, + status: webhook.status, + topic: webhook.topic, + delivery_url: webhook.delivery_url, + secret: webhook.secret, + api_version: webhook.api_version, + date_created: webhook.date_created, + date_modified: webhook.date_modified, + // metadata: webhook.meta_data || [], + }; + } + + // 网络钩子操作方法 + // 获取站点的 webhooks 列表 + async getWebhooks(params: UnifiedSearchParamsDTO): Promise { + try { + const result = await this.wpService.getWebhooks(this.site, params); + + return { + items: (result.items as WooWebhook[]).map(this.mapWebhook), + 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)}`); + } + } + + // 获取所有webhooks + async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise { + try { + const api = (this.wpService as any).createApi(this.site, 'wc/v3'); + const webhooks = await this.wpService.sdkGetAll(api, 'webhooks', params); + return webhooks.map((webhook: any) => this.mapWebhook(webhook)); + } catch (error) { + throw new Error(`Failed to get all webhooks: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // 获取单个 webhook 详情 + async getWebhook(id: string | number): Promise { + try { + const result = await this.wpService.getWebhook(this.site, id); + return this.mapWebhook(result as WooWebhook); + } catch (error) { + throw new Error(`Failed to get webhook: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // 创建新的 webhook + async createWebhook(data: CreateWebhookDTO): Promise { + try { + const params = { + name: data.name, + status: 'active', // 默认状态为活跃 + topic: data.topic, + delivery_url: data.delivery_url, + secret: data.secret, + api_version: data.api_version || 'wp/v2', + }; + const result = await this.wpService.createWebhook(this.site, params); + return this.mapWebhook(result as WooWebhook); + } catch (error) { + throw new Error(`Failed to create webhook: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // 更新现有的 webhook + async updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise { + try { + const params = { + ...(data.name ? { name: data.name } : {}), + ...(data.status ? { status: data.status } : {}), + ...(data.topic ? { topic: data.topic } : {}), + ...(data.delivery_url ? { delivery_url: data.delivery_url } : {}), + ...(data.secret ? { secret: data.secret } : {}), + ...(data.api_version ? { api_version: data.api_version } : {}), + }; + const result = await this.wpService.updateWebhook(this.site, id, params); + return this.mapWebhook(result as WooWebhook); + } catch (error) { + throw new Error(`Failed to update webhook: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // 删除指定的 webhook + async deleteWebhook(id: string | number): Promise { + try { + await this.wpService.deleteWebhook(this.site, id); + return true; + } catch (error) { + throw new Error(`Failed to delete webhook: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // ========== 其他方法 ========== + async getLinks(): Promise> { + const baseUrl = this.site.apiUrl; + const links = [ + { title: '访问网站', url: baseUrl }, + { title: '管理后台', url: `${baseUrl}/wp-admin/` }, + { title: '订单管理', url: `${baseUrl}/wp-admin/edit.php?post_type=shop_order` }, + { title: '产品管理', url: `${baseUrl}/wp-admin/edit.php?post_type=product` }, + { title: '客户管理', url: `${baseUrl}/wp-admin/users.php` }, + { title: '插件管理', url: `${baseUrl}/wp-admin/plugins.php` }, + { title: '主题管理', url: `${baseUrl}/wp-admin/themes.php` }, + { title: 'WooCommerce设置', url: `${baseUrl}/wp-admin/admin.php?page=wc-settings` }, + { title: 'WooCommerce报告', url: `${baseUrl}/wp-admin/admin.php?page=wc-reports` }, + ]; + return links; + } + + batchProcessOrders?(data: { create?: any[]; update?: any[]; delete?: Array; }): Promise { + throw new Error('Method not implemented.'); + } + + batchProcessCustomers?(data: { create?: any[]; update?: any[]; delete?: Array; }): Promise { + throw new Error('Method not implemented.'); + } } diff --git a/src/interface/site-adapter.interface.ts b/src/interface/site-adapter.interface.ts index edba72b..83c21ae 100644 --- a/src/interface/site-adapter.interface.ts +++ b/src/interface/site-adapter.interface.ts @@ -19,7 +19,7 @@ import { import { UnifiedPaginationDTO, UnifiedSearchParamsDTO } from '../dto/api.dto'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; -export interface ISiteAdapter { +export interface ISiteAdapter { // ========== 客户映射方法 ========== /** * 将平台客户数据转换为统一客户数据格式 @@ -36,19 +36,16 @@ export interface ISiteAdapter { mapUnifiedToPlatformCustomer(data: Partial): any; /** + * 获取单个客户 * 获取单个客户 */ - getCustomer(where: Partial>): Promise; + getCustomer(id: string | number): Promise; /** + * 获取客户列表 * 获取客户列表 */ - getOrders(params: UnifiedSearchParamsDTO): Promise>; - - /** - * 获取订单总数 - */ - countOrders(params: Record): Promise; + getCustomers(params: UnifiedSearchParamsDTO): Promise>; /** * 获取所有客户 @@ -63,12 +60,12 @@ export interface ISiteAdapter { /** * 更新客户 */ - updateCustomer(where: Partial>, data: Partial): Promise; + updateCustomer(id: string | number, data: Partial): Promise; /** * 删除客户 */ - deleteCustomer(where: Partial>): Promise; + deleteCustomer(id: string | number): Promise; /** * 批量处理客户 @@ -105,69 +102,96 @@ export interface ISiteAdapter { */ createMedia(file: any): Promise; + // ========== 订单映射方法 ========== // ========== 订单映射方法 ========== /** + * 将平台订单数据转换为统一订单数据格式 + * @param data 平台特定订单数据 + * @returns 统一订单数据格式 * 将平台订单数据转换为统一订单数据格式 * @param data 平台特定订单数据 * @returns 统一订单数据格式 */ mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO; + mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO; /** + * 将统一订单数据格式转换为平台订单数据 + * @param data 统一订单数据格式 + * @returns 平台特定订单数据 * 将统一订单数据格式转换为平台订单数据 * @param data 统一订单数据格式 * @returns 平台特定订单数据 */ mapUnifiedToPlatformOrder(data: Partial): any; + mapUnifiedToPlatformOrder(data: Partial): any; /** + * 将统一订单创建参数转换为平台订单创建参数 + * @param data 统一订单创建参数 + * @returns 平台订单创建参数 * 将统一订单创建参数转换为平台订单创建参数 * @param data 统一订单创建参数 * @returns 平台订单创建参数 */ mapCreateOrderParams(data: Partial): any; + mapCreateOrderParams(data: Partial): any; /** + * 将统一订单更新参数转换为平台订单更新参数 + * @param data 统一订单更新参数 + * @returns 平台订单更新参数 * 将统一订单更新参数转换为平台订单更新参数 * @param data 统一订单更新参数 * @returns 平台订单更新参数 */ mapUpdateOrderParams(data: Partial): any; + mapUpdateOrderParams(data: Partial): any; /** + * 获取单个订单 * 获取单个订单 */ - getOrder(where: Partial>): Promise; + getOrder(id: string | number): Promise; /** + * 获取订单列表 * 获取订单列表 */ getOrders(params: UnifiedSearchParamsDTO): Promise>; + getOrders(params: UnifiedSearchParamsDTO): Promise>; /** + * 获取所有订单 * 获取所有订单 */ getAllOrders(params?: UnifiedSearchParamsDTO): Promise; + getAllOrders(params?: UnifiedSearchParamsDTO): Promise; /** + * 获取订单总数 * 获取订单总数 */ - countOrders(params: Record): Promise; + countOrders(params: Record): Promise; /** + * 创建订单 * 创建订单 */ createOrder(data: Partial): Promise; + createOrder(data: Partial): Promise; /** + * 更新订单 * 更新订单 */ - updateOrder(where: Partial>, data: Partial): Promise; + updateOrder(id: string | number, data: Partial): Promise; /** + * 删除订单 * 删除订单 */ - deleteOrder(where: Partial>): Promise; + deleteOrder(id: string | number): Promise; /** * 获取订单备注 @@ -264,7 +288,7 @@ export interface ISiteAdapter { /** * 获取单个产品 */ - getProduct(where: Partial>): Promise; + getProduct(id: string | number): Promise; /** * 获取产品列表 @@ -284,12 +308,12 @@ export interface ISiteAdapter { /** * 更新产品 */ - updateProduct(where: Partial>, data: Partial): Promise; + updateProduct(id: string | number, data: Partial): Promise; /** * 删除产品 */ - deleteProduct(where: Partial>): Promise; + deleteProduct(id: string | number): Promise; /** * 批量处理产品 @@ -343,12 +367,12 @@ export interface ISiteAdapter { /** * 更新评论 */ - updateReview(where: Partial>, data: UpdateReviewDTO): Promise; + updateReview(id: number, data: UpdateReviewDTO): Promise; /** * 删除评论 */ - deleteReview(where: Partial>): Promise; + deleteReview(id: number): Promise; // ========== 订阅映射方法 ========== /** @@ -466,7 +490,7 @@ export interface ISiteAdapter { /** * 获取单个webhook */ - getWebhook(where: Partial>): Promise; + getWebhook(id: string | number): Promise; /** * 获取webhooks列表 @@ -486,16 +510,16 @@ export interface ISiteAdapter { /** * 更新webhook */ - updateWebhook(where: Partial>, data: UpdateWebhookDTO): Promise; + updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise; /** * 删除webhook */ - deleteWebhook(where: Partial>): Promise; + deleteWebhook(id: string | number): Promise; // ========== 站点/其他方法 ========== /** * 获取站点链接列表 */ - getLinks(): Promise>; + getLinks(): Promise>; }