2026-05-26 13:37:55 +08:00
|
|
|
|
const { query, transaction } = require('../config/database')
|
|
|
|
|
|
const { paginate } = require('../utils/pagination')
|
|
|
|
|
|
const orderService = require('../services/orderService')
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const { requireAuth } = require('./users')
|
|
|
|
|
|
|
|
|
|
|
|
const ORDER_UPDATEABLE_FIELDS = ['status', 'total_price', 'totalPrice', 'cart']
|
|
|
|
|
|
|
|
|
|
|
|
function allowedUpdateFields(body) {
|
|
|
|
|
|
const result = {}
|
|
|
|
|
|
for (const key of ORDER_UPDATEABLE_FIELDS) {
|
|
|
|
|
|
if (key in body) {
|
|
|
|
|
|
result[key] = body[key]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
2026-05-23 14:15:45 +08:00
|
|
|
|
|
|
|
|
|
|
async function getOrders(ctx) {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const operator = await requireAuth(ctx)
|
|
|
|
|
|
if (!operator) return
|
|
|
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
|
const { page, pageSize, status } = ctx.query
|
2026-06-03 14:15:55 +08:00
|
|
|
|
let sql = `
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
o.*,
|
|
|
|
|
|
JSON_ARRAYAGG(
|
|
|
|
|
|
JSON_OBJECT(
|
|
|
|
|
|
'id', oi.id,
|
|
|
|
|
|
'order_id', oi.order_id,
|
|
|
|
|
|
'goods_id', oi.goods_id,
|
|
|
|
|
|
'goods_name', oi.goods_name,
|
|
|
|
|
|
'price', oi.price,
|
|
|
|
|
|
'quantity', oi.quantity,
|
2026-06-04 18:43:38 +08:00
|
|
|
|
'Weight', oi.weight,
|
2026-06-03 14:15:55 +08:00
|
|
|
|
'subtotal', oi.subtotal,
|
2026-06-04 18:43:38 +08:00
|
|
|
|
'unit', oi.unit,
|
|
|
|
|
|
'image', g.images
|
2026-06-03 14:15:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
) as items_json
|
|
|
|
|
|
FROM orders o
|
|
|
|
|
|
LEFT JOIN order_items oi ON o.id = oi.order_id
|
2026-06-04 18:43:38 +08:00
|
|
|
|
LEFT JOIN goods g ON oi.goods_id = g.id
|
2026-06-03 14:15:55 +08:00
|
|
|
|
WHERE 1=1
|
|
|
|
|
|
`
|
2026-05-26 13:37:55 +08:00
|
|
|
|
const params = []
|
2026-06-03 14:15:55 +08:00
|
|
|
|
|
|
|
|
|
|
if (operator.role !== 1) {
|
|
|
|
|
|
sql += ' AND o.user_id = ?'
|
|
|
|
|
|
params.push(operator.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
|
if (status) {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
sql += ' AND o.status = ?'
|
2026-05-26 13:37:55 +08:00
|
|
|
|
params.push(status)
|
|
|
|
|
|
}
|
2026-06-03 14:15:55 +08:00
|
|
|
|
sql += ' GROUP BY o.id ORDER BY o.created_at DESC'
|
|
|
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
|
const result = await paginate(query, sql, params, page, pageSize)
|
|
|
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const rows = (result.data || []).map(row => {
|
|
|
|
|
|
let items = []
|
|
|
|
|
|
try {
|
|
|
|
|
|
const itemsJson = row.items_json
|
|
|
|
|
|
if (itemsJson) {
|
|
|
|
|
|
items = typeof itemsJson === 'string' ? JSON.parse(itemsJson) : itemsJson
|
|
|
|
|
|
if (items.length === 1 && items[0].id === null) {
|
|
|
|
|
|
items = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
const { items_json, ...order } = row
|
|
|
|
|
|
return { ...order, items }
|
|
|
|
|
|
})
|
|
|
|
|
|
result.data = rows
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
ctx.body = {
|
|
|
|
|
|
code: 200,
|
2026-05-26 13:37:55 +08:00
|
|
|
|
...result
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function getOrderById(ctx) {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const operator = await requireAuth(ctx)
|
|
|
|
|
|
if (!operator) return
|
|
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
const orderId = ctx.params.id
|
|
|
|
|
|
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
if (orders.length > 0) {
|
2026-05-26 13:37:55 +08:00
|
|
|
|
const order = orders[0]
|
2026-06-03 14:15:55 +08:00
|
|
|
|
|
|
|
|
|
|
if (operator.role !== 1 && order.user_id !== operator.id) {
|
|
|
|
|
|
ctx.body = { code: 403, message: '无权查看此订单' }
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
|
order.items = await orderService.getOrderItems(orderId)
|
2026-05-23 14:15:45 +08:00
|
|
|
|
ctx.body = {
|
|
|
|
|
|
code: 200,
|
2026-05-26 13:37:55 +08:00
|
|
|
|
data: order
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ctx.body = {
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '订单不存在'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function createOrder(ctx) {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const operator = await requireAuth(ctx)
|
|
|
|
|
|
if (!operator) return
|
|
|
|
|
|
|
|
|
|
|
|
const { totalPrice, cart, remark, customerName, customerPhone, orderType, status } = ctx.request.body
|
|
|
|
|
|
const userId = ctx.request.body.userId || operator.id
|
|
|
|
|
|
|
|
|
|
|
|
if (operator.role !== 1 && userId !== operator.id) {
|
|
|
|
|
|
ctx.body = { code: 403, message: '无权为他人创建订单' }
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!cart || (Array.isArray(cart) && cart.length === 0)) {
|
|
|
|
|
|
ctx.body = { code: 400, message: '购物车不能为空' }
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const items = typeof cart === 'string' ? JSON.parse(cart) : cart
|
|
|
|
|
|
const calculatedTotalPrice = await orderService.recalculateTotalPrice(items)
|
|
|
|
|
|
const orderId = `ORD${Date.now()}${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`
|
|
|
|
|
|
const orderStatus = status || 'pending'
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
|
|
|
|
|
const userInfo = JSON.stringify({
|
|
|
|
|
|
remark: remark || '',
|
|
|
|
|
|
customerName: customerName || '',
|
|
|
|
|
|
customerPhone: customerPhone || '',
|
|
|
|
|
|
orderType: orderType || 'customer'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
await transaction(async (conn) => {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
for (const item of items) {
|
|
|
|
|
|
const goodsId = item.id || item.goods_id || item.goodsId
|
|
|
|
|
|
if (!goodsId) continue
|
|
|
|
|
|
const qty = item.pricingType === 2 ? 1 : (item.quantity || 1)
|
|
|
|
|
|
|
|
|
|
|
|
const [rows] = await conn.execute('SELECT id, name, stock FROM goods WHERE id = ? FOR UPDATE', [goodsId])
|
|
|
|
|
|
if (rows.length === 0) {
|
|
|
|
|
|
throw new Error('商品不存在')
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rows[0].stock < qty) {
|
|
|
|
|
|
throw new Error(`${rows[0].name} 库存不足(当前库存: ${rows[0].stock},需要: ${qty})`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (orderStatus === 'completed') {
|
|
|
|
|
|
await conn.execute('UPDATE goods SET stock = stock - ?, sales = COALESCE(sales, 0) + ? WHERE id = ?', [qty, qty, goodsId])
|
|
|
|
|
|
await conn.execute('UPDATE stock SET quantity = quantity - ? WHERE goods_id = ?', [qty, goodsId])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
|
await conn.execute(
|
|
|
|
|
|
'INSERT INTO orders (id, user_id, status, total_price, cart, user_info) VALUES (?, ?, ?, ?, ?, ?)',
|
2026-06-03 14:15:55 +08:00
|
|
|
|
[orderId, userId || null, orderStatus, calculatedTotalPrice, typeof cart === 'string' ? cart : JSON.stringify(cart), userInfo]
|
2026-05-26 13:37:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
await orderService.insertOrderItems(conn, orderId, cart)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
|
|
|
|
|
|
const created = orders[0]
|
|
|
|
|
|
created.items = await orderService.getOrderItems(orderId)
|
|
|
|
|
|
|
|
|
|
|
|
if (created.status === 'completed') {
|
|
|
|
|
|
setImmediate(() => orderService.processOrderComplete(created))
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
ctx.body = {
|
|
|
|
|
|
code: 200,
|
2026-05-26 13:37:55 +08:00
|
|
|
|
data: created
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function updateOrder(ctx) {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const operator = await requireAuth(ctx)
|
|
|
|
|
|
if (!operator) return
|
|
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
const orderId = ctx.params.id
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const body = allowedUpdateFields(ctx.request.body)
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
if (orders.length > 0) {
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const order = orders[0]
|
|
|
|
|
|
|
|
|
|
|
|
if (operator.role !== 1 && order.user_id !== operator.id) {
|
|
|
|
|
|
ctx.body = { code: 403, message: '无权修改此订单' }
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const prevStatus = order.status
|
2026-05-23 14:15:45 +08:00
|
|
|
|
const updateFields = []
|
|
|
|
|
|
const updateParams = []
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
|
if (body.status !== undefined) {
|
2026-05-23 14:15:45 +08:00
|
|
|
|
updateFields.push('status = ?')
|
2026-06-03 14:15:55 +08:00
|
|
|
|
updateParams.push(body.status)
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const totalPrice = body.total_price !== undefined ? body.total_price : body.totalPrice
|
2026-05-26 13:37:55 +08:00
|
|
|
|
if (totalPrice !== undefined) {
|
2026-05-23 14:15:45 +08:00
|
|
|
|
updateFields.push('total_price = ?')
|
2026-05-26 13:37:55 +08:00
|
|
|
|
updateParams.push(totalPrice)
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
2026-06-03 14:15:55 +08:00
|
|
|
|
if (body.cart !== undefined) {
|
|
|
|
|
|
const cartStr = typeof body.cart === 'string' ? body.cart : JSON.stringify(body.cart)
|
2026-05-23 14:15:45 +08:00
|
|
|
|
updateFields.push('cart = ?')
|
2026-05-26 13:37:55 +08:00
|
|
|
|
updateParams.push(cartStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
|
if (updateFields.length === 0) {
|
|
|
|
|
|
ctx.body = { code: 400, message: '没有需要更新的字段' }
|
|
|
|
|
|
return
|
2026-05-26 13:37:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
|
const newStatus = body.status !== undefined ? body.status : prevStatus
|
|
|
|
|
|
|
|
|
|
|
|
await transaction(async (conn) => {
|
|
|
|
|
|
if (newStatus === 'completed' && prevStatus !== 'completed') {
|
|
|
|
|
|
const items = body.cart !== undefined
|
|
|
|
|
|
? (typeof body.cart === 'string' ? JSON.parse(body.cart) : body.cart)
|
|
|
|
|
|
: JSON.parse(order.cart || '[]')
|
|
|
|
|
|
|
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
|
const goodsId = item.id || item.goods_id || item.goodsId
|
|
|
|
|
|
if (!goodsId) continue
|
|
|
|
|
|
const qty = item.pricingType === 2 ? 1 : (item.quantity || 1)
|
|
|
|
|
|
|
|
|
|
|
|
const [rows] = await conn.execute('SELECT stock FROM goods WHERE id = ? FOR UPDATE', [goodsId])
|
|
|
|
|
|
if (rows.length > 0 && rows[0].stock >= qty) {
|
|
|
|
|
|
await conn.execute('UPDATE goods SET stock = stock - ?, sales = COALESCE(sales, 0) + ? WHERE id = ?', [qty, qty, goodsId])
|
|
|
|
|
|
await conn.execute('UPDATE stock SET quantity = quantity - ? WHERE goods_id = ?', [qty, goodsId])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateParams.push(orderId)
|
|
|
|
|
|
await conn.execute(`UPDATE orders SET ${updateFields.join(', ')} WHERE id = ?`, updateParams)
|
|
|
|
|
|
|
|
|
|
|
|
if (body.cart !== undefined) {
|
2026-05-26 13:37:55 +08:00
|
|
|
|
await conn.execute('DELETE FROM order_items WHERE order_id = ?', [orderId])
|
2026-06-03 14:15:55 +08:00
|
|
|
|
await orderService.insertOrderItems(conn, orderId, body.cart)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
const updatedOrders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
|
2026-05-26 13:37:55 +08:00
|
|
|
|
const completed = updatedOrders[0]
|
|
|
|
|
|
completed.items = await orderService.getOrderItems(orderId)
|
|
|
|
|
|
|
|
|
|
|
|
if (completed.status === 'completed' && prevStatus !== 'completed') {
|
|
|
|
|
|
setImmediate(() => orderService.processOrderComplete(completed))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
|
// 订单状态变更时发送微信订阅消息通知
|
|
|
|
|
|
if (newStatus !== prevStatus && completed.user_id) {
|
|
|
|
|
|
setImmediate(() => orderService.sendWechatNotification(completed.user_id, completed.id, newStatus, completed.total_price))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 14:15:45 +08:00
|
|
|
|
ctx.body = {
|
|
|
|
|
|
code: 200,
|
2026-05-26 13:37:55 +08:00
|
|
|
|
data: completed
|
2026-05-23 14:15:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ctx.body = {
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '订单不存在'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
getOrders,
|
|
|
|
|
|
getOrderById,
|
|
|
|
|
|
createOrder,
|
|
|
|
|
|
updateOrder
|
2026-05-26 13:37:55 +08:00
|
|
|
|
}
|