Files
services/controllers/orders.js
T

281 lines
8.3 KiB
JavaScript
Raw Normal View History

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,
'weight', oi.weight,
'subtotal', oi.subtotal,
'unit', oi.unit
)
) as items_json
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
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
}