Files
services/controllers/orders.js
T
2026-06-03 14:15:55 +08:00

281 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { query, transaction } = require('../config/database')
const { paginate } = require('../utils/pagination')
const orderService = require('../services/orderService')
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
}
async function getOrders(ctx) {
const operator = await requireAuth(ctx)
if (!operator) return
const { page, pageSize, status } = ctx.query
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
`
const params = []
if (operator.role !== 1) {
sql += ' AND o.user_id = ?'
params.push(operator.id)
}
if (status) {
sql += ' AND o.status = ?'
params.push(status)
}
sql += ' GROUP BY o.id ORDER BY o.created_at DESC'
const result = await paginate(query, sql, params, page, pageSize)
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
ctx.body = {
code: 200,
...result
}
}
async function getOrderById(ctx) {
const operator = await requireAuth(ctx)
if (!operator) return
const orderId = ctx.params.id
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
if (orders.length > 0) {
const order = orders[0]
if (operator.role !== 1 && order.user_id !== operator.id) {
ctx.body = { code: 403, message: '无权查看此订单' }
return
}
order.items = await orderService.getOrderItems(orderId)
ctx.body = {
code: 200,
data: order
}
} else {
ctx.body = {
code: 404,
message: '订单不存在'
}
}
}
async function createOrder(ctx) {
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
}
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'
const userInfo = JSON.stringify({
remark: remark || '',
customerName: customerName || '',
customerPhone: customerPhone || '',
orderType: orderType || 'customer'
})
await transaction(async (conn) => {
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])
}
}
await conn.execute(
'INSERT INTO orders (id, user_id, status, total_price, cart, user_info) VALUES (?, ?, ?, ?, ?, ?)',
[orderId, userId || null, orderStatus, calculatedTotalPrice, typeof cart === 'string' ? cart : JSON.stringify(cart), userInfo]
)
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))
}
ctx.body = {
code: 200,
data: created
}
}
async function updateOrder(ctx) {
const operator = await requireAuth(ctx)
if (!operator) return
const orderId = ctx.params.id
const body = allowedUpdateFields(ctx.request.body)
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
if (orders.length > 0) {
const order = orders[0]
if (operator.role !== 1 && order.user_id !== operator.id) {
ctx.body = { code: 403, message: '无权修改此订单' }
return
}
const prevStatus = order.status
const updateFields = []
const updateParams = []
if (body.status !== undefined) {
updateFields.push('status = ?')
updateParams.push(body.status)
}
const totalPrice = body.total_price !== undefined ? body.total_price : body.totalPrice
if (totalPrice !== undefined) {
updateFields.push('total_price = ?')
updateParams.push(totalPrice)
}
if (body.cart !== undefined) {
const cartStr = typeof body.cart === 'string' ? body.cart : JSON.stringify(body.cart)
updateFields.push('cart = ?')
updateParams.push(cartStr)
}
if (updateFields.length === 0) {
ctx.body = { code: 400, message: '没有需要更新的字段' }
return
}
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) {
await conn.execute('DELETE FROM order_items WHERE order_id = ?', [orderId])
await orderService.insertOrderItems(conn, orderId, body.cart)
}
})
const updatedOrders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
const completed = updatedOrders[0]
completed.items = await orderService.getOrderItems(orderId)
if (completed.status === 'completed' && prevStatus !== 'completed') {
setImmediate(() => orderService.processOrderComplete(completed))
}
// 订单状态变更时发送微信订阅消息通知
if (newStatus !== prevStatus && completed.user_id) {
setImmediate(() => orderService.sendWechatNotification(completed.user_id, completed.id, newStatus, completed.total_price))
}
ctx.body = {
code: 200,
data: completed
}
} else {
ctx.body = {
code: 404,
message: '订单不存在'
}
}
}
module.exports = {
getOrders,
getOrderById,
createOrder,
updateOrder
}