Files
jurong_circle_black/routes/orders.js

1503 lines
47 KiB
JavaScript
Raw Normal View History

2025-08-26 10:06:23 +08:00
const express = require('express');
const { getDB } = require('../database');
const { auth, adminAuth } = require('../middleware/auth');
const router = express.Router();
2025-09-02 09:29:20 +08:00
// 订单管理路由
2025-08-28 09:14:56 +08:00
2025-08-26 10:06:23 +08:00
2025-09-02 09:29:20 +08:00
// 获取订单列表
2025-08-26 10:06:23 +08:00
router.get('/', auth, async (req, res) => {
try {
const { page = 1, limit = 10, search = '', orderNumber = '', username = '', status = '', startDate = '', endDate = '' } = req.query;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 确保参数为有效数字
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(limit) || 10;
const offset = (pageNum - 1) * limitNum;
const isAdmin = req.user.role === 'admin';
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
let whereClause = 'WHERE 1=1';
const params = [];
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 非管理员只能查看自己的订单
if (!isAdmin) {
whereClause += ' AND o.user_id = ?';
params.push(req.user.id);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (search) {
whereClause += ' AND (o.order_no LIKE ? OR u.username LIKE ?)';
params.push(`%${search}%`, `%${search}%`);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (orderNumber) {
whereClause += ' AND o.order_no LIKE ?';
params.push(`%${orderNumber}%`);
}
2025-09-02 09:29:20 +08:00
if (username) {
2025-08-26 10:06:23 +08:00
whereClause += ' AND u.username LIKE ?';
params.push(`%${username}%`);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (status && status.trim()) {
whereClause += ' AND o.status = ?';
params.push(status);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (startDate && startDate.trim()) {
whereClause += ' AND DATE(o.created_at) >= ?';
params.push(startDate);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (endDate && endDate.trim()) {
whereClause += ' AND DATE(o.created_at) <= ?';
params.push(endDate);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 获取总数
const countQuery = `
SELECT COUNT(*) as total
FROM orders as o
LEFT JOIN users u ON o.user_id = u.id
${whereClause}
`;
2025-09-02 09:29:20 +08:00
console.log(countQuery, params);
2025-08-26 10:06:23 +08:00
const [countResult] = await getDB().execute(countQuery, params);
const total = countResult[0].total;
2025-09-02 09:29:20 +08:00
console.log(total, '数量');
2025-08-26 10:06:23 +08:00
// 获取订单列表
const query = `
SELECT
o.id, o.order_no, o.user_id, o.total_amount, o.total_points,
2025-09-02 09:29:20 +08:00
o.status, o.address, o.created_at, o.updated_at,o.total_rongdou,
2025-08-26 10:06:23 +08:00
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}
`;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const [orders] = await getDB().execute(query, [...params]);
2025-09-02 09:29:20 +08:00
// 为每个订单获取商品详情
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');
2025-09-02 09:29:20 +08:00
if (order.address) {
try {
order.address = order.address;
2025-09-02 09:29:20 +08:00
} catch (e) {
order.address = null;
}
}
order.items = orderItems;
}
2025-08-26 10:06:23 +08:00
res.json({
success: true,
data: {
orders,
pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
}
});
2025-09-02 09:29:20 +08:00
2025-09-02 09:29:20 +08:00
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();
}
});
2025-09-02 09:29:20 +08:00
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: '获取预订单详情失败' });
}
});
2025-08-26 10:06:23 +08:00
} catch (error) {
console.error('获取订单列表失败:', error);
res.status(500).json({ success: false, message: '获取订单列表失败' });
}
});
2025-08-26 10:06:23 +08:00
router.get('/:id', auth, async (req, res) => {
try {
const { id } = req.params;
const isAdmin = req.user.role === 'admin';
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
let whereClause = 'WHERE o.id = ?';
const params = [id];
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 非管理员只能查看自己的订单
if (!isAdmin) {
whereClause += ' AND o.user_id = ?';
params.push(req.user.id);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
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}
`;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const [orders] = await getDB().execute(query, params);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (orders.length === 0) {
return res.status(404).json({ success: false, message: '订单不存在' });
}
2025-09-02 09:29:20 +08:00
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');
2025-09-02 09:29:20 +08:00
if (order.address) {
try {
order.address = order.address;
2025-09-02 09:29:20 +08:00
} catch (e) {
order.address = null;
}
}
order.items = orderItems;
2025-08-26 10:06:23 +08:00
res.json({
success: true,
2025-09-02 09:29:20 +08:00
data: { order }
2025-08-26 10:06:23 +08:00
});
} catch (error) {
console.error('获取订单详情失败:', error);
res.status(500).json({ success: false, message: '获取订单详情失败' });
}
});
2025-09-02 09:29:20 +08:00
// 创建预订单
router.post('/create-from-cart', auth, async (req, res) => {
2025-08-26 10:06:23 +08:00
const db = getDB();
await db.query('START TRANSACTION');
2025-09-02 09:29:20 +08:00
try {
const { cart_item_ids } = req.body;
2025-08-26 10:06:23 +08:00
const user_id = req.user.id;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 验证必填字段
2025-09-02 09:29:20 +08:00
if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) {
2025-08-26 10:06:23 +08:00
await db.query('ROLLBACK');
2025-09-02 09:29:20 +08:00
return res.status(400).json({ success: false, message: '请选择要购买的商品' });
2025-08-26 10:06:23 +08:00
}
2025-09-02 09:29:20 +08:00
// 获取购物车商品信息和支付方式
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) {
2025-08-26 10:06:23 +08:00
await db.query('ROLLBACK');
2025-09-02 09:29:20 +08:00
return res.status(400).json({ success: false, message: '购物车商品不存在' });
2025-08-26 10:06:23 +08:00
}
2025-09-02 09:29:20 +08:00
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]
);
}
2025-09-02 09:29:20 +08:00
// 验证商品状态和库存,计算总价和支付方式
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;
}
2025-08-26 10:06:23 +08:00
}
2025-09-02 09:29:20 +08:00
// 去重支付方式
const uniquePaymentMethods = [...new Set(allPaymentMethods)];
console.log('订单支付方式:', uniquePaymentMethods);
// 生成预订单号
const orderNumber = 'PRE' + Date.now() + Math.random().toString(36).substr(2, 5).toUpperCase();
// 创建预订单状态为pre_order
2025-08-26 10:06:23 +08:00
const [orderResult] = await db.execute(
2025-09-02 09:29:20 +08:00
`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]
2025-08-26 10:06:23 +08:00
);
2025-09-02 09:29:20 +08:00
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]
);
}
// 删除购物车中的商品
2025-08-26 10:06:23 +08:00
await db.execute(
2025-09-02 09:29:20 +08:00
`DELETE FROM cart_items WHERE id IN (${placeholders})`,
cart_item_ids
2025-08-26 10:06:23 +08:00
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
await db.query('COMMIT');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
res.status(201).json({
success: true,
2025-09-02 09:29:20 +08:00
message: '预订单创建成功',
2025-08-26 10:06:23 +08:00
data: {
2025-09-02 09:29:20 +08:00
preOrderId,
2025-08-26 10:06:23 +08:00
orderNumber,
2025-09-02 09:29:20 +08:00
totalAmount,
totalPoints,
totalRongdou,
paymentMethods: uniquePaymentMethods
}
});
2025-08-26 10:06:23 +08:00
} catch (error) {
await db.query('ROLLBACK');
2025-09-02 09:29:20 +08:00
console.error('创建预订单失败:', error);
res.status(500).json({ success: false, message: '创建预订单失败' });
2025-08-26 10:06:23 +08:00
}
});
2025-08-28 09:14:56 +08:00
/**
* @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: 服务器错误
*/
2025-08-26 10:06:23 +08:00
router.put('/:id/cancel', auth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
try {
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const orderId = req.params.id;
const userId = req.user.id;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 检查订单是否存在且属于当前用户
const [orders] = await db.execute(
'SELECT id, user_id, total_points, status FROM orders WHERE id = ? AND user_id = ?',
[orderId, userId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (orders.length === 0) {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '订单不存在' });
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const order = orders[0];
2025-09-02 09:29:20 +08:00
if (order.status !== 'pending' && order.status !== 'pre_order') {
2025-08-26 10:06:23 +08:00
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '只能取消待处理或待支付的订单' });
2025-08-26 10:06:23 +08:00
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 退还用户积分
await db.execute(
'UPDATE users SET points = points + ? WHERE id = ?',
[order.total_points, userId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 记录积分历史
await db.execute(
`INSERT INTO points_history (user_id, amount, type, description, created_at)
VALUES (?, ?, 'refund', '订单取消退还积分', NOW())`,
[userId, order.total_points]
);
2025-09-02 09:29:20 +08:00
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]
);
2025-08-26 10:06:23 +08:00
// 更新订单状态
await db.execute(
'UPDATE orders SET status = "cancelled", updated_at = NOW() WHERE id = ?',
[orderId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
await db.query('COMMIT');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
res.json({ success: true, message: '订单已取消' });
} catch (error) {
await db.query('ROLLBACK');
console.error('取消订单失败:', error);
res.status(500).json({ success: false, message: '取消订单失败' });
}
});
2025-08-28 09:14:56 +08:00
/**
* @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: 服务器错误
*/
2025-08-26 10:06:23 +08:00
router.put('/:id/confirm', auth, async (req, res) => {
try {
const orderId = req.params.id;
const userId = req.user.id;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 检查订单是否存在且属于当前用户
const [orders] = await getDB().execute(
'SELECT id, status FROM orders WHERE id = ? AND user_id = ?',
[orderId, userId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (orders.length === 0) {
return res.status(404).json({ success: false, message: '订单不存在' });
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const order = orders[0];
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (order.status !== 'shipped') {
return res.status(400).json({ success: false, message: '只能确认已发货的订单' });
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 更新订单状态
await getDB().execute(
'UPDATE orders SET status = "completed", updated_at = NOW() WHERE id = ?',
[orderId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
res.json({ success: true, message: '确认收货成功' });
} catch (error) {
console.error('确认收货失败:', error);
res.status(500).json({ success: false, message: '确认收货失败' });
}
});
2025-08-28 09:14:56 +08:00
/**
* @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: 服务器错误
*/
2025-08-26 10:06:23 +08:00
router.put('/:id/status', auth, adminAuth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
try {
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const orderId = req.params.id;
const { status } = req.body;
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const validStatuses = ['pending', 'shipped', 'completed', 'cancelled'];
if (!validStatuses.includes(status)) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '无效的订单状态' });
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 检查订单是否存在
const [orders] = await db.execute(
2025-09-02 09:29:20 +08:00
'SELECT id, user_id, total_points, total_rongdou, status FROM orders WHERE id = ?',
2025-08-26 10:06:23 +08:00
[orderId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
if (orders.length === 0) {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '订单不存在' });
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
const order = orders[0];
2025-09-02 09:29:20 +08:00
// 如果是取消订单,需要退还积分和融豆
if (status === 'cancelled' && order.status !== 'cancelled' && order.status !== 'pre_order') {
2025-08-26 10:06:23 +08:00
// 退还用户积分
2025-09-02 09:29:20 +08:00
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]
);
}
2025-08-26 10:06:23 +08:00
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 更新订单状态
await db.execute(
'UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ?',
[status, orderId]
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
await db.query('COMMIT');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
res.json({ success: true, message: '订单状态已更新' });
} catch (error) {
await db.query('ROLLBACK');
console.error('更新订单状态失败:', error);
res.status(500).json({ success: false, message: '更新订单状态失败' });
}
});
2025-09-02 09:29:20 +08:00
/**
* @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,
2025-09-02 09:29:20 +08:00
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)
}))
2025-09-02 09:29:20 +08:00
}
});
} 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;
2025-09-02 09:29:20 +08:00
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);
2025-09-02 09:29:20 +08:00
// if (order.payment_methods_list) {
// try {
// // 数据库中存储的是序列化的JSON字符串直接解析
2025-09-02 09:29:20 +08:00
// allPaymentMethods = JSON.parse(JSON.parse(order.payment_methods_list));
// } catch (e) {
// console.error('解析支付方式失败:', e, 'raw data:', order.payment_methods_list);
// allPaymentMethods = [];
// }
// }
2025-09-02 09:29:20 +08:00
// // 去重支付方式
// allPaymentMethods = [...new Set(allPaymentMethods)];
2025-09-02 09:29:20 +08:00
// // 判断支付方式类型
// const hasPoints = allPaymentMethods.includes('points') || allPaymentMethods.includes('points_rongdou');
// const hasRongdou = allPaymentMethods.includes('rongdou') || allPaymentMethods.includes('points_rongdou');
// const isComboPayment = allPaymentMethods.includes('points_rongdou');
2025-09-02 09:29:20 +08:00
// console.log('订单支付方式:', allPaymentMethods, { hasPoints, hasRongdou, isComboPayment });
2025-09-02 09:29:20 +08:00
// 获取收货地址信息
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: '支付方式配置错误' });
}
2025-09-02 09:29:20 +08:00
// // 根据支付方式处理扣费逻辑
// let totalRongdouNeeded = order.total_rongdou; // 需要的融豆总数
// let pointsToDeduct = 0; // 需要扣除的积分
// let rongdouToDeduct = 0; // 需要扣除的融豆
2025-09-02 09:29:20 +08:00
// if (!hasRongdou && !hasPoints) {
// await connection.rollback();
// return res.status(400).json({ success: false, message: '商品支付方式配置错误' });
// }
2025-09-02 09:29:20 +08:00
// 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); // 积分可转换的融豆数
2025-09-02 09:29:20 +08:00
// if (availablePointsInRongdou >= totalRongdouNeeded) {
// // 积分足够支付全部
// pointsToDeduct = totalRongdouNeeded * 10000;
// rongdouToDeduct = 0;
// } else {
// // 积分不够,需要组合支付
// pointsToDeduct = availablePointsInRongdou * 10000;
// rongdouToDeduct = totalRongdouNeeded - availablePointsInRongdou;
2025-09-02 09:29:20 +08:00
// if (user.balance < rongdouToDeduct) {
// await connection.rollback();
// return res.status(400).json({ success: false, message: '积分和融豆余额不足' });
// }
// }
// }
2025-09-02 09:29:20 +08:00
// console.log('扣费计算:', { totalRongdouNeeded, pointsToDeduct, rongdouToDeduct, userPoints: user.points, userBalance: user.balance });
2025-09-02 09:29:20 +08:00
// 扣除积分
// 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]
// );
// }
2025-09-02 09:29:20 +08:00
2025-10-15 17:26:20 +08:00
// 更新优惠券记录
if (couponRecordId) {
await connection.execute(
'UPDATE coupon_use SET use_time = NOW(), order_id = ? WHERE id = ?',
[order_id, couponRecordId]
);
}
2025-09-02 09:29:20 +08:00
// 更新订单状态和收货地址
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()
WHERE id = ?`,
[addressStr, order_id]
);
2025-10-15 17:26:20 +08:00
// 减组合库存
// await connection.execute(
// 'UPDATE product_spec_combinations SET stock = stock - 1 WHERE id = ?',
// [order.product_combination_id]
// );
2025-09-02 09:29:20 +08:00
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();
}
});
2025-08-28 09:14:56 +08:00
/**
* @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: 服务器错误
*/
2025-08-26 10:06:23 +08:00
router.get('/stats', auth, adminAuth, async (req, res) => {
try {
// 总订单数
const [totalOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 待发货订单数
const [pendingOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "pending"');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 已完成订单数
const [completedOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "completed"');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 本月新增订单
const [monthOrders] = await getDB().execute(
'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 上月订单数(用于计算增长率)
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))'
);
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 计算月增长率
const lastMonthCount = lastMonthOrders[0].count;
const currentMonthCount = monthOrders[0].count;
let monthGrowthRate = 0;
if (lastMonthCount > 0) {
monthGrowthRate = ((currentMonthCount - lastMonthCount) / lastMonthCount * 100).toFixed(1);
}
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
// 总积分消费
const [totalPointsConsumed] = await getDB().execute('SELECT SUM(points_cost) as total FROM orders WHERE status != "cancelled"');
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
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: '获取订单统计失败' });
}
});
2025-09-02 09:29:20 +08:00
2025-08-26 10:06:23 +08:00
module.exports = router;