forked from yoone/API
feat(订单): 添加订单商品列表和关联订单查询功能
- 新增getOrderItemList接口用于查询订单商品列表 - 新增getRelatedByOrder接口用于查询关联订单 - 在QueryOrderDTO中添加isSubscriptionOnly字段用于筛选订阅订单 - 优化订单查询SQL,添加订阅订单过滤条件 - 为日期参数添加默认值处理
This commit is contained in:
parent
eff9efc2c3
commit
8778b8138d
|
|
@ -22,6 +22,7 @@ import {
|
|||
CreateOrderNoteDTO,
|
||||
QueryOrderDTO,
|
||||
QueryOrderSalesDTO,
|
||||
QueryOrderItemDTO,
|
||||
} from '../dto/order.dto';
|
||||
import { User } from '../decorator/user.decorator';
|
||||
import { ErpOrderStatus } from '../enums/base.enum';
|
||||
|
|
@ -107,6 +108,16 @@ export class OrderController {
|
|||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse()
|
||||
@Get('/getOrderItemList')
|
||||
async getOrderItemList(@Query() param: QueryOrderItemDTO) {
|
||||
try {
|
||||
return successResponse(await this.orderService.getOrderItemList(param));
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '获取失败');
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
type: OrderDetailRes,
|
||||
})
|
||||
|
|
@ -119,6 +130,16 @@ export class OrderController {
|
|||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse()
|
||||
@Get('/:orderId/related')
|
||||
async getRelatedByOrder(@Param('orderId') orderId: number) {
|
||||
try {
|
||||
return successResponse(await this.orderService.getRelatedByOrder(orderId));
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '获取失败');
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
type: BooleanRes,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -91,6 +91,10 @@ export class QueryOrderDTO {
|
|||
@ApiProperty()
|
||||
@Rule(RuleType.string())
|
||||
payment_method: string;
|
||||
|
||||
@ApiProperty({ description: '仅订阅订单(父订阅订单或包含订阅商品)' })
|
||||
@Rule(RuleType.bool().default(false))
|
||||
isSubscriptionOnly?: boolean;
|
||||
}
|
||||
|
||||
export class QueryOrderSalesDTO {
|
||||
|
|
@ -119,11 +123,11 @@ export class QueryOrderSalesDTO {
|
|||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.date().required())
|
||||
@Rule(RuleType.date())
|
||||
startDate: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.date().required())
|
||||
@Rule(RuleType.date())
|
||||
endDate: Date;
|
||||
}
|
||||
|
||||
|
|
@ -141,3 +145,37 @@ export class CreateOrderNoteDTO {
|
|||
@Rule(RuleType.string())
|
||||
content: string;
|
||||
}
|
||||
|
||||
export class QueryOrderItemDTO {
|
||||
@ApiProperty({ example: '1', description: '页码' })
|
||||
@Rule(RuleType.number())
|
||||
current: number;
|
||||
|
||||
@ApiProperty({ example: '10', description: '每页大小' })
|
||||
@Rule(RuleType.number())
|
||||
pageSize: number;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string().allow(''))
|
||||
siteId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string().allow(''))
|
||||
name: string; // 商品名称关键字
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string().allow(''))
|
||||
externalProductId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string().allow(''))
|
||||
externalVariationId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.date())
|
||||
startDate: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.date())
|
||||
endDate: Date;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
} from '../enums/base.enum';
|
||||
import { Variation } from '../entity/variation.entity';
|
||||
import { CreateOrderNoteDTO, QueryOrderSalesDTO } from '../dto/order.dto';
|
||||
import dayjs = require('dayjs');
|
||||
import { OrderDetailRes } from '../dto/reponse.dto';
|
||||
import { OrderNote } from '../entity/order_note.entity';
|
||||
import { User } from '../entity/user.entity';
|
||||
|
|
@ -310,6 +311,7 @@ export class OrderService {
|
|||
externalOrderId: string;
|
||||
orderItems: Record<string, any>[];
|
||||
}) {
|
||||
console.log('saveOrderItems params',params)
|
||||
const { siteId, orderId, externalOrderId, orderItems } = params;
|
||||
const currentOrderItems = await this.orderItemModel.find({
|
||||
where: { siteId, externalOrderId: externalOrderId },
|
||||
|
|
@ -614,6 +616,7 @@ export class OrderService {
|
|||
customer_email,
|
||||
payment_method,
|
||||
billing_phone,
|
||||
isSubscriptionOnly = false,
|
||||
}, userId = undefined) {
|
||||
const parameters: any[] = [];
|
||||
|
||||
|
|
@ -699,12 +702,14 @@ export class OrderService {
|
|||
totalQuery += ` AND o.date_created <= ?`;
|
||||
parameters.push(endDate);
|
||||
}
|
||||
// 支付方式筛选(使用参数化,避免SQL注入)
|
||||
if (payment_method) {
|
||||
sqlQuery += ` AND o.payment_method like "%${payment_method}%" `;
|
||||
totalQuery += ` AND o.payment_method like "%${payment_method}%" `;
|
||||
sqlQuery += ` AND o.payment_method LIKE ?`;
|
||||
totalQuery += ` AND o.payment_method LIKE ?`;
|
||||
parameters.push(`%${payment_method}%`);
|
||||
}
|
||||
const user = await this.userModel.findOneBy({ id: userId });
|
||||
if (user?.permissions?.includes('order-10-days')) {
|
||||
if (user?.permissions?.includes('order-10-days') && !startDate && !endDate) {
|
||||
sqlQuery += ` AND o.date_created >= ?`;
|
||||
totalQuery += ` AND o.date_created >= ?`;
|
||||
const tenDaysAgo = new Date();
|
||||
|
|
@ -729,6 +734,21 @@ export class OrderService {
|
|||
}
|
||||
}
|
||||
|
||||
// 仅订阅订单过滤:父订阅订单 或 行项目包含订阅相关元数据(兼容 JSON 与字符串存储)
|
||||
if (isSubscriptionOnly) {
|
||||
const subCond = `
|
||||
AND (
|
||||
EXISTS (
|
||||
SELECT 1 FROM subscription s
|
||||
WHERE s.siteId = o.siteId AND s.parent_id = o.externalOrderId
|
||||
)
|
||||
|
||||
)
|
||||
`;
|
||||
sqlQuery += subCond;
|
||||
totalQuery += subCond;
|
||||
}
|
||||
|
||||
if (customer_email) {
|
||||
sqlQuery += ` AND o.customer_email LIKE ?`;
|
||||
totalQuery += ` AND o.customer_email LIKE ?`;
|
||||
|
|
@ -774,7 +794,6 @@ export class OrderService {
|
|||
|
||||
// 执行查询
|
||||
const orders = await this.orderModel.query(sqlQuery, parameters);
|
||||
|
||||
return { items: orders, total, current, pageSize };
|
||||
}
|
||||
|
||||
|
|
@ -786,7 +805,8 @@ export class OrderService {
|
|||
keyword,
|
||||
customer_email,
|
||||
billing_phone,
|
||||
}) {
|
||||
isSubscriptionOnly = false,
|
||||
}: any) {
|
||||
const query = this.orderModel
|
||||
.createQueryBuilder('order')
|
||||
.select('order.orderStatus', 'status')
|
||||
|
|
@ -824,11 +844,24 @@ export class OrderService {
|
|||
);
|
||||
}
|
||||
|
||||
if (isSubscriptionOnly) {
|
||||
query.andWhere(`(
|
||||
EXISTS (
|
||||
SELECT 1 FROM subscription s
|
||||
WHERE s.siteId = order.siteId AND s.parent_id = order.externalOrderId
|
||||
)
|
||||
)`);
|
||||
}
|
||||
|
||||
return await query.getRawMany();
|
||||
}
|
||||
|
||||
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage }: QueryOrderSalesDTO) {
|
||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||
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');
|
||||
startDate = (startDate as any) || defaultStart as any;
|
||||
endDate = (endDate as any) || defaultEnd as any;
|
||||
const offset = (current - 1) * pageSize;
|
||||
|
||||
// -------------------------
|
||||
|
|
@ -1032,6 +1065,10 @@ export class OrderService {
|
|||
name,
|
||||
}: QueryOrderSalesDTO) {
|
||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||
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');
|
||||
startDate = (startDate as any) || defaultStart as any;
|
||||
endDate = (endDate as any) || defaultEnd as any;
|
||||
// 分页查询
|
||||
let sqlQuery = `
|
||||
WITH product_purchase_counts AS (
|
||||
|
|
@ -1134,6 +1171,64 @@ export class OrderService {
|
|||
pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
async getOrderItemList({
|
||||
siteId,
|
||||
startDate,
|
||||
endDate,
|
||||
current,
|
||||
pageSize,
|
||||
name,
|
||||
externalProductId,
|
||||
externalVariationId,
|
||||
}: any) {
|
||||
const params: any[] = [];
|
||||
let sql = `
|
||||
SELECT
|
||||
oi.*,
|
||||
o.id AS orderId,
|
||||
o.externalOrderId AS orderExternalOrderId,
|
||||
o.date_created AS orderDateCreated,
|
||||
o.customer_email AS orderCustomerEmail,
|
||||
o.orderStatus AS orderStatus,
|
||||
o.siteId AS orderSiteId,
|
||||
CASE WHEN
|
||||
JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"is_subscription"')
|
||||
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_wcs_bought_as_subscription"')
|
||||
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_wcsatt_scheme"')
|
||||
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_subscription"')
|
||||
THEN 1 ELSE 0 END AS isSubscriptionItem
|
||||
FROM order_item oi
|
||||
INNER JOIN \`order\` o ON o.id = oi.orderId
|
||||
WHERE 1=1
|
||||
`;
|
||||
let countSql = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM order_item oi
|
||||
INNER JOIN \`order\` o ON o.id = oi.orderId
|
||||
WHERE 1=1
|
||||
`;
|
||||
const pushFilter = (cond: string, value: any) => {
|
||||
sql += cond; countSql += cond; params.push(value);
|
||||
};
|
||||
if (startDate) pushFilter(' AND o.date_created >= ?', startDate);
|
||||
if (endDate) pushFilter(' AND o.date_created <= ?', endDate);
|
||||
if (siteId) pushFilter(' AND oi.siteId = ?', siteId);
|
||||
if (name) {
|
||||
pushFilter(' AND oi.name LIKE ?', `%${name}%`);
|
||||
}
|
||||
if (externalProductId) pushFilter(' AND oi.externalProductId = ?', externalProductId);
|
||||
if (externalVariationId) pushFilter(' AND oi.externalVariationId = ?', externalVariationId);
|
||||
|
||||
sql += ' ORDER BY o.date_created DESC LIMIT ? OFFSET ?';
|
||||
const listParams = [...params, pageSize, (current - 1) * pageSize];
|
||||
|
||||
const items = await this.orderItemModel.query(sql, listParams);
|
||||
const [countRow] = await this.orderItemModel.query(countSql, params);
|
||||
const total = Number(countRow?.total || 0);
|
||||
|
||||
return { items, total, current, pageSize };
|
||||
}
|
||||
async getOrderDetail(id: number): Promise<OrderDetailRes> {
|
||||
const order = await this.orderModel.findOne({ where: { id } });
|
||||
const site = this.sites.find(site => site.id === order.siteId);
|
||||
|
|
@ -1212,6 +1307,75 @@ export class OrderService {
|
|||
};
|
||||
}
|
||||
|
||||
async getRelatedByOrder(orderId: number) {
|
||||
const order = await this.orderModel.findOne({ where: { id: orderId } });
|
||||
if (!order) throw new Error('订单不存在');
|
||||
const items = await this.orderItemModel.find({ where: { orderId } });
|
||||
const siteId = order.siteId;
|
||||
const productIds = items.map(i => i.externalProductId).filter(Boolean);
|
||||
const variationIds = items.map(i => i.externalVariationId).filter(Boolean);
|
||||
|
||||
const subSql = `
|
||||
SELECT * FROM subscription s
|
||||
WHERE s.siteId = ? AND s.parent_id = ?
|
||||
`;
|
||||
const subscriptions = await this.orderModel.query(subSql, [siteId, order.externalOrderId]);
|
||||
|
||||
let conds: string[] = [];
|
||||
let params: any[] = [siteId, orderId];
|
||||
if (productIds.length > 0) {
|
||||
conds.push(`oi.externalProductId IN (${productIds.map(() => '?').join(',')})`);
|
||||
params = [...productIds, ...params];
|
||||
}
|
||||
if (variationIds.length > 0) {
|
||||
conds.push(`oi.externalVariationId IN (${variationIds.map(() => '?').join(',')})`);
|
||||
params = [...variationIds, ...params];
|
||||
}
|
||||
const whereCond = conds.length ? `AND (${conds.join(' OR ')})` : '';
|
||||
const relatedItemOrdersSql = `
|
||||
SELECT DISTINCT o.*
|
||||
FROM order_item oi
|
||||
INNER JOIN \`order\` o ON o.id = oi.orderId
|
||||
WHERE oi.siteId = ?
|
||||
${whereCond}
|
||||
AND o.id <> ?
|
||||
ORDER BY o.date_created DESC
|
||||
LIMIT 100
|
||||
`;
|
||||
const relatedByItems = await this.orderItemModel.query(relatedItemOrdersSql, params);
|
||||
|
||||
const relatedBySubscriptionSql = `
|
||||
SELECT DISTINCT o.*
|
||||
FROM \`order\` o
|
||||
WHERE o.siteId = ?
|
||||
AND o.customer_email = ?
|
||||
AND o.id <> ?
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM order_item oi
|
||||
WHERE oi.orderId = o.id
|
||||
AND (
|
||||
JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"is_subscription"')
|
||||
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_wcs_bought_as_subscription"')
|
||||
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_wcsatt_scheme"')
|
||||
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_subscription"')
|
||||
)
|
||||
)
|
||||
ORDER BY o.date_created DESC
|
||||
LIMIT 100
|
||||
`;
|
||||
const relatedBySubscription = await this.orderModel.query(relatedBySubscriptionSql, [siteId, order.customer_email, orderId]);
|
||||
|
||||
const allOrdersMap = new Map<number, any>();
|
||||
relatedByItems.forEach(o => allOrdersMap.set(o.id, o));
|
||||
relatedBySubscription.forEach(o => allOrdersMap.set(o.id, o));
|
||||
|
||||
return {
|
||||
order,
|
||||
subscriptions,
|
||||
orders: Array.from(allOrdersMap.values()),
|
||||
};
|
||||
}
|
||||
|
||||
async delOrder(id: number) {
|
||||
const order = await this.orderModel.findOne({ where: { id } });
|
||||
if (!order) throw new Error('订单不存在');
|
||||
|
|
|
|||
Loading…
Reference in New Issue