forked from yoone/API
Compare commits
No commits in common. "f7335d9717656ead0603c908c6f39e39f7df6906" and "e939e3e978c0574f5b3b164fd9ae11bf69be0e90" have entirely different histories.
f7335d9717
...
e939e3e978
|
|
@ -22,7 +22,7 @@ import {
|
||||||
CreateVariationDTO,
|
CreateVariationDTO,
|
||||||
UpdateReviewDTO,
|
UpdateReviewDTO,
|
||||||
} from '../dto/site-api.dto';
|
} from '../dto/site-api.dto';
|
||||||
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto';
|
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
|
||||||
import {
|
import {
|
||||||
ShopyyAllProductQuery,
|
ShopyyAllProductQuery,
|
||||||
ShopyyCustomer,
|
ShopyyCustomer,
|
||||||
|
|
@ -37,7 +37,6 @@ import {
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
} from '../enums/base.enum';
|
} from '../enums/base.enum';
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
import dayjs = require('dayjs');
|
|
||||||
export class ShopyyAdapter implements ISiteAdapter {
|
export class ShopyyAdapter implements ISiteAdapter {
|
||||||
shopyyFinancialStatusMap= {
|
shopyyFinancialStatusMap= {
|
||||||
'200': '待支付',
|
'200': '待支付',
|
||||||
|
|
@ -228,10 +227,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
// ========== 订单映射方法 ==========
|
// ========== 订单映射方法 ==========
|
||||||
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||||
// console.log(item)
|
|
||||||
if(!item) throw new Error('订单数据不能为空')
|
|
||||||
// 提取账单和送货地址 如果不存在则为空对象
|
// 提取账单和送货地址 如果不存在则为空对象
|
||||||
const billing = (item).bill_address || {};
|
const billing = (item as any).billing_address || {};
|
||||||
const shipping = (item as any).shipping_address || {};
|
const shipping = (item as any).shipping_address || {};
|
||||||
|
|
||||||
// 构建账单地址对象
|
// 构建账单地址对象
|
||||||
|
|
@ -316,7 +313,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
product_id: p.product_id,
|
product_id: p.product_id,
|
||||||
quantity: p.quantity,
|
quantity: p.quantity,
|
||||||
total: String(p.price ?? ''),
|
total: String(p.price ?? ''),
|
||||||
sku: p.sku_code || '',
|
sku: p.sku || p.sku_code || '',
|
||||||
price: String(p.price ?? ''),
|
price: String(p.price ?? ''),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -443,7 +440,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新账单地址
|
// 更新账单地址
|
||||||
params.billing_address = params?.billing_address || {};
|
params.billing_address = params.billing_address || {};
|
||||||
if (data.billing.first_name !== undefined) {
|
if (data.billing.first_name !== undefined) {
|
||||||
params.billing_address.first_name = data.billing.first_name;
|
params.billing_address.first_name = data.billing.first_name;
|
||||||
}
|
}
|
||||||
|
|
@ -560,21 +557,9 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
per_page,
|
per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
mapGetAllOrdersParams(params: UnifiedSearchParamsDTO) :ShopyyGetAllOrdersParams{
|
|
||||||
|
|
||||||
const pay_at_min = dayjs(params.after || '').valueOf().toString();
|
|
||||||
const pay_at_max = dayjs(params.before || '').valueOf().toString();
|
|
||||||
|
|
||||||
return {
|
|
||||||
page: params.page || 1,
|
|
||||||
per_page: params.per_page || 20,
|
|
||||||
pay_at_min: pay_at_min,
|
|
||||||
pay_at_max: pay_at_max,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||||
const normalizedParams = this.mapGetAllOrdersParams(params);
|
const data = await this.shopyyService.getAllOrders(this.site.id, params);
|
||||||
const data = await this.shopyyService.getAllOrders(this.site.id, normalizedParams);
|
|
||||||
return data.map(this.mapPlatformToUnifiedOrder.bind(this));
|
return data.map(this.mapPlatformToUnifiedOrder.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,23 +52,6 @@ export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
|
||||||
orderBy?: Record<string, 'asc' | 'desc'> | string;
|
orderBy?: Record<string, 'asc' | 'desc'> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shopyy获取所有订单参数DTO
|
|
||||||
*/
|
|
||||||
export class ShopyyGetAllOrdersParams {
|
|
||||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
|
||||||
page?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '每页数量', example: 20, required: false })
|
|
||||||
per_page?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '支付时间范围开始', example: '2023-01-01T00:00:00Z', required: false })
|
|
||||||
pay_at_min?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '支付时间范围结束', example: '2023-01-01T23:59:59Z', required: false })
|
|
||||||
pay_at_max?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量操作错误项
|
* 批量操作错误项
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,13 @@ export class QueryOrderDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryOrderSalesDTO {
|
export class QueryOrderSalesDTO {
|
||||||
@ApiProperty({ description: '是否为原产品还是库存产品' })
|
@ApiProperty()
|
||||||
@Rule(RuleType.bool().default(false))
|
@Rule(RuleType.bool().default(false))
|
||||||
isSource: boolean;
|
isSource: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Rule(RuleType.bool().default(false))
|
||||||
|
exceptPackage: boolean;
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
@ApiProperty({ example: '1', description: '页码' })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
|
|
@ -110,31 +114,19 @@ export class QueryOrderSalesDTO {
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '排序对象,格式如 { productName: "asc", sku: "desc" }',type: 'any', required: false })
|
@ApiProperty()
|
||||||
@Rule(RuleType.object().allow(null))
|
|
||||||
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
||||||
// filter
|
|
||||||
@ApiProperty({ description: '是否排除套餐' })
|
|
||||||
@Rule(RuleType.bool().default(false))
|
|
||||||
exceptPackage: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '站点ID' })
|
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
siteId: number;
|
siteId: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '名称' })
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ description: 'SKU' })
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
|
||||||
sku: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '开始日期' })
|
|
||||||
@Rule(RuleType.date())
|
@Rule(RuleType.date())
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
|
|
||||||
@ApiProperty({ description: '结束日期' })
|
@ApiProperty()
|
||||||
@Rule(RuleType.date())
|
@Rule(RuleType.date())
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ export interface ShopyyOrder {
|
||||||
customer_email?: string;
|
customer_email?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
// 地址字段
|
// 地址字段
|
||||||
bill_address?: {
|
billing_address?: {
|
||||||
first_name?: string;
|
first_name?: string;
|
||||||
last_name?: string;
|
last_name?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
|
||||||
|
|
@ -272,14 +272,6 @@ export class Order {
|
||||||
@Expose()
|
@Expose()
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@ApiProperty({ type: 'json', nullable: true, description: '订单项列表' })
|
|
||||||
@Expose()
|
|
||||||
orderItems?: any[];
|
|
||||||
|
|
||||||
@ApiProperty({ type: 'json', nullable: true, description: '销售项列表' })
|
|
||||||
@Expose()
|
|
||||||
orderSales?: any[];
|
|
||||||
|
|
||||||
// 在插入或更新前处理用户代理字符串
|
// 在插入或更新前处理用户代理字符串
|
||||||
@BeforeInsert()
|
@BeforeInsert()
|
||||||
@BeforeUpdate()
|
@BeforeUpdate()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Exclude, Expose } from 'class-transformer';
|
import { Exclude, Expose } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
// BeforeInsert,
|
BeforeInsert,
|
||||||
// BeforeUpdate,
|
BeforeUpdate,
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
|
@ -22,22 +22,22 @@ export class OrderSale {
|
||||||
@Expose()
|
@Expose()
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
@ApiProperty({ name:'原始订单ID' })
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty({ name:'站点' })
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: number; // 来源站点唯一标识
|
siteId: number; // 来源站点唯一标识
|
||||||
|
|
||||||
@ApiProperty({name: "原始订单 itemId"})
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
externalOrderItemId: string; // WooCommerce 订单item ID
|
externalOrderItemId: string; // WooCommerce 订单item ID
|
||||||
|
|
||||||
@ApiProperty({name: "产品 ID"})
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
@ -62,35 +62,25 @@ export class OrderSale {
|
||||||
@Expose()
|
@Expose()
|
||||||
isPackage: boolean;
|
isPackage: boolean;
|
||||||
|
|
||||||
@ApiProperty({ description: '品牌', type: 'string',nullable: true})
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
@Expose()
|
@Expose()
|
||||||
@Column({ nullable: true })
|
isYoone: boolean;
|
||||||
brand?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '口味', type: 'string', nullable: true })
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
@Expose()
|
@Expose()
|
||||||
@Column({ nullable: true })
|
isZex: boolean;
|
||||||
flavor?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '湿度', type: 'string', nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
|
@Column({ type: 'int', nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
@Column({ nullable: true })
|
size: number | null;
|
||||||
humidity?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '尺寸', type: 'string', nullable: true })
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
@Expose()
|
@Expose()
|
||||||
@Column({ nullable: true })
|
isYooneNew: boolean;
|
||||||
size?: string;
|
|
||||||
|
|
||||||
@ApiProperty({name: '强度', nullable: true })
|
|
||||||
@Column({ nullable: true })
|
|
||||||
@Expose()
|
|
||||||
strength: string | null;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '版本', type: 'string', nullable: true })
|
|
||||||
@Expose()
|
|
||||||
@Column({ nullable: true })
|
|
||||||
version?: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '2022-12-12 11:11:11',
|
example: '2022-12-12 11:11:11',
|
||||||
|
|
@ -107,4 +97,25 @@ export class OrderSale {
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
|
|
||||||
|
// === 自动计算逻辑 ===
|
||||||
|
@BeforeInsert()
|
||||||
|
@BeforeUpdate()
|
||||||
|
setFlags() {
|
||||||
|
if (!this.name) return;
|
||||||
|
const lower = this.name.toLowerCase();
|
||||||
|
this.isYoone = lower.includes('yoone');
|
||||||
|
this.isZex = lower.includes('zex');
|
||||||
|
this.isYooneNew = this.isYoone && lower.includes('new');
|
||||||
|
let size: number | null = null;
|
||||||
|
const sizes = [3, 6, 9, 12, 15, 18];
|
||||||
|
for (const s of sizes) {
|
||||||
|
if (lower.includes(s.toString())) {
|
||||||
|
size = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,20 +75,17 @@ export class SyncUniuniShipmentJob implements IJob{
|
||||||
'255': 'Gateway_To_Gateway_Transit'
|
'255': 'Gateway_To_Gateway_Transit'
|
||||||
};
|
};
|
||||||
async onTick() {
|
async onTick() {
|
||||||
|
try {
|
||||||
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
||||||
const results = await Promise.all(
|
shipments.forEach(shipment => {
|
||||||
shipments.map(async shipment => {
|
this.logisticsService.updateShipmentState(shipment);
|
||||||
return await this.logisticsService.updateShipmentState(shipment);
|
});
|
||||||
})
|
} catch (error) {
|
||||||
)
|
this.logger.error(`更新运单状态失败 ${error.message}`);
|
||||||
this.logger.info(`更新运单状态完毕 ${JSON.stringify(results)}`);
|
}
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onComplete(result: any) {
|
onComplete(result: any) {
|
||||||
this.logger.info(`更新运单状态完成 ${result}`);
|
|
||||||
}
|
|
||||||
onError(error: any) {
|
|
||||||
this.logger.error(`更新运单状态失败 ${error.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,486 +0,0 @@
|
||||||
import { Inject, Provide } from '@midwayjs/core';
|
|
||||||
import axios from 'axios';
|
|
||||||
import * as crypto from 'crypto';
|
|
||||||
import dayjs = require('dayjs');
|
|
||||||
import utc = require('dayjs/plugin/utc');
|
|
||||||
import timezone = require('dayjs/plugin/timezone');
|
|
||||||
|
|
||||||
// 扩展dayjs功能
|
|
||||||
dayjs.extend(utc);
|
|
||||||
dayjs.extend(timezone);
|
|
||||||
|
|
||||||
// 全局参数配置接口
|
|
||||||
interface FreightwavesConfig {
|
|
||||||
appSecret: string;
|
|
||||||
apiBaseUrl: string;
|
|
||||||
partner: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 地址信息接口
|
|
||||||
interface Address {
|
|
||||||
name: string;
|
|
||||||
phone: string;
|
|
||||||
company: string;
|
|
||||||
countryCode: string;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
address1: string;
|
|
||||||
address2: string;
|
|
||||||
postCode: string;
|
|
||||||
zoneCode?: string;
|
|
||||||
countryName: string;
|
|
||||||
cityName: string;
|
|
||||||
stateName: string;
|
|
||||||
companyName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 包裹尺寸接口
|
|
||||||
interface Dimensions {
|
|
||||||
length: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
lengthUnit: 'IN' | 'CM';
|
|
||||||
weight: number;
|
|
||||||
weightUnit: 'LB' | 'KG';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 包裹信息接口
|
|
||||||
interface Package {
|
|
||||||
dimensions: Dimensions;
|
|
||||||
currency: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 申报信息接口
|
|
||||||
interface Declaration {
|
|
||||||
boxNo: string;
|
|
||||||
sku: string;
|
|
||||||
cnname: string;
|
|
||||||
enname: string;
|
|
||||||
declaredPrice: number;
|
|
||||||
declaredQty: number;
|
|
||||||
material: string;
|
|
||||||
intendedUse: string;
|
|
||||||
cweight: number;
|
|
||||||
hsCode: string;
|
|
||||||
battery: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 费用试算请求接口
|
|
||||||
interface RateTryRequest {
|
|
||||||
shipCompany: string;
|
|
||||||
partnerOrderNumber: string;
|
|
||||||
warehouseId?: string;
|
|
||||||
shipper: Address;
|
|
||||||
reciver: Address;
|
|
||||||
packages: Package[];
|
|
||||||
partner: string;
|
|
||||||
signService?: 0 | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建订单请求接口
|
|
||||||
interface CreateOrderRequest extends RateTryRequest {
|
|
||||||
declaration: Declaration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询订单请求接口
|
|
||||||
interface QueryOrderRequest {
|
|
||||||
partnerOrderNumber?: string;
|
|
||||||
shipOrderId?: string;
|
|
||||||
partner: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改订单请求接口
|
|
||||||
interface ModifyOrderRequest extends CreateOrderRequest {
|
|
||||||
shipOrderId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 订单退款请求接口
|
|
||||||
interface RefundOrderRequest {
|
|
||||||
shipOrderId: string;
|
|
||||||
partner: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通用响应接口
|
|
||||||
interface FreightwavesResponse<T> {
|
|
||||||
code: string;
|
|
||||||
msg: string;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 费用试算响应数据接口
|
|
||||||
interface RateTryResponseData {
|
|
||||||
shipCompany: string;
|
|
||||||
channelCode: string;
|
|
||||||
totalAmount: number;
|
|
||||||
currency: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建订单响应数据接口
|
|
||||||
interface CreateOrderResponseData {
|
|
||||||
partnerOrderNumber: string;
|
|
||||||
shipOrderId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询订单响应数据接口
|
|
||||||
interface QueryOrderResponseData {
|
|
||||||
thirdOrderId: string;
|
|
||||||
shipCompany: string;
|
|
||||||
expressFinish: 0 | 1 | 2;
|
|
||||||
expressFailMsg: string;
|
|
||||||
expressOrder: {
|
|
||||||
mainTrackingNumber: string;
|
|
||||||
labelPath: string[];
|
|
||||||
totalAmount: number;
|
|
||||||
currency: string;
|
|
||||||
balance: number;
|
|
||||||
};
|
|
||||||
partnerOrderNumber: string;
|
|
||||||
shipOrderId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改订单响应数据接口
|
|
||||||
interface ModifyOrderResponseData extends CreateOrderResponseData {}
|
|
||||||
|
|
||||||
// 订单退款响应数据接口
|
|
||||||
interface RefundOrderResponseData {}
|
|
||||||
|
|
||||||
@Provide()
|
|
||||||
export class FreightwavesService {
|
|
||||||
@Inject() logger;
|
|
||||||
|
|
||||||
// 默认配置
|
|
||||||
private config: FreightwavesConfig = {
|
|
||||||
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
|
|
||||||
apiBaseUrl: 'https://tms.freightwaves.ca',
|
|
||||||
partner: '25072621035200000060',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化配置
|
|
||||||
setConfig(config: Partial<FreightwavesConfig>): void {
|
|
||||||
this.config = { ...this.config, ...config };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成签名
|
|
||||||
private generateSignature(body: any, date: string): string {
|
|
||||||
const bodyString = JSON.stringify(body);
|
|
||||||
const signatureStr = `${bodyString}${this.config.appSecret}${date}`;
|
|
||||||
return crypto.createHash('md5').update(signatureStr).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送请求
|
|
||||||
private async sendRequest<T>(url: string, data: any): Promise<FreightwavesResponse<T>> {
|
|
||||||
try {
|
|
||||||
// 设置请求头 - 使用太平洋时间 (America/Los_Angeles)
|
|
||||||
const date = dayjs().tz('America/Los_Angeles').format('YYYY-mm-dd HH:mm:ss');
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'requestDate': date,
|
|
||||||
'signature': this.generateSignature(data, date),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 记录请求前的详细信息
|
|
||||||
this.log(`Sending request to: ${this.config.apiBaseUrl}${url}`, {
|
|
||||||
headers,
|
|
||||||
data
|
|
||||||
});
|
|
||||||
|
|
||||||
// 发送请求 - 临时禁用SSL证书验证以解决UNABLE_TO_VERIFY_LEAF_SIGNATURE错误
|
|
||||||
const response = await axios.post<FreightwavesResponse<T>>(
|
|
||||||
`${this.config.apiBaseUrl}${url}`,
|
|
||||||
data,
|
|
||||||
{
|
|
||||||
headers,
|
|
||||||
httpsAgent: new (require('https').Agent)({
|
|
||||||
rejectUnauthorized: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 记录响应信息
|
|
||||||
this.log(`Received response from: ${this.config.apiBaseUrl}${url}`, {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
data: response.data
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理响应
|
|
||||||
if (response.data.code !== '00000200') {
|
|
||||||
this.log(`Freightwaves API error: ${response.data.msg}`, { url, data, response: response.data });
|
|
||||||
throw new Error(response.data.msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
// 更详细的错误记录
|
|
||||||
if (error.response) {
|
|
||||||
// 请求已发送,服务器返回错误状态码
|
|
||||||
this.log(`Freightwaves API request failed with status: ${error.response.status}`, {
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
response: error.response.data,
|
|
||||||
status: error.response.status,
|
|
||||||
headers: error.response.headers
|
|
||||||
});
|
|
||||||
} else if (error.request) {
|
|
||||||
// 请求已发送,但没有收到响应
|
|
||||||
this.log(`Freightwaves API request no response received`, {
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
request: error.request
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 请求配置时发生错误
|
|
||||||
this.log(`Freightwaves API request configuration error: ${error.message}`, {
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
error: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 费用试算
|
|
||||||
* @param params 费用试算参数
|
|
||||||
* @returns 费用试算结果
|
|
||||||
*/
|
|
||||||
async rateTry(params: Omit<RateTryRequest, 'partner'>): Promise<RateTryResponseData> {
|
|
||||||
const requestData: RateTryRequest = {
|
|
||||||
...params,
|
|
||||||
partner: this.config.partner,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await this.sendRequest<RateTryResponseData>('/shipService/order/rateTry', requestData);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建订单
|
|
||||||
* @param params 创建订单参数
|
|
||||||
* @returns 创建订单结果
|
|
||||||
*/
|
|
||||||
async createOrder(params: Omit<CreateOrderRequest, 'partner'>): Promise<CreateOrderResponseData> {
|
|
||||||
const requestData: CreateOrderRequest = {
|
|
||||||
...params,
|
|
||||||
partner: this.config.partner,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await this.sendRequest<CreateOrderResponseData>('/shipService/order/createOrder?apipost_id=0422aa', requestData);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询订单
|
|
||||||
* @param params 查询订单参数
|
|
||||||
* @returns 查询订单结果
|
|
||||||
*/
|
|
||||||
async queryOrder(params: Omit<QueryOrderRequest, 'partner'>): Promise<QueryOrderResponseData> {
|
|
||||||
const requestData: QueryOrderRequest = {
|
|
||||||
...params,
|
|
||||||
partner: this.config.partner,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await this.sendRequest<QueryOrderResponseData>('/shipService/order/queryOrder', requestData);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改订单
|
|
||||||
* @param params 修改订单参数
|
|
||||||
* @returns 修改订单结果
|
|
||||||
*/
|
|
||||||
async modifyOrder(params: Omit<ModifyOrderRequest, 'partner'>): Promise<ModifyOrderResponseData> {
|
|
||||||
const requestData: ModifyOrderRequest = {
|
|
||||||
...params,
|
|
||||||
partner: this.config.partner,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await this.sendRequest<ModifyOrderResponseData>('/shipService/order/modifyOrder', requestData);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 订单退款
|
|
||||||
* @param params 订单退款参数
|
|
||||||
* @returns 订单退款结果
|
|
||||||
*/
|
|
||||||
async refundOrder(params: Omit<RefundOrderRequest, 'partner'>): Promise<RefundOrderResponseData> {
|
|
||||||
const requestData: RefundOrderRequest = {
|
|
||||||
...params,
|
|
||||||
partner: this.config.partner,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await this.sendRequest<RefundOrderResponseData>('/shipService/order/refundOrder', requestData);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试创建订单方法
|
|
||||||
* 用于演示如何使用createOrder方法
|
|
||||||
*/
|
|
||||||
async testCreateOrder() {
|
|
||||||
try {
|
|
||||||
// 设置必要的配置
|
|
||||||
this.setConfig({
|
|
||||||
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
|
|
||||||
apiBaseUrl: 'https://tms.freightwaves.ca',
|
|
||||||
partner: '25072621035200000060'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 准备测试数据
|
|
||||||
const testParams: Omit<CreateOrderRequest, 'partner'> = {
|
|
||||||
shipCompany: 'DHL',
|
|
||||||
partnerOrderNumber: `test-order-${Date.now()}`,
|
|
||||||
warehouseId: '25072621035200000060',
|
|
||||||
shipper: {
|
|
||||||
name: 'John Doe',
|
|
||||||
phone: '123-456-7890',
|
|
||||||
company: 'Test Company',
|
|
||||||
countryCode: 'CA',
|
|
||||||
city: 'Toronto',
|
|
||||||
state: 'ON',
|
|
||||||
address1: '123 Main St',
|
|
||||||
address2: 'Suite 400',
|
|
||||||
postCode: 'M5V 2T6',
|
|
||||||
countryName: 'Canada',
|
|
||||||
cityName: 'Toronto',
|
|
||||||
stateName: 'Ontario',
|
|
||||||
companyName: 'Test Company Inc.'
|
|
||||||
},
|
|
||||||
reciver: {
|
|
||||||
name: 'Jane Smith',
|
|
||||||
phone: '987-654-3210',
|
|
||||||
company: 'Receiver Company',
|
|
||||||
countryCode: 'CA',
|
|
||||||
city: 'Vancouver',
|
|
||||||
state: 'BC',
|
|
||||||
address1: '456 Oak St',
|
|
||||||
address2: '',
|
|
||||||
postCode: 'V6J 2A9',
|
|
||||||
countryName: 'Canada',
|
|
||||||
cityName: 'Vancouver',
|
|
||||||
stateName: 'British Columbia',
|
|
||||||
companyName: 'Receiver Company Ltd.'
|
|
||||||
},
|
|
||||||
packages: [
|
|
||||||
{
|
|
||||||
dimensions: {
|
|
||||||
length: 10,
|
|
||||||
width: 8,
|
|
||||||
height: 6,
|
|
||||||
lengthUnit: 'IN',
|
|
||||||
weight: 5,
|
|
||||||
weightUnit: 'LB'
|
|
||||||
},
|
|
||||||
currency: 'CAD',
|
|
||||||
description: 'Test Package'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
declaration: {
|
|
||||||
boxNo: 'BOX-001',
|
|
||||||
sku: 'TEST-SKU-001',
|
|
||||||
cnname: '测试产品',
|
|
||||||
enname: 'Test Product',
|
|
||||||
declaredPrice: 100,
|
|
||||||
declaredQty: 1,
|
|
||||||
material: 'Plastic',
|
|
||||||
intendedUse: 'General use',
|
|
||||||
cweight: 5,
|
|
||||||
hsCode: '39269090',
|
|
||||||
battery: 'No'
|
|
||||||
},
|
|
||||||
signService: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// 调用创建订单方法
|
|
||||||
this.log('开始测试创建订单...');
|
|
||||||
this.log('测试参数:', testParams);
|
|
||||||
|
|
||||||
// 注意:在实际环境中取消注释以下行来执行真实请求
|
|
||||||
const result = await this.createOrder(testParams);
|
|
||||||
this.log('创建订单成功:', result);
|
|
||||||
|
|
||||||
|
|
||||||
// 返回模拟结果
|
|
||||||
return {
|
|
||||||
partnerOrderNumber: testParams.partnerOrderNumber,
|
|
||||||
shipOrderId: `simulated-shipOrderId-${Date.now()}`
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.log('测试创建订单失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试查询订单方法
|
|
||||||
* @returns 查询订单结果
|
|
||||||
*/
|
|
||||||
async testQueryOrder() {
|
|
||||||
try {
|
|
||||||
// 设置必要的配置
|
|
||||||
this.setConfig({
|
|
||||||
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
|
|
||||||
apiBaseUrl: 'https://console-mock.apipost.cn/mock/0',
|
|
||||||
partner: '25072621035200000060'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 准备测试数据 - 可以通过partnerOrderNumber或shipOrderId查询
|
|
||||||
const testParams: Omit<QueryOrderRequest, 'partner'> = {
|
|
||||||
// 选择其中一个参数进行测试
|
|
||||||
partnerOrderNumber: 'test-order-123456789', // 示例订单号
|
|
||||||
// shipOrderId: 'simulated-shipOrderId-123456789' // 或者使用运单号
|
|
||||||
};
|
|
||||||
|
|
||||||
// 调用查询订单方法
|
|
||||||
this.log('开始测试查询订单...');
|
|
||||||
this.log('测试参数:', testParams);
|
|
||||||
|
|
||||||
// 注意:在实际环境中取消注释以下行来执行真实请求
|
|
||||||
const result = await this.queryOrder(testParams);
|
|
||||||
this.log('查询订单成功:', result);
|
|
||||||
|
|
||||||
this.log('测试完成:查询订单方法调用成功(模拟)');
|
|
||||||
|
|
||||||
// 返回模拟结果
|
|
||||||
return {
|
|
||||||
thirdOrderId: 'thirdOrder-123456789',
|
|
||||||
shipCompany: 'DHL',
|
|
||||||
expressFinish: 0,
|
|
||||||
expressFailMsg: '',
|
|
||||||
expressOrder: {
|
|
||||||
mainTrackingNumber: '1234567890',
|
|
||||||
labelPath: ['https://example.com/label.pdf'],
|
|
||||||
totalAmount: 100,
|
|
||||||
currency: 'CAD',
|
|
||||||
balance: 50
|
|
||||||
},
|
|
||||||
partnerOrderNumber: testParams.partnerOrderNumber,
|
|
||||||
shipOrderId: 'simulated-shipOrderId-123456789'
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.log('测试查询订单失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 辅助日志方法,处理logger可能未定义的情况
|
|
||||||
* @param message 日志消息
|
|
||||||
* @param data 附加数据
|
|
||||||
*/
|
|
||||||
private log(message: string, data?: any) {
|
|
||||||
if (this.logger) {
|
|
||||||
this.logger.info(message, data);
|
|
||||||
} else {
|
|
||||||
// 如果logger未定义,使用console输出
|
|
||||||
if (data) {
|
|
||||||
console.log(message, data);
|
|
||||||
} else {
|
|
||||||
console.log(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -125,10 +125,6 @@ export class LogisticsService {
|
||||||
try {
|
try {
|
||||||
const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number);
|
const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number);
|
||||||
console.log('updateShipmentState data:', data);
|
console.log('updateShipmentState data:', data);
|
||||||
// huo
|
|
||||||
if(data.status === 'FAIL'){
|
|
||||||
throw new Error('获取运单状态失败,原因为'+ data.ret_msg)
|
|
||||||
}
|
|
||||||
shipment.state = data.data[0].state;
|
shipment.state = data.data[0].state;
|
||||||
if (shipment.state in [203, 215, 216, 230]) { // todo,写常数
|
if (shipment.state in [203, 215, 216, 230]) { // todo,写常数
|
||||||
shipment.finished = true;
|
shipment.finished = true;
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { UnifiedOrderDTO } from '../dto/site-api.dto';
|
import { UnifiedOrderDTO } from '../dto/site-api.dto';
|
||||||
import { CustomerService } from './customer.service';
|
import { CustomerService } from './customer.service';
|
||||||
import { ProductService } from './product.service';
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
|
|
||||||
|
|
@ -111,9 +110,7 @@ export class OrderService {
|
||||||
|
|
||||||
@Logger()
|
@Logger()
|
||||||
logger; // 注入 Logger 实例
|
logger; // 注入 Logger 实例
|
||||||
@Inject()
|
|
||||||
productService: ProductService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量同步订单
|
* 批量同步订单
|
||||||
* 流程说明:
|
* 流程说明:
|
||||||
|
|
@ -149,8 +146,8 @@ export class OrderService {
|
||||||
const existingOrder = await this.orderModel.findOne({
|
const existingOrder = await this.orderModel.findOne({
|
||||||
where: { externalOrderId: String(order.id), siteId: siteId },
|
where: { externalOrderId: String(order.id), siteId: siteId },
|
||||||
});
|
});
|
||||||
if (!existingOrder) {
|
if(!existingOrder){
|
||||||
console.log("数据库中不存在", order.id, '订单状态:', order.status)
|
console.log("数据库中不存在",order.id, '订单状态:', order.status )
|
||||||
}
|
}
|
||||||
// 同步单个订单
|
// 同步单个订单
|
||||||
await this.syncSingleOrder(siteId, order);
|
await this.syncSingleOrder(siteId, order);
|
||||||
|
|
@ -214,8 +211,8 @@ export class OrderService {
|
||||||
const existingOrder = await this.orderModel.findOne({
|
const existingOrder = await this.orderModel.findOne({
|
||||||
where: { externalOrderId: String(order.id), siteId: siteId },
|
where: { externalOrderId: String(order.id), siteId: siteId },
|
||||||
});
|
});
|
||||||
if (!existingOrder) {
|
if(!existingOrder){
|
||||||
console.log("数据库不存在", siteId, "订单:", order.id, '订单状态:' + order.status)
|
console.log("数据库不存在", siteId , "订单:",order.id, '订单状态:' + order.status )
|
||||||
}
|
}
|
||||||
// 同步单个订单
|
// 同步单个订单
|
||||||
await this.syncSingleOrder(siteId, order, true);
|
await this.syncSingleOrder(siteId, order, true);
|
||||||
|
|
@ -274,7 +271,7 @@ export class OrderService {
|
||||||
try {
|
try {
|
||||||
const site = await this.siteService.get(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
// 仅处理 WooCommerce 站点
|
// 仅处理 WooCommerce 站点
|
||||||
if (site.type !== 'woocommerce') {
|
if(site.type !== 'woocommerce'){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 将订单状态同步到 WooCommerce,然后切换至下一状态
|
// 将订单状态同步到 WooCommerce,然后切换至下一状态
|
||||||
|
|
@ -284,11 +281,6 @@ export class OrderService {
|
||||||
console.error('更新订单状态失败,原因为:', error)
|
console.error('更新订单状态失败,原因为:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getOrderByExternalOrderId(siteId: number, externalOrderId: string) {
|
|
||||||
return await this.orderModel.findOne({
|
|
||||||
where: { externalOrderId: String(externalOrderId), siteId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 同步单个订单
|
* 同步单个订单
|
||||||
* 流程说明:
|
* 流程说明:
|
||||||
|
|
@ -326,28 +318,47 @@ export class OrderService {
|
||||||
// console.log('同步进单个订单', order)
|
// console.log('同步进单个订单', order)
|
||||||
// 如果订单状态为 AUTO_DRAFT,则跳过处理
|
// 如果订单状态为 AUTO_DRAFT,则跳过处理
|
||||||
if (order.status === OrderStatus.AUTO_DRAFT) {
|
if (order.status === OrderStatus.AUTO_DRAFT) {
|
||||||
this.logger.debug('订单状态为 AUTO_DRAFT,跳过处理', siteId, order.id)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 这里其实不用过滤不可编辑的行为,而是应在 save 中做判断
|
// 检查数据库中是否已存在该订单
|
||||||
// if(!order.is_editable && !forceUpdate){
|
const existingOrder = await this.orderModel.findOne({
|
||||||
// this.logger.debug('订单不可编辑,跳过处理', siteId, order.id)
|
where: { externalOrderId: String(order.id), siteId: siteId },
|
||||||
// return;
|
});
|
||||||
// }
|
// 自动更新订单状态(如果需要)
|
||||||
// 自动转换远程订单的状态(如果需要)
|
|
||||||
await this.autoUpdateOrderStatus(siteId, order);
|
await this.autoUpdateOrderStatus(siteId, order);
|
||||||
// 这里的 saveOrder 已经包括了创建订单和更新订单
|
|
||||||
let orderRecord: Order = await this.saveOrder(siteId, orderData);
|
if(existingOrder){
|
||||||
// 如果订单从未完成变为完成状态,则更新库存
|
// 矫正数据库中的订单数据
|
||||||
|
const updateData: any = { status: order.status };
|
||||||
|
if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
|
||||||
|
updateData.orderStatus = this.mapOrderStatus(order.status as any);
|
||||||
|
}
|
||||||
|
// 更新订单主数据
|
||||||
|
await this.orderModel.update({ externalOrderId: String(order.id), siteId: siteId }, updateData);
|
||||||
|
// 更新 fulfillments 数据
|
||||||
|
await this.saveOrderFulfillments({
|
||||||
|
siteId,
|
||||||
|
orderId: existingOrder.id,
|
||||||
|
externalOrderId:order.id,
|
||||||
|
fulfillments: fulfillments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const externalOrderId = String(order.id);
|
||||||
|
// 如果订单从未完成变为完成状态,则更新库存
|
||||||
if (
|
if (
|
||||||
orderRecord &&
|
existingOrder &&
|
||||||
orderRecord.orderStatus !== ErpOrderStatus.COMPLETED &&
|
existingOrder.orderStatus !== ErpOrderStatus.COMPLETED &&
|
||||||
orderData.status === OrderStatus.COMPLETED
|
orderData.status === OrderStatus.COMPLETED
|
||||||
) {
|
) {
|
||||||
await this.updateStock(orderRecord);
|
this.updateStock(existingOrder);
|
||||||
// 不再直接返回,继续执行后续的更新操作
|
// 不再直接返回,继续执行后续的更新操作
|
||||||
}
|
}
|
||||||
const externalOrderId = String(order.id);
|
// 如果订单不可编辑且不强制更新,则跳过处理
|
||||||
|
if (existingOrder && !existingOrder.is_editable && !forceUpdate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 保存订单主数据
|
||||||
|
const orderRecord = await this.saveOrder(siteId, orderData);
|
||||||
const orderId = orderRecord.id;
|
const orderId = orderRecord.id;
|
||||||
// 保存订单项
|
// 保存订单项
|
||||||
await this.saveOrderItems({
|
await this.saveOrderItems({
|
||||||
|
|
@ -451,14 +462,13 @@ export class OrderService {
|
||||||
* @param order 订单数据
|
* @param order 订单数据
|
||||||
* @returns 保存后的订单实体
|
* @returns 保存后的订单实体
|
||||||
*/
|
*/
|
||||||
// 这里 omit 是因为处理在外头了 其实 saveOrder 应该包括 savelineitems 等
|
async saveOrder(siteId: number, order: Partial<UnifiedOrderDTO>): Promise<Order> {
|
||||||
async saveOrder(siteId: number, order: Omit<UnifiedOrderDTO, 'line_items' | 'refunds'>): Promise<Order> {
|
|
||||||
// 将外部订单ID转换为字符串
|
// 将外部订单ID转换为字符串
|
||||||
const externalOrderId = String(order.id)
|
const externalOrderId = String(order.id)
|
||||||
delete order.id
|
delete order.id
|
||||||
|
|
||||||
// 创建订单实体对象
|
// 创建订单实体对象
|
||||||
const entity = plainToClass(Order, { ...order, externalOrderId, siteId });
|
const entity = plainToClass(Order, {...order, externalOrderId, siteId});
|
||||||
// 检查数据库中是否已存在该订单
|
// 检查数据库中是否已存在该订单
|
||||||
const existingOrder = await this.orderModel.findOne({
|
const existingOrder = await this.orderModel.findOne({
|
||||||
where: { externalOrderId, siteId: siteId },
|
where: { externalOrderId, siteId: siteId },
|
||||||
|
|
@ -701,8 +711,6 @@ export class OrderService {
|
||||||
*
|
*
|
||||||
* @param orderItem 订单项实体
|
* @param orderItem 订单项实体
|
||||||
*/
|
*/
|
||||||
// TODO 这里存的是库存商品实际
|
|
||||||
// 所以叫做 orderInventoryItems 可能更合适
|
|
||||||
async saveOrderSale(orderItem: OrderItem) {
|
async saveOrderSale(orderItem: OrderItem) {
|
||||||
const currentOrderSale = await this.orderSaleModel.find({
|
const currentOrderSale = await this.orderSaleModel.find({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -717,53 +725,50 @@ export class OrderService {
|
||||||
// 从数据库查询产品,关联查询组件
|
// 从数据库查询产品,关联查询组件
|
||||||
const product = await this.productModel.findOne({
|
const product = await this.productModel.findOne({
|
||||||
where: { siteSkus: Like(`%${orderItem.sku}%`) },
|
where: { siteSkus: Like(`%${orderItem.sku}%`) },
|
||||||
relations: ['components','attributes','attributes.dict'],
|
relations: ['components'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!product) return;
|
if (!product) return;
|
||||||
const componentDetails: { product: Product, quantity: number }[] = product.components?.length > 0 ? await Promise.all(product.components.map(async comp => {
|
|
||||||
return {
|
|
||||||
product: await this.productModel.findOne({
|
|
||||||
where: { sku: comp.sku },
|
|
||||||
relations: ['components', 'attributes','attributes.dict'],
|
|
||||||
}),
|
|
||||||
quantity: comp.quantity * orderItem.quantity,
|
|
||||||
}
|
|
||||||
})) : [{ product, quantity: orderItem.quantity }]
|
|
||||||
|
|
||||||
const orderSales: OrderSale[] = componentDetails.map(componentDetail => {
|
const orderSales: OrderSale[] = [];
|
||||||
if (!componentDetail.product) return null
|
|
||||||
const attrsObj = this.productService.getAttributesObject(product.attributes)
|
if (product.components && product.components.length > 0) {
|
||||||
const orderSale = plainToClass(OrderSale, {
|
for (const comp of product.components) {
|
||||||
|
const baseProduct = await this.productModel.findOne({
|
||||||
|
where: { sku: comp.sku },
|
||||||
|
});
|
||||||
|
if (baseProduct) {
|
||||||
|
const orderSaleItem: OrderSale = plainToClass(OrderSale, {
|
||||||
|
orderId: orderItem.orderId,
|
||||||
|
siteId: orderItem.siteId,
|
||||||
|
externalOrderItemId: orderItem.externalOrderItemId,
|
||||||
|
productId: baseProduct.id,
|
||||||
|
name: baseProduct.name,
|
||||||
|
quantity: comp.quantity * orderItem.quantity,
|
||||||
|
sku: comp.sku,
|
||||||
|
isPackage: orderItem.name.toLowerCase().includes('package'),
|
||||||
|
});
|
||||||
|
orderSales.push(orderSaleItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const orderSaleItem: OrderSale = plainToClass(OrderSale, {
|
||||||
orderId: orderItem.orderId,
|
orderId: orderItem.orderId,
|
||||||
siteId: orderItem.siteId,
|
siteId: orderItem.siteId,
|
||||||
externalOrderItemId: orderItem.externalOrderItemId,
|
externalOrderItemId: orderItem.externalOrderItemId,
|
||||||
productId: componentDetail.product.id,
|
productId: product.id,
|
||||||
name: componentDetail.product.name,
|
name: product.name,
|
||||||
quantity: componentDetail.quantity * orderItem.quantity,
|
quantity: orderItem.quantity,
|
||||||
sku: componentDetail.product.sku,
|
sku: product.sku,
|
||||||
// 理论上直接存 product 的全部数据才是对的,因为这样我的数据才全面。
|
isPackage: orderItem.name.toLowerCase().includes('package'),
|
||||||
isPackage: componentDetail.product.type === 'bundle',
|
|
||||||
isYoone: attrsObj?.['brand']?.name === 'yoone',
|
|
||||||
isZyn: attrsObj?.['brand']?.name === 'zyn',
|
|
||||||
isZex: attrsObj?.['brand']?.name === 'zex',
|
|
||||||
isYooneNew: attrsObj?.['brand']?.name === 'yoone' && attrsObj?.['version']?.name === 'new',
|
|
||||||
strength: attrsObj?.['strength']?.name,
|
|
||||||
});
|
});
|
||||||
return orderSale
|
orderSales.push(orderSaleItem);
|
||||||
}).filter(v => v !== null)
|
}
|
||||||
console.log("orderSales",orderSales)
|
|
||||||
if (orderSales.length > 0) {
|
if (orderSales.length > 0) {
|
||||||
await this.orderSaleModel.save(orderSales);
|
await this.orderSaleModel.save(orderSales);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// // extract stren
|
|
||||||
// extractNumberFromString(str: string): number {
|
|
||||||
// if (!str) return 0;
|
|
||||||
|
|
||||||
// const num = parseInt(str, 10);
|
|
||||||
// return isNaN(num) ? 0 : num;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存订单退款信息
|
* 保存订单退款信息
|
||||||
|
|
@ -1424,7 +1429,7 @@ export class OrderService {
|
||||||
* @param params 查询参数
|
* @param params 查询参数
|
||||||
* @returns 销售统计和分页信息
|
* @returns 销售统计和分页信息
|
||||||
*/
|
*/
|
||||||
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage, orderBy }: QueryOrderSalesDTO) {
|
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage }: QueryOrderSalesDTO) {
|
||||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||||
const defaultStart = dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
const defaultStart = dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
const defaultEnd = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
const defaultEnd = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
@ -1577,14 +1582,14 @@ export class OrderService {
|
||||||
`;
|
`;
|
||||||
let yooneSql = `
|
let yooneSql = `
|
||||||
SELECT
|
SELECT
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '3mg' THEN os.quantity ELSE 0 END) AS yoone3Quantity,
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 3 THEN os.quantity ELSE 0 END) AS yoone3Quantity,
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '6mg' THEN os.quantity ELSE 0 END) AS yoone6Quantity,
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 6 THEN os.quantity ELSE 0 END) AS yoone6Quantity,
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '9mg' THEN os.quantity ELSE 0 END) AS yoone9Quantity,
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 9 THEN os.quantity ELSE 0 END) AS yoone9Quantity,
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '12mg' THEN os.quantity ELSE 0 END) AS yoone12Quantity,
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 12 THEN os.quantity ELSE 0 END) AS yoone12Quantity,
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '12mg' THEN os.quantity ELSE 0 END) AS yoone12QuantityNew,
|
SUM(CASE WHEN os.isYooneNew = 1 AND os.size = 12 THEN os.quantity ELSE 0 END) AS yoone12QuantityNew,
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '15mg' THEN os.quantity ELSE 0 END) AS yoone15Quantity,
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 15 THEN os.quantity ELSE 0 END) AS yoone15Quantity,
|
||||||
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '18mg' THEN os.quantity ELSE 0 END) AS yoone18Quantity,
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 18 THEN os.quantity ELSE 0 END) AS yoone18Quantity,
|
||||||
SUM(CASE WHEN os.brand = 'zex' THEN os.quantity ELSE 0 END) AS zexQuantity
|
SUM(CASE WHEN os.isZex = 1 THEN os.quantity ELSE 0 END) AS zexQuantity
|
||||||
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 ?
|
||||||
|
|
@ -1640,12 +1645,11 @@ export class OrderService {
|
||||||
* @returns 订单项统计和分页信息
|
* @returns 订单项统计和分页信息
|
||||||
*/
|
*/
|
||||||
async getOrderItems({
|
async getOrderItems({
|
||||||
current,
|
|
||||||
pageSize,
|
|
||||||
siteId,
|
siteId,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
sku,
|
current,
|
||||||
|
pageSize,
|
||||||
name,
|
name,
|
||||||
}: QueryOrderSalesDTO) {
|
}: QueryOrderSalesDTO) {
|
||||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||||
|
|
@ -1903,8 +1907,8 @@ export class OrderService {
|
||||||
const key = it?.externalSubscriptionId
|
const key = it?.externalSubscriptionId
|
||||||
? `sub:${it.externalSubscriptionId}`
|
? `sub:${it.externalSubscriptionId}`
|
||||||
: it?.externalOrderId
|
: it?.externalOrderId
|
||||||
? `ord:${it.externalOrderId}`
|
? `ord:${it.externalOrderId}`
|
||||||
: `id:${it?.id}`;
|
: `id:${it?.id}`;
|
||||||
if (!seen.has(key)) {
|
if (!seen.has(key)) {
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
relatedList.push(it);
|
relatedList.push(it);
|
||||||
|
|
@ -2198,14 +2202,14 @@ export class OrderService {
|
||||||
for (const sale of sales) {
|
for (const sale of sales) {
|
||||||
const product = await productRepo.findOne({ where: { sku: sale.sku } });
|
const product = await productRepo.findOne({ where: { sku: sale.sku } });
|
||||||
const saleItem = {
|
const saleItem = {
|
||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
siteId: order.siteId,
|
siteId: order.siteId,
|
||||||
externalOrderItemId: '-1',
|
externalOrderItemId: '-1',
|
||||||
productId: product.id,
|
productId: product.id,
|
||||||
name: product.name,
|
name: product.name,
|
||||||
sku: sale.sku,
|
sku: sale.sku,
|
||||||
quantity: sale.quantity,
|
quantity: sale.quantity,
|
||||||
};
|
};
|
||||||
await orderSaleRepo.save(saleItem);
|
await orderSaleRepo.save(saleItem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -2338,83 +2342,83 @@ export class OrderService {
|
||||||
//换货功能更新OrderSale和Orderitem数据
|
//换货功能更新OrderSale和Orderitem数据
|
||||||
async updateExchangeOrder(orderId: number, data: any) {
|
async updateExchangeOrder(orderId: number, data: any) {
|
||||||
throw new Error('暂未实现')
|
throw new Error('暂未实现')
|
||||||
// try {
|
// try {
|
||||||
// const dataSource = this.dataSourceManager.getDataSource('default');
|
// const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
// let transactionError = undefined;
|
// let transactionError = undefined;
|
||||||
|
|
||||||
// await dataSource.transaction(async manager => {
|
// await dataSource.transaction(async manager => {
|
||||||
// const orderRepo = manager.getRepository(Order);
|
// const orderRepo = manager.getRepository(Order);
|
||||||
// const orderSaleRepo = manager.getRepository(OrderSale);
|
// const orderSaleRepo = manager.getRepository(OrderSale);
|
||||||
// const orderItemRepo = manager.getRepository(OrderItem);
|
// const orderItemRepo = manager.getRepository(OrderItem);
|
||||||
|
|
||||||
|
|
||||||
// const productRepo = manager.getRepository(ProductV2);
|
// const productRepo = manager.getRepository(ProductV2);
|
||||||
|
|
||||||
// const order = await orderRepo.findOneBy({ id: orderId });
|
// const order = await orderRepo.findOneBy({ id: orderId });
|
||||||
// let product: ProductV2;
|
// let product: ProductV2;
|
||||||
|
|
||||||
// await orderSaleRepo.delete({ orderId });
|
// await orderSaleRepo.delete({ orderId });
|
||||||
// await orderItemRepo.delete({ orderId });
|
// await orderItemRepo.delete({ orderId });
|
||||||
// for (const sale of data['sales']) {
|
// for (const sale of data['sales']) {
|
||||||
// product = await productRepo.findOneBy({ sku: sale['sku'] });
|
// product = await productRepo.findOneBy({ sku: sale['sku'] });
|
||||||
// await orderSaleRepo.save({
|
// await orderSaleRepo.save({
|
||||||
// orderId,
|
// orderId,
|
||||||
// siteId: order.siteId,
|
// siteId: order.siteId,
|
||||||
// productId: product.id,
|
// productId: product.id,
|
||||||
// name: product.name,
|
// name: product.name,
|
||||||
// sku: sale['sku'],
|
// sku: sale['sku'],
|
||||||
// quantity: sale['quantity'],
|
// quantity: sale['quantity'],
|
||||||
// });
|
// });
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// for (const item of data['items']) {
|
// for (const item of data['items']) {
|
||||||
// product = await productRepo.findOneBy({ sku: item['sku'] });
|
// product = await productRepo.findOneBy({ sku: item['sku'] });
|
||||||
|
|
||||||
// await orderItemRepo.save({
|
// await orderItemRepo.save({
|
||||||
// orderId,
|
// orderId,
|
||||||
// siteId: order.siteId,
|
// siteId: order.siteId,
|
||||||
// productId: product.id,
|
// productId: product.id,
|
||||||
// name: product.name,
|
// name: product.name,
|
||||||
// externalOrderId: order.externalOrderId,
|
// externalOrderId: order.externalOrderId,
|
||||||
// externalProductId: product.externalProductId,
|
// externalProductId: product.externalProductId,
|
||||||
|
|
||||||
// sku: item['sku'],
|
// sku: item['sku'],
|
||||||
// quantity: item['quantity'],
|
// quantity: item['quantity'],
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// //将是否换货状态改为true
|
// //将是否换货状态改为true
|
||||||
// await orderRepo.update(
|
// await orderRepo.update(
|
||||||
// order.id
|
// order.id
|
||||||
// , {
|
// , {
|
||||||
// is_exchange: true
|
// is_exchange: true
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// //查询这个用户换过多少次货
|
// //查询这个用户换过多少次货
|
||||||
// const counts = await orderRepo.countBy({
|
// const counts = await orderRepo.countBy({
|
||||||
// is_editable: true,
|
// is_editable: true,
|
||||||
// customer_email: order.customer_email,
|
// customer_email: order.customer_email,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// //批量更新当前用户换货次数
|
// //批量更新当前用户换货次数
|
||||||
// await orderRepo.update({
|
// await orderRepo.update({
|
||||||
// customer_email: order.customer_email
|
// customer_email: order.customer_email
|
||||||
// }, {
|
// }, {
|
||||||
// exchange_frequency: counts
|
// exchange_frequency: counts
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// }).catch(error => {
|
// }).catch(error => {
|
||||||
// transactionError = error;
|
// transactionError = error;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// if (transactionError !== undefined) {
|
// if (transactionError !== undefined) {
|
||||||
// throw new Error(`更新物流信息错误:${transactionError.message}`);
|
// throw new Error(`更新物流信息错误:${transactionError.message}`);
|
||||||
// }
|
// }
|
||||||
// return true;
|
// return true;
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// throw new Error(`更新发货产品失败:${error.message}`);
|
// throw new Error(`更新发货产品失败:${error.message}`);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2460,17 +2464,17 @@ export class OrderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 过滤掉NaN和非数字值,只保留有效的数字ID
|
// 过滤掉NaN和非数字值,只保留有效的数字ID
|
||||||
const validIds = ids?.filter?.(id => Number.isFinite(id) && id > 0);
|
const validIds = ids?.filter?.(id => Number.isFinite(id) && id > 0);
|
||||||
|
|
||||||
const dataSource = this.dataSourceManager.getDataSource('default');
|
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
|
|
||||||
// 优化事务使用
|
// 优化事务使用
|
||||||
return await dataSource.transaction(async manager => {
|
return await dataSource.transaction(async manager => {
|
||||||
// 准备查询条件
|
// 准备查询条件
|
||||||
const whereCondition: any = {};
|
const whereCondition: any = {};
|
||||||
if (validIds.length > 0) {
|
if(validIds.length > 0){
|
||||||
whereCondition.id = In(validIds);
|
whereCondition.id = In(validIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2486,7 +2490,7 @@ export class OrderService {
|
||||||
|
|
||||||
// 获取所有订单ID
|
// 获取所有订单ID
|
||||||
const orderIds = orders.map(order => order.id);
|
const orderIds = orders.map(order => order.id);
|
||||||
|
|
||||||
// 获取所有订单项
|
// 获取所有订单项
|
||||||
const orderItems = await manager.getRepository(OrderItem).find({
|
const orderItems = await manager.getRepository(OrderItem).find({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -2507,13 +2511,13 @@ export class OrderService {
|
||||||
const exportDataList: ExportData[] = orders.map(order => {
|
const exportDataList: ExportData[] = orders.map(order => {
|
||||||
// 获取订单的订单项
|
// 获取订单的订单项
|
||||||
const items = orderItemsByOrderId[order.id] || [];
|
const items = orderItemsByOrderId[order.id] || [];
|
||||||
|
|
||||||
// 计算总盒数
|
// 计算总盒数
|
||||||
const boxCount = items.reduce((total, item) => total + item.quantity, 0);
|
const boxCount = items.reduce((total, item) => total + item.quantity, 0);
|
||||||
|
|
||||||
// 构建订单内容
|
// 构建订单内容
|
||||||
const orderContent = items.map(item => `${item.name} (${item.sku || ''}) x ${item.quantity}`).join('; ');
|
const orderContent = items.map(item => `${item.name} (${item.sku || ''}) x ${item.quantity}`).join('; ');
|
||||||
|
|
||||||
// 构建姓名地址
|
// 构建姓名地址
|
||||||
const shipping = order.shipping;
|
const shipping = order.shipping;
|
||||||
const billing = order.billing;
|
const billing = order.billing;
|
||||||
|
|
@ -2527,10 +2531,10 @@ export class OrderService {
|
||||||
const postcode = shipping?.postcode || billing?.postcode || '';
|
const postcode = shipping?.postcode || billing?.postcode || '';
|
||||||
const country = shipping?.country || billing?.country || '';
|
const country = shipping?.country || billing?.country || '';
|
||||||
const nameAddress = `${name} ${address} ${address2} ${city} ${state} ${postcode} ${country}`;
|
const nameAddress = `${name} ${address} ${address2} ${city} ${state} ${postcode} ${country}`;
|
||||||
|
|
||||||
// 获取电话号码
|
// 获取电话号码
|
||||||
const phone = shipping?.phone || billing?.phone || '';
|
const phone = shipping?.phone || billing?.phone || '';
|
||||||
|
|
||||||
// 获取快递号
|
// 获取快递号
|
||||||
const trackingNumber = order.shipment?.tracking_id || '';
|
const trackingNumber = order.shipment?.tracking_id || '';
|
||||||
|
|
||||||
|
|
@ -2566,84 +2570,84 @@ 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
|
||||||
*/
|
*/
|
||||||
async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?: string; writeFile?: boolean } = {}): Promise<string | Buffer> {
|
async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?: string; writeFile?: boolean } = {}): Promise<string | Buffer> {
|
||||||
try {
|
try {
|
||||||
// 检查数据是否为空
|
// 检查数据是否为空
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
throw new Error('导出数据不能为空');
|
throw new Error('导出数据不能为空');
|
||||||
}
|
|
||||||
|
|
||||||
const { type = 'string', fileName, writeFile = false } = options;
|
|
||||||
|
|
||||||
// 生成表头
|
|
||||||
const headers = Object.keys(data[0]);
|
|
||||||
let csvContent = headers.join(',') + '\n';
|
|
||||||
|
|
||||||
// 处理数据行
|
|
||||||
data.forEach(item => {
|
|
||||||
const row = headers.map(key => {
|
|
||||||
const value = item[key as keyof any];
|
|
||||||
// 处理特殊字符
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
// 转义双引号,将"替换为""
|
|
||||||
const escapedValue = value.replace(/"/g, '""');
|
|
||||||
// 如果包含逗号或换行符,需要用双引号包裹
|
|
||||||
if (escapedValue.includes(',') || escapedValue.includes('\n')) {
|
|
||||||
return `"${escapedValue}"`;
|
|
||||||
}
|
|
||||||
return escapedValue;
|
|
||||||
}
|
|
||||||
// 处理日期类型
|
|
||||||
if (value instanceof Date) {
|
|
||||||
return value.toISOString();
|
|
||||||
}
|
|
||||||
// 处理undefined和null
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return String(value);
|
|
||||||
}).join(',');
|
|
||||||
csvContent += row + '\n';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果需要写入文件
|
|
||||||
if (writeFile && fileName) {
|
|
||||||
// 获取当前用户目录
|
|
||||||
const userHomeDir = os.homedir();
|
|
||||||
|
|
||||||
// 构建目标路径(下载目录)
|
|
||||||
const downloadsDir = path.join(userHomeDir, 'Downloads');
|
|
||||||
|
|
||||||
// 确保下载目录存在
|
|
||||||
if (!fs.existsSync(downloadsDir)) {
|
|
||||||
fs.mkdirSync(downloadsDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = path.join(downloadsDir, fileName);
|
|
||||||
|
|
||||||
// 写入文件
|
|
||||||
fs.writeFileSync(filePath, csvContent, 'utf8');
|
|
||||||
|
|
||||||
console.log(`数据已成功导出至 ${filePath}`);
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据类型返回不同结果
|
|
||||||
if (type === 'buffer') {
|
|
||||||
return Buffer.from(csvContent, 'utf8');
|
|
||||||
}
|
|
||||||
|
|
||||||
return csvContent;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('导出CSV时出错:', error);
|
|
||||||
throw new Error(`导出CSV文件失败: ${error.message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { type = 'string', fileName, writeFile = false } = options;
|
||||||
|
|
||||||
|
// 生成表头
|
||||||
|
const headers = Object.keys(data[0]);
|
||||||
|
let csvContent = headers.join(',') + '\n';
|
||||||
|
|
||||||
|
// 处理数据行
|
||||||
|
data.forEach(item => {
|
||||||
|
const row = headers.map(key => {
|
||||||
|
const value = item[key as keyof any];
|
||||||
|
// 处理特殊字符
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// 转义双引号,将"替换为""
|
||||||
|
const escapedValue = value.replace(/"/g, '""');
|
||||||
|
// 如果包含逗号或换行符,需要用双引号包裹
|
||||||
|
if (escapedValue.includes(',') || escapedValue.includes('\n')) {
|
||||||
|
return `"${escapedValue}"`;
|
||||||
|
}
|
||||||
|
return escapedValue;
|
||||||
|
}
|
||||||
|
// 处理日期类型
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value.toISOString();
|
||||||
|
}
|
||||||
|
// 处理undefined和null
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
}).join(',');
|
||||||
|
csvContent += row + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果需要写入文件
|
||||||
|
if (writeFile && fileName) {
|
||||||
|
// 获取当前用户目录
|
||||||
|
const userHomeDir = os.homedir();
|
||||||
|
|
||||||
|
// 构建目标路径(下载目录)
|
||||||
|
const downloadsDir = path.join(userHomeDir, 'Downloads');
|
||||||
|
|
||||||
|
// 确保下载目录存在
|
||||||
|
if (!fs.existsSync(downloadsDir)) {
|
||||||
|
fs.mkdirSync(downloadsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join(downloadsDir, fileName);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
fs.writeFileSync(filePath, csvContent, 'utf8');
|
||||||
|
|
||||||
|
console.log(`数据已成功导出至 ${filePath}`);
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型返回不同结果
|
||||||
|
if (type === 'buffer') {
|
||||||
|
return Buffer.from(csvContent, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return csvContent;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出CSV时出错:', error);
|
||||||
|
throw new Error(`导出CSV文件失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除每个分号前面一个左右括号和最后一个左右括号包含的内容(包括括号本身)
|
* 删除每个分号前面一个左右括号和最后一个左右括号包含的内容(包括括号本身)
|
||||||
|
|
@ -2720,4 +2724,9 @@ export class OrderService {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1461,17 +1461,12 @@ export class ProductService {
|
||||||
return {
|
return {
|
||||||
sku,
|
sku,
|
||||||
name: val(rec.name),
|
name: val(rec.name),
|
||||||
nameCn: val(rec.nameCn),
|
nameCn: val(rec.nameCn),
|
||||||
description: val(rec.description),
|
description: val(rec.description),
|
||||||
price: num(rec.price),
|
price: num(rec.price),
|
||||||
promotionPrice: num(rec.promotionPrice),
|
promotionPrice: num(rec.promotionPrice),
|
||||||
type: val(rec.type),
|
type: val(rec.type),
|
||||||
siteSkus: rec.siteSkus
|
siteSkus: rec.siteSkus ? String(rec.siteSkus).split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
||||||
? String(rec.siteSkus)
|
|
||||||
.split(/[;,]/) // 支持英文分号或英文逗号分隔
|
|
||||||
.map(s => s.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: undefined,
|
|
||||||
category, // 添加分类字段
|
category, // 添加分类字段
|
||||||
|
|
||||||
attributes: attributes.length > 0 ? attributes : undefined,
|
attributes: attributes.length > 0 ? attributes : undefined,
|
||||||
|
|
@ -1536,14 +1531,7 @@ export class ProductService {
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
getAttributesObject(attributes:DictItem[]){
|
|
||||||
if(!attributes) return {}
|
|
||||||
const obj:any = {}
|
|
||||||
attributes.forEach(attr=>{
|
|
||||||
obj[attr.dict.name] = attr
|
|
||||||
})
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
// 将单个产品转换为 CSV 行数组
|
// 将单个产品转换为 CSV 行数组
|
||||||
transformProductToCsvRow(
|
transformProductToCsvRow(
|
||||||
p: Product,
|
p: Product,
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,7 @@ export class ShopyyService {
|
||||||
|
|
||||||
const { items: firstPageItems, totalPages} = firstPage;
|
const { items: firstPageItems, totalPages} = firstPage;
|
||||||
|
|
||||||
|
// const { page = 1, per_page = 100 } = params;
|
||||||
// 如果只有一页数据,直接返回
|
// 如果只有一页数据,直接返回
|
||||||
if (totalPages <= 1) {
|
if (totalPages <= 1) {
|
||||||
return firstPageItems;
|
return firstPageItems;
|
||||||
|
|
@ -333,7 +334,7 @@ export class ShopyyService {
|
||||||
// 创建当前批次的并发请求
|
// 创建当前批次的并发请求
|
||||||
for (let i = 0; i < batchSize; i++) {
|
for (let i = 0; i < batchSize; i++) {
|
||||||
const page = currentPage + i;
|
const page = currentPage + i;
|
||||||
const pagePromise = this.getOrders(site, page, 100, params)
|
const pagePromise = this.getOrders(site, page, 100)
|
||||||
.then(pageResult => pageResult.items)
|
.then(pageResult => pageResult.items)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(`获取第 ${page} 页数据失败:`, error);
|
console.error(`获取第 ${page} 页数据失败:`, error);
|
||||||
|
|
|
||||||
|
|
@ -73,16 +73,16 @@ export class StatisticsService {
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
orderId,
|
orderId,
|
||||||
SUM(CASE WHEN brand = 'zyn' THEN quantity ELSE 0 END) AS zyn_quantity,
|
SUM(CASE WHEN name LIKE '%zyn%' THEN quantity ELSE 0 END) AS zyn_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' THEN quantity ELSE 0 END) AS yoone_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' THEN quantity ELSE 0 END) AS yoone_quantity,
|
||||||
SUM(CASE WHEN brand = 'zex' THEN quantity ELSE 0 END) AS zex_quantity,
|
SUM(CASE WHEN name LIKE '%zex%' THEN quantity ELSE 0 END) AS zex_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '3mg' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '6mg' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '9mg' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '12mg' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '15mg' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
||||||
FROM order_sale
|
FROM order_sale
|
||||||
GROUP BY orderId
|
GROUP BY orderId
|
||||||
),
|
),
|
||||||
|
|
@ -269,16 +269,16 @@ export class StatisticsService {
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
orderId,
|
orderId,
|
||||||
SUM(CASE WHEN brand = 'zyn' THEN quantity ELSE 0 END) AS zyn_quantity,
|
SUM(CASE WHEN name LIKE '%zyn%' THEN quantity ELSE 0 END) AS zyn_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' THEN quantity ELSE 0 END) AS yoone_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' THEN quantity ELSE 0 END) AS yoone_quantity,
|
||||||
SUM(CASE WHEN brand = 'zex' THEN quantity ELSE 0 END) AS zex_quantity,
|
SUM(CASE WHEN name LIKE '%zex%' THEN quantity ELSE 0 END) AS zex_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '3mg' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '6mg' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '9mg' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '12mg' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '15mg' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
||||||
FROM order_sale
|
FROM order_sale
|
||||||
GROUP BY orderId
|
GROUP BY orderId
|
||||||
),
|
),
|
||||||
|
|
@ -466,16 +466,16 @@ export class StatisticsService {
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
orderId,
|
orderId,
|
||||||
SUM(CASE WHEN brand = 'zyn' THEN quantity ELSE 0 END) AS zyn_quantity,
|
SUM(CASE WHEN name LIKE '%zyn%' THEN quantity ELSE 0 END) AS zyn_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' THEN quantity ELSE 0 END) AS yoone_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' THEN quantity ELSE 0 END) AS yoone_quantity,
|
||||||
SUM(CASE WHEN brand = 'zex' THEN quantity ELSE 0 END) AS zex_quantity,
|
SUM(CASE WHEN name LIKE '%zex%' THEN quantity ELSE 0 END) AS zex_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '3mg' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '6mg' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '9mg' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '12mg' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
||||||
SUM(CASE WHEN brand = 'yoone' AND strength = '15mg' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
||||||
FROM order_sale
|
FROM order_sale
|
||||||
GROUP BY orderId
|
GROUP BY orderId
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// Test script for FreightwavesService createOrder method
|
|
||||||
|
|
||||||
const { FreightwavesService } = require('./dist/service/test-freightwaves.service');
|
|
||||||
|
|
||||||
async function testFreightwavesService() {
|
|
||||||
try {
|
|
||||||
// Create an instance of the FreightwavesService
|
|
||||||
const service = new FreightwavesService();
|
|
||||||
|
|
||||||
// Call the test method
|
|
||||||
console.log('Starting test for createOrder method...');
|
|
||||||
const result = await service.testQueryOrder();
|
|
||||||
|
|
||||||
console.log('Test completed successfully!');
|
|
||||||
console.log('Result:', result);
|
|
||||||
console.log('\nTo run the actual createOrder request:');
|
|
||||||
console.log('1. Uncomment the createOrder call in the testCreateOrder method');
|
|
||||||
console.log('2. Update the test-secret, test-partner-id with real credentials');
|
|
||||||
console.log('3. Run this script again');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Test failed:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the test
|
|
||||||
testFreightwavesService();
|
|
||||||
Loading…
Reference in New Issue