Files

188 lines
4.8 KiB
JavaScript
Raw Permalink Normal View History

2026-06-03 14:15:55 +08:00
const { query, transaction } = require('../config/database')
2026-05-26 13:37:55 +08:00
const { paginate } = require('../utils/pagination')
2026-05-23 14:15:45 +08:00
2026-06-03 14:15:55 +08:00
async function ensureStockLogTable() {
await query(`
CREATE TABLE IF NOT EXISTS stock_logs (
id INT NOT NULL AUTO_INCREMENT,
goods_id INT NOT NULL,
change_type VARCHAR(20) NOT NULL COMMENT 'inbound/adjust/purchase/sale',
delta INT NOT NULL,
quantity_after INT NOT NULL,
operator_id INT,
remark VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY goods_id (goods_id),
KEY created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表'
`)
}
2026-05-23 14:15:45 +08:00
// 获取库存列表
async function getStockList(ctx) {
const keyword = ctx.query.keyword
const threshold = parseInt(ctx.query.threshold) || 0
let sql = `
2026-06-03 14:15:55 +08:00
SELECT
2026-05-23 14:15:45 +08:00
s.id,
s.goods_id,
g.name as goods_name,
g.images as goods_image,
g.unit as goods_unit,
s.quantity,
s.warehouse
FROM stock s
LEFT JOIN goods g ON s.goods_id = g.id
WHERE 1=1
`
const params = []
if (keyword) {
sql += ' AND (g.name LIKE ? OR g.barcode LIKE ?)'
params.push(`%${keyword}%`, `%${keyword}%`)
}
if (threshold > 0) {
sql += ' AND s.quantity <= ?'
params.push(threshold)
}
sql += ' ORDER BY s.quantity ASC'
2026-05-26 13:37:55 +08:00
const result = await paginate(query, sql, params, ctx.query.page, ctx.query.pageSize)
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 adjustStock(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
}
const goodsId = parseInt(ctx.params.id)
const { quantity, type, remark } = ctx.request.body || {}
2026-05-23 14:15:45 +08:00
2026-06-03 14:15:55 +08:00
const qty = parseInt(quantity)
if (!qty || qty <= 0 || qty > 100000) {
ctx.body = { code: 400, message: '请输入 1-100000 之间的整数' }
return
}
if (type !== 'add' && type !== 'sub') {
ctx.body = { code: 400, message: 'type 必须是 add 或 sub' }
2026-05-23 14:15:45 +08:00
return
}
2026-06-03 14:15:55 +08:00
await ensureStockLogTable()
2026-05-23 14:15:45 +08:00
2026-06-03 14:15:55 +08:00
try {
const newQuantity = await transaction(async (conn) => {
const [rows] = await conn.execute('SELECT * FROM stock WHERE goods_id = ? FOR UPDATE', [goodsId])
let current = rows[0]
if (!current) {
if (type === 'sub') {
throw new Error('库存不足,无法减少')
2026-05-23 14:15:45 +08:00
}
2026-06-03 14:15:55 +08:00
await conn.execute(
'INSERT INTO stock (goods_id, quantity, warehouse) VALUES (?, ?, ?)',
[goodsId, qty, '默认仓库']
)
return qty
2026-05-23 14:15:45 +08:00
}
2026-06-03 14:15:55 +08:00
const delta = type === 'add' ? qty : -qty
const next = current.quantity + delta
if (next < 0) throw new Error('库存不足,无法减少')
await conn.execute('UPDATE stock SET quantity = ? WHERE goods_id = ?', [next, goodsId])
await conn.execute(
'UPDATE goods SET stock = ? WHERE id = ?',
[Math.max(0, next), goodsId]
)
await conn.execute(
'INSERT INTO stock_logs (goods_id, change_type, delta, quantity_after, operator_id, remark) VALUES (?, ?, ?, ?, ?, ?)',
[goodsId, 'adjust', delta, next, operator.id, remark || '库存调整']
)
return next
})
const [updated] = await query('SELECT * FROM stock WHERE goods_id = ?', [goodsId])
ctx.body = { code: 200, data: updated, message: '库存调整成功', newQuantity }
} catch (error) {
ctx.status = 400
ctx.body = { code: 400, message: error.message || '调整失败' }
2026-05-23 14:15:45 +08:00
}
}
// 获取单个商品的库存
async function getStockByGoodsId(ctx) {
const goodsId = ctx.params.id
const stockRecords = await query('SELECT * FROM stock WHERE goods_id = ?', [goodsId])
if (stockRecords.length > 0) {
ctx.body = {
code: 200,
data: stockRecords[0]
}
} else {
ctx.body = {
code: 200,
data: { goods_id: goodsId, quantity: 0, warehouse: '默认仓库' }
}
}
}
2026-06-04 08:20:49 +08:00
// 获取库存变动日志
async function getStockLogs(ctx) {
const goodsId = ctx.query.goods_id
const page = parseInt(ctx.query.page) || 1
const pageSize = parseInt(ctx.query.pageSize) || 20
await ensureStockLogTable()
let sql = `
SELECT
sl.id,
sl.goods_id,
g.name as goods_name,
sl.change_type,
sl.delta,
sl.quantity_after,
sl.operator_id,
sl.remark,
sl.created_at
FROM stock_logs sl
LEFT JOIN goods g ON sl.goods_id = g.id
WHERE 1=1
`
const params = []
if (goodsId) {
sql += ' AND sl.goods_id = ?'
params.push(goodsId)
}
sql += ' ORDER BY sl.created_at DESC'
const result = await paginate(query, sql, params, page, pageSize)
ctx.body = {
code: 200,
...result
}
}
2026-05-23 14:15:45 +08:00
module.exports = {
getStockList,
adjustStock,
2026-06-04 08:20:49 +08:00
getStockByGoodsId,
getStockLogs
2026-05-23 14:15:45 +08:00
}