forked from yoone/API
1
0
Fork 0

Compare commits

...

5 Commits

Author SHA1 Message Date
黄珑 188e3a00b6 Feature: Add creating excel util 2025-11-08 11:45:10 +08:00
tikkhun e3b102259c improment(order): 移除不必要的状态回滚逻辑 2025-10-14 06:12:43 +00:00
tikkhun c9342396df refactor(enums): 统一退款相关状态命名从REFUND改为RETURN
将订单状态和ERP订单状态中的退款相关枚举值从REFUND_前缀改为RETURN_前缀,保持命名一致性
2025-10-14 06:12:43 +00:00
tikkhun ca4502d4a3 feat: 对订单状态进行自动变更 2025-10-14 06:12:43 +00:00
zhuotianyuan d04841c7af 20251013-zty-换货功能 2025-10-13 16:49:35 +08:00
7 changed files with 266 additions and 43 deletions

View File

@ -20,6 +20,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"exceljs": "^4.4.0",
"mysql2": "^3.11.5", "mysql2": "^3.11.5",
"nodemailer": "^7.0.5", "nodemailer": "^7.0.5",
"swagger-ui-dist": "^5.18.2", "swagger-ui-dist": "^5.18.2",

View File

@ -115,10 +115,10 @@ export class OrderController {
@Post('/updateOrderItems/:orderId') @Post('/updateOrderItems/:orderId')
async updateOrderItems( async updateOrderItems(
@Param('orderId') orderId: number, @Param('orderId') orderId: number,
@Body() data: any @Body() data: any,
) { ) {
try { try {
const res = await this.orderService.updateOrderSales(orderId, data); const res = await this.orderService.updateExchangeOrder(orderId, data);
return successResponse(res); return successResponse(res);
} catch (error) { } catch (error) {
return errorResponse(error?.message || '更新失败'); return errorResponse(error?.message || '更新失败');
@ -221,4 +221,18 @@ export class OrderController {
return errorResponse(error?.message || '获取失败'); return errorResponse(error?.message || '获取失败');
} }
} }
@ApiOkResponse()
@Get('/excel/price')
async getOrdersPriceInExcel() {
try {
return successResponse(await this.orderService.getOrdersPriceInExcel({
siteId: 0,
startDate: 0,
endDate: 0
}));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
} }

View File

@ -21,7 +21,9 @@ import {
} from '../dto/wp_product.dto'; } from '../dto/wp_product.dto';
import { WPService } from '../service/wp.service'; import { WPService } from '../service/wp.service';
import { WpSite } from '../interface'; import { WpSite } from '../interface';
import {
ProductsRes,
} from '../dto/reponse.dto';
@Controller('/wp_product') @Controller('/wp_product')
export class WpProductController { export class WpProductController {
@Inject() @Inject()
@ -187,4 +189,19 @@ export class WpProductController {
return errorResponse(error.message || '产品变体更新失败'); return errorResponse(error.message || '产品变体更新失败');
} }
} }
@ApiOkResponse({
description: '通过name搜索产品/订单',
type: ProductsRes,
})
@Get('/search')
async searchProducts(@Query('name') name: string) {
try {
// 调用服务获取产品数据
const products = await this.wpProductService.findProductsByName(name);
return successResponse(products);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
} }

View File

