import { ISiteAdapter } from '../interface/site-adapter.interface'; import { ShopyyService } from '../service/shopyy.service'; import { UnifiedCustomerDTO, UnifiedMediaDTO, UnifiedOrderDTO, UnifiedOrderLineItemDTO, UnifiedProductDTO, UnifiedProductVariationDTO, UnifiedSubscriptionDTO, UnifiedReviewPaginationDTO, UnifiedReviewDTO, UnifiedWebhookDTO, UnifiedWebhookPaginationDTO, CreateWebhookDTO, UpdateWebhookDTO, UnifiedAddressDTO, UnifiedShippingLineDTO, OrderFulfillmentStatus, FulfillmentDTO, CreateReviewDTO, CreateVariationDTO, UpdateReviewDTO, } from '../dto/site-api.dto'; import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto'; import { ShopyyAllProductQuery, ShopyyCustomer, ShopyyOrder, ShopyyOrderQuery, ShopyyProduct, ShopyyProductQuery, ShopyyVariant, ShopyyWebhook, } from '../dto/shopyy.dto'; import { OrderStatus, } from '../enums/base.enum'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; import dayjs = require('dayjs'); export class ShopyyAdapter implements ISiteAdapter { shopyyFinancialStatusMap= { '200': '待支付', '210': "支付中", '220':"部分支付", '230':"已支付", '240':"支付失败", '250':"部分退款", '260':"已退款", '290':"已取消", } constructor(private site: any, private shopyyService: ShopyyService) { 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); } // ========== 客户映射方法 ========== 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 || '' }; 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, }; } mapUnifiedToPlatformCustomer(data: Partial) { 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); return this.mapPlatformToUnifiedCustomer(customer); } async getCustomers(params: UnifiedSearchParamsDTO): Promise> { const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchCustomersPaged(this.site, params); return { items: items.map(this.mapPlatformToUnifiedCustomer.bind(this)), total, totalPages, page, per_page }; } async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllCustomers 暂未实现 throw new Error('Shopyy getAllCustomers 暂未实现'); } async createCustomer(data: Partial): Promise { const createdCustomer = await this.shopyyService.createCustomer(this.site, data); return this.mapPlatformToUnifiedCustomer(createdCustomer); } async updateCustomer(where: {id: string | number}, data: Partial): Promise { const updatedCustomer = await this.shopyyService.updateCustomer(this.site, where.id, data); return this.mapPlatformToUnifiedCustomer(updatedCustomer); } async deleteCustomer(where: {id: string | number}): Promise { return await this.shopyyService.deleteCustomer(this.site, where.id); } batchProcessCustomers?(data: BatchOperationDTO): Promise { throw new Error('Method not implemented.'); } // ========== 媒体映射方法 ========== mapPlatformToUnifiedMedia(data: any): UnifiedMediaDTO { // 映射媒体项目 return { id: data.id, date_created: data.created_at, date_modified: data.updated_at, source_url: data.src, title: data.alt || '', media_type: '', // Shopyy API未提供,暂时留空 mime_type: '', // Shopyy API未提供,暂时留空 }; } mapUnifiedToPlatformMedia(data: Partial) { return data } async getMedia( params: UnifiedSearchParamsDTO ): Promise> { const requestParams = this.mapMediaSearchParams(params); const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchResourcePaged( this.site, 'media', // Shopyy的媒体API端点可能需要调整 requestParams ); return { items: items.map(this.mapPlatformToUnifiedMedia.bind(this)), total, totalPages, page, per_page, }; } async getAllMedia(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllMedia 暂未实现 throw new Error('Shopyy getAllMedia 暂未实现'); } async createMedia(file: any): Promise { const createdMedia = await this.shopyyService.createMedia(this.site, file); return this.mapPlatformToUnifiedMedia(createdMedia); } async updateMedia(where: {id: string | number}, data: any): Promise { const updatedMedia = await this.shopyyService.updateMedia(this.site, where.id, data); return this.mapPlatformToUnifiedMedia(updatedMedia); } async deleteMedia(where: {id: string | number}): Promise { return await this.shopyyService.deleteMedia(this.site, where.id); } mapMediaSearchParams(params: UnifiedSearchParamsDTO): any { return this.mapSearchParams(params) } // ========== 订单映射方法 ========== mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO { // console.log(item) if(!item) throw new Error('订单数据不能为空') // 提取账单和送货地址 如果不存在则为空对象 const billing = (item).bill_address || {}; const shipping = (item as any).shipping_address || {}; // 构建账单地址对象 const billingObj: UnifiedAddressDTO = { 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 as any).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 || '', method_title: item.payment_method || '', country: billing.country_name || billing.country_code || item.payment_country || '', }; // 构建送货地址对象 const shippingObj: UnifiedAddressDTO = { first_name: shipping.first_name || item.firstname || '', last_name: shipping.last_name || item.lastname || '', fullname: shipping.name || '', company: shipping.company || '', address_1: shipping.address1 || (typeof item.shipping_address === 'string' ? 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 || '', method_title: item.payment_method || '', country: shipping.country_name || shipping.country_code || item.shipping_country || '', }; // 构建送货地址对象 const shipping_lines: UnifiedShippingLineDTO[] = [ { id: item.fulfillments?.[0]?.id || 0, method_title: item.payment_method || '', method_id: item.payment_method || '', total: Number(item.current_shipping_price).toExponential(2) || '0.00', total_tax: '0.00', taxes: [], meta_data: [], }, ]; // 格式化地址为字符串 const formatAddress = (addr: UnifiedAddressDTO) => { return [ addr.fullname, addr.company, addr.address_1, addr.address_2, addr.city, addr.state, addr.postcode, addr.country, addr.phone, ] .filter(Boolean) .join(', '); }; const lineItems: UnifiedOrderLineItemDTO[] = (item.products || []).map( (p: any) => ({ id: p.id, name: p.product_title || p.name, product_id: p.product_id, quantity: p.quantity, total: String(p.price ?? ''), sku: p.sku_code || '', price: String(p.price ?? ''), }) ); // 货币符号 const currencySymbols: Record = { 'EUR': '€', 'USD': '$', 'GBP': '£', 'JPY': '¥', 'AUD': 'A$', 'CAD': 'C$', 'CHF': 'CHF', 'CNY': '¥', 'HKD': 'HK$', 'NZD': 'NZ$', 'SGD': 'S$' // 可以根据需要添加更多货币代码和符号 }; // 映射订单状态,如果不存在则默认 pending const status = this.shopyyOrderStatusMap[item.status ?? item.order_status] || OrderStatus.PENDING; const finalcial_status = this.shopyyFinancialStatusMap[item.financial_status] // 发货状态 const fulfillment_status = this.shopyyFulfillmentStatusMap[item.fulfillment_status]; return { id: item.id || item.order_id, number: item.order_number || item.order_sn, status, financial_status: finalcial_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, customer_email: item.customer_email || item.email, line_items: lineItems, sales: lineItems, // 兼容前端 billing: billingObj, shipping: shippingObj, billing_full_address: formatAddress(billingObj), shipping_full_address: formatAddress(shippingObj), payment_method: item.payment_method, shipping_lines: shipping_lines || [], fee_lines: item.fee_lines || [], coupon_lines: item.coupon_lines || [], customer_ip_address: item.ip || '', device_type: item.source_device || '', utm_source: item.utm_source || '', source_type: 'shopyy', // FIXME date_paid: typeof item.pay_at === 'number' ? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString() : null, refunds: [], currency_symbol: (currencySymbols[item.currency] || '$') || '', date_created: typeof item.created_at === 'number' ? new Date(item.created_at * 1000).toISOString() : item.date_added || (typeof item.created_at === 'string' ? item.created_at : ''), date_modified: typeof item.updated_at === 'number' ? new Date(item.updated_at * 1000).toISOString() : item.date_updated || item.last_modified || (typeof item.updated_at === 'string' ? item.updated_at : ''), fulfillment_status, fulfillments: item.fulfillments?.map?.((f) => ({ id: f.id, tracking_number: f.tracking_number || '', shipping_provider: f.tracking_company || '', shipping_method: f.tracking_company || '', date_created: typeof f.created_at === 'number' ? new Date(f.created_at * 1000).toISOString() : f.created_at || '', // status: f.payment_tracking_status })) || [], raw: item, }; } mapUnifiedToPlatformOrder(data: Partial) { return data } mapCreateOrderParams(data: Partial): any { return data } mapUpdateOrderParams(data: Partial): any { // 构建 ShopYY 订单更新参数(仅包含传入的字段) const params: any = {}; // 仅当字段存在时才添加到更新参数中 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; } if (data.payment_method !== undefined) { params.payment_method = data.payment_method; } 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; } // 更新账单地址 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; } } 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; } } // 更新订单项 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(where: {id: string | number}): Promise { const data = await this.shopyyService.getOrder(this.site.id, String(where.id)); return this.mapPlatformToUnifiedOrder(data); } async getOrders( params: UnifiedSearchParamsDTO ): Promise> { // 转换订单查询参数 const normalizedParams = this.mapOrderSearchParams(params); const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchResourcePaged( this.site, 'orders', normalizedParams ); return { items: items.map(this.mapPlatformToUnifiedOrder.bind(this)), total, totalPages, page, per_page, }; } mapGetAllOrdersParams(params: UnifiedSearchParamsDTO) :ShopyyGetAllOrdersParams{ const pay_at_min = dayjs(params.after || '').valueOf().toString(); const pay_at_max = dayjs(params.before || '').valueOf().toString(); return { page: params.page || 1, per_page: params.per_page || 20, pay_at_min: pay_at_min, pay_at_max: pay_at_max, } } async getAllOrders(params?: UnifiedSearchParamsDTO): Promise { const normalizedParams = this.mapGetAllOrdersParams(params); const data = await this.shopyyService.getAllOrders(this.site.id, normalizedParams); return data.map(this.mapPlatformToUnifiedOrder.bind(this)); } async countOrders(where: Record): Promise { // 使用最小分页只获取总数 const searchParams = { where, page: 1, per_page: 1, } const data = await this.getOrders(searchParams); return data.total || 0; } async createOrder(data: Partial): Promise { // 使用映射方法转换参数 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 { // 使用映射方法转换参数 const requestParams = this.mapUpdateOrderParams(data); return await this.shopyyService.updateOrder(this.site, String(where.id), requestParams); } async deleteOrder(where: {id: string | number}): Promise { return await this.shopyyService.deleteOrder(this.site, where.id); } 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 cancelFulfillment(orderId: string | number, data: { reason?: string; shipment_id?: string; }): Promise { // 取消订单履行 try { // 调用 ShopyyService 的取消履行方法 const cancelShipData = { order_id: String(orderId), fulfillment_id: data.shipment_id || '' }; const result = await this.shopyyService.cancelFulfillment(this.site, cancelShipData); return { success: result, order_id: orderId, shipment_id: data.shipment_id, reason: data.reason, cancelled_at: new Date().toISOString() }; } catch (error) { throw new Error(`履行失败: ${error.message}`); } } async getOrderFulfillments(orderId: string | number): Promise { return await this.shopyyService.getFulfillments(this.site, String(orderId)); } async createOrderFulfillment(orderId: string | number, data: FulfillmentDTO): Promise { // 调用 Shopyy Service 的 createFulfillment 方法 const fulfillmentData = { tracking_number: data.tracking_number, carrier_code: data.shipping_provider, carrier_name: data.shipping_provider, shipping_method: data.shipping_method || 'standard' }; return await this.shopyyService.createFulfillment(this.site, String(orderId), fulfillmentData); } async updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: { tracking_number?: string; tracking_provider?: string; date_shipped?: string; status_shipped?: string; }): Promise { return await this.shopyyService.updateFulfillment(this.site, String(orderId), fulfillmentId, data); } async deleteOrderFulfillment(orderId: string | number, fulfillmentId: string): Promise { return await this.shopyyService.deleteFulfillment(this.site, String(orderId), fulfillmentId); } batchProcessOrders?(data: BatchOperationDTO): Promise { throw new Error('Method not implemented.'); } 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]; } } // 处理ID参数 if (baseParams.id) { baseParams.ids = baseParams.id; delete baseParams.id; } return baseParams; } // ========== 产品映射方法 ========== mapPlatformToUnifiedProduct(item: ShopyyProduct): UnifiedProductDTO { // 映射产品状态 function mapProductStatus(status: number) { return status === 1 ? 'publish' : 'draft'; } return { id: item.id, name: item.name || item.title, type: String(item.product_type ?? ''), status: mapProductStatus(item.status), sku: item.variant?.sku || '', regular_price: String(item.variant?.price ?? ''), sale_price: String(item.special_price ?? ''), price: String(item.price ?? ''), stock_status: item.inventory_tracking === 1 ? 'instock' : 'outofstock', stock_quantity: item.inventory_quantity, images: (item.images || []).map((img: any) => ({ id: img.id || 0, src: img.src, name: '', alt: img.alt || '', // 排序 position: img.position || '', })), attributes: (item.options || []).map(option => ({ id: option.id || 0, name: option.option_name || '', options: (option.values || []).map(value => value.option_value || ''), })), tags: (item.tags || []).map((t: any) => ({ id: t.id || 0, name: t.name || '', })), // shopyy叫做专辑 categories: item.collections.map((c: any) => ({ id: c.id || 0, name: c.title || '', })), variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [], permalink: `${this.site.websiteUrl}/products/${item.handle}`, date_created: typeof item.created_at === 'number' ? new Date(item.created_at * 1000).toISOString() : String(item.created_at ?? ''), date_modified: typeof item.updated_at === 'number' ? new Date(item.updated_at * 1000).toISOString() : String(item.updated_at ?? ''), raw: item, }; } mapUnifiedToPlatformProduct(data: Partial) { return data } mapCreateProductParams(data: Partial): Partial { // 构建 ShopYY 产品创建参数 const params: any = { name: data.name || '', product_type: data.type || 1, // 默认简单产品 status: this.mapStatus(data.status || 'publish'), price: data.price || '0.00', special_price: data.sale_price || '', inventory_tracking: data.stock_quantity !== undefined ? 1 : 0, inventory_quantity: data.stock_quantity || 0, }; // 添加变体信息 if (data.variations && data.variations.length > 0) { params.variants = data.variations.map((variation: UnifiedProductVariationDTO) => ({ sku: variation.sku || '', price: variation.price || '0.00', special_price: variation.sale_price || '', inventory_tracking: variation.stock_quantity !== undefined ? 1 : 0, inventory_quantity: variation.stock_quantity || 0, })); } // 添加图片信息 if (data.images && data.images.length > 0) { params.images = data.images.map((image: any) => ({ src: image.src, alt: image.alt || '', position: image.position || 0, })); } // 添加标签信息 if (data.tags && data.tags.length > 0) { params.tags = data.tags.map((tag: any) => tag.name || ''); } // 添加分类信息 if (data.categories && data.categories.length > 0) { params.collections = data.categories.map((category: any) => ({ // id: category.id, title: category.name, })); } return params; } mapUpdateProductParams(data: Partial): any { // 映射产品状态: publish -> 1, draft -> 0 const mapStatus = (status: string) => { return status === 'publish' ? 1 : 0; }; // 构建 ShopYY 产品更新参数(仅包含传入的字段) const params: any = {}; // 仅当字段存在时才添加到更新参数中 if (data.name !== undefined) params.name = data.name; if (data.type !== undefined) params.product_type = data.type; if (data.status !== undefined) params.status = mapStatus(data.status); if (data.price !== undefined) params.price = data.price; if (data.sale_price !== undefined) params.special_price = data.sale_price; if (data.sku !== undefined) params.sku = data.sku; if (data.stock_quantity !== undefined) { params.inventory_tracking = 1; params.inventory_quantity = data.stock_quantity; } if (data.stock_status !== undefined) { params.inventory_tracking = 1; params.inventory_quantity = data.stock_status === 'instock' ? (data.stock_quantity || 1) : 0; } // 添加变体信息(如果存在) if (data.variations && data.variations.length > 0) { params.variants = data.variations.map((variation: UnifiedProductVariationDTO) => { const variationParams: any = {}; if (variation.id !== undefined) variationParams.id = variation.id; if (variation.sku !== undefined) variationParams.sku = variation.sku; if (variation.price !== undefined) variationParams.price = variation.price; if (variation.sale_price !== undefined) variationParams.special_price = variation.sale_price; if (variation.stock_quantity !== undefined) { variationParams.inventory_tracking = 1; variationParams.inventory_quantity = variation.stock_quantity; } if (variation.stock_status !== undefined) { variationParams.inventory_tracking = 1; variationParams.inventory_quantity = variation.stock_status === 'instock' ? (variation.stock_quantity || 1) : 0; } return variationParams; }); } // 添加图片信息(如果存在) if (data.images && data.images.length > 0) { params.images = data.images.map((image: any) => ({ id: image.id, src: image.src, alt: image.alt || '', position: image.position || 0, })); } // 添加标签信息(如果存在) if (data.tags && data.tags.length > 0) { params.tags = data.tags.map((tag: any) => tag.name || ''); } // 添加分类信息(如果存在) if (data.categories && data.categories.length > 0) { params.collections = data.categories.map((category: any) => ({ id: category.id, title: category.name, })); } 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 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.mapPlatformToUnifiedProduct.bind(this)) return { items: finalItems as UnifiedProductDTO[], total, totalPages, page, per_page, }; } mapAllProductParams(params: UnifiedSearchParamsDTO): Partial{ const mapped = { ...params.where, } as any if(params.per_page){mapped.limit = params.per_page} return mapped } async getAllProducts(params?: UnifiedSearchParamsDTO): Promise { // 转换搜索参数 const requestParams = this.mapAllProductParams(params); const response = await this.shopyyService.request( this.site, 'products', 'GET', null, requestParams ); if(response.code !==0){ throw new Error(response.msg || '获取产品列表失败') } const { data = [] } = response; const finalItems = data.map(this.mapPlatformToUnifiedProduct.bind(this)) return finalItems } async createProduct(data: Partial): Promise { // 使用映射方法转换参数 const requestParams = this.mapCreateProductParams(data); const res = await this.shopyyService.createProduct(this.site, requestParams); 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参数'); } // 使用映射方法转换参数 const requestParams = this.mapUpdateProductParams(data); await this.shopyyService.updateProduct(this.site, productId, 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参数'); } // Use batch delete await this.shopyyService.batchProcessProducts(this.site, { delete: [productId] }); return true; } // 通过sku获取产品详情的私有方法 private async getProductBySku(sku: string): Promise { // 使用Shopyy API的搜索功能通过sku查询产品 const response = await this.getAllProducts({ where: {sku} }); console.log('getProductBySku', response) const product = response?.[0] if (!product) { throw new Error(`未找到sku为${sku}的产品`); } return product } async batchProcessProducts( data: { create?: any[]; update?: any[]; delete?: Array } ): Promise { return await this.shopyyService.batchProcessProducts(this.site, data); } mapProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery { return this.mapSearchParams(query) } mapAllProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery { return this.mapSearchParams(query) } // ========== 评论映射方法 ========== mapUnifiedToPlatformReview(data: Partial) { return data } mapCreateReviewParams(data: CreateReviewDTO) { return data } mapUpdateReviewParams(data: UpdateReviewDTO) { return data } async getReviews( params: UnifiedSearchParamsDTO ): Promise { const requestParams = this.mapReviewSearchParams(params); const { items, total, totalPages, page, per_page } = await this.shopyyService.getReviews( this.site, requestParams ); return { items: items.map(this.mapPlatformToUnifiedReview), total, totalPages, page, per_page, }; } async getAllReviews(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllReviews 暂未实现 throw new Error('Shopyy getAllReviews 暂未实现'); } async createReview(data: any): Promise { const createdReview = await this.shopyyService.createReview(this.site, data); return this.mapPlatformToUnifiedReview(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 deleteReview(where: {id: string | number}): Promise { return await this.shopyyService.deleteReview(this.site, where.id); } mapPlatformToUnifiedReview(review: any): UnifiedReviewDTO { // 将ShopYY评论数据映射到统一评论DTO格式 return { id: review.id || review.review_id, product_id: review.product_id || review.goods_id, author: review.author_name || review.username || '', email: review.author_email || review.user_email || '', content: review.comment || review.content || '', rating: Number(review.score || review.rating || 0), status: String(review.status || 'approved'), date_created: typeof review.created_at === 'number' ? new Date(review.created_at * 1000).toISOString() : String(review.created_at || review.date_added || '') }; } mapReviewSearchParams(params: UnifiedSearchParamsDTO): any { const { search, page, per_page, where } = params; const shopyyParams: any = { page: page || 1, limit: per_page || 10, }; if (search) { shopyyParams.search = search; } if (where.status) { shopyyParams.status = where.status; } return shopyyParams; } // ========== 订阅映射方法 ========== 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 getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllSubscriptions 暂未实现 throw new Error('Shopyy getAllSubscriptions 暂未实现'); } // ========== 产品变体映射方法 ========== mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO { // 映射变体 return { id: variant.id, name: variant.sku || '', sku: variant.sku || '', regular_price: String(variant.price ?? ''), sale_price: String(variant.special_price ?? ''), price: String(variant.price ?? ''), stock_status: variant.inventory_tracking === 1 ? 'instock' : 'outofstock', stock_quantity: variant.inventory_quantity, }; } mapUnifiedToPlatformVariation(data: Partial) { return data } mapCreateVariationParams(data: CreateVariationDTO) { return data } mapUpdateVariationParams(data: Partial): any { // 构建 ShopYY 变体更新参数(仅包含传入的字段) const params: any = {}; // 仅当字段存在时才添加到更新参数中 if (data.id !== undefined) { params.id = data.id; } if (data.sku !== undefined) { params.sku = data.sku; } if (data.price !== undefined) { params.price = data.price; } if (data.sale_price !== undefined) { params.special_price = data.sale_price; } // 处理库存信息 if (data.stock_quantity !== undefined) { params.inventory_tracking = 1; params.inventory_quantity = data.stock_quantity; } if (data.stock_status !== undefined) { params.inventory_tracking = 1; params.inventory_quantity = data.stock_status === 'instock' ? (data.stock_quantity || 1) : 0; } return params; } async getVariation(productId: string | number, variationId: string | number): Promise { throw new Error('Shopyy getVariation 暂未实现'); } async getVariations(productId: string | number, params: UnifiedSearchParamsDTO): Promise { throw new Error('Shopyy getVariations 暂未实现'); } async getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise { throw new Error('Shopyy getAllVariations 暂未实现'); } async createVariation(productId: string | number, data: any): Promise { throw new Error('Shopyy createVariation 暂未实现'); } async updateVariation(productId: string | number, variationId: string | number, data: Partial): Promise { // 使用映射方法转换参数 const requestParams = this.mapUpdateVariationParams(data); await this.shopyyService.updateVariation(this.site, String(productId), String(variationId), requestParams); return { ...data, id: variationId }; } async deleteVariation(productId: string | number, variationId: string | number): Promise { throw new Error('Shopyy deleteVariation 暂未实现'); } // ========== Webhook映射方法 ========== mapUnifiedToPlatformWebhook(data: Partial) { return data } mapCreateWebhookParams(data: CreateWebhookDTO) { return data } mapUpdateWebhookParams(data: UpdateWebhookDTO) { return data } async getWebhook(where: {id: string | number}): Promise { const webhook = await this.shopyyService.getWebhook(this.site, where.id); return this.mapPlatformToUnifiedWebhook(webhook); } async getWebhooks(params: UnifiedSearchParamsDTO): Promise { const { items, total, totalPages, page, per_page } = await this.shopyyService.getWebhooks(this.site, params); return { items: items.map(this.mapPlatformToUnifiedWebhook), total, totalPages, page, per_page, }; } async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise { // Shopyy getAllWebhooks 暂未实现 throw new Error('Shopyy getAllWebhooks 暂未实现'); } async createWebhook(data: CreateWebhookDTO): Promise { const createdWebhook = await this.shopyyService.createWebhook(this.site, data); return this.mapPlatformToUnifiedWebhook(createdWebhook); } async updateWebhook(where: {id: string | number}, data: UpdateWebhookDTO): Promise { const updatedWebhook = await this.shopyyService.updateWebhook(this.site, where.id, data); return this.mapPlatformToUnifiedWebhook(updatedWebhook); } async deleteWebhook(where: {id: string | number}): Promise { return await this.shopyyService.deleteWebhook(this.site, where.id); } mapPlatformToUnifiedWebhook(item: ShopyyWebhook): UnifiedWebhookDTO { return { id: item.id, name: item.webhook_name || `Webhook-${item.id}`, topic: item.event_code || '', delivery_url: item.url || '', status: 'active', }; } // ========== 站点/其他方法 ========== async getLinks(): Promise> { // ShopYY站点的管理后台链接通常基于apiUrl构建 const url = this.site.websiteUrl // 提取基础域名,去掉可能的路径部分 const baseUrl = url.replace(/\/api\/.*$/i, ''); const links = [ { title: '访问网站', url: baseUrl }, { title: '管理后台', url: `${baseUrl}/admin/` }, { title: '订单管理', url: `${baseUrl}/admin/orders.htm` }, { title: '产品管理', url: `${baseUrl}/admin/products.htm` }, { title: '客户管理', url: `${baseUrl}/admin/customers.htm` }, { title: '插件管理', url: `${baseUrl}/admin/apps.htm` }, { title: '店铺设置', url: `${baseUrl}/admin/settings.htm` }, { title: '营销中心', url: `${baseUrl}/admin/marketing.htm` }, ]; 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 || {}), 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; } // 映射产品状态: 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, // 部分发货 '310': OrderFulfillmentStatus.PARTIALLY_FULFILLED, // 已发货 '320': OrderFulfillmentStatus.FULFILLED, // 已取消 '330': OrderFulfillmentStatus.CANCELLED, // 确认发货 } }