1503 lines
47 KiB
JavaScript
1503 lines
47 KiB
JavaScript
const express = require('express');
|
||
const { getDB } = require('../database');
|
||
const { auth, adminAuth } = require('../middleware/auth');
|
||
|
||
const router = express.Router();
|
||
|
||
// 订单管理路由
|
||
|
||
|
||
// 获取订单列表
|
||
router.get('/', auth, async (req, res) => {
|
||
try {
|
||
const { page = 1, limit = 10, search = '', orderNumber = '', username = '', status = '', startDate = '', endDate = '' } = req.query;
|
||
|
||
|
||
|
||
// 确保参数为有效数字
|
||
const pageNum = parseInt(page) || 1;
|
||
const limitNum = parseInt(limit) || 10;
|
||
const offset = (pageNum - 1) * limitNum;
|
||
const isAdmin = req.user.role === 'admin';
|
||
|
||
let whereClause = 'WHERE 1=1';
|
||
const params = [];
|
||
|
||
// 非管理员只能查看自己的订单
|
||
if (!isAdmin) {
|
||
whereClause += ' AND o.user_id = ?';
|
||
params.push(req.user.id);
|
||
}
|
||
|
||
if (search) {
|
||
whereClause += ' AND (o.order_no LIKE ? OR u.username LIKE ?)';
|
||
params.push(`%${search}%`, `%${search}%`);
|
||
}
|
||
|
||
if (orderNumber) {
|
||
whereClause += ' AND o.order_no LIKE ?';
|
||
params.push(`%${orderNumber}%`);
|
||
}
|
||
|
||
if (username) {
|
||
whereClause += ' AND u.username LIKE ?';
|
||
params.push(`%${username}%`);
|
||
}
|
||
|
||
if (status && status.trim()) {
|
||
whereClause += ' AND o.status = ?';
|
||
params.push(status);
|
||
}
|
||
|
||
if (startDate && startDate.trim()) {
|
||
whereClause += ' AND DATE(o.created_at) >= ?';
|
||
params.push(startDate);
|
||
}
|
||
|
||
if (endDate && endDate.trim()) {
|
||
whereClause += ' AND DATE(o.created_at) <= ?';
|
||
params.push(endDate);
|
||
}
|
||
|
||
// 获取总数
|
||
const countQuery = `
|
||
SELECT COUNT(*) as total
|
||
FROM orders as o
|
||
LEFT JOIN users u ON o.user_id = u.id
|
||
${whereClause}
|
||
`;
|
||
console.log(countQuery, params);
|
||
|
||
const [countResult] = await getDB().execute(countQuery, params);
|
||
const total = countResult[0].total;
|
||
console.log(total, '数量');
|
||
|
||
// 获取订单列表
|
||
const query = `
|
||
SELECT
|
||
o.id, o.order_no, o.user_id, o.total_amount, o.total_points,
|
||
o.status, o.address, o.created_at, o.updated_at,o.total_rongdou,
|
||
u.username
|
||
FROM orders o
|
||
LEFT JOIN users u ON o.user_id = u.id
|
||
${whereClause}
|
||
ORDER BY o.created_at DESC
|
||
LIMIT ${limitNum} OFFSET ${offset}
|
||
`;
|
||
|
||
const [orders] = await getDB().execute(query, [...params]);
|
||
|
||
// 为每个订单获取商品详情
|
||
for (const order of orders) {
|
||
const [orderItems] = await getDB().execute(
|
||
`SELECT
|
||
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
|
||
oi.spec_combination_id,
|
||
p.name as product_name, p.image_url, p.description,
|
||
psc.spec_values as spec_info
|
||
FROM order_items oi
|
||
LEFT JOIN products p ON oi.product_id = p.id
|
||
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
|
||
WHERE oi.order_id = ?`,
|
||
[order.id]
|
||
);
|
||
|
||
// 处理规格信息
|
||
for (const item of orderItems) {
|
||
if (item.spec_info) {
|
||
try {
|
||
item.spec_info = JSON.parse(item.spec_info);
|
||
} catch (e) {
|
||
item.spec_info = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理地址信息
|
||
console.log(order.address,'order.address');
|
||
|
||
if (order.address) {
|
||
try {
|
||
order.address = order.address;
|
||
} catch (e) {
|
||
order.address = null;
|
||
}
|
||
}
|
||
|
||
order.items = orderItems;
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
orders,
|
||
pagination: {
|
||
page: pageNum,
|
||
limit: limitNum,
|
||
total,
|
||
pages: Math.ceil(total / limitNum)
|
||
}
|
||
}
|
||
});
|
||
|
||
|
||
router.post('/confirm', auth, async (req, res) => {
|
||
const connection = await getDB().getConnection();
|
||
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
const { pre_order_id, address } = req.body;
|
||
const userId = req.user.id;
|
||
|
||
// 验证必填字段
|
||
if (!pre_order_id || !address) {
|
||
return res.status(400).json({ success: false, message: '预订单ID和收货地址为必填项' });
|
||
}
|
||
|
||
const { recipient_name, phone, province, city, district, detail_address } = address;
|
||
if (!recipient_name || !phone || !province || !city || !district || !detail_address) {
|
||
return res.status(400).json({ success: false, message: '收货地址信息不完整' });
|
||
}
|
||
|
||
// 获取预订单信息
|
||
const [preOrders] = await connection.execute(
|
||
`SELECT id, order_no, user_id, total_amount, total_points, total_rongdou, status
|
||
FROM orders WHERE id = ? AND user_id = ? AND status = 'pre_order'`,
|
||
[pre_order_id, userId]
|
||
);
|
||
|
||
if (preOrders.length === 0) {
|
||
await connection.rollback();
|
||
return res.status(404).json({ success: false, message: '预订单不存在或已处理' });
|
||
}
|
||
|
||
const preOrder = preOrders[0];
|
||
|
||
// 获取用户当前积分和融豆
|
||
const [users] = await connection.execute(
|
||
'SELECT points, rongdou FROM users WHERE id = ?',
|
||
[userId]
|
||
);
|
||
|
||
if (users.length === 0) {
|
||
await connection.rollback();
|
||
return res.status(404).json({ success: false, message: '用户不存在' });
|
||
}
|
||
|
||
const user = users[0];
|
||
|
||
// 检查积分和融豆是否足够
|
||
if (preOrder.total_points > 0 && user.points < preOrder.total_points) {
|
||
await connection.rollback();
|
||
return res.status(400).json({ success: false, message: '积分不足' });
|
||
}
|
||
|
||
if (preOrder.total_rongdou > 0 && user.rongdou < preOrder.total_rongdou) {
|
||
await connection.rollback();
|
||
return res.status(400).json({ success: false, message: '融豆不足' });
|
||
}
|
||
|
||
// 扣除积分
|
||
if (preOrder.total_points > 0) {
|
||
await connection.execute(
|
||
'UPDATE users SET points = points - ? WHERE id = ?',
|
||
[preOrder.total_points, userId]
|
||
);
|
||
|
||
// 记录积分变动历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, preOrder.total_points, `订单消费 - ${preOrder.order_no}`, pre_order_id]
|
||
);
|
||
}
|
||
|
||
// 扣除融豆
|
||
if (preOrder.total_rongdou > 0) {
|
||
await connection.execute(
|
||
'UPDATE users SET rongdou = rongdou - ? WHERE id = ?',
|
||
[preOrder.total_rongdou, userId]
|
||
);
|
||
|
||
// 记录融豆变动历史
|
||
await connection.execute(
|
||
`INSERT INTO rongdou_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, preOrder.total_rongdou, `订单消费 - ${preOrder.order_no}`, pre_order_id]
|
||
);
|
||
}
|
||
|
||
// 更新订单状态和收货地址
|
||
const addressStr = JSON.stringify({
|
||
recipient_name,
|
||
phone,
|
||
province,
|
||
city,
|
||
district,
|
||
detail_address
|
||
});
|
||
|
||
await connection.execute(
|
||
`UPDATE orders SET status = 'pending', address = ?, updated_at = NOW()
|
||
WHERE id = ?`,
|
||
[addressStr, pre_order_id]
|
||
);
|
||
|
||
await connection.commit();
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '订单确认成功',
|
||
data: {
|
||
order_id: pre_order_id,
|
||
order_no: preOrder.order_no
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('确认下单失败:', error);
|
||
res.status(500).json({ success: false, message: '确认下单失败' });
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
});
|
||
|
||
|
||
router.get('/pre-order/:id', auth, async (req, res) => {
|
||
try {
|
||
const preOrderId = req.params.id;
|
||
const userId = req.user.id;
|
||
|
||
// 获取预订单基本信息
|
||
const [orders] = await getDB().execute(
|
||
`SELECT id, order_no, user_id, total_amount, total_points, total_rongdou,
|
||
status, created_at FROM orders WHERE id = ? AND user_id = ? AND status = 'pre_order'`,
|
||
[preOrderId, userId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
return res.status(404).json({ success: false, message: '预订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
// 获取预订单商品详情
|
||
const [orderItems] = await getDB().execute(
|
||
`SELECT
|
||
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
|
||
oi.spec_combination_id,
|
||
p.name as product_name, p.image_url, p.description,
|
||
psc.spec_values as spec_info
|
||
FROM order_items oi
|
||
LEFT JOIN products p ON oi.product_id = p.id
|
||
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
|
||
WHERE oi.order_id = ?`,
|
||
[preOrderId]
|
||
);
|
||
|
||
// 处理规格信息
|
||
for (const item of orderItems) {
|
||
if (item.spec_info) {
|
||
try {
|
||
item.spec_info = JSON.parse(item.spec_info);
|
||
} catch (e) {
|
||
item.spec_info = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
...order,
|
||
items: orderItems
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('获取预订单详情失败:', error);
|
||
res.status(500).json({ success: false, message: '获取预订单详情失败' });
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('获取订单列表失败:', error);
|
||
res.status(500).json({ success: false, message: '获取订单列表失败' });
|
||
}
|
||
});
|
||
|
||
|
||
router.get('/:id', auth, async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
const isAdmin = req.user.role === 'admin';
|
||
|
||
let whereClause = 'WHERE o.id = ?';
|
||
const params = [id];
|
||
|
||
// 非管理员只能查看自己的订单
|
||
if (!isAdmin) {
|
||
whereClause += ' AND o.user_id = ?';
|
||
params.push(req.user.id);
|
||
}
|
||
|
||
const query = `
|
||
SELECT
|
||
o.id, o.order_no, o.user_id, o.total_amount, o.total_points,
|
||
o.status, o.address, o.created_at, o.updated_at,
|
||
u.username, u.phone
|
||
FROM orders o
|
||
LEFT JOIN users u ON o.user_id = u.id
|
||
${whereClause}
|
||
`;
|
||
|
||
const [orders] = await getDB().execute(query, params);
|
||
|
||
if (orders.length === 0) {
|
||
return res.status(404).json({ success: false, message: '订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
// 获取订单商品详情
|
||
const [orderItems] = await getDB().execute(
|
||
`SELECT
|
||
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
|
||
oi.spec_combination_id,
|
||
p.name as product_name, p.image_url, p.description,
|
||
psc.spec_values as spec_info
|
||
FROM order_items oi
|
||
LEFT JOIN products p ON oi.product_id = p.id
|
||
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
|
||
WHERE oi.order_id = ?`,
|
||
[order.id]
|
||
);
|
||
|
||
// 处理规格信息
|
||
for (const item of orderItems) {
|
||
if (item.spec_info) {
|
||
try {
|
||
item.spec_info = JSON.parse(item.spec_info);
|
||
} catch (e) {
|
||
item.spec_info = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理地址信息
|
||
// console.log(order.address,'order.address');
|
||
|
||
if (order.address) {
|
||
try {
|
||
order.address = order.address;
|
||
} catch (e) {
|
||
order.address = null;
|
||
}
|
||
}
|
||
|
||
order.items = orderItems;
|
||
|
||
res.json({
|
||
success: true,
|
||
data: { order }
|
||
});
|
||
} catch (error) {
|
||
console.error('获取订单详情失败:', error);
|
||
res.status(500).json({ success: false, message: '获取订单详情失败' });
|
||
}
|
||
});
|
||
|
||
// 创建预订单
|
||
router.post('/create-from-cart', auth, async (req, res) => {
|
||
const db = getDB();
|
||
await db.query('START TRANSACTION');
|
||
|
||
try {
|
||
const { cart_item_ids } = req.body;
|
||
const user_id = req.user.id;
|
||
|
||
// 验证必填字段
|
||
if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: '请选择要购买的商品' });
|
||
}
|
||
|
||
// 获取购物车商品信息和支付方式
|
||
const placeholders = cart_item_ids.map(() => '?').join(',');
|
||
const cartQuery = `
|
||
SELECT
|
||
c.id, c.product_id, c.quantity, c.specification_id,
|
||
p.name, p.price, p.points_price, p.rongdou_price, p.stock, p.status, p.payment_methods,
|
||
psc.price_adjustment, psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock
|
||
FROM cart_items c
|
||
LEFT JOIN products p ON c.product_id = p.id
|
||
LEFT JOIN product_spec_combinations psc ON c.specification_id = psc.id
|
||
WHERE c.id IN (${placeholders}) AND c.user_id = ?
|
||
`;
|
||
|
||
const [cartItems] = await db.execute(cartQuery, [...cart_item_ids, user_id]);
|
||
|
||
if (cartItems.length === 0) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: '购物车商品不存在' });
|
||
}
|
||
|
||
for (const item of cartItems) {
|
||
|
||
const [stock] = await getDB().execute(
|
||
`SELECT stock FROM product_spec_combinations WHERE id = ?`,
|
||
[item.specification_id]
|
||
);
|
||
|
||
if (stock[0].stock < item.quantity) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` });
|
||
}
|
||
await db.execute(
|
||
`UPDATE product_spec_combinations SET stock = stock - ? WHERE id = ?`,
|
||
[item.quantity, item.specification_id]
|
||
);
|
||
}
|
||
|
||
// 验证商品状态和库存,计算总价和支付方式
|
||
let totalAmount = 0;
|
||
let totalPoints = 0;
|
||
let totalRongdou = 0;
|
||
let allPaymentMethods = [];
|
||
|
||
for (const item of cartItems) {
|
||
if (item.status !== 'active') {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: `商品 ${item.name} 已下架` });
|
||
}
|
||
|
||
const availableStock = item.specification_id ? item.spec_stock : item.stock;
|
||
if (availableStock < item.quantity) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` });
|
||
}
|
||
|
||
// 解析商品支付方式
|
||
let productPaymentMethods = ['rongdou']; // 默认支付方式
|
||
if (item.payment_methods) {
|
||
try {
|
||
productPaymentMethods = JSON.parse(item.payment_methods);
|
||
} catch (e) {
|
||
console.error('解析商品支付方式失败:', e);
|
||
}
|
||
}
|
||
console.log(productPaymentMethods,'productPaymentMethods');
|
||
|
||
allPaymentMethods = allPaymentMethods.concat(productPaymentMethods);
|
||
|
||
const finalPrice = item.price + (item.price_adjustment || 0);
|
||
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
|
||
|
||
totalAmount += finalPrice * item.quantity;
|
||
|
||
// 根据支付方式计算积分和融豆需求
|
||
const hasPoints = productPaymentMethods.includes('points') || productPaymentMethods.includes('points_rongdou');
|
||
const hasRongdou = productPaymentMethods.includes('rongdou') || productPaymentMethods.includes('points_rongdou');
|
||
|
||
if (hasPoints && !hasRongdou) {
|
||
// 仅积分支付:按10000积分=1融豆计算
|
||
totalPoints += finalRongdouPrice * item.quantity * 10000;
|
||
totalRongdou += finalRongdouPrice * item.quantity;
|
||
} else if (!hasPoints && hasRongdou) {
|
||
// 仅融豆支付
|
||
totalRongdou += finalRongdouPrice * item.quantity;
|
||
} else {
|
||
// 组合支付或默认:记录融豆价格,前端可选择支付方式
|
||
totalRongdou += finalRongdouPrice * item.quantity;
|
||
}
|
||
}
|
||
|
||
// 去重支付方式
|
||
const uniquePaymentMethods = [...new Set(allPaymentMethods)];
|
||
console.log('订单支付方式:', uniquePaymentMethods);
|
||
|
||
// 生成预订单号
|
||
const orderNumber = 'PRE' + Date.now() + Math.random().toString(36).substr(2, 5).toUpperCase();
|
||
|
||
// 创建预订单(状态为pre_order)
|
||
const [orderResult] = await db.execute(
|
||
`INSERT INTO orders (order_no, user_id, total_amount, total_points, total_rongdou,
|
||
status, created_at, updated_at)
|
||
VALUES (?, ?, ?, ?, ?, 'pre_order', NOW(), NOW())`,
|
||
[orderNumber, user_id, totalAmount, totalPoints, totalRongdou]
|
||
);
|
||
|
||
const preOrderId = orderResult.insertId;
|
||
|
||
// 创建预订单项
|
||
for (const item of cartItems) {
|
||
const finalPrice = item.price + (item.price_adjustment || 0);
|
||
const finalPointsPrice = item.points_price + (item.points_adjustment || 0);
|
||
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
|
||
|
||
await db.execute(
|
||
`INSERT INTO order_items (order_id, product_id, spec_combination_id, quantity,
|
||
price, points, points_price, rongdou, rongdou_price, created_at)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
|
||
[preOrderId, item.product_id, item.specification_id, item.quantity,
|
||
finalPrice, finalPointsPrice * item.quantity, finalPointsPrice, finalRongdouPrice * item.quantity, finalRongdouPrice]
|
||
);
|
||
}
|
||
|
||
// 删除购物车中的商品
|
||
await db.execute(
|
||
`DELETE FROM cart_items WHERE id IN (${placeholders})`,
|
||
cart_item_ids
|
||
);
|
||
|
||
await db.query('COMMIT');
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '预订单创建成功',
|
||
data: {
|
||
preOrderId,
|
||
orderNumber,
|
||
totalAmount,
|
||
totalPoints,
|
||
totalRongdou,
|
||
paymentMethods: uniquePaymentMethods
|
||
}
|
||
});
|
||
} catch (error) {
|
||
await db.query('ROLLBACK');
|
||
console.error('创建预订单失败:', error);
|
||
res.status(500).json({ success: false, message: '创建预订单失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/orders/{id}/cancel:
|
||
* put:
|
||
* summary: 用户取消订单
|
||
* tags: [Orders]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: id
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 订单ID
|
||
* responses:
|
||
* 200:
|
||
* description: 订单取消成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* message:
|
||
* type: string
|
||
* 400:
|
||
* description: 只能取消待处理的订单
|
||
* 401:
|
||
* description: 未授权
|
||
* 404:
|
||
* description: 订单不存在
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.put('/:id/cancel', auth, async (req, res) => {
|
||
const db = getDB();
|
||
await db.query('START TRANSACTION');
|
||
|
||
try {
|
||
|
||
const orderId = req.params.id;
|
||
const userId = req.user.id;
|
||
|
||
// 检查订单是否存在且属于当前用户
|
||
const [orders] = await db.execute(
|
||
'SELECT id, user_id, total_points, status FROM orders WHERE id = ? AND user_id = ?',
|
||
[orderId, userId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(404).json({ success: false, message: '订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
if (order.status !== 'pending' && order.status !== 'pre_order') {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: '只能取消待处理或待支付的订单' });
|
||
}
|
||
|
||
// 退还用户积分
|
||
await db.execute(
|
||
'UPDATE users SET points = points + ? WHERE id = ?',
|
||
[order.total_points, userId]
|
||
);
|
||
|
||
// 记录积分历史
|
||
await db.execute(
|
||
`INSERT INTO points_history (user_id, amount, type, description, created_at)
|
||
VALUES (?, ?, 'refund', '订单取消退还积分', NOW())`,
|
||
[userId, order.total_points]
|
||
);
|
||
|
||
console.log(12345,order.id);
|
||
const [orderDetails] = await db.execute(
|
||
'SELECT * FROM order_items WHERE order_id = ?',
|
||
[order.id]
|
||
);
|
||
console.log(12345,orderDetails);
|
||
// 返还库存
|
||
await db.execute(
|
||
'UPDATE product_spec_combinations SET stock = stock + ? WHERE id = ?',
|
||
[orderDetails[0].quantity, orderDetails[0].spec_combination_id]
|
||
);
|
||
|
||
|
||
// 更新订单状态
|
||
await db.execute(
|
||
'UPDATE orders SET status = "cancelled", updated_at = NOW() WHERE id = ?',
|
||
[orderId]
|
||
);
|
||
|
||
await db.query('COMMIT');
|
||
|
||
res.json({ success: true, message: '订单已取消' });
|
||
} catch (error) {
|
||
await db.query('ROLLBACK');
|
||
console.error('取消订单失败:', error);
|
||
res.status(500).json({ success: false, message: '取消订单失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/orders/{id}/confirm:
|
||
* put:
|
||
* summary: 确认收货
|
||
* tags: [Orders]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: id
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 订单ID
|
||
* responses:
|
||
* 200:
|
||
* description: 确认收货成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* message:
|
||
* type: string
|
||
* 400:
|
||
* description: 只能确认已发货的订单
|
||
* 401:
|
||
* description: 未授权
|
||
* 404:
|
||
* description: 订单不存在
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.put('/:id/confirm', auth, async (req, res) => {
|
||
try {
|
||
const orderId = req.params.id;
|
||
const userId = req.user.id;
|
||
|
||
// 检查订单是否存在且属于当前用户
|
||
const [orders] = await getDB().execute(
|
||
'SELECT id, status FROM orders WHERE id = ? AND user_id = ?',
|
||
[orderId, userId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
return res.status(404).json({ success: false, message: '订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
if (order.status !== 'shipped') {
|
||
return res.status(400).json({ success: false, message: '只能确认已发货的订单' });
|
||
}
|
||
|
||
// 更新订单状态
|
||
await getDB().execute(
|
||
'UPDATE orders SET status = "completed", updated_at = NOW() WHERE id = ?',
|
||
[orderId]
|
||
);
|
||
|
||
res.json({ success: true, message: '确认收货成功' });
|
||
} catch (error) {
|
||
console.error('确认收货失败:', error);
|
||
res.status(500).json({ success: false, message: '确认收货失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/orders/{id}/status:
|
||
* put:
|
||
* summary: 更新订单状态(管理员)
|
||
* tags: [Orders]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: id
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 订单ID
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* status:
|
||
* type: string
|
||
* enum: [pending, shipped, completed, cancelled]
|
||
* description: 订单状态
|
||
* required:
|
||
* - status
|
||
* responses:
|
||
* 200:
|
||
* description: 订单状态更新成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* message:
|
||
* type: string
|
||
* 400:
|
||
* description: 无效的订单状态
|
||
* 401:
|
||
* description: 未授权
|
||
* 403:
|
||
* description: 无管理员权限
|
||
* 404:
|
||
* description: 订单不存在
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.put('/:id/status', auth, adminAuth, async (req, res) => {
|
||
const db = getDB();
|
||
await db.query('START TRANSACTION');
|
||
|
||
try {
|
||
|
||
const orderId = req.params.id;
|
||
const { status } = req.body;
|
||
|
||
const validStatuses = ['pending', 'shipped', 'completed', 'cancelled'];
|
||
if (!validStatuses.includes(status)) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(400).json({ success: false, message: '无效的订单状态' });
|
||
}
|
||
|
||
// 检查订单是否存在
|
||
const [orders] = await db.execute(
|
||
'SELECT id, user_id, total_points, total_rongdou, status FROM orders WHERE id = ?',
|
||
[orderId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
await db.query('ROLLBACK');
|
||
return res.status(404).json({ success: false, message: '订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
// 如果是取消订单,需要退还积分和融豆
|
||
if (status === 'cancelled' && order.status !== 'cancelled' && order.status !== 'pre_order') {
|
||
// 退还用户积分
|
||
if (order.total_points > 0) {
|
||
await db.execute(
|
||
'UPDATE users SET points = points + ? WHERE id = ?',
|
||
[order.total_points, order.user_id]
|
||
);
|
||
|
||
// 记录积分历史
|
||
await db.execute(
|
||
`INSERT INTO points_history (user_id, amount, type, description, created_at)
|
||
VALUES (?, ?, 'earn', '订单取消退还积分', NOW())`,
|
||
[order.user_id, order.total_points]
|
||
);
|
||
}
|
||
|
||
// 退还用户融豆
|
||
if (order.total_rongdou > 0) {
|
||
await db.execute(
|
||
'UPDATE users SET balance = balance - ? WHERE id = ?',
|
||
[order.total_rongdou, order.user_id]
|
||
);
|
||
|
||
// 记录融豆历史
|
||
await db.execute(
|
||
`INSERT INTO rongdou_history (user_id, amount, type, description, created_at)
|
||
VALUES (?, ?, 'earn', '订单取消退还融豆', NOW())`,
|
||
[order.user_id, order.total_rongdou]
|
||
);
|
||
}
|
||
}
|
||
|
||
// 更新订单状态
|
||
await db.execute(
|
||
'UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ?',
|
||
[status, orderId]
|
||
);
|
||
|
||
await db.query('COMMIT');
|
||
|
||
res.json({ success: true, message: '订单状态已更新' });
|
||
} catch (error) {
|
||
await db.query('ROLLBACK');
|
||
console.error('更新订单状态失败:', error);
|
||
res.status(500).json({ success: false, message: '更新订单状态失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/orders/pending-payment/{id}:
|
||
* get:
|
||
* summary: 获取待支付预订单详情
|
||
* tags: [Orders]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* parameters:
|
||
* - in: path
|
||
* name: id
|
||
* required: true
|
||
* schema:
|
||
* type: integer
|
||
* description: 预订单ID
|
||
* responses:
|
||
* 200:
|
||
* description: 获取预订单详情成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* id:
|
||
* type: integer
|
||
* order_no:
|
||
* type: string
|
||
* total_amount:
|
||
* type: integer
|
||
* total_points:
|
||
* type: integer
|
||
* total_rongdou:
|
||
* type: integer
|
||
* status:
|
||
* type: string
|
||
* created_at:
|
||
* type: string
|
||
* items:
|
||
* type: array
|
||
* items:
|
||
* type: object
|
||
* properties:
|
||
* id:
|
||
* type: integer
|
||
* product_id:
|
||
* type: integer
|
||
* product_name:
|
||
* type: string
|
||
* quantity:
|
||
* type: integer
|
||
* price:
|
||
* type: integer
|
||
* points_price:
|
||
* type: integer
|
||
* rongdou_price:
|
||
* type: integer
|
||
* spec_info:
|
||
* type: object
|
||
* 401:
|
||
* description: 未授权
|
||
* 404:
|
||
* description: 预订单不存在
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.get('/pending-payment/:id', auth, async (req, res) => {
|
||
try {
|
||
const preOrderId = req.params.id;
|
||
const userId = req.user.id;
|
||
|
||
// 获取预订单基本信息
|
||
const [orders] = await getDB().execute(
|
||
`SELECT id, order_no, user_id, total_amount, total_points, total_rongdou,
|
||
status, created_at FROM orders WHERE id = ? AND user_id = ? AND status = 'pre_order'`,
|
||
[preOrderId, userId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
return res.status(404).json({ success: false, message: '预订单不存在' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
// 获取预订单商品详情
|
||
const [orderItems] = await getDB().execute(
|
||
`SELECT
|
||
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
|
||
oi.spec_combination_id,
|
||
p.name as product_name, p.image_url, p.description, p.payment_methods,
|
||
psc.spec_values as spec_info
|
||
FROM order_items oi
|
||
LEFT JOIN products p ON oi.product_id = p.id
|
||
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
|
||
WHERE oi.order_id = ?`,
|
||
[preOrderId]
|
||
);
|
||
|
||
// 处理规格信息
|
||
for (const item of orderItems) {
|
||
if (item.spec_info) {
|
||
try {
|
||
item.spec_info = JSON.parse(item.spec_info);
|
||
} catch (e) {
|
||
item.spec_info = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
...order,
|
||
items: orderItems.map(item => ({
|
||
...item,
|
||
payment_methods: JSON.parse(item.payment_methods)
|
||
}))
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('获取预订单详情失败:', error);
|
||
res.status(500).json({ success: false, message: '获取预订单详情失败' });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/orders/confirm-payment:
|
||
* post:
|
||
* summary: 确认支付订单
|
||
* description: |
|
||
* 根据商品支付方式确认订单支付:
|
||
* - 仅积分支付:按10000积分=1融豆的比例扣除积分
|
||
* - 仅融豆支付:直接扣除融豆
|
||
* - 组合支付:优先扣除积分(按10000:1转换),不足部分扣除融豆
|
||
* tags: [Orders]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - order_id
|
||
* - address_id
|
||
* properties:
|
||
* order_id:
|
||
* type: integer
|
||
* description: 订单ID
|
||
* example: 123
|
||
* address_id:
|
||
* type: integer
|
||
* description: 收货地址ID
|
||
* example: 456
|
||
* responses:
|
||
* 200:
|
||
* description: 确认支付成功
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: true
|
||
* message:
|
||
* type: string
|
||
* example: "订单支付成功"
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* order_id:
|
||
* type: integer
|
||
* example: 123
|
||
* order_no:
|
||
* type: string
|
||
* example: "ORD20240101123456"
|
||
* 400:
|
||
* description: 请求参数错误或余额不足
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: false
|
||
* message:
|
||
* type: string
|
||
* enum: ["订单ID和收货地址ID为必填项", "积分不足", "融豆不足", "积分和融豆余额不足", "商品支付方式配置错误"]
|
||
* 401:
|
||
* description: 未授权
|
||
* 404:
|
||
* description: 订单或地址不存在
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* example: false
|
||
* message:
|
||
* type: string
|
||
* enum: ["订单不存在或已处理", "收货地址不存在", "用户不存在"]
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.post('/confirm-payment', auth, async (req, res) => {
|
||
const connection = await getDB().getConnection();
|
||
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
const { orderId: order_id, addressId: address_id, couponRecordId, paymentMethod } = req.body;
|
||
const userId = req.user.id;
|
||
|
||
// 验证必填字段
|
||
if (!order_id || !address_id) {
|
||
return res.status(400).json({ success: false, message: '订单ID和收货地址ID为必填项' });
|
||
}
|
||
|
||
// 获取订单信息和商品支付方式
|
||
const [orders] = await connection.execute(
|
||
`SELECT o.id, o.order_no, o.user_id, o.total_amount, o.total_points, o.total_rongdou, o.status,
|
||
GROUP_CONCAT(DISTINCT p.payment_methods) as payment_methods_list
|
||
FROM orders o
|
||
JOIN order_items oi ON o.id = oi.order_id
|
||
JOIN products p ON oi.product_id = p.id
|
||
WHERE o.id = ? AND o.user_id = ? AND o.status = 'pre_order'
|
||
GROUP BY o.id`,
|
||
[order_id, userId]
|
||
);
|
||
|
||
if (orders.length === 0) {
|
||
await connection.rollback();
|
||
return res.status(404).json({ success: false, message: '订单不存在或已处理' });
|
||
}
|
||
|
||
const order = orders[0];
|
||
|
||
// 解析支付方式
|
||
// let allPaymentMethods = [];
|
||
// // console.log(typeof order.payment_methods_list);
|
||
|
||
// if (order.payment_methods_list) {
|
||
// try {
|
||
// // 数据库中存储的是序列化的JSON字符串,直接解析
|
||
|
||
// allPaymentMethods = JSON.parse(JSON.parse(order.payment_methods_list));
|
||
// } catch (e) {
|
||
// console.error('解析支付方式失败:', e, 'raw data:', order.payment_methods_list);
|
||
// allPaymentMethods = [];
|
||
// }
|
||
// }
|
||
|
||
// // 去重支付方式
|
||
// allPaymentMethods = [...new Set(allPaymentMethods)];
|
||
|
||
// // 判断支付方式类型
|
||
// const hasPoints = allPaymentMethods.includes('points') || allPaymentMethods.includes('points_rongdou');
|
||
// const hasRongdou = allPaymentMethods.includes('rongdou') || allPaymentMethods.includes('points_rongdou');
|
||
// const isComboPayment = allPaymentMethods.includes('points_rongdou');
|
||
|
||
// console.log('订单支付方式:', allPaymentMethods, { hasPoints, hasRongdou, isComboPayment });
|
||
|
||
// 获取收货地址信息
|
||
const [addresses] = await connection.execute(
|
||
'SELECT id, receiver_name, receiver_phone, province, city, district, detailed_address as detail_address FROM user_addresses WHERE id = ? AND user_id = ?',
|
||
[address_id, userId]
|
||
);
|
||
|
||
if (addresses.length === 0) {
|
||
await connection.rollback();
|
||
return res.status(404).json({ success: false, message: '收货地址不存在' });
|
||
}
|
||
|
||
const address = addresses[0];
|
||
|
||
// 获取用户当前积分和融豆
|
||
const [users] = await connection.execute(
|
||
'SELECT points, balance FROM users WHERE id = ?',
|
||
[userId]
|
||
);
|
||
|
||
if (users.length === 0) {
|
||
await connection.rollback();
|
||
return res.status(404).json({ success: false, message: '用户不存在' });
|
||
}
|
||
|
||
const user = users[0];
|
||
if (user.balance > 0) {
|
||
return res.status(400).json({ success: false, message: '融豆不足' });
|
||
}
|
||
user.balance = Math.abs(user.balance);
|
||
|
||
// 开始扣钱
|
||
switch (paymentMethod) {
|
||
case 'points':
|
||
// 积分支付逻辑
|
||
if (user.points < order.total_points) {
|
||
await connection.rollback();
|
||
return res.status(400).json({ success: false, message: '积分不足' });
|
||
}
|
||
await connection.execute(
|
||
'UPDATE users SET points = points - ? WHERE id = ?',
|
||
[order.total_points, userId]
|
||
);
|
||
|
||
// 记录积分变动历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, order.total_points, `订单支付 - ${order.order_no}`, order_id]
|
||
);
|
||
|
||
// 供应商分佣
|
||
// await connection.execute(
|
||
// 'UPDATE users SET points = points + ? WHERE id = ?',
|
||
// [order.total_points * 0.1, order.shop_id]
|
||
// );
|
||
// console.log(123,order)
|
||
|
||
break;
|
||
case 'beans':
|
||
// 融豆支付逻辑
|
||
if (user.balance < order.total_rongdou) {
|
||
await connection.rollback();
|
||
return res.status(400).json({ success: false, message: '融豆不足' });
|
||
}
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance + ? WHERE id = ?',
|
||
[order.total_rongdou, userId]
|
||
);
|
||
|
||
// 记录融豆变动历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, order.total_rongdou, `订单支付 - ${order.order_no}`, order_id]
|
||
);
|
||
break;
|
||
case 'mixed':
|
||
// 积分和融豆组合支付逻辑
|
||
|
||
if(user.points < order.total_points) {
|
||
const needPoints = (user.points/10000).floor(0) * 10000;
|
||
const needBeans = order.total_rongdou - needPoints/10000;
|
||
if(user.balance < needBeans) {
|
||
await connection.rollback();
|
||
return res.status(400).json({ success: false, message: '融豆不足' });
|
||
}
|
||
|
||
await connection.execute(
|
||
'UPDATE users SET points = points - ? WHERE id = ?',
|
||
[needPoints, userId]
|
||
);
|
||
|
||
// 记录积分变动历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, needPoints, `订单支付 - ${order.order_no}`, order_id]
|
||
);
|
||
|
||
await connection.execute(
|
||
'UPDATE users SET balance = balance + ? WHERE id = ?',
|
||
[needBeans, userId]
|
||
);
|
||
|
||
// 记录融豆变动历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, needBeans, `订单支付 - ${order.order_no}`, order_id]
|
||
);
|
||
} else {
|
||
await connection.execute(
|
||
'UPDATE users SET points = points - ? WHERE id = ?',
|
||
[order.total_points, userId]
|
||
);
|
||
// 记录积分变动历史
|
||
await connection.execute(
|
||
`INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
VALUES (?, 'spend', ?, ?, ?)`,
|
||
[userId, order.total_points, `订单支付 - ${order.order_no}`, order_id]
|
||
);
|
||
}
|
||
|
||
break;
|
||
default:
|
||
await connection.rollback();
|
||
return res.status(400).json({ success: false, message: '支付方式配置错误' });
|
||
}
|
||
|
||
// // 根据支付方式处理扣费逻辑
|
||
// let totalRongdouNeeded = order.total_rongdou; // 需要的融豆总数
|
||
// let pointsToDeduct = 0; // 需要扣除的积分
|
||
// let rongdouToDeduct = 0; // 需要扣除的融豆
|
||
|
||
// if (!hasRongdou && !hasPoints) {
|
||
// await connection.rollback();
|
||
// return res.status(400).json({ success: false, message: '商品支付方式配置错误' });
|
||
// }
|
||
|
||
// if (hasPoints && !hasRongdou) {
|
||
// // 只支持积分支付,按10000积分=1融豆转换
|
||
// const pointsNeeded = totalRongdouNeeded * 10000;
|
||
// if (user.points < pointsNeeded) {
|
||
// await connection.rollback();
|
||
// return res.status(400).json({ success: false, message: '积分不足' });
|
||
// }
|
||
// pointsToDeduct = pointsNeeded;
|
||
// rongdouToDeduct = 0;
|
||
// } else if (!hasPoints && hasRongdou) {
|
||
// // 只支持融豆支付
|
||
// if (user.balance < totalRongdouNeeded) {
|
||
// await connection.rollback();
|
||
// return res.status(400).json({ success: false, message: '融豆不足' });
|
||
// }
|
||
// pointsToDeduct = 0;
|
||
// rongdouToDeduct = totalRongdouNeeded;
|
||
// } else if (hasPoints && hasRongdou) {
|
||
// // 组合支付:先扣积分,不足部分用融豆
|
||
// const availablePointsInRongdou = Math.floor(user.points / 10000); // 积分可转换的融豆数
|
||
|
||
// if (availablePointsInRongdou >= totalRongdouNeeded) {
|
||
// // 积分足够支付全部
|
||
// pointsToDeduct = totalRongdouNeeded * 10000;
|
||
// rongdouToDeduct = 0;
|
||
// } else {
|
||
// // 积分不够,需要组合支付
|
||
// pointsToDeduct = availablePointsInRongdou * 10000;
|
||
// rongdouToDeduct = totalRongdouNeeded - availablePointsInRongdou;
|
||
|
||
// if (user.balance < rongdouToDeduct) {
|
||
// await connection.rollback();
|
||
// return res.status(400).json({ success: false, message: '积分和融豆余额不足' });
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// console.log('扣费计算:', { totalRongdouNeeded, pointsToDeduct, rongdouToDeduct, userPoints: user.points, userBalance: user.balance });
|
||
|
||
// 扣除积分
|
||
// if (pointsToDeduct > 0) {
|
||
// await connection.execute(
|
||
// 'UPDATE users SET points = points - ? WHERE id = ?',
|
||
// [pointsToDeduct, userId]
|
||
// );
|
||
|
||
// // 记录积分变动历史
|
||
// await connection.execute(
|
||
// `INSERT INTO points_history (user_id, type, amount, description, order_id)
|
||
// VALUES (?, 'spend', ?, ?, ?)`,
|
||
// [userId, pointsToDeduct, `订单支付 - ${order.order_no}`, order_id]
|
||
// );
|
||
// }
|
||
|
||
// // 扣除融豆
|
||
// if (rongdouToDeduct > 0) {
|
||
// await connection.execute(
|
||
// 'UPDATE users SET balance = balance + ? WHERE id = ?',
|
||
// [rongdouToDeduct, userId]
|
||
// );
|
||
|
||
// // 记录融豆变动历史
|
||
// await connection.execute(
|
||
// `INSERT INTO rongdou_history (user_id, type, amount, description, order_id)
|
||
// VALUES (?, 'spend', ?, ?, ?)`,
|
||
// [userId, rongdouToDeduct, `订单支付 - ${order.order_no}`, order_id]
|
||
// );
|
||
// }
|
||
|
||
// 更新优惠券记录
|
||
if (couponRecordId) {
|
||
await connection.execute(
|
||
'UPDATE coupon_use SET use_time = NOW(), order_id = ? WHERE id = ?',
|
||
[order_id, couponRecordId]
|
||
);
|
||
}
|
||
|
||
// 更新订单状态和收货地址
|
||
const addressStr = JSON.stringify({
|
||
recipient_name: address.receiver_name,
|
||
phone: address.receiver_phone,
|
||
province: address.province,
|
||
city: address.city,
|
||
district: address.district,
|
||
detail_address: address.detail_address
|
||
});
|
||
|
||
await connection.execute(
|
||
`UPDATE orders SET status = 'pending', address = ?, updated_at = NOW(), coupon_record_id = ?
|
||
WHERE id = ?`,
|
||
[addressStr, couponRecordId === undefined ? null : couponRecordId, order_id]
|
||
);
|
||
|
||
// 减组合库存
|
||
// await connection.execute(
|
||
// 'UPDATE product_spec_combinations SET stock = stock - 1 WHERE id = ?',
|
||
// [order.product_combination_id]
|
||
// );
|
||
|
||
await connection.commit();
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '订单支付成功',
|
||
data: {
|
||
order_id: order_id,
|
||
order_no: order.order_no
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('确认支付失败:', error);
|
||
res.status(500).json({ success: false, message: '确认支付失败' });
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/orders/stats:
|
||
* get:
|
||
* summary: 获取订单统计信息(管理员权限)
|
||
* tags: [Orders]
|
||
* security:
|
||
* - bearerAuth: []
|
||
* responses:
|
||
* 200:
|
||
* description: 成功获取订单统计信息
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* properties:
|
||
* success:
|
||
* type: boolean
|
||
* data:
|
||
* type: object
|
||
* properties:
|
||
* totalOrders:
|
||
* type: integer
|
||
* description: 总订单数
|
||
* pendingOrders:
|
||
* type: integer
|
||
* description: 待发货订单数
|
||
* completedOrders:
|
||
* type: integer
|
||
* description: 已完成订单数
|
||
* monthOrders:
|
||
* type: integer
|
||
* description: 本月新增订单数
|
||
* monthGrowthRate:
|
||
* type: number
|
||
* description: 月增长率
|
||
* totalPointsConsumed:
|
||
* type: number
|
||
* description: 总积分消费
|
||
* 401:
|
||
* description: 未授权
|
||
* 403:
|
||
* description: 无管理员权限
|
||
* 500:
|
||
* description: 服务器错误
|
||
*/
|
||
router.get('/stats', auth, adminAuth, async (req, res) => {
|
||
try {
|
||
// 总订单数
|
||
const [totalOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders');
|
||
|
||
// 待发货订单数
|
||
const [pendingOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "pending"');
|
||
|
||
// 已完成订单数
|
||
const [completedOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "completed"');
|
||
|
||
// 本月新增订单
|
||
const [monthOrders] = await getDB().execute(
|
||
'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
|
||
);
|
||
|
||
// 上月订单数(用于计算增长率)
|
||
const [lastMonthOrders] = await getDB().execute(
|
||
'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))'
|
||
);
|
||
|
||
// 计算月增长率
|
||
const lastMonthCount = lastMonthOrders[0].count;
|
||
const currentMonthCount = monthOrders[0].count;
|
||
let monthGrowthRate = 0;
|
||
if (lastMonthCount > 0) {
|
||
monthGrowthRate = ((currentMonthCount - lastMonthCount) / lastMonthCount * 100).toFixed(1);
|
||
}
|
||
|
||
// 总积分消费
|
||
const [totalPointsConsumed] = await getDB().execute('SELECT SUM(points_cost) as total FROM orders WHERE status != "cancelled"');
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
totalOrders: totalOrders[0].count,
|
||
pendingOrders: pendingOrders[0].count,
|
||
completedOrders: completedOrders[0].count,
|
||
monthOrders: monthOrders[0].count,
|
||
monthGrowthRate: parseFloat(monthGrowthRate),
|
||
totalPointsConsumed: totalPointsConsumed[0].total || 0
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('获取订单统计失败:', error);
|
||
res.status(500).json({ success: false, message: '获取订单统计失败' });
|
||
}
|
||
});
|
||
|
||
|
||
module.exports = router; |