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,
|
WooProductSearchParams,
|
||||||
WpMediaGetListParams,
|
WpMediaGetListParams,
|
||||||
WooFulfillment,
|
WooFulfillment,
|
||||||
|
MetaDataFulfillment,
|
||||||
} from '../dto/woocommerce.dto';
|
} from '../dto/woocommerce.dto';
|
||||||
import { Site } from '../entity/site.entity';
|
import { Site } from '../entity/site.entity';
|
||||||
import { WPService } from '../service/wp.service';
|
import { WPService } from '../service/wp.service';
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
import { toArray, toNumber } from '../utils/trans.util';
|
import { toArray, toNumber } from '../utils/trans.util';
|
||||||
|
import dayjs = require('dayjs');
|
||||||
|
|
||||||
export class WooCommerceAdapter implements ISiteAdapter {
|
export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
// 构造函数接收站点配置与服务实例
|
// 构造函数接收站点配置与服务实例
|
||||||
|
|
@ -201,7 +203,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
async updateCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
async updateCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
const api = this.wpService.createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
let customerId: string | number;
|
let customerId: string | number;
|
||||||
|
|
||||||
// 先根据条件获取客户ID
|
// 先根据条件获取客户ID
|
||||||
if (where.id) {
|
if (where.id) {
|
||||||
customerId = where.id;
|
customerId = where.id;
|
||||||
|
|
@ -210,7 +212,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
const customer = await this.getCustomer(where);
|
const customer = await this.getCustomer(where);
|
||||||
customerId = customer.id;
|
customerId = customer.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api.put(`customers/${customerId}`, data);
|
const res = await api.put(`customers/${customerId}`, data);
|
||||||
return this.mapPlatformToUnifiedCustomer(res.data);
|
return this.mapPlatformToUnifiedCustomer(res.data);
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +220,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
async deleteCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>): Promise<boolean> {
|
async deleteCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>): Promise<boolean> {
|
||||||
const api = this.wpService.createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
let customerId: string | number;
|
let customerId: string | number;
|
||||||
|
|
||||||
// 先根据条件获取客户ID
|
// 先根据条件获取客户ID
|
||||||
if (where.id) {
|
if (where.id) {
|
||||||
customerId = where.id;
|
customerId = where.id;
|
||||||
|
|
@ -227,7 +229,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
const customer = await this.getCustomer(where);
|
const customer = await this.getCustomer(where);
|
||||||
customerId = customer.id;
|
customerId = customer.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.delete(`customers/${customerId}`, { force: true });
|
await api.delete(`customers/${customerId}`, { force: true });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +257,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
mapMediaSearchParams(params: UnifiedSearchParamsDTO): Partial<WpMediaGetListParams> {
|
mapMediaSearchParams(params: UnifiedSearchParamsDTO): Partial<WpMediaGetListParams> {
|
||||||
const page = params.page
|
const page = params.page
|
||||||
const per_page = Number( params.per_page ?? 20);
|
const per_page = Number(params.per_page ?? 20);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...params.where,
|
...params.where,
|
||||||
|
|
@ -293,12 +295,12 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMedia(where: {id: string | number}, data: any): Promise<any> {
|
async updateMedia(where: { id: string | number }, data: any): Promise<any> {
|
||||||
// 更新媒体信息
|
// 更新媒体信息
|
||||||
return await this.wpService.updateMedia(Number(this.site.id), Number(where.id), data);
|
return await this.wpService.updateMedia(Number(this.site.id), Number(where.id), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMedia(where: {id: string | number}): Promise<boolean> {
|
async deleteMedia(where: { id: string | number }): Promise<boolean> {
|
||||||
// 删除媒体资源
|
// 删除媒体资源
|
||||||
await this.wpService.deleteMedia(Number(this.site.id), Number(where.id), true);
|
await this.wpService.deleteMedia(Number(this.site.id), Number(where.id), true);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -348,7 +350,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
// 集合过滤参数
|
// 集合过滤参数
|
||||||
if (where.exclude) mapped.exclude = toArray(where.exclude);
|
if (where.exclude) mapped.exclude = toArray(where.exclude);
|
||||||
if (where.ids || where.number || where.id || where.include) mapped.include = [...new Set([where.number,where.id,...toArray(where.ids),...toArray(where.include)])].filter(Boolean);
|
if (where.ids || where.number || where.id || where.include) mapped.include = [...new Set([where.number, where.id, ...toArray(where.ids), ...toArray(where.include)])].filter(Boolean);
|
||||||
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
|
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
|
||||||
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
|
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
|
||||||
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
|
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
|
||||||
|
|
@ -397,14 +399,17 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
mapPlatformToUnifiedOrder(item: WooOrder): UnifiedOrderDTO {
|
mapPlatformToUnifiedOrder(item: WooOrder): UnifiedOrderDTO {
|
||||||
// 将 WooCommerce 订单数据映射为统一订单DTO
|
// 将 WooCommerce 订单数据映射为统一订单DTO
|
||||||
// 包含账单地址与收货地址以及创建与更新时间
|
// 包含账单地址与收货地址以及创建与更新时间
|
||||||
|
|
||||||
// 映射物流追踪信息,将后端格式转换为前端期望的格式
|
// 映射物流追踪信息,将后端格式转换为前端期望的格式
|
||||||
const fulfillments = (item.fulfillments || []).map((track) => ({
|
const metaFulfillments: MetaDataFulfillment[] = item.meta_data?.find?.(_meta => _meta.key === "_wc_shipment_tracking_items")?.value || []
|
||||||
tracking_id: track.tracking_id,
|
const fulfillments = metaFulfillments.map((track) => {
|
||||||
tracking_number: track.tracking_number,
|
return ({
|
||||||
shipping_provider: track.tracking_provider,
|
tracking_id: track.tracking_id,
|
||||||
date_created: track.data_sipped,
|
tracking_number: track.tracking_number,
|
||||||
}));
|
tracking_product_code: track.tracking_product_code,
|
||||||
|
shipping_provider: track.tracking_provider,
|
||||||
|
date_created: dayjs(track.date_shipped).toString(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
|
@ -446,7 +451,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订单操作方法
|
// 订单操作方法
|
||||||
async getOrder(where: {id: string | number}): Promise<UnifiedOrderDTO> {
|
async getOrder(where: { id: string | number }): Promise<UnifiedOrderDTO> {
|
||||||
// 获取单个订单详情
|
// 获取单个订单详情
|
||||||
const api = this.wpService.createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${where.id}`);
|
const res = await api.get(`orders/${where.id}`);
|
||||||
|
|
@ -457,30 +462,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
const requestParams = this.mapOrderSearchParams(params);
|
const requestParams = this.mapOrderSearchParams(params);
|
||||||
const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
|
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 {
|
return {
|
||||||
items: ordersWithFulfillments.map(this.mapPlatformToUnifiedOrder),
|
items: items.map(this.mapPlatformToUnifiedOrder),
|
||||||
total,
|
total,
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
|
|
@ -514,12 +497,12 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedOrder(res.data);
|
return this.mapPlatformToUnifiedOrder(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOrder(where: {id: string | number}, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
async updateOrder(where: { id: string | number }, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||||||
// 更新订单并返回布尔结果
|
// 更新订单并返回布尔结果
|
||||||
return await this.wpService.updateOrder(this.site, String(where.id), data as any);
|
return await this.wpService.updateOrder(this.site, String(where.id), data as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOrder(where: {id: string | number}): Promise<boolean> {
|
async deleteOrder(where: { id: string | number }): Promise<boolean> {
|
||||||
// 删除订单
|
// 删除订单
|
||||||
const api = this.wpService.createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
await api.delete(`orders/${where.id}`, { force: true });
|
await api.delete(`orders/${where.id}`, { force: true });
|
||||||
|
|
@ -606,7 +589,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
status: data.status,
|
status: data.status,
|
||||||
sku: data.sku,
|
sku: data.sku,
|
||||||
regular_price: data.regular_price,
|
regular_price: data.regular_price,
|
||||||
sale_price: data.sale_price,
|
sale_price: data.sale_price,
|
||||||
price: data.price,
|
price: data.price,
|
||||||
|
|
@ -690,10 +673,10 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapCreateProductParams(data: Partial<UnifiedProductDTO>):Partial<WooProduct> {
|
mapCreateProductParams(data: Partial<UnifiedProductDTO>): Partial<WooProduct> {
|
||||||
const {id,...mapped}= this.mapUnifiedToPlatformProduct(data);
|
const { id, ...mapped } = this.mapUnifiedToPlatformProduct(data);
|
||||||
// 创建不带 id
|
// 创建不带 id
|
||||||
return mapped
|
return mapped
|
||||||
}
|
}
|
||||||
mapUpdateProductParams(data: Partial<UnifiedProductDTO>): Partial<WooProduct> {
|
mapUpdateProductParams(data: Partial<UnifiedProductDTO>): Partial<WooProduct> {
|
||||||
return this.mapUnifiedToPlatformProduct(data);
|
return this.mapUnifiedToPlatformProduct(data);
|
||||||
|
|
@ -844,28 +827,28 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 判断是否是这个站点的sku
|
// 判断是否是这个站点的sku
|
||||||
isSiteSkuThisSite(sku: string,){
|
isSiteSkuThisSite(sku: string,) {
|
||||||
return sku.startsWith(this.site.skuPrefix+'-');
|
return sku.startsWith(this.site.skuPrefix + '-');
|
||||||
}
|
}
|
||||||
async getProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<UnifiedProductDTO>{
|
async getProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<UnifiedProductDTO> {
|
||||||
const { id, sku } = where;
|
const { id, sku } = where;
|
||||||
if(id) return this.getProductById(id);
|
if (id) return this.getProductById(id);
|
||||||
if(sku) return this.getProductBySku(sku);
|
if (sku) return this.getProductBySku(sku);
|
||||||
throw new Error('必须提供id或sku参数');
|
throw new Error('必须提供id或sku参数');
|
||||||
}
|
}
|
||||||
async getProductBySku(sku: string){
|
async getProductBySku(sku: string) {
|
||||||
// const api = this.wpService.createApi(this.site, 'wc/v3');
|
// const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
// const res = await api.get(`products`,{
|
// const res = await api.get(`products`,{
|
||||||
// sku
|
// sku
|
||||||
// });
|
// });
|
||||||
// const product = res.data[0];
|
// const product = res.data[0];
|
||||||
const res = await this.wpService.getProducts(this.site,{
|
const res = await this.wpService.getProducts(this.site, {
|
||||||
sku,
|
sku,
|
||||||
page:1,
|
page: 1,
|
||||||
per_page:1,
|
per_page: 1,
|
||||||
});
|
});
|
||||||
const product = res?.items?.[0];
|
const product = res?.items?.[0];
|
||||||
if(!product) return null
|
if (!product) return null
|
||||||
return this.mapPlatformToUnifiedProduct(product);
|
return this.mapPlatformToUnifiedProduct(product);
|
||||||
}
|
}
|
||||||
// 产品操作方法
|
// 产品操作方法
|
||||||
|
|
@ -976,7 +959,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
async updateProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
async updateProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||||||
// 更新产品并返回统一产品DTO
|
// 更新产品并返回统一产品DTO
|
||||||
const product = await this.getProduct(where);
|
const product = await this.getProduct(where);
|
||||||
if(!product){
|
if (!product) {
|
||||||
throw new Error('产品不存在');
|
throw new Error('产品不存在');
|
||||||
}
|
}
|
||||||
const updateData = this.mapUpdateProductParams(data);
|
const updateData = this.mapUpdateProductParams(data);
|
||||||
|
|
@ -987,7 +970,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
async deleteProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<boolean> {
|
async deleteProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<boolean> {
|
||||||
// 删除产品
|
// 删除产品
|
||||||
const product = await this.getProduct(where);
|
const product = await this.getProduct(where);
|
||||||
if(!product){
|
if (!product) {
|
||||||
throw new Error('产品不存在');
|
throw new Error('产品不存在');
|
||||||
}
|
}
|
||||||
const api = this.wpService.createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
|
|
@ -1392,12 +1375,12 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
const result = await this.wpService.getWebhooks(this.site, params);
|
const result = await this.wpService.getWebhooks(this.site, params);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: (result.items as WooWebhook[]).map(this.mapPlatformToUnifiedWebhook),
|
items: (result.items as WooWebhook[]).map(this.mapPlatformToUnifiedWebhook),
|
||||||
total: result.total,
|
total: result.total,
|
||||||
page: Number(params.page || 1),
|
page: Number(params.page || 1),
|
||||||
per_page: Number(params.per_page || 20),
|
per_page: Number(params.per_page || 20),
|
||||||
totalPages: result.totalPages,
|
totalPages: result.totalPages,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to get webhooks: ${error instanceof Error ? error.message : String(error)}`);
|
throw new Error(`Failed to get webhooks: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
}
|
}
|
||||||
|
|
@ -1415,7 +1398,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取单个 webhook 详情
|
// 获取单个 webhook 详情
|
||||||
async getWebhook(where: {id: string | number}): Promise<UnifiedWebhookDTO> {
|
async getWebhook(where: { id: string | number }): Promise<UnifiedWebhookDTO> {
|
||||||
try {
|
try {
|
||||||
const result = await this.wpService.getWebhook(this.site, where.id);
|
const result = await this.wpService.getWebhook(this.site, where.id);
|
||||||
return this.mapPlatformToUnifiedWebhook(result as WooWebhook);
|
return this.mapPlatformToUnifiedWebhook(result as WooWebhook);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Del,
|
Del,
|
||||||
|
Files,
|
||||||
Get,
|
Get,
|
||||||
Inject,
|
Inject,
|
||||||
Param,
|
Param,
|
||||||
|
|
@ -27,7 +28,6 @@ import {
|
||||||
} from '../dto/order.dto';
|
} from '../dto/order.dto';
|
||||||
import { User } from '../decorator/user.decorator';
|
import { User } from '../decorator/user.decorator';
|
||||||
import { ErpOrderStatus } from '../enums/base.enum';
|
import { ErpOrderStatus } from '../enums/base.enum';
|
||||||
|
|
||||||
@Controller('/order')
|
@Controller('/order')
|
||||||
export class OrderController {
|
export class OrderController {
|
||||||
@Inject()
|
@Inject()
|
||||||
|
|
@ -264,4 +264,21 @@ export class OrderController {
|
||||||
return errorResponse(error?.message || '导出失败');
|
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;
|
tracking_id?: string;
|
||||||
@ApiProperty({ description: '物流单号', required: false })
|
@ApiProperty({ description: '物流单号', required: false })
|
||||||
tracking_number?: string;
|
tracking_number?: string;
|
||||||
|
@ApiProperty({ description: "物流产品代码" , required: false})
|
||||||
|
tracking_product_code?: string;
|
||||||
@ApiProperty({ description: '物流公司', required: false })
|
@ApiProperty({ description: '物流公司', required: false })
|
||||||
shipping_provider?: string;
|
shipping_provider?: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -369,9 +369,19 @@ export interface WooOrder {
|
||||||
date_created_gmt?: string;
|
date_created_gmt?: string;
|
||||||
date_modified?: string;
|
date_modified?: string;
|
||||||
date_modified_gmt?: 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;
|
||||||
|
}
|
||||||
// 这个是一个插件的物流追踪信息
|
// 这个是一个插件的物流追踪信息
|
||||||
// 接口:
|
// 接口:
|
||||||
export interface WooFulfillment {
|
export interface WooFulfillment {
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ export class Order {
|
||||||
@Expose()
|
@Expose()
|
||||||
cart_tax: number;
|
cart_tax: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ type: "总金额"})
|
||||||
@Column('decimal', { precision: 10, scale: 2, default: 0 })
|
@Column('decimal', { precision: 10, scale: 2, default: 0 })
|
||||||
@Expose()
|
@Expose()
|
||||||
total: number;
|
total: number;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Inject, Logger, Provide } from '@midwayjs/core';
|
import { Inject, Logger, Provide } from '@midwayjs/core';
|
||||||
import { WPService } from './wp.service';
|
import { WPService } from './wp.service';
|
||||||
|
import * as xlsx from 'xlsx';
|
||||||
import { Order } from '../entity/order.entity';
|
import { Order } from '../entity/order.entity';
|
||||||
import { In, Like, Repository } from 'typeorm';
|
import { In, Like, Repository } from 'typeorm';
|
||||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
|
@ -32,7 +33,7 @@ import { UpdateStockDTO } from '../dto/stock.dto';
|
||||||
import { StockService } from './stock.service';
|
import { StockService } from './stock.service';
|
||||||
import { OrderItemOriginal } from '../entity/order_item_original.entity';
|
import { OrderItemOriginal } from '../entity/order_item_original.entity';
|
||||||
import { SiteApiService } from './site-api.service';
|
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 fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
@ -1479,7 +1480,7 @@ export class OrderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemSql = `
|
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
|
FROM order_sale os
|
||||||
INNER JOIN \`order\` o ON o.id = os.orderId
|
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||||
WHERE o.date_paid BETWEEN ? AND ?
|
WHERE o.date_paid BETWEEN ? AND ?
|
||||||
|
|
@ -1501,7 +1502,7 @@ export class OrderService {
|
||||||
}
|
}
|
||||||
itemSql += nameCondition;
|
itemSql += nameCondition;
|
||||||
itemSql += `
|
itemSql += `
|
||||||
GROUP BY os.productId, os.name
|
GROUP BY os.productId, os.name, os.sku
|
||||||
ORDER BY totalQuantity DESC
|
ORDER BY totalQuantity DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
`;
|
`;
|
||||||
|
|
@ -2579,8 +2580,8 @@ export class OrderService {
|
||||||
* 导出数据为CSV格式
|
* 导出数据为CSV格式
|
||||||
* @param {any[]} data 数据数组
|
* @param {any[]} data 数据数组
|
||||||
* @param {Object} options 配置选项
|
* @param {Object} options 配置选项
|
||||||
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
||||||
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
||||||
* @param {boolean} [options.writeFile=false] 是否写入文件
|
* @param {boolean} [options.writeFile=false] 是否写入文件
|
||||||
* @returns {string|Buffer} 根据type返回字符串或Buffer
|
* @returns {string|Buffer} 根据type返回字符串或Buffer
|
||||||
*/
|
*/
|
||||||
|
|
@ -2728,4 +2729,58 @@ export class OrderService {
|
||||||
|
|
||||||
return result;
|
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