forked from yoone/API
Compare commits
6 Commits
e8424afd91
...
ff5d7e81a1
| Author | SHA1 | Date |
|---|---|---|
|
|
ff5d7e81a1 | |
|
|
3968fd8965 | |
|
|
c26918d4db | |
|
|
16d27179e7 | |
|
|
a556ab69bf | |
|
|
efbd318917 |
|
|
@ -31,11 +31,13 @@ import {
|
|||
WooProductSearchParams,
|
||||
WpMediaGetListParams,
|
||||
WooFulfillment,
|
||||
MetaDataFulfillment,
|
||||
} from '../dto/woocommerce.dto';
|
||||
import { Site } from '../entity/site.entity';
|
||||
import { WPService } from '../service/wp.service';
|
||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||
import { toArray, toNumber } from '../utils/trans.util';
|
||||
import dayjs = require('dayjs');
|
||||
|
||||
export class WooCommerceAdapter implements ISiteAdapter {
|
||||
// 构造函数接收站点配置与服务实例
|
||||
|
|
@ -397,14 +399,17 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
mapPlatformToUnifiedOrder(item: WooOrder): UnifiedOrderDTO {
|
||||
// 将 WooCommerce 订单数据映射为统一订单DTO
|
||||
// 包含账单地址与收货地址以及创建与更新时间
|
||||
|
||||
// 映射物流追踪信息,将后端格式转换为前端期望的格式
|
||||
const fulfillments = (item.fulfillments || []).map((track) => ({
|
||||
const metaFulfillments: MetaDataFulfillment[] = item.meta_data?.find?.(_meta => _meta.key === "_wc_shipment_tracking_items")?.value || []
|
||||
const fulfillments = metaFulfillments.map((track) => {
|
||||
return ({
|
||||
tracking_id: track.tracking_id,
|
||||
tracking_number: track.tracking_number,
|
||||
tracking_product_code: track.tracking_product_code,
|
||||
shipping_provider: track.tracking_provider,
|
||||
date_created: track.data_sipped,
|
||||
}));
|
||||
date_created: dayjs(track.date_shipped).toString(),
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
|
|
@ -457,30 +462,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
const requestParams = this.mapOrderSearchParams(params);
|
||||
const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
|
||||
|
||||
// 并行获取所有订单的履行信息
|
||||
const ordersWithFulfillments = await Promise.all(
|
||||
items.map(async (order: any) => {
|
||||
try {
|
||||
// 获取订单的履行信息
|
||||
const fulfillments = await this.getOrderFulfillments(order.id);
|
||||
// 将履行信息添加到订单对象中
|
||||
return {
|
||||
...order,
|
||||
fulfillments: fulfillments || []
|
||||
};
|
||||
} catch (error) {
|
||||
// 如果获取履行信息失败,仍然返回订单,只是履行信息为空数组
|
||||
console.error(`获取订单 ${order.id} 的履行信息失败:`, error);
|
||||
return {
|
||||
...order,
|
||||
fulfillments: []
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
items: ordersWithFulfillments.map(this.mapPlatformToUnifiedOrder),
|
||||
items: items.map(this.mapPlatformToUnifiedOrder),
|
||||
total,
|
||||
totalPages,
|
||||
page,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
Body,
|
||||
Controller,
|
||||
Del,
|
||||
Files,
|
||||
Get,
|
||||
Inject,
|
||||
Param,
|
||||
|
|
@ -27,7 +28,6 @@ import {
|
|||
} from '../dto/order.dto';
|
||||
import { User } from '../decorator/user.decorator';
|
||||
import { ErpOrderStatus } from '../enums/base.enum';
|
||||
|
||||
@Controller('/order')
|
||||
export class OrderController {
|
||||
@Inject()
|
||||
|
|
@ -264,4 +264,21 @@ export class OrderController {
|
|||
return errorResponse(error?.message || '导出失败');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 导入产品(CSV 文件)
|
||||
@ApiOkResponse()
|
||||
@Post('/import')
|
||||
async importWintopay(@Files() files: any) {
|
||||
try {
|
||||
// 条件判断:确保存在文件
|
||||
const file = files?.[0];
|
||||
if (!file) return errorResponse('未接收到上传文件');
|
||||
|
||||
const result = await this.orderService.importWintopayTable(file);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -811,7 +811,8 @@ export class FulfillmentDTO {
|
|||
tracking_id?: string;
|
||||
@ApiProperty({ description: '物流单号', required: false })
|
||||
tracking_number?: string;
|
||||
|
||||
@ApiProperty({ description: "物流产品代码" , required: false})
|
||||
tracking_product_code?: string;
|
||||
@ApiProperty({ description: '物流公司', required: false })
|
||||
shipping_provider?: string;
|
||||
|
||||
|
|
|
|||
|
|
@ -369,8 +369,18 @@ export interface WooOrder {
|
|||
date_created_gmt?: string;
|
||||
date_modified?: string;
|
||||
date_modified_gmt?: string;
|
||||
// 物流追踪信息
|
||||
fulfillments?: WooFulfillment[];
|
||||
}
|
||||
export interface MetaDataFulfillment {
|
||||
custom_tracking_link: string;
|
||||
custom_tracking_provider: string;
|
||||
date_shipped: number;
|
||||
source: string;
|
||||
status_shipped: string;
|
||||
tracking_id: string;
|
||||
tracking_number: string;
|
||||
tracking_product_code: string;
|
||||
tracking_provider: string;
|
||||
user_id: number;
|
||||
}
|
||||
// 这个是一个插件的物流追踪信息
|
||||
// 接口:
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export class Order {
|
|||
@Expose()
|
||||
cart_tax: number;
|
||||
|
||||
@ApiProperty()
|
||||
@ApiProperty({ type: "总金额"})
|
||||
@Column('decimal', { precision: 10, scale: 2, default: 0 })
|
||||
@Expose()
|
||||
total: number;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Inject, Logger, Provide } from '@midwayjs/core';
|
||||
import { WPService } from './wp.service';
|
||||
import * as xlsx from 'xlsx';
|
||||
import { Order } from '../entity/order.entity';
|
||||
import { In, Like, Repository } from 'typeorm';
|
||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||
|
|
@ -32,7 +33,7 @@ import { UpdateStockDTO } from '../dto/stock.dto';
|
|||
import { StockService } from './stock.service';
|
||||
import { OrderItemOriginal } from '../entity/order_item_original.entity';
|
||||
import { SiteApiService } from './site-api.service';
|
||||
import { SyncOperationResult } from '../dto/api.dto';
|
||||
import { BatchErrorItem, SyncOperationResult,BatchOperationResult } from '../dto/api.dto';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
|
@ -1479,7 +1480,7 @@ export class OrderService {
|
|||
}
|
||||
|
||||
let itemSql = `
|
||||
SELECT os.productId, os.name, SUM(os.quantity) AS totalQuantity, COUNT(DISTINCT os.orderId) AS totalOrders
|
||||
SELECT os.productId, os.name, os.sku, SUM(os.quantity) AS totalQuantity, COUNT(DISTINCT os.orderId) AS totalOrders
|
||||
FROM order_sale os
|
||||
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||
WHERE o.date_paid BETWEEN ? AND ?
|
||||
|
|
@ -1501,7 +1502,7 @@ export class OrderService {
|
|||
}
|
||||
itemSql += nameCondition;
|
||||
itemSql += `
|
||||
GROUP BY os.productId, os.name
|
||||
GROUP BY os.productId, os.name, os.sku
|
||||
ORDER BY totalQuantity DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`;
|
||||
|
|
@ -2728,4 +2729,58 @@ export class OrderService {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 从 CSV 导入产品;存在则更新,不存在则创建
|
||||
async importWintopayTable(file: any): Promise<BatchOperationResult> {
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
const errors: BatchErrorItem[] = [];
|
||||
const records = await this.getRecordsFromTable(file);
|
||||
created++
|
||||
updated++
|
||||
|
||||
return { total: records.length, processed: records.length - errors.length, created: 0, updated: 0, errors };
|
||||
}
|
||||
|
||||
async getRecordsFromTable(file: any) {
|
||||
// 解析文件(使用 xlsx 包自动识别文件类型并解析)
|
||||
try {
|
||||
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)
|
||||
// 添加codepage: 65001以确保正确处理UTF-8编码的中文
|
||||
const workbook = xlsx.read(buffer, { type: 'buffer', codepage: 65001 });
|
||||
// 获取第一个工作表
|
||||
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(`文件解析失败:${e?.message || e}`);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue