forked from yoone/API
feat(dto): 新增订单支付状态枚举类型
feat(controller): 重命名产品导入方法为importProductsFromTable feat(service): 使用xlsx替换csv解析器处理产品导入 refactor(adapter): 完善订单数据结构定义和类型映射 docs(dto): 补充Shopyy订单和产品接口的详细注释
This commit is contained in:
parent
68574dbc7a
commit
56deb447b3
|
|
@ -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,
|
||||
|
|
@ -230,8 +233,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
// console.log(item)
|
||||
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 ?? ''),
|
||||
})
|
||||
);
|
||||
// 货币符号
|
||||
|
|
@ -339,7 +342,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
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];
|
||||
const fulfillment_status = this.fulfillmentStatusMap[item.fulfillment_status];
|
||||
return {
|
||||
id: item.id || item.order_id,
|
||||
number: item.order_number || item.order_sn,
|
||||
|
|
@ -402,11 +405,11 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
return data
|
||||
}
|
||||
|
||||
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any {
|
||||
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): ShopyyOrderCreateParams {
|
||||
return data
|
||||
}
|
||||
|
||||
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any {
|
||||
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): ShopyyOrderUpdateParams {
|
||||
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
|
||||
const params: any = {};
|
||||
|
||||
|
|
@ -537,8 +540,16 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
}
|
||||
|
||||
async getOrder(where: { id: string | number }): Promise<UnifiedOrderDTO> {
|
||||
const data = await this.shopyyService.getOrder(this.site.id, String(where.id));
|
||||
return this.mapPlatformToUnifiedOrder(data);
|
||||
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(
|
||||
|
|
@ -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 ?? ''),
|
||||
|
|
@ -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 ?? ''),
|
||||
|
|
@ -1302,8 +1314,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
[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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -1665,13 +1665,16 @@ export class ProductService {
|
|||
|
||||
return rows.join('\n');
|
||||
}
|
||||
|
||||
// 从 CSV 导入产品;存在则更新,不存在则创建
|
||||
async importProductsCSV(file: any): Promise<BatchOperationResult> {
|
||||
async getRecordsFromTable(file: any) {
|
||||
// 解析文件(使用 xlsx 包自动识别文件类型并解析)
|
||||
try {
|
||||
let buffer: Buffer;
|
||||
|
||||
// 处理文件输入,获取 buffer
|
||||
if (Buffer.isBuffer(file)) {
|
||||
buffer = file;
|
||||
} else if (file?.data) {
|
||||
}
|
||||
else if (file?.data) {
|
||||
if (typeof file.data === 'string') {
|
||||
buffer = fs.readFileSync(file.data);
|
||||
} else {
|
||||
|
|
@ -1681,35 +1684,30 @@ export class ProductService {
|
|||
throw new Error('无效的文件输入');
|
||||
}
|
||||
|
||||
// 解析 CSV(使用 csv-parse/sync 按表头解析)
|
||||
let records: any[] = [];
|
||||
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 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<BatchOperationResult> {
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
const errors: BatchErrorItem[] = [];
|
||||
|
||||
const records = await this.getRecordsFromTable(file);
|
||||
// 逐条处理记录
|
||||
for (const rec of records) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -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<any> {
|
||||
async getOrder(siteId: string, orderId: string): Promise<ShopyyGetOneOrderResult> {
|
||||
const site = await this.siteService.get(Number(siteId));
|
||||
|
||||
// ShopYY API: GET /orders/{id}
|
||||
|
|
|
|||
Loading…
Reference in New Issue