@ -244,14 +244,15 @@ export class Order {
utm_source: string; utm_source: string;
@ApiProperty() @ApiProperty()
@Column({ default: '' }) @Column({ default: false })
@Expose() @Expose()
is_exchange: string; is_exchange: boolean;
@ApiProperty() @ApiProperty()
@Column({ default: '' }) @Column('decimal', { precision: 10, scale: 0, default: 0 })
@Expose() @Expose()
exchange_frequency: string; exchange_frequency: number;
@ApiProperty({ @ApiProperty({
example: '2022-12-12 11:11:11', example: '2022-12-12 11:11:11',

View File

@ -31,19 +31,22 @@ export enum StockRecordOperationType {
IN = 'in', IN = 'in',
OUT = 'out', OUT = 'out',
} }
// Order status. Options: pending, processing, on-hold, completed, cancelled, refunded, failed and trash. Default is pending.
// 原始订单状态
export enum OrderStatus { export enum OrderStatus {
PENDING = 'pending', PENDING = 'pending', // default // 待付款
PROCESSING = 'processing', PROCESSING = 'processing', // 正在处理
COMPLETED = 'completed', ON_HOLD = 'on-hold', // 保留
ON_HOLD = 'on-hold', COMPLETED = 'completed', // 已完成
CANCEL = 'cancelled', CANCEL = 'cancelled', // 已取消
REFUNDED = 'refunded', REFUNDED = 'refunded', // 已退款
FAILED = 'failed', FAILED = 'failed', // 失败订单
DRAFT = 'draft', DRAFT = 'draft', // 草稿
REFUND_REQUESTED = 'refund_requested', // 已申请退款 // TRASH = 'trash',
REFUND_APPROVED = 'refund_approved', // 退款申请已通过 // refund 也就是退款相关的状态
REFUND_CANCELLED = 'refund_cancelled', // 已取消退款 RETURN_REQUESTED = 'return-requested', // 已申请退款
RETURN_APPROVED = 'return-approved', // 退款申请已通过
RETURN_CANCELLED = 'return-cancelled', // 已取消退款
} }
export enum ErpOrderStatus { export enum ErpOrderStatus {
@ -56,9 +59,9 @@ export enum ErpOrderStatus {
AFTER_SALE_PROCESSING = 'after_sale_pending', // 售后处理中 AFTER_SALE_PROCESSING = 'after_sale_pending', // 售后处理中
PENDING_RESHIPMENT = 'pending_reshipment', // 待补发 PENDING_RESHIPMENT = 'pending_reshipment', // 待补发
PENDING_REFUND = 'pending_refund', // 待退款 PENDING_REFUND = 'pending_refund', // 待退款
REFUND_REQUESTED = 'refund_requested', // 已申请退款 RETURN_REQUESTED = 'return-requested', // 已申请退款
REFUND_APPROVED = 'refund_approved', // 退款申请已通过 RETURN_APPROVED = 'return-approved', // 退款申请已通过
REFUND_CANCELLED = 'refund_cancelled', // 已取消退款 RETURN_CANCELLED = 'return-cancelled', // 已取消退款
} }
export enum ShipmentType { export enum ShipmentType {

View File

@ -31,6 +31,7 @@ import { ShipmentItem } from '../entity/shipment_item.entity';
import { UpdateStockDTO } from '../dto/stock.dto'; import { UpdateStockDTO } from '../dto/stock.dto';
import { StockService } from './stock.service'; import { StockService } from './stock.service';
import { OrderSaleOriginal } from '../entity/order_item_original.entity'; import { OrderSaleOriginal } from '../entity/order_item_original.entity';
import { createExcel } from '../utils/files.util';
@Provide() @Provide()
export class OrderService { export class OrderService {
@ -111,7 +112,33 @@ export class OrderService {
const order = await this.wPService.getOrder(siteId, orderId); const order = await this.wPService.getOrder(siteId, orderId);
await this.syncSingleOrder(siteId, order, true); await this.syncSingleOrder(siteId, order, true);
} }
// 订单状态切换表
orderAutoNextStatusMap = {
[OrderStatus.RETURN_APPROVED]: OrderStatus.ON_HOLD, // 退款申请已通过转为 on-hold
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
}
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
async autoUpdateOrderStatus(siteId: string, order: any) {
console.log('更新订单状态', order)
// 其他状态保持不变
const originStatus = order.status;
// 如果有值就赋值
if (!this.orderAutoNextStatusMap[originStatus]) {
return
}
try {
const site = this.sites.find(v => v.id === siteId);
if (!site) {
throw new Error(`更新订单信息,但失败,原因为 ${siteId} 的站点信息不存在`)
}
// 同步更新回 wordpress 的 order 状态
await this.wPService.updateOrder(site, order.id, { status: order.status });
order.status = this.orderAutoNextStatusMap[originStatus];
} catch (error) {
console.error('更新订单状态失败,原因为:', error)
}
}
// wordpress 发来,
async syncSingleOrder(siteId: string, order: any, forceUpdate = false) { async syncSingleOrder(siteId: string, order: any, forceUpdate = false) {
let { let {
line_items, line_items,
@ -121,9 +148,12 @@ export class OrderService {
refunds, refunds,
...orderData ...orderData
} = order; } = order;
console.log('同步进单个订单', order)
const existingOrder = await this.orderModel.findOne({ const existingOrder = await this.orderModel.findOne({
where: { externalOrderId: order.id, siteId: siteId }, where: { externalOrderId: order.id, siteId: siteId },
}); });
// 更新状态
await this.autoUpdateOrderStatus(siteId, order);
const orderId = (await this.saveOrder(siteId, orderData)).id; const orderId = (await this.saveOrder(siteId, orderData)).id;
const externalOrderId = order.id; const externalOrderId = order.id;
if ( if (
@ -213,7 +243,7 @@ export class OrderService {
el => el.key === '_wc_order_attribution_utm_source' el => el.key === '_wc_order_attribution_utm_source'
)?.value || ''; )?.value || '';
order.customer_email = order?.billing?.email || order?.shipping?.email; order.customer_email = order?.billing?.email || order?.shipping?.email;
// order.billing_phone = order?.billing?.phone || order?.shipping?.phone; // order.billing_phone = order?.billing?.phone || order?.shipping?.phone;
const entity = plainToClass(Order, order); const entity = plainToClass(Order, order);
const existingOrder = await this.orderModel.findOne({ const existingOrder = await this.orderModel.findOne({
where: { externalOrderId: order.externalOrderId, siteId: siteId }, where: { externalOrderId: order.externalOrderId, siteId: siteId },
@ -230,7 +260,7 @@ export class OrderService {
const customer = await this.customerModel.findOne({ const customer = await this.customerModel.findOne({
where: { email: order.customer_email }, where: { email: order.customer_email },
}); });
if(!customer) { if (!customer) {
await this.customerModel.save({ await this.customerModel.save({
email: order.customer_email, email: order.customer_email,
rate: 0, rate: 0,
@ -263,12 +293,12 @@ export class OrderService {
return ErpOrderStatus.REFUNDED; return ErpOrderStatus.REFUNDED;
case OrderStatus.FAILED: case OrderStatus.FAILED:
return ErpOrderStatus.FAILED; return ErpOrderStatus.FAILED;
case OrderStatus.REFUND_REQUESTED: case OrderStatus.RETURN_REQUESTED:
return ErpOrderStatus.REFUND_REQUESTED; return ErpOrderStatus.RETURN_REQUESTED;
case OrderStatus.REFUND_APPROVED: case OrderStatus.RETURN_APPROVED:
return ErpOrderStatus.REFUND_APPROVED; return ErpOrderStatus.RETURN_APPROVED;
case OrderStatus.REFUND_CANCELLED: case OrderStatus.RETURN_CANCELLED:
return ErpOrderStatus.REFUND_CANCELLED; return ErpOrderStatus.RETURN_CANCELLED;
default: default:
return ErpOrderStatus.PENDING; return ErpOrderStatus.PENDING;
} }
@ -333,7 +363,7 @@ export class OrderService {
} }
} }
async saveOrderItemsG(orderItem: OrderItem) { async saveOrderItemsG(orderItem: OrderItem) {
const existingOrderItem = await this.orderItemModel.findOne({ const existingOrderItem = await this.orderItemModel.findOne({
where: { where: {
externalOrderId: orderItem.externalOrderId, externalOrderId: orderItem.externalOrderId,
@ -572,6 +602,35 @@ export class OrderService {
} }
} }
async getOrdersPriceInExcel({
siteId,
startDate,
endDate
}) {
const orders = await this.orderModel.find({where: {
status: In([OrderStatus.PROCESSING, OrderStatus.COMPLETED])
}});
// const orders = await this.orderModel.findBy({
// status: OrderStatus.PROCESSING
// });
const arr = [];
arr.push(['id', 'price', 'discount_total', 'site_name', 'date_created', 'date_completed']);
console.log('arr', arr);
orders.forEach(order => {
let site = this.sites.find(site => site.id === order.siteId);
arr.push([
order.externalOrderId,
order.total - order.total_tax - order.shipping_total,
order.discount_total,
site?.siteName,
order.date_created,
order.date_completed
]);
});
const path = await createExcel(arr);
return path;
}
async getOrders({ async getOrders({
externalOrderId, externalOrderId,
siteId, siteId,
@ -597,7 +656,7 @@ export class OrderService {
o.total as total, o.total as total,
o.date_created as date_created, o.date_created as date_created,
o.customer_email as customer_email, o.customer_email as customer_email,
o.exchange_frequency as exchange_frequency,
o.transaction_id as transaction_id, o.transaction_id as transaction_id,
o.orderStatus as orderStatus, o.orderStatus as orderStatus,
o.customer_ip_address as customer_ip_address, o.customer_ip_address as customer_ip_address,
@ -673,7 +732,7 @@ export class OrderService {
sqlQuery += ` AND o.payment_method like "%${payment_method}%" `; sqlQuery += ` AND o.payment_method like "%${payment_method}%" `;
totalQuery += ` AND o.payment_method like "%${payment_method}%" `; totalQuery += ` AND o.payment_method like "%${payment_method}%" `;
} }
const user = await this.userModel.findOneBy({id: userId}); const user = await this.userModel.findOneBy({ id: userId });
if (user?.permissions?.includes('order-10-days')) { if (user?.permissions?.includes('order-10-days')) {
sqlQuery += ` AND o.date_created >= ?`; sqlQuery += ` AND o.date_created >= ?`;
totalQuery += ` AND o.date_created >= ?`; totalQuery += ` AND o.date_created >= ?`;
@ -1157,9 +1216,9 @@ export class OrderService {
}); });
} }
// update order_item_origin if not exist // update order_item_origin if not exist
const order_item_origin_count = await this.orderItemOriginalModel.countBy({ orderId: id }); const order_item_origin_count = await this.orderItemOriginalModel.countBy({ orderId: id });
if (order_item_origin_count === 0 && items.length!=0) { if (order_item_origin_count === 0 && items.length != 0) {
items.forEach(async sale => { items.forEach(async sale => {
const { id: saleId, ...saleData } = sale; const { id: saleId, ...saleData } = sale;
await this.orderItemOriginalModel.save(saleData); await this.orderItemOriginalModel.save(saleData);
@ -1368,10 +1427,10 @@ export class OrderService {
const productRepo = manager.getRepository(Product); const productRepo = manager.getRepository(Product);
const order = await orderRepo.findOneBy({ id: orderId }); const order = await orderRepo.findOneBy({ id: orderId });
let product:Product; let product: Product;
await orderSaleRepo.delete({ orderId }); await orderSaleRepo.delete({ orderId });
for (const sale of sales) { for (const sale of 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,
@ -1383,7 +1442,97 @@ export class OrderService {
}); });
}; };
//await orderRepo.save();
}).catch(error => {
transactionError = error;
});
if (transactionError !== undefined) {
throw new Error(`更新物流信息错误:${transactionError.message}`);
}
return true;
} catch (error) {
throw new Error(`更新发货产品失败:${error.message}`);
}
}
//换货确认按钮改成调用这个方法
//换货功能更新OrderSale和Orderitem数据
async updateExchangeOrder(orderId: number, data: any) {
try {
const dataSource = this.dataSourceManager.getDataSource('default');
let transactionError = undefined;
await dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order);
const orderSaleRepo = manager.getRepository(OrderSale);
const orderItemRepo = manager.getRepository(OrderItem);
const OrderItemOriginalRepo = manager.getRepository(OrderItemOriginal);
const productRepo = manager.getRepository(Product);
const WpProductRepo = manager.getRepository(WpProduct);
const order = await orderRepo.findOneBy({ id: orderId });
let product: Product;
let wpProduct: WpProduct;
let wpOrderItemOriginal: OrderItemOriginal;
await orderSaleRepo.delete({ orderId });
await orderItemRepo.delete({ orderId });
for (const sale of data['sales']) {
product = await productRepo.findOneBy({ sku: sale['sku'] });
await orderSaleRepo.save({
orderId,
siteId: order.siteId,
productId: product.id,
name: product.name,
sku: sale['sku'],
quantity: sale['quantity'],
});
};
for (const item of data['items']) {
wpProduct = await WpProductRepo.findOneBy({ sku: item['sku'] });
wpOrderItemOriginal = await OrderItemOriginalRepo.findOneBy({ sku: item['sku'] });
let externalVariationId = wpOrderItemOriginal?.externalVariationId;
let price = wpOrderItemOriginal?.price;
if (wpOrderItemOriginal == null) {
externalVariationId = '0';
price = 0;
}
await orderItemRepo.save({
orderId,
siteId: order.siteId,
productId: wpProduct.id,
name: wpProduct.name,
externalOrderId: order.externalOrderId,
externalProductId: wpProduct.externalProductId,
externalVariationId: externalVariationId,
price: price,
sku: item['sku'],
quantity: item['quantity'],
});
};
//将是否换货状态改为true
await orderRepo.update(
order.id
, {
is_exchange: true
});
//查询这个用户换过多少次货
const counts = await orderRepo.countBy({
is_editable: true,
customer_email: order.customer_email,
});
//批量更新当前用户换货次数
await orderRepo.update({
customer_email: order.customer_email
}, {
exchange_frequency: counts
});
}).catch(error => { }).catch(error => {
transactionError = error; transactionError = error;

View File

@ -507,4 +507,42 @@ const excludeValues = [];
//await this.variationModel.delete({ siteId, externalProductId: productId }); //await this.variationModel.delete({ siteId, externalProductId: productId });
//await this.wpProductModel.delete({ siteId, externalProductId: productId }); //await this.wpProductModel.delete({ siteId, externalProductId: productId });
} }
async findProductsByName(name: string): Promise<WpProduct[]> {
const nameFilter = name ? name.split(' ').filter(Boolean) : [];
const query = this.wpProductModel.createQueryBuilder('product');
// 保证 sku 不为空
query.where('product.sku IS NOT NULL AND product.on_delete = false');
if (nameFilter.length > 0 || name) {
const params: Record<string, string> = {};
const conditions: string[] = [];
// 英文名关键词全部匹配AND
if (nameFilter.length > 0) {
const nameConds = nameFilter.map((word, index) => {
const key = `name${index}`;
params[key] = `%${word}%`;
return `product.name LIKE :${key}`;
});
conditions.push(`(${nameConds.join(' AND ')})`);
}
// 中文名模糊匹配
if (name) {
params['nameCn'] = `%${name}%`;
conditions.push(`product.nameCn LIKE :nameCn`);
}
// 英文名关键词匹配 OR 中文名匹配
query.andWhere(`(${conditions.join(' OR ')})`, params);
}
query.take(50);
return await query.getMany();
}
} }