2026-05-26 13:37:55 +08:00
|
|
|
const { query, transaction } = require('../config/database')
|
|
|
|
|
const { paginate } = require('../utils/pagination')
|
2026-05-26 09:18:48 +08:00
|
|
|
|
|
|
|
|
async function getPurchases(ctx) {
|
|
|
|
|
let sql = `
|
|
|
|
|
SELECT p.*,
|
|
|
|
|
(SELECT COUNT(*) FROM purchase_items WHERE purchase_id = p.id) as items_count
|
|
|
|
|
FROM purchases p
|
|
|
|
|
WHERE 1=1`
|
|
|
|
|
const params = []
|
|
|
|
|
|
|
|
|
|
if (ctx.query.keyword) {
|
|
|
|
|
sql += ' AND (p.supplier_name LIKE ?)'
|
|
|
|
|
params.push(`%${ctx.query.keyword}%`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sql += ' ORDER BY p.created_at DESC'
|
2026-05-26 13:37:55 +08:00
|
|
|
const result = await paginate(query, sql, params, ctx.query.page, ctx.query.pageSize)
|
2026-05-26 09:18:48 +08:00
|
|
|
|
|
|
|
|
ctx.body = {
|
|
|
|
|
code: 200,
|
2026-05-26 13:37:55 +08:00
|
|
|
...result
|
2026-05-26 09:18:48 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getPurchaseById(ctx) {
|
|
|
|
|
const id = parseInt(ctx.params.id)
|
|
|
|
|
const purchases = await query('SELECT * FROM purchases WHERE id = ?', [id])
|
|
|
|
|
|
|
|
|
|
if (purchases.length === 0) {
|
|
|
|
|
ctx.body = { code: 404, message: '采购单不存在' }
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const purchase = purchases[0]
|
|
|
|
|
const items = await query(
|
|
|
|
|
`SELECT pi.*, g.name as goods_name
|
|
|
|
|
FROM purchase_items pi
|
|
|
|
|
LEFT JOIN goods g ON pi.goods_id = g.id
|
|
|
|
|
WHERE pi.purchase_id = ?`,
|
|
|
|
|
[id]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
purchase.items = items
|
|
|
|
|
purchase.items_count = items.length
|
|
|
|
|
|
|
|
|
|
ctx.body = {
|
|
|
|
|
code: 200,
|
|
|
|
|
data: purchase
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createPurchase(ctx) {
|
2026-06-03 14:15:55 +08:00
|
|
|
const { supplier_id, items, remarks } = ctx.request.body || {}
|
2026-05-26 09:18:48 +08:00
|
|
|
|
2026-06-03 14:15:55 +08:00
|
|
|
if (!supplier_id || !Array.isArray(items) || items.length === 0) {
|
2026-05-26 09:18:48 +08:00
|
|
|
ctx.body = { code: 400, message: '请选择供应商和采购商品' }
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const suppliers = await query('SELECT * FROM suppliers WHERE id = ?', [supplier_id])
|
|
|
|
|
if (suppliers.length === 0) {
|
|
|
|
|
ctx.body = { code: 404, message: '供应商不存在' }
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const supplier = suppliers[0]
|
|
|
|
|
|
|
|
|
|
let total = 0
|
|
|
|
|
for (const item of items) {
|
2026-06-03 14:15:55 +08:00
|
|
|
const qty = parseInt(item.quantity) || 0
|
|
|
|
|
const price = parseFloat(item.purchase_price) || 0
|
|
|
|
|
if (qty <= 0 || price < 0) {
|
|
|
|
|
ctx.body = { code: 400, message: '数量/单价不合法' }
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
total += price * qty
|
2026-05-26 09:18:48 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
const result = await transaction(async (conn) => {
|
2026-06-03 14:15:55 +08:00
|
|
|
const [purchaseResult] = await conn.execute(
|
2026-05-26 13:37:55 +08:00
|
|
|
'INSERT INTO purchases (supplier_id, supplier_name, total, remarks) VALUES (?, ?, ?, ?)',
|
|
|
|
|
[supplier_id, supplier.name, total, remarks || '']
|
2026-05-26 09:18:48 +08:00
|
|
|
)
|
2026-06-03 14:15:55 +08:00
|
|
|
const purchaseId = purchaseResult.insertId
|
2026-05-26 13:37:55 +08:00
|
|
|
|
|
|
|
|
for (const item of items) {
|
2026-06-03 14:15:55 +08:00
|
|
|
const [goods] = await conn.execute('SELECT name FROM goods WHERE id = ?', [item.goods_id])
|
|
|
|
|
const goodsName = goods.length > 0 ? goods[0].name : ''
|
2026-05-26 13:37:55 +08:00
|
|
|
await conn.execute(
|
|
|
|
|
'INSERT INTO purchase_items (purchase_id, goods_id, goods_name, quantity, purchase_price) VALUES (?, ?, ?, ?, ?)',
|
|
|
|
|
[purchaseId, item.goods_id, goodsName, item.quantity || 0, item.purchase_price || 0]
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return purchaseId
|
|
|
|
|
})
|
2026-05-26 09:18:48 +08:00
|
|
|
|
|
|
|
|
ctx.body = {
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '采购单创建成功',
|
2026-05-26 13:37:55 +08:00
|
|
|
data: { id: result }
|
2026-05-26 09:18:48 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function inboundPurchase(ctx) {
|
2026-06-03 14:15:55 +08:00
|
|
|
const operator = ctx.state.user
|
|
|
|
|
if (!operator) {
|
|
|
|
|
ctx.status = 401
|
|
|
|
|
ctx.body = { code: 401, message: '未登录' }
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-26 09:18:48 +08:00
|
|
|
const id = parseInt(ctx.params.id)
|
|
|
|
|
const purchases = await query('SELECT * FROM purchases WHERE id = ?', [id])
|
|
|
|
|
|
|
|
|
|
if (purchases.length === 0) {
|
|
|
|
|
ctx.body = { code: 404, message: '采购单不存在' }
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const purchase = purchases[0]
|
|
|
|
|
if (purchase.status === 1) {
|
|
|
|
|
ctx.body = { code: 400, message: '该采购单已入库' }
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const items = await query('SELECT * FROM purchase_items WHERE purchase_id = ?', [id])
|
2026-06-03 14:15:55 +08:00
|
|
|
if (items.length === 0) {
|
|
|
|
|
ctx.body = { code: 400, message: '采购单无明细' }
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-26 09:18:48 +08:00
|
|
|
|
2026-05-26 13:37:55 +08:00
|
|
|
await transaction(async (conn) => {
|
|
|
|
|
for (const item of items) {
|
2026-06-03 14:15:55 +08:00
|
|
|
const [goods] = await conn.execute('SELECT id, stock FROM goods WHERE id = ? FOR UPDATE', [item.goods_id])
|
|
|
|
|
if (goods.length === 0) {
|
|
|
|
|
throw new Error(`商品 ${item.goods_id} 不存在,无法入库`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [stockRows] = await conn.execute('SELECT quantity FROM stock WHERE goods_id = ? FOR UPDATE', [item.goods_id])
|
|
|
|
|
if (stockRows.length > 0) {
|
2026-05-26 13:37:55 +08:00
|
|
|
await conn.execute(
|
|
|
|
|
'UPDATE stock SET quantity = quantity + ? WHERE goods_id = ?',
|
|
|
|
|
[item.quantity, item.goods_id]
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
await conn.execute(
|
|
|
|
|
'INSERT INTO stock (goods_id, quantity, warehouse) VALUES (?, ?, ?)',
|
|
|
|
|
[item.goods_id, item.quantity, '默认仓库']
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
await conn.execute(
|
|
|
|
|
'UPDATE goods SET stock = stock + ? WHERE id = ?',
|
2026-05-26 09:18:48 +08:00
|
|
|
[item.quantity, item.goods_id]
|
|
|
|
|
)
|
2026-06-03 14:15:55 +08:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await conn.execute(
|
|
|
|
|
'INSERT INTO stock_logs (goods_id, change_type, delta, quantity_after, operator_id, remark) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
|
|
[item.goods_id, 'purchase', item.quantity, (stockRows[0]?.quantity || 0) + item.quantity, operator.id, `采购入库 #${id}`]
|
|
|
|
|
)
|
|
|
|
|
} catch {}
|
2026-05-26 09:18:48 +08:00
|
|
|
}
|
2026-05-26 13:37:55 +08:00
|
|
|
await conn.execute('UPDATE purchases SET status = 1 WHERE id = ?', [id])
|
|
|
|
|
})
|
2026-05-26 09:18:48 +08:00
|
|
|
|
|
|
|
|
ctx.body = {
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '入库成功'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
getPurchases,
|
|
|
|
|
getPurchaseById,
|
|
|
|
|
createPurchase,
|
|
|
|
|
inboundPurchase
|
|
|
|
|
}
|