diff --git a/src/adapter/shopyy.adapter.ts b/src/adapter/shopyy.adapter.ts index e14e6d2..48d1014 100644 --- a/src/adapter/shopyy.adapter.ts +++ b/src/adapter/shopyy.adapter.ts @@ -21,13 +21,16 @@ import { CreateReviewDTO, CreateVariationDTO, UpdateReviewDTO, + OrderPaymentStatus, } from '../dto/site-api.dto'; import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto'; import { ShopyyAllProductQuery, ShopyyCustomer, ShopyyOrder, + ShopyyOrderCreateParams, ShopyyOrderQuery, + ShopyyOrderUpdateParams, ShopyyProduct, ShopyyProductQuery, ShopyyVariant, @@ -38,15 +41,15 @@ import { } from '../enums/base.enum'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; export class ShopyyAdapter implements ISiteAdapter { - shopyyFinancialStatusMap= { + shopyyFinancialStatusMap = { '200': '待支付', '210': "支付中", - '220':"部分支付", - '230':"已支付", - '240':"支付失败", - '250':"部分退款", - '260':"已退款", - '290':"已取消", + '220': "部分支付", + '230': "已支付", + '240': "支付失败", + '250': "部分退款", + '260': "已退款", + '290': "已取消", } constructor(private site: any, private shopyyService: ShopyyService) { this.mapPlatformToUnifiedCustomer = this.mapPlatformToUnifiedCustomer.bind(this); @@ -123,8 +126,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){ + 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); @@ -153,12 +156,12 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedCustomer(createdCustomer); } - async updateCustomer(where: {id: string | number}, data: Partial): Promise { + 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 { + async deleteCustomer(where: { id: string | number }): Promise { return await this.shopyyService.deleteCustomer(this.site, where.id); } @@ -212,12 +215,12 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedMedia(createdMedia); } - async updateMedia(where: {id: string | number}, data: any): Promise { + 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 { + async deleteMedia(where: { id: string | number }): Promise { return await this.shopyyService.deleteMedia(this.site, where.id); } @@ -228,10 +231,10 @@ export class ShopyyAdapter implements ISiteAdapter { // ========== 订单映射方法 ========== mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO { // console.log(item) - if(!item) throw new Error('订单数据不能为空') + if (!item) throw new Error('订单数据不能为空') // 提取账单和送货地址 如果不存在则为空对象 - const billing = (item).bill_address || {}; - const shipping = (item as any).shipping_address || {}; + const billing = item.billing_address || {}; + const shipping = item.shipping_address || {}; // 构建账单地址对象 const billingObj: UnifiedAddressDTO = { @@ -309,14 +312,14 @@ export class ShopyyAdapter implements ISiteAdapter { }; 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 ?? ''), + (product) => ({ + id: product.id, + name: product.product_title || product.name, + product_id: product.product_id, + quantity: product.quantity, + total: String(product.price ?? ''), + sku: product.sku || product.sku_code || '', + price: String(product.price ?? ''), }) ); // 货币符号 @@ -334,12 +337,12 @@ export class ShopyyAdapter implements ISiteAdapter { 'SGD': 'S$' // 可以根据需要添加更多货币代码和符号 }; - + // 映射订单状态,如果不存在则默认 pending const status = this.shopyyOrderStatusMap[item.status ?? item.order_status] || OrderStatus.PENDING; - const finalcial_status = this.shopyyFinancialStatusMap[item.financial_status] + const finalcial_status = this.shopyyFinancialStatusMap[item.financial_status] // 发货状态 - const fulfillment_status = this.shopyyFulfillmentStatusMap[item.fulfillment_status]; + const fulfillment_status = this.fulfillmentStatusMap[item.fulfillment_status]; return { id: item.id || item.order_id, number: item.order_number || item.order_sn, @@ -388,7 +391,7 @@ export class ShopyyAdapter implements ISiteAdapter { 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 || '', @@ -402,11 +405,11 @@ export class ShopyyAdapter implements ISiteAdapter { return data } - mapCreateOrderParams(data: Partial): any { + mapCreateOrderParams(data: Partial): ShopyyOrderCreateParams { return data } - mapUpdateOrderParams(data: Partial): any { + mapUpdateOrderParams(data: Partial): ShopyyOrderUpdateParams { // 构建 ShopYY 订单更新参数(仅包含传入的字段) const params: any = {}; @@ -536,9 +539,17 @@ export class ShopyyAdapter implements ISiteAdapter { 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 getOrder(where: { id: string | number }): Promise { + const data = await this.getOrders({ + where: { + id: where.id, + }, + page: 1, + per_page: 1, + }) + return data.items[0] || null + // const data = await this.shopyyService.getOrder(this.site.id, String(where.id)); + // return this.mapPlatformToUnifiedOrder(data); } async getOrders( @@ -565,7 +576,7 @@ export class ShopyyAdapter implements ISiteAdapter { return data.map(this.mapPlatformToUnifiedOrder.bind(this)); } - async countOrders(where: Record): Promise { + async countOrders(where: Record): Promise { // 使用最小分页只获取总数 const searchParams = { where, @@ -583,13 +594,13 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedOrder(createdOrder); } - async updateOrder(where: {id: string | number}, data: Partial): Promise { + 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 { + async deleteOrder(where: { id: string | number }): Promise { return await this.shopyyService.deleteOrder(this.site, where.id); } @@ -662,7 +673,7 @@ export class ShopyyAdapter implements ISiteAdapter { mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial { // 首先使用通用参数转换 const baseParams = this.mapSearchParams(params); - + // 订单状态映射 const statusMap = { 'pending': '100', // 100 未完成 @@ -670,7 +681,7 @@ export class ShopyyAdapter implements ISiteAdapter { 'completed': "180", // 180 已完成(确认收货) 'cancelled': '190', // 190 取消 }; - + // 如果有状态参数,进行特殊映射 if (baseParams.status) { const unifiedStatus = baseParams.status @@ -678,13 +689,13 @@ export class ShopyyAdapter implements ISiteAdapter { baseParams.status = statusMap[unifiedStatus]; } } - + // 处理ID参数 if (baseParams.id) { baseParams.ids = baseParams.id; delete baseParams.id; } - + return baseParams; } @@ -699,7 +710,7 @@ export class ShopyyAdapter implements ISiteAdapter { name: item.name || item.title, type: String(item.product_type ?? ''), status: mapProductStatus(item.status), - sku: item.variant?.sku || '', + sku: item.variant?.sku || item.variant?.sku_code || '', regular_price: String(item.variant?.price ?? ''), sale_price: String(item.special_price ?? ''), price: String(item.price ?? ''), @@ -728,7 +739,7 @@ export class ShopyyAdapter implements ISiteAdapter { name: c.title || '', })), variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [], - permalink: `${this.site.websiteUrl}/products/${item.handle}`, + permalink: `${this.site.websiteUrl}/products/${item.handle}`, date_created: typeof item.created_at === 'number' ? new Date(item.created_at * 1000).toISOString() @@ -864,8 +875,8 @@ export class ShopyyAdapter implements ISiteAdapter { return params; } - async getProduct(where: {id?: string | number, sku?: string}): Promise { - if(!where.id && !where.sku){ + async getProduct(where: { id?: string | number, sku?: string }): Promise { + if (!where.id && !where.sku) { throw new Error('必须传入 id 或 sku') } if (where.id) { @@ -901,11 +912,11 @@ export class ShopyyAdapter implements ISiteAdapter { per_page, }; } - mapAllProductParams(params: UnifiedSearchParamsDTO): Partial{ - const mapped = { + mapAllProductParams(params: UnifiedSearchParamsDTO): Partial { + const mapped = { ...params.where, - } as any - if(params.per_page){mapped.limit = params.per_page} + } as any + if (params.per_page) { mapped.limit = params.per_page } return mapped } @@ -919,7 +930,7 @@ export class ShopyyAdapter implements ISiteAdapter { null, requestParams ); - if(response.code !==0){ + if (response.code !== 0) { throw new Error(response.msg || '获取产品列表失败') } const { data = [] } = response; @@ -934,7 +945,7 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedProduct(res); } - async updateProduct(where: {id?: string | number, sku?: string}, data: Partial): Promise { + async updateProduct(where: { id?: string | number, sku?: string }, data: Partial): Promise { let productId: string; if (where.id) { productId = String(where.id); @@ -951,7 +962,7 @@ export class ShopyyAdapter implements ISiteAdapter { return true; } - async deleteProduct(where: {id?: string | number, sku?: string}): Promise { + async deleteProduct(where: { id?: string | number, sku?: string }): Promise { let productId: string | number; if (where.id) { productId = where.id; @@ -966,11 +977,11 @@ export class ShopyyAdapter implements ISiteAdapter { 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} }); + const response = await this.getAllProducts({ where: { sku } }); console.log('getProductBySku', response) const product = response?.[0] if (!product) { @@ -1034,12 +1045,12 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedReview(createdReview); } - async updateReview(where: {id: string | number}, data: any): Promise { + 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 { + async deleteReview(where: { id: string | number }): Promise { return await this.shopyyService.deleteReview(this.site, where.id); } @@ -1101,10 +1112,11 @@ export class ShopyyAdapter implements ISiteAdapter { // ========== 产品变体映射方法 ========== mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO { // 映射变体 + console.log('ivarianttem', variant) return { id: variant.id, - name: variant.sku || '', - sku: variant.sku || '', + name: variant.title || '', + sku: variant.sku || variant.sku_code || '', regular_price: String(variant.price ?? ''), sale_price: String(variant.special_price ?? ''), price: String(variant.price ?? ''), @@ -1195,7 +1207,7 @@ export class ShopyyAdapter implements ISiteAdapter { return data } - async getWebhook(where: {id: string | number}): Promise { + async getWebhook(where: { id: string | number }): Promise { const webhook = await this.shopyyService.getWebhook(this.site, where.id); return this.mapPlatformToUnifiedWebhook(webhook); } @@ -1221,12 +1233,12 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapPlatformToUnifiedWebhook(createdWebhook); } - async updateWebhook(where: {id: string | number}, data: UpdateWebhookDTO): Promise { + 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 { + async deleteWebhook(where: { id: string | number }): Promise { return await this.shopyyService.deleteWebhook(this.site, where.id); } @@ -1269,14 +1281,14 @@ export class ShopyyAdapter implements ISiteAdapter { // 处理分页参数 const page = Number(params.page || 1); const limit = Number(params.per_page ?? 20); - + // 处理 where 条件 const query: any = { ...(params.where || {}), page, limit, } - if(params.orderBy){ + if (params.orderBy) { const [field, dir] = Object.entries(params.orderBy)[0]; query.order_by = dir === 'desc' ? 'desc' : 'asc'; query.order_field = field @@ -1286,24 +1298,24 @@ export class ShopyyAdapter implements ISiteAdapter { // 映射产品状态: publish -> 1, draft -> 0 mapStatus = (status: string) => { - return status === 'publish' ? 1 : 0; + return status === 'publish' ? 1 : 0; }; // 映射库存状态: instock -> 1, outofstock -> 0 - mapStockStatus = (stockStatus: string) => { + 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 未发货;310 部分发货;320 已发货;330(确认收货) + fulfillmentStatusMap = { // 未发货 '300': OrderFulfillmentStatus.PENDING, // 部分发货 @@ -1314,4 +1326,23 @@ export class ShopyyAdapter implements ISiteAdapter { '330': OrderFulfillmentStatus.CANCELLED, // 确认发货 } + // 支付状态 200 待支付;210 支付中;220 部分支付;230 已支付;240 支付失败;250 部分退款;260 已退款 ;290 已取消; + financialStatusMap = { + // 待支付 + '200': OrderPaymentStatus.PENDING, + // 支付中 + '210': OrderPaymentStatus.PAYING, + // 部分支付 + '220': OrderPaymentStatus.PARTIALLY_PAID, + // 已支付 + '230': OrderPaymentStatus.PAID, + // 支付失败 + '240': OrderPaymentStatus.FAILED, + // 部分退款 + '250': OrderPaymentStatus.PARTIALLY_REFUNDED, + // 已退款 + '260': OrderPaymentStatus.REFUNDED, + // 已取消 + '290': OrderPaymentStatus.CANCELLED, + } } \ No newline at end of file diff --git a/src/controller/product.controller.ts b/src/controller/product.controller.ts index c263d16..4489514 100644 --- a/src/controller/product.controller.ts +++ b/src/controller/product.controller.ts @@ -117,7 +117,7 @@ export class ProductController { const file = files?.[0]; if (!file) return errorResponse('未接收到上传文件'); - const result = await this.productService.importProductsCSV(file); + const result = await this.productService.importProductsFromTable(file); return successResponse(result); } catch (error) { return errorResponse(error?.message || error); diff --git a/src/dto/shopyy.dto.ts b/src/dto/shopyy.dto.ts index 37bd5e7..b28f5c4 100644 --- a/src/dto/shopyy.dto.ts +++ b/src/dto/shopyy.dto.ts @@ -51,84 +51,170 @@ export class ShopyyAllProductQuery { /** 商品更新时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */ updated_at_max?: string; } +export interface ShopyyCollection { + id?: number; + title?: string; +} // 产品类型 export interface ShopyyProduct { - // 产品主键 + // ======================================== + // 基本信息 + // ======================================== + // 产品主键ID id: number; - // 产品名称或标题 + // 产品名称 name?: string; + // 产品标题 title?: string; // 产品类型 product_type?: string | number; // 产品状态数值 1为发布 其他为草稿 status: number; - // 变体信息 - variant?: { - sku?: string; - price?: string; - }; - // 价格 - special_price?: string; + // ======================================== + // 价格信息 + // ======================================== + // 产品原价 price?: string; + // 产品特价 + special_price?: string; + + + // ======================================== + // 库存信息 + // ======================================== // 库存追踪标识 1表示跟踪 inventory_tracking?: number; // 库存数量 inventory_quantity?: number; - // 图片列表 - images?: Array<{ - id?: number; - src: string; - alt?: string; - position?: string | number; - }>; - // 主图 + + // ======================================== + // 图片信息 + // ======================================== + // 主图信息 image?: { + // 图片URL src: string; + // 图片文件名 file_name?: string; + // 图片描述 alt?: string; + // 图片文件大小 file_size?: number; + // 图片宽度 width?: number; + // 图片高度 height?: number; + // 图片ID id?: number; + // 图片位置 position?: number | string; + // 图片文件类型 file_type?: string; }; - // 标签 + // 图片列表 + images?: Array<{ + // 图片ID + id?: number; + // 图片URL + src: string; + // 图片描述 + alt?: string; + // 图片位置 + position?: string | number; + }>; + + // ======================================== + // 分类信息 + // ======================================== + // 产品标签列表 tags?: ShopyyTag[]; + // 产品分类集合 + collections?: ShopyyCollection[]; + + // ======================================== + // 变体信息 + // ======================================== + // 单个变体信息 + variant?: ShopyyVariant; // 变体列表 variants?: ShopyyVariant[]; - // 分类集合 - collections?: Array<{ id?: number; title?: string }>; + + // ======================================== + // 选项信息 + // ======================================== // 规格选项列表 options?: Array<{ + // 选项ID id?: number; + // 选项位置 position?: number | string; + // 选项名称 option_name?: string; - values?: Array<{ option_value?: string; id?: number; position?: number }>; + // 选项值列表 + values?: Array<{ + // 选项值 + option_value?: string; + // 选项值ID + id?: number; + // 选项值位置 + position?: number; + }>; }>; - // 发布与标识 + + // ======================================== + // 发布信息 + // ======================================== + // 产品发布时间 published_at?: string; + // 产品唯一标识(URL友好) handle?: string; + // 产品SPU编码(标准化产品单元) spu?: string; - // 创建与更新时间 + + // ======================================== + // 时间戳信息 + // ======================================== + // 产品创建时间 created_at?: string | number; + // 产品更新时间 updated_at?: string | number; } // 变体类型 export interface ShopyyVariant { id: number; - sku?: string; - price?: string; + title: string; + sku: string; + price?: number; special_price?: string; inventory_tracking?: number; inventory_quantity?: number; - available?: number; + available: number; barcode?: string; weight?: number; + image_id?: number; image?: { src: string; id?: number; file_name?: string; alt?: string; position?: number | string }; position?: number | string; sku_code?: string; + // 补充字段 + sku_value?: string; + updated_at?: number; + compare_at_price?: number; + option1_title?: string; + option1_value?: number; + created_at?: number; + option3_value?: number; + option2_value_title?: string; + option3?: number; + option1?: number; + option1_value_title?: string; + option2?: number; + ext2?: string; + ext1?: string; + option2_title?: string; + option3_value_title?: string; + option3_title?: string; + option2_value?: number; } // // 订单查询参数类型 @@ -166,122 +252,691 @@ export interface ShopyyOrderQuery { // 订单列表类型 group?: string; } +// 订单详情返回结果类型 +export interface ShopyyGetOneOrderResult { + // 基本信息 + id: number; + store_id: number; + order_number: string; + visitor_id: string; + checkout_token: string; + ip: string; + currency_rate: string; + currency_code: string; + status: number; + financial_status: number; + fulfillment_status: number; + current_subtotal_price: string; + current_shipping_price: string; + current_insurance_price: string; + current_tip_price: string; + current_tax_price: string; + current_total_price: string; + current_coupon_price: string; + current_payment_price: string; + current_promotion_price: string; + current_offer_price: string; + total_price: string; + total_weight: string; + total_num: number; + shipping_zone_plan_name: string; + payment_id: number; + payment_method: string; + coupon_code: string; + coupon_id: number; + promotion_id: number; + promotion_name: string; + source_device: string; + utm_source: string; + utm_medium: string; + landing_page: string; + cancelled_at: number; + cancel_reason: string; + latest_error_reason: string; + note: string; + admin_note: string; + paypal_seller_protection: string; + recart_mail_sended: number; + recart_mail_happened_at: number; + recart_mail_result: string; + domain: string; + closed_at: number; + delivery_at: number; + act_at: number; + pay_at: number; + updated_at: number; + created_at: number; + ext3: string; + ext4: string; + utm_term: string; + utm_campaign: string; + utm_content: string; + version: string; + submit_type: string; + additional_info: any[]; + is_test: number; + financial_additional_status: number; + admin_id: number; + admin_name: string; + checkout_type: string; + payment_type: string; + user_id: number; + brand_id: number; + free_shipping_id: number; + free_shipping_plan_name: string; + free_shipping_price: string; + refund_status: number; + refund_price: string; + sys_payment_id: number; + // 交易信息 + transaction: { + transaction_no: string; + amount: string; + payment_method: string; + merchant_account: string; + merchant_id: string; + note: string; + created_at: number; + updated_at: number; + admin_id: number; + admin_name: string; + }; + + // 商品列表 + products: Array<{ + id: number; + store_id: number; + order_id: number; + customer_id: number; + product_id: number; + variant_id: number; + product_title: string; + product_type: string; + variant_title: string; + spu: string; + sku: string; + sku_code: string; + sku_value: string; + vendor: string; + src: string; + price: string; + discount_price: string; + tax_price: string; + weight: string; + quantity: number; + taxable: number; + note: string; + data_from: string; + created_at: number; + updated_at: number; + barcode: string; + property_md5: string; + promotion_id: number; + promotion_name: string; + diy_offer_id: number; + diy_offer_name: string; + inner_title: string; + spm: string; + property: any[]; + refund_price: string; + available_refund_quantity: number; + gift_tip: string; + data_from_text: string; + product_detail_url: string; + }>; + + // 配送地址 + shipping_address: { + id: number; + phone: string; + first_name: string; + last_name: string; + country_name: string; + country_id: number; + country_code: string; + province: string; + province_code: string; + province_id: number; + city: string; + area: string; + address1: string; + address2: string; + zip: string; + company: string; + tel_area_code: string; + }; + + // 账单地址 + bill_address: { + id: number; + phone: string; + first_name: string; + last_name: string; + country_name: string; + country_code: string; + province_code: string; + province: string; + city: string; + area: string; + address1: string; + address2: string; + zip: string; + company: string; + name: string; + country_id: number; + province_id: number; + tel_area_code: string; + }; + + // 客户系统信息 + customer_sysinfo: { + os: string; + timezone: string; + browser: string; + language: string; + screen_size: string; + ip: string; + user_agent: string; + viewport_size: string; + date_time: string; + cookies: string; + }; + + // 客户信息 + customer: { + country_code: string; + first_pay_at: number; + visit_at: number; + total_spent: string; + orders_count: number; + tags: string[]; + note: string; + contact: string; + }; + + // 订单优惠列表 + order_offer_list: Array<{ + id: number; + store_id: number; + order_id: number; + params: string; + title: string; + price: string; + key_name: string; + values: string; + descript: string; + from_id: number; + from_name: string; + created_at: number; + updated_at: number; + }>; + + // IP详细信息 + ip_detail: { + continent_code: string; + continent_name: string; + country_code: string; + country_name: string; + province_code: string; + province_name: string; + city_name: string; + street_name: string; + latitude: number; + longitude: number; + time_zone: string; + isp: string; + organization: string; + user_type: string; + asNumber: number; + asName: string; + }; + + // 物流信息 + fulfillments: Array<{ + id: number; + tracking_company: string; + tracking_number: string; + created_at: number; + courier_code: string; + courier_id: number; + note: string; + payment_tracking_status: number; + payment_tracking_at: number; + payment_tracking_result: string; + type: number; + }>; + + // 物流商品信息 + fulfillment_products: Array<{ + id: number; + store_id: number; + order_id: number; + order_product_id: number; + product_id: number; + variant_id: number; + fulfillment_id: number; + quantity: number; + created_at: number; + updated_at: number; + }>; + + // 退款信息 + refunds: any[]; +} + +export interface ShopyyOrderCreateParams {} +export interface ShopyyOrderUpdateParams {} // 订单类型 export interface ShopyyOrder { - // 主键与外部ID + // ======================================== + // 基本信息 + // ======================================== + // 订单主键ID id?: number; + // 订单外部ID order_id?: number; + // 订单唯一标识ID + _id?: string; // 订单号 order_number?: string; + // 订单序列号 order_sn?: string; - // 状态 + // 订单总商品数量 + total_num?: number; + // 订单总重量 + total_weight?: number; + + // ======================================== + // 状态信息 + // ======================================== + // 订单状态 status?: number | string; + // 订单状态(备用) order_status?: number | string; - // 币种 + // 订单关闭时间 + closed_at?: number; + // 订单取消时间 + cancelled_at?: number; + // 订单取消原因 + cancel_reason?: string; + // 测试订单标识 + is_test?: number; + + // ======================================== + // 币种与金额 + // ======================================== + // 货币代码 currency_code?: string; + // 货币类型 currency?: string; - // 金额 + // 货币汇率 + currency_rate?: string | number; + // 订单总金额 total_price?: string | number; + // 订单总金额(备用) total_amount?: string | number; + // 当前订单总金额 current_total_price?: string | number; + // 当前订单小计金额 current_subtotal_price?: string | number; + // 当前订单运费 current_shipping_price?: string | number; + // 当前订单税费 current_tax_price?: string | number; + // 当前订单优惠券金额 current_coupon_price?: string | number; - current_payment_price?: string | number; + // 当前订单支付金额 + current_payment_price?: number; + // 当前订单小费金额 + current_tip_price?: number; + // 当前订单保险金额 + current_insurance_price?: number; + // 当前订单促销金额 + current_promotion_price?: number; + // 当前订单优惠金额 + current_offer_price?: number; + + // ======================================== + // 客户信息 + // ======================================== // 客户ID customer_id?: number; + // 用户ID user_id?: number; - // 客户信息 + // 客户名称 customer_name?: string; + // 客户名 firstname?: string; + // 客户姓 lastname?: string; + // 客户邮箱 customer_email?: string; + // 客户邮箱(备用) email?: string; - // 地址字段 - bill_address?: { - first_name?: string; - last_name?: string; - name?: string; - company?: string; - phone?: string; - address1?: string; - address2?: string; - city?: string; - province?: string; - zip?: string; - country_name?: string; - country_code?: string; - }; - shipping_address?: { - first_name?: string; - last_name?: string; - name?: string; - company?: string; - phone?: string; - address1?: string; - address2?: string; - city?: string; - province?: string; - zip?: string; - country_name?: string; - country_code?: string; - } | string; + // 客户电话 telephone?: string; - payment_address?: string; - payment_city?: string; - payment_zone?: string; - payment_postcode?: string; - payment_country?: string; - shipping_city?: string; - shipping_zone?: string; - shipping_postcode?: string; - shipping_country?: string; - // 订单项集合 - products?: Array<{ + // 客户系统信息 + customer_sysinfo?: { + // 操作系统 + os?: string; + // 时区 + timezone?: string; + // 视口大小 + viewport_size?: string; + // IP地址 + ip?: string; + // 创建时间 + created_at?: number; + // 语言 + language?: string; + // Cookie信息 + cookies?: string; + // 日期时间 + date_time?: string; + // 更新时间 + updated_at?: number; + // 浏览器 + browser?: string; + // 主题 + theme_dir?: string; + // 屏幕大小 + screen_size?: string; + // 系统信息ID id?: number; + // 用户代理 + user_agent?: string; + }; + + // ======================================== + // 地址信息 + // ======================================== + // 配送地址 + shipping_address?: { + // 收件人 + first_name?: string; + // 收件人姓 + last_name?: string; + // 收件人全名 name?: string; + // 公司名称 + company?: string; + // 联系电话 + phone?: string; + // 地址1 + address1?: string; + // 地址2 + address2?: string; + // 城市 + city?: string; + // 省份 + province?: string; + // 邮政编码 + zip?: string; + // 国家名称 + country_name?: string; + // 国家代码 + country_code?: string; + // 地区 + area?: string; + // 创建时间 + created_at?: number; + // 省份代码 + province_code?: string; + // 电话区号 + tel_area_code?: string; + // 省份ID + province_id?: number; + // 地址ID + id?: number; + // 国家ID + country_id?: number; + // 完整地址 + full_address?: string; + // 验证状态 + verify_status?: string; + }; + // 账单地址 + billing_address?: { + // 地区 + area?: string; + // 邮政编码 + zip?: string; + // 地址2 + address2?: string; + // 城市 + city?: string; + // 地址1 + address1?: string; + // 创建时间 + created_at?: number; + // 收件人姓 + last_name?: string; + // 省份代码 + province_code?: string; + // 国家代码 + country_code?: string; + // 电话区号 + tel_area_code?: string; + // 省份 + province?: string; + // 更新时间 + updated_at?: number; + // 联系电话 + phone?: string; + // 省份ID + province_id?: number; + // 国家名称 + country_name?: string; + // 收件人全名 + name?: string; + // 公司名称 + company?: string; + // 地址ID + id?: number; + // 收件人 + first_name?: string; + // 国家ID + country_id?: number; + }; + // 支付地址 + payment_address?: string; + // 支付城市 + payment_city?: string; + // 支付区域 + payment_zone?: string; + // 支付邮政编码 + payment_postcode?: string; + // 支付国家 + payment_country?: string; + // 配送城市 + shipping_city?: string; + // 配送区域 + shipping_zone?: string; + // 配送邮政编码 + shipping_postcode?: string; + // 配送国家 + shipping_country?: string; + + // ======================================== + // 商品信息 + // ======================================== + // 订单商品列表 + products?: Array<{ + // 商品ID + id?: number; + // 商品名称 + name?: string; + // 商品标题 product_title?: string; + // 变体标题 + variant_title?: string; + // 商品ID product_id?: number; + // 变体ID + variant_id?: number; + // 商品数量 quantity?: number; + // 商品价格 price?: string | number; + // 折扣价格 + discount_price?: number; + // 税费价格 + tax_price?: number; + // SKU编码 sku?: string; + // SKU值 + sku_value?: string; + // SKU代码 sku_code?: string; + // 条形码 + barcode?: string; + // 商品来源 + data_from?: string; + // 促销ID + promotion_id?: number; + // 促销名称 + promotion_name?: string; + // DIY优惠名称 + diy_offer_name?: string; + // DIY优惠ID + diy_offer_id?: number; + // 供应商 + vendor?: string; + // 商品是否应税 + taxable?: number; + // 商品图片URL + src?: string; + // 属性MD5值 + property_md5?: string; + // 商品重量 + weight?: number; + // 商品推广码 + spm?: string; + // 商品类型 + product_type?: string; + // 商品SPU编码 + spu?: string; + // 内部标题 + inner_title?: string; + // 商品属性 + properties?: any[]; + // 是否为属性商品 + is_property?: number; + // 商品详情URL + product_detail_url?: string; + // 详情URL + detail_url?: string; + // 商品备注 + note?: string; + // 创建时间 + created_at?: number; + // 更新时间 + updated_at?: number; }>; + + // ======================================== + // 支付信息 + // ======================================== // 支付方式 payment_method?: string; + // 支付ID payment_id?: number; + // 支付类型 + payment_type?: string; + // 支付卡信息列表 payment_cards?: Array<{ + // 商店ID store_id?: number; + // 卡号长度 card_len?: number; + // 卡号后缀 card_suffix?: number; + // 有效期年份 year?: number; + // 支付状态 payment_status?: number; + // 创建时间 created_at?: number; + // 有效期月份 month?: number; + // 更新时间 updated_at?: number; + // 支付ID payment_id?: number; + // 支付接口 payment_interface?: string; + // 卡号前缀 card_prefix?: number; + // 卡ID id?: number; + // 订单ID order_id?: number; + // 卡号 card?: string; + // 交易号 transaction_no?: string; }>; + // 支付时间 + pay_at?: number | null; + // 支付时间(备用) + date_paid?: number | string; + // 系统支付ID + sys_payment_id?: number; + + // ======================================== + // 交易信息 + // ======================================== + // 交易信息 + transaction?: { + // 交易备注 + note?: string; + // 交易金额 + amount?: number | string; + // 创建时间 + created_at?: number; + // 商家ID + merchant_id?: string; + // 支付类型 + payment_type?: string; + // 商家账户 + merchant_account?: string; + // 更新时间 + updated_at?: number; + // 支付ID + payment_id?: number; + // 管理员ID + admin_id?: number; + // 管理员名称 + admin_name?: string; + // 交易ID + id?: number; + // 支付方式 + payment_method?: string; + // 交易号 + transaction_no?: string; + }; + + // ======================================== + // 物流信息 + // ======================================== + // 物流状态 + fulfillment_status?: number; + // 物流履行列表 fulfillments?: Array<{ // 物流回传状态 payment_tracking_status?: number; - // 备注 + // 物流备注 note?: string; // 更新时间 updated_at?: number; - // 追踪接口编号 + // 物流追踪接口编号 courier_code?: string; - // 物流公司 id + // 物流公司ID courier_id?: number; // 创建时间 created_at?: number; + // 物流ID id?: number; // 物流单号 tracking_number?: string; @@ -291,9 +946,9 @@ export interface ShopyyOrder { payment_tracking_result?: string; // 物流回传时间 payment_tracking_at?: number; - // 商品 + // 物流商品列表 products?: Array<{ - // 订单商品表 id + // 订单商品表ID order_product_id?: number; // 数量 quantity?: number; @@ -301,70 +956,190 @@ export interface ShopyyOrder { updated_at?: number; // 创建时间 created_at?: number; - // 发货商品表 id - id?: number + // 发货商品表ID + id?: number; }>; }>; + // 物流区域计划列表 shipping_zone_plans?: Array<{ + // 物流价格 shipping_price?: number | string; + // 更新时间 updated_at?: number; + // 创建时间 created_at?: number; + // ID id?: number; + // 物流区域名称 shipping_zone_name?: string; + // 物流区域ID shipping_zone_id?: number; + // 物流区域计划ID shipping_zone_plan_id?: number; + // 物流区域计划名称 shipping_zone_plan_name?: string; + // 商品ID列表 + product_ids?: number[]; }>; - transaction?: { - note?: string; - amount?: number | string; - created_at?: number; - merchant_id?: string; - payment_type?: string; - merchant_account?: string; - updated_at?: number; - payment_id?: number; - admin_id?: number; - admin_name?: string; - id?: number; - payment_method?: string; - transaction_no?: string; - }; - coupon_code?: string; - coupon_name?: string; - store_id?: number; - visitor_id?: string; - currency_rate?: string | number; - landing_page?: string; - note?: string; - admin_note?: string; - source_device?: string; - checkout_type?: string; - version?: string; - brand_id?: number; - tags?: string[]; - financial_status?: number; - fulfillment_status?: number; - // 创建与更新时间可能为时间戳 - date_paid?: number | string; - created_at?: number | string; - date_added?: string; - updated_at?: number | string; - date_updated?: string; - last_modified?: string; - // 支付时间 - pay_at?: number | null; - ip?: string; - utm_source?: string; - // 配送方式 + // 物流区域计划名称 + shipping_zone_plan_name?: string; + // 配送方式列表 shipping_lines?: Array; - // 费用项 + + // ======================================== + // 促销与优惠券 + // ======================================== + // 优惠券代码 + coupon_code?: string; + // 优惠券名称 + coupon_name?: string; + // 优惠券ID + coupon_id?: number; + // 促销ID + promotion_id?: number; + // 商品促销ID列表 + product_promotion_ids?: number[]; + // 免费配送价格 + free_shipping_price?: number; + // 免费配送计划名称 + free_shipping_plan_name?: string; + // 免费配送ID + free_shipping_id?: number; + // DIY优惠列表 + diy_offers?: any[]; + // 费用项列表 fee_lines?: Array; - // 优惠券项 + // 优惠券项列表 coupon_lines?: Array; + + // ======================================== + // 财务信息 + // ======================================== + // 财务状态 + financial_status?: number; + // 财务附加状态 + financial_additional_status?: number; + // 退款状态 + refund_status?: number; + // 退款金额 + refund_price?: number; + // 退款列表 + refunds?: any[]; + + // ======================================== + // 跟踪信息 + // ======================================== + // 访问者ID + visitor_id?: string; + // 来源设备 + source_device?: string; + // 着陆页 + landing_page?: string; + // 结账类型 + checkout_type?: string; + // 结账令牌 + checkout_token?: string; + // IP地址 + ip?: string; + // UTM来源 + utm_source?: string; + // UTM媒介 + utm_medium?: string; + // UTM术语 + utm_term?: string; + // UTM内容 + utm_content?: string; + // UTM广告系列 + utm_campaign?: string; + // UTM信息列表 + utms?: Array<{ + // UTM术语 + utm_term?: string; + // 更新时间 + updated_at?: number; + // UTM广告系列 + utm_campaign?: string; + // UTM媒介 + utm_medium?: string; + // 创建时间 + created_at?: number; + // ID + id?: number; + // 访问ID + visit_id?: string; + // 类型 + type?: string; + // 来源设备 + source_device?: string; + // UTM内容 + utm_content?: string; + // UTM来源 + utm_source?: string; + }>; + + // ======================================== + // 系统信息 + // ======================================== + // 商店ID + store_id?: number; + // 品牌ID + brand_id?: number; + // 版本 + version?: string; + // 域名 + domain?: string; + // 扩展字段1 + ext1?: string; + // 扩展字段2 + ext2?: string; + // 扩展字段3 + ext3?: string; + // 扩展字段4 + ext4?: string; + + // ======================================== + // 附加信息 + // ======================================== + // 订单备注 + note?: string; + // 管理员备注 + admin_note?: string; + // 订单标签 + tags?: string[]; + // 最新错误原因 + latest_error_reason?: string; + // 备注属性 + note_attributes?: any[]; + // 附加信息 + additional_info?: any; + // 重新营销邮件发送状态 + recart_mail_sended?: number; + // 重新营销邮件结果 + recart_mail_result?: string; + // 重新营销邮件发生时间 + recart_mail_happened_at?: number; + + // ======================================== + // 时间戳信息 + // ======================================== + // 订单创建时间 + created_at?: number | string; + // 订单添加时间 + date_added?: string; + // 订单更新时间 + updated_at?: number | string; + // 订单更新日期 + date_updated?: string; + // 订单最后修改时间 + last_modified?: string; + // 订单支付时间 + act_at?: number; + // 订单提交类型 + submit_type?: string; } + export class ShopyyShippingLineDTO { // 配送方式DTO用于承载统一配送方式数据 id?: string | number; diff --git a/src/dto/site-api.dto.ts b/src/dto/site-api.dto.ts index 5c8e1d2..25eec8b 100644 --- a/src/dto/site-api.dto.ts +++ b/src/dto/site-api.dto.ts @@ -18,6 +18,24 @@ export enum OrderFulfillmentStatus { // 确认发货 CONFIRMED, } +export enum OrderPaymentStatus { + // 待支付 + PENDING, + // 支付中 + PAYING, + // 部分支付 + PARTIALLY_PAID, + // 已支付 + PAID, + // 支付失败 + FAILED, + // 部分退款 + PARTIALLY_REFUNDED, + // 已退款 + REFUNDED, + // 已取消 + CANCELLED, +} // export class UnifiedProductWhere { sku?: string; diff --git a/src/service/product.service.ts b/src/service/product.service.ts index 78ffeb9..6245def 100644 --- a/src/service/product.service.ts +++ b/src/service/product.service.ts @@ -1,6 +1,6 @@ import { Inject, Provide } from '@midwayjs/core'; -import { parse } from 'csv-parse'; import * as fs from 'fs'; +import * as xlsx from 'xlsx'; import { In, Like, Not, Repository } from 'typeorm'; import { Product } from '../entity/product.entity'; import { PaginationParams } from '../interface'; @@ -238,18 +238,18 @@ export class ProductService { // 处理分页参数(支持新旧两种格式) const page = query.page || 1; const pageSize = query.per_page || 10; - + // 处理搜索参数 const name = query.where?.name || query.search || ''; - + // 处理品牌过滤 const brandId = query.where?.brandId; const brandIds = query.where?.brandIds; - + // 处理分类过滤 const categoryId = query.where?.categoryId; const categoryIds = query.where?.categoryIds; - + // 处理排序参数 const orderBy = query.orderBy; @@ -270,17 +270,17 @@ export class ProductService { if (query.where?.id) { qb.andWhere('product.id = :id', { id: query.where.id }); } - + // 处理产品ID列表过滤 if (query.where?.ids && query.where.ids.length > 0) { qb.andWhere('product.id IN (:...ids)', { ids: query.where.ids }); } - + // 处理where对象中的id过滤 if (query.where?.id) { qb.andWhere('product.id = :whereId', { whereId: query.where.id }); } - + // 处理where对象中的ids过滤 if (query.where?.ids && query.where.ids.length > 0) { qb.andWhere('product.id IN (:...whereIds)', { whereIds: query.where.ids }); @@ -290,17 +290,17 @@ export class ProductService { if (query.where?.sku) { qb.andWhere('product.sku = :sku', { sku: query.where.sku }); } - + // 处理SKU列表过滤 if (query.where?.skus && query.where.skus.length > 0) { qb.andWhere('product.sku IN (:...skus)', { skus: query.where.skus }); } - + // 处理where对象中的sku过滤 if (query.where?.sku) { qb.andWhere('product.sku = :whereSku', { whereSku: query.where.sku }); } - + // 处理where对象中的skus过滤 if (query.where?.skus && query.where.skus.length > 0) { qb.andWhere('product.sku IN (:...whereSkus)', { whereSkus: query.where.skus }); @@ -315,7 +315,7 @@ export class ProductService { if (query.where?.type) { qb.andWhere('product.type = :type', { type: query.where.type }); } - + // 处理where对象中的type过滤 if (query.where?.type) { qb.andWhere('product.type = :whereType', { whereType: query.where.type }); @@ -325,16 +325,16 @@ export class ProductService { if (query.where?.minPrice !== undefined) { qb.andWhere('product.price >= :minPrice', { minPrice: query.where.minPrice }); } - + if (query.where?.maxPrice !== undefined) { qb.andWhere('product.price <= :maxPrice', { maxPrice: query.where.maxPrice }); } - + // 处理where对象中的价格范围过滤 if (query.where?.minPrice !== undefined) { qb.andWhere('product.price >= :whereMinPrice', { whereMinPrice: query.where.minPrice }); } - + if (query.where?.maxPrice !== undefined) { qb.andWhere('product.price <= :whereMaxPrice', { whereMaxPrice: query.where.maxPrice }); } @@ -343,16 +343,16 @@ export class ProductService { if (query.where?.minPromotionPrice !== undefined) { qb.andWhere('product.promotionPrice >= :minPromotionPrice', { minPromotionPrice: query.where.minPromotionPrice }); } - + if (query.where?.maxPromotionPrice !== undefined) { qb.andWhere('product.promotionPrice <= :maxPromotionPrice', { maxPromotionPrice: query.where.maxPromotionPrice }); } - + // 处理where对象中的促销价格范围过滤 if (query.where?.minPromotionPrice !== undefined) { qb.andWhere('product.promotionPrice >= :whereMinPromotionPrice', { whereMinPromotionPrice: query.where.minPromotionPrice }); } - + if (query.where?.maxPromotionPrice !== undefined) { qb.andWhere('product.promotionPrice <= :whereMaxPromotionPrice', { whereMaxPromotionPrice: query.where.maxPromotionPrice }); } @@ -361,16 +361,16 @@ export class ProductService { if (query.where?.createdAtStart) { qb.andWhere('product.createdAt >= :createdAtStart', { createdAtStart: new Date(query.where.createdAtStart) }); } - + if (query.where?.createdAtEnd) { qb.andWhere('product.createdAt <= :createdAtEnd', { createdAtEnd: new Date(query.where.createdAtEnd) }); } - + // 处理where对象中的创建时间范围过滤 if (query.where?.createdAtStart) { qb.andWhere('product.createdAt >= :whereCreatedAtStart', { whereCreatedAtStart: new Date(query.where.createdAtStart) }); } - + if (query.where?.createdAtEnd) { qb.andWhere('product.createdAt <= :whereCreatedAtEnd', { whereCreatedAtEnd: new Date(query.where.createdAtEnd) }); } @@ -379,16 +379,16 @@ export class ProductService { if (query.where?.updatedAtStart) { qb.andWhere('product.updatedAt >= :updatedAtStart', { updatedAtStart: new Date(query.where.updatedAtStart) }); } - + if (query.where?.updatedAtEnd) { qb.andWhere('product.updatedAt <= :updatedAtEnd', { updatedAtEnd: new Date(query.where.updatedAtEnd) }); } - + // 处理where对象中的更新时间范围过滤 if (query.where?.updatedAtStart) { qb.andWhere('product.updatedAt >= :whereUpdatedAtStart', { whereUpdatedAtStart: new Date(query.where.updatedAtStart) }); } - + if (query.where?.updatedAtEnd) { qb.andWhere('product.updatedAt <= :whereUpdatedAtEnd', { whereUpdatedAtEnd: new Date(query.where.updatedAtEnd) }); } @@ -407,7 +407,7 @@ export class ProductService { return 'product.id IN ' + subQuery; }); } - + // 处理品牌ID列表过滤 if (brandIds && brandIds.length > 0) { qb.andWhere(qb => { @@ -427,17 +427,17 @@ export class ProductService { if (categoryId) { qb.andWhere('product.categoryId = :categoryId', { categoryId }); } - + // 处理分类ID列表过滤 if (categoryIds && categoryIds.length > 0) { qb.andWhere('product.categoryId IN (:...categoryIds)', { categoryIds }); } - + // 处理where对象中的分类ID过滤 if (query.where?.categoryId) { qb.andWhere('product.categoryId = :whereCategoryId', { whereCategoryId: query.where.categoryId }); } - + // 处理where对象中的分类ID列表过滤 if (query.where?.categoryIds && query.where.categoryIds.length > 0) { qb.andWhere('product.categoryId IN (:...whereCategoryIds)', { whereCategoryIds: query.where.categoryIds }); @@ -555,7 +555,7 @@ export class ProductService { // 如果提供了 categoryId,设置分类 if (categoryId) { - categoryItem = await this.categoryModel.findOne({ + categoryItem = await this.categoryModel.findOne({ where: { id: categoryId }, relations: ['attributes', 'attributes.attributeDict'] }); @@ -566,12 +566,12 @@ export class ProductService { // 如果属性是分类,特殊处理 if (attr.dictName === 'category') { if (attr.id) { - categoryItem = await this.categoryModel.findOne({ + categoryItem = await this.categoryModel.findOne({ where: { id: attr.id }, relations: ['attributes', 'attributes.attributeDict'] }); } else if (attr.name) { - categoryItem = await this.categoryModel.findOne({ + categoryItem = await this.categoryModel.findOne({ where: { name: attr.name }, relations: ['attributes', 'attributes.attributeDict'] }); @@ -637,7 +637,7 @@ export class ProductService { if (sku) { product.sku = sku; } else { - product.sku = await this.templateService.render('product.sku', {product}); + product.sku = await this.templateService.render('product.sku', { product }); } const savedProduct = await this.productModel.save(product); @@ -1440,7 +1440,7 @@ export class ProductService { // 解析属性字段(分号分隔多值) const parseList = (v: string) => (v ? String(v).split(';').map(s => s.trim()).filter(Boolean) : []); - + // 将属性解析为 DTO 输入 const attributes: any[] = []; @@ -1461,16 +1461,16 @@ export class ProductService { return { sku, name: val(rec.name), - nameCn: val(rec.nameCn), + nameCn: val(rec.nameCn), description: val(rec.description), price: num(rec.price), promotionPrice: num(rec.promotionPrice), type: val(rec.type), siteSkus: rec.siteSkus ? String(rec.siteSkus) - .split(/[;,]/) // 支持英文分号或英文逗号分隔 - .map(s => s.trim()) - .filter(Boolean) + .split(/[;,]/) // 支持英文分号或英文逗号分隔 + .map(s => s.trim()) + .filter(Boolean) : undefined, category, // 添加分类字段 @@ -1536,10 +1536,10 @@ export class ProductService { return dto; } - getAttributesObject(attributes:DictItem[]){ - if(!attributes) return {} - const obj:any = {} - attributes.forEach(attr=>{ + getAttributesObject(attributes: DictItem[]) { + if (!attributes) return {} + const obj: any = {} + attributes.forEach(attr => { obj[attr.dict.name] = attr }) return obj @@ -1665,57 +1665,55 @@ export class ProductService { return rows.join('\n'); } - - // 从 CSV 导入产品;存在则更新,不存在则创建 - async importProductsCSV(file: any): Promise { - let buffer: Buffer; - if (Buffer.isBuffer(file)) { - buffer = file; - } else if (file?.data) { - if (typeof file.data === 'string') { - buffer = fs.readFileSync(file.data); - } else { - buffer = file.data; - } - } else { - throw new Error('无效的文件输入'); - } - - // 解析 CSV(使用 csv-parse/sync 按表头解析) - let records: any[] = []; + async getRecordsFromTable(file: any) { + // 解析文件(使用 xlsx 包自动识别文件类型并解析) try { - records = await new Promise((resolve, reject) => { - parse(buffer, { - columns: true, - skip_empty_lines: true, - trim: true, - bom: true, - }, (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }) + let buffer: Buffer; + + // 处理文件输入,获取 buffer + if (Buffer.isBuffer(file)) { + buffer = file; + } + else if (file?.data) { + if (typeof file.data === 'string') { + buffer = fs.readFileSync(file.data); + } else { + buffer = file.data; + } + } else { + throw new Error('无效的文件输入'); + } + + let records: any[] = [] + // xlsx 包会自动根据文件内容识别文件类型(CSV 或 XLSX) + const workbook = xlsx.read(buffer, { type: 'buffer' }); + // 获取第一个工作表 + const worksheet = workbook.Sheets[workbook.SheetNames[0]]; + // 将工作表转换为 JSON 数组 + records = xlsx.utils.sheet_to_json(worksheet); + console.log('Parsed records count:', records.length); if (records.length > 0) { console.log('First record keys:', Object.keys(records[0])); } + return records; } catch (e: any) { - throw new Error(`CSV 解析失败:${e?.message || e}`) + throw new Error(`文件解析失败:${e?.message || e}`); } + } + // 从 CSV 导入产品;存在则更新,不存在则创建 + async importProductsFromTable(file: any): Promise { let created = 0; let updated = 0; const errors: BatchErrorItem[] = []; - + const records = await this.getRecordsFromTable(file); // 逐条处理记录 for (const rec of records) { try { const data = this.transformCsvRecordToData(rec); if (!data) { - errors.push({ identifier: data.sku, error: '缺少 SKU 的记录已跳过'}); + errors.push({ identifier: data.sku, error: '缺少 SKU 的记录已跳过' }); continue; } const { sku } = data; @@ -1735,7 +1733,7 @@ export class ProductService { updated += 1; } } catch (e: any) { - errors.push({ identifier: '' + rec.sku, error: `产品${rec?.sku}导入失败:${e?.message || String(e)}`}); + errors.push({ identifier: '' + rec.sku, error: `产品${rec?.sku}导入失败:${e?.message || String(e)}` }); } } @@ -1749,7 +1747,7 @@ export class ProductService { .createQueryBuilder('stock') .select('DISTINCT(stock.sku)', 'sku') .getRawMany(); - + const skus = stockSkus.map(s => s.sku).filter(Boolean); let added = 0; const errors: string[] = []; @@ -1783,7 +1781,7 @@ export class ProductService { where: { id }, relations: ['category', 'attributes', 'attributes.dict', 'components'] }); - + if (!product) { throw new Error(`产品 ID ${id} 不存在`); } @@ -1867,7 +1865,7 @@ export class ProductService { await this.bindSiteSkus(localProduct.id, [unifiedProduct.sku]); return result; } catch (error) { - throw new Error(`同步产品到站点失败: ${error?.response?.data?.message??error.message}`); + throw new Error(`同步产品到站点失败: ${error?.response?.data?.message ?? error.message}`); } } @@ -1893,7 +1891,7 @@ export class ProductService { siteId, siteSku: item.siteSku }); - + results.synced++; results.processed++; } catch (error) { @@ -1915,18 +1913,18 @@ export class ProductService { * @returns 同步后的本地产品 */ async syncProductFromSite(siteId: number, siteProductId: string | number, sku: string): Promise { - const adapter = await this.siteApiService.getAdapter(siteId); - const siteProduct = await adapter.getProduct({ id: siteProductId }); + const adapter = await this.siteApiService.getAdapter(siteId); + const siteProduct = await adapter.getProduct({ id: siteProductId }); // 从站点获取产品信息 if (!siteProduct) { throw new Error(`站点产品 ID ${siteProductId} 不存在`); } // 将站点产品转换为本地产品格式 const productData = await this.mapUnifiedToLocalProduct(siteProduct); - return await this.upsertProduct({sku}, productData); + return await this.upsertProduct({ sku }, productData); } - async upsertProduct(where: Partial>, productData: any) { - const existingProduct = await this.productModel.findOne({ where: where}); + async upsertProduct(where: Partial>, productData: any) { + const existingProduct = await this.productModel.findOne({ where: where }); if (existingProduct) { // 更新现有产品 const updateData: UpdateProductDTO = productData; @@ -1944,7 +1942,7 @@ export class ProductService { * @param siteProductIds 站点产品ID数组 * @returns 批量同步结果 */ - async batchSyncFromSite(siteId: number, data: Array<{siteProductId:string, sku: string}>): Promise<{ synced: number, errors: string[] }> { + async batchSyncFromSite(siteId: number, data: Array<{ siteProductId: string, sku: string }>): Promise<{ synced: number, errors: string[] }> { const results = { synced: 0, errors: [] @@ -2030,13 +2028,13 @@ export class ProductService { * @param localProduct 本地产品对象 * @returns 统一产品对象 */ - private async mapLocalToUnifiedProduct(localProduct: Product,siteSku?: string): Promise> { - const tags = localProduct.attributes?.map(a => ({name: a.name})) || []; + private async mapLocalToUnifiedProduct(localProduct: Product, siteSku?: string): Promise> { + const tags = localProduct.attributes?.map(a => ({ name: a.name })) || []; // 将本地产品数据转换为UnifiedProductDTO格式 const unifiedProduct: any = { id: localProduct.id ? String(localProduct.id) : undefined, // 如果产品已存在,使用现有ID name: localProduct.name, - type: localProduct.type === 'single'? 'simple' : 'bundle', // 默认类型,可以根据实际需要调整 + type: localProduct.type === 'single' ? 'simple' : 'bundle', // 默认类型,可以根据实际需要调整 status: 'publish', // 默认状态,可以根据实际需要调整 sku: siteSku || await this.templateService.render('site.product.sku', { product: localProduct, sku: localProduct.sku }), regular_price: String(localProduct.price || 0), diff --git a/src/service/shopyy.service.ts b/src/service/shopyy.service.ts index 4a663cb..275e823 100644 --- a/src/service/shopyy.service.ts +++ b/src/service/shopyy.service.ts @@ -8,7 +8,7 @@ import * as FormData from 'form-data'; import { SiteService } from './site.service'; import { Site } from '../entity/site.entity'; import { UnifiedReviewDTO } from '../dto/site-api.dto'; -import { ShopyyReview } from '../dto/shopyy.dto'; +import { ShopyyGetOneOrderResult, ShopyyReview } from '../dto/shopyy.dto'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; import { UnifiedSearchParamsDTO } from '../dto/api.dto'; /** @@ -366,7 +366,7 @@ export class ShopyyService { * @param orderId 订单ID * @returns 订单详情 */ - async getOrder(siteId: string, orderId: string): Promise { + async getOrder(siteId: string, orderId: string): Promise { const site = await this.siteService.get(Number(siteId)); // ShopYY API: GET /orders/{id}