2025-09-17 18:04:28 +08:00
|
|
|
const express = require('express');
|
|
|
|
|
const router = express.Router();
|
2025-09-24 17:49:32 +08:00
|
|
|
const Material = require('../models/Material');
|
|
|
|
|
const WarehouseTransaction = require('../models/WarehouseTransaction');
|
|
|
|
|
const { Op } = require('sequelize');
|
2025-09-17 18:04:28 +08:00
|
|
|
|
2025-09-24 17:49:32 +08:00
|
|
|
// 仓库物资列表(支持分页、搜索和筛选)
|
|
|
|
|
router.get('/', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const {
|
|
|
|
|
keyword = '',
|
|
|
|
|
category = '',
|
|
|
|
|
status = '',
|
|
|
|
|
page = 1,
|
|
|
|
|
pageSize = 10
|
|
|
|
|
} = req.query;
|
|
|
|
|
|
|
|
|
|
const where = {};
|
|
|
|
|
|
|
|
|
|
// 搜索条件
|
|
|
|
|
if (keyword) {
|
|
|
|
|
where[Op.or] = [
|
|
|
|
|
{ code: { [Op.like]: `%${keyword}%` } },
|
|
|
|
|
{ name: { [Op.like]: `%${keyword}%` } }
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 类别筛选
|
|
|
|
|
if (category) {
|
|
|
|
|
where.category = category;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 状态筛选
|
|
|
|
|
if (status) {
|
|
|
|
|
where.status = status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
|
|
|
|
const limit = parseInt(pageSize);
|
|
|
|
|
|
|
|
|
|
const { count, rows } = await Material.findAndCountAll({
|
|
|
|
|
where,
|
|
|
|
|
offset,
|
|
|
|
|
limit,
|
|
|
|
|
order: [['update_time', 'DESC']]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: rows,
|
|
|
|
|
total: count,
|
|
|
|
|
page: parseInt(page),
|
|
|
|
|
pageSize: limit,
|
|
|
|
|
totalPages: Math.ceil(count / limit)
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '获取物资列表失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取单个物资详情
|
|
|
|
|
router.get('/:id', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const material = await Material.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!material) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '物资不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: material
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '获取物资详情失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 创建新物资
|
|
|
|
|
router.post('/', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { code, name, category, unit, stockQuantity, warningQuantity, supplier, remark } = req.body;
|
|
|
|
|
|
|
|
|
|
// 检查物资编号是否已存在
|
|
|
|
|
const existingMaterial = await Material.findOne({ where: { code } });
|
|
|
|
|
if (existingMaterial) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
code: 400,
|
|
|
|
|
message: '物资编号已存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const material = await Material.create({
|
|
|
|
|
code,
|
|
|
|
|
name,
|
|
|
|
|
category,
|
|
|
|
|
unit,
|
|
|
|
|
stockQuantity,
|
|
|
|
|
warningQuantity,
|
|
|
|
|
supplier,
|
|
|
|
|
remark
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '创建物资成功',
|
|
|
|
|
data: material
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '创建物资失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 更新物资信息
|
|
|
|
|
router.put('/:id', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { code, name, category, unit, stockQuantity, warningQuantity, supplier, remark } = req.body;
|
|
|
|
|
|
|
|
|
|
const material = await Material.findByPk(id);
|
|
|
|
|
if (!material) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '物资不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查物资编号是否已存在(排除当前物资)
|
|
|
|
|
if (code && code !== material.code) {
|
|
|
|
|
const existingMaterial = await Material.findOne({ where: { code } });
|
|
|
|
|
if (existingMaterial) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
code: 400,
|
|
|
|
|
message: '物资编号已存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await material.update({
|
|
|
|
|
code,
|
|
|
|
|
name,
|
|
|
|
|
category,
|
|
|
|
|
unit,
|
|
|
|
|
stockQuantity,
|
|
|
|
|
warningQuantity,
|
|
|
|
|
supplier,
|
|
|
|
|
remark
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '更新物资成功',
|
|
|
|
|
data: material
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '更新物资失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 删除物资
|
|
|
|
|
router.delete('/:id', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const material = await Material.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!material) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '物资不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await material.destroy();
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '删除物资成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '删除物资失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 物资入库
|
|
|
|
|
router.post('/in', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { materialId, quantity, operator, remark } = req.body;
|
|
|
|
|
|
|
|
|
|
// 开始事务
|
|
|
|
|
const transaction = await Material.sequelize.transaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 查找物资
|
|
|
|
|
const material = await Material.findByPk(materialId, { transaction });
|
|
|
|
|
if (!material) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '物资不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新库存
|
|
|
|
|
material.stockQuantity += parseInt(quantity);
|
|
|
|
|
await material.save({ transaction });
|
|
|
|
|
|
|
|
|
|
// 记录入库记录
|
|
|
|
|
await WarehouseTransaction.create({
|
|
|
|
|
materialId,
|
|
|
|
|
type: 'in',
|
|
|
|
|
quantity,
|
|
|
|
|
operator,
|
|
|
|
|
remark
|
|
|
|
|
}, { transaction });
|
|
|
|
|
|
|
|
|
|
// 提交事务
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '入库成功',
|
|
|
|
|
data: {
|
|
|
|
|
materialId,
|
|
|
|
|
quantity,
|
|
|
|
|
newStock: material.stockQuantity
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// 回滚事务
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '入库失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 物资出库
|
|
|
|
|
router.post('/out', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { materialId, quantity, operator, remark } = req.body;
|
|
|
|
|
|
|
|
|
|
// 开始事务
|
|
|
|
|
const transaction = await Material.sequelize.transaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 查找物资
|
|
|
|
|
const material = await Material.findByPk(materialId, { transaction });
|
|
|
|
|
if (!material) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '物资不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查库存是否足够
|
|
|
|
|
if (material.stockQuantity < parseInt(quantity)) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
code: 400,
|
|
|
|
|
message: '库存不足'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新库存
|
|
|
|
|
material.stockQuantity -= parseInt(quantity);
|
|
|
|
|
await material.save({ transaction });
|
|
|
|
|
|
|
|
|
|
// 记录出库记录
|
|
|
|
|
await WarehouseTransaction.create({
|
|
|
|
|
materialId,
|
|
|
|
|
type: 'out',
|
|
|
|
|
quantity,
|
|
|
|
|
operator,
|
|
|
|
|
remark
|
|
|
|
|
}, { transaction });
|
|
|
|
|
|
|
|
|
|
// 提交事务
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '出库成功',
|
|
|
|
|
data: {
|
|
|
|
|
materialId,
|
|
|
|
|
quantity,
|
|
|
|
|
newStock: material.stockQuantity
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// 回滚事务
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '出库失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取库存统计信息
|
|
|
|
|
router.get('/stats', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
// 统计总类别数
|
|
|
|
|
const totalCategories = await Material.count({
|
|
|
|
|
distinct: true,
|
|
|
|
|
col: 'category'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 统计库存总量
|
|
|
|
|
const totalQuantityResult = await Material.sum('stockQuantity');
|
|
|
|
|
const totalQuantity = totalQuantityResult || 0;
|
|
|
|
|
|
|
|
|
|
// 统计低库存物资数
|
|
|
|
|
const lowStockCount = await Material.count({
|
|
|
|
|
where: {
|
|
|
|
|
status: 'low'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 统计缺货物资数
|
|
|
|
|
const outOfStockCount = await Material.count({
|
|
|
|
|
where: {
|
|
|
|
|
status: 'out'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 统计各类别物资数量
|
|
|
|
|
const categoryStats = await Material.findAll({
|
|
|
|
|
attributes: [
|
|
|
|
|
'category',
|
|
|
|
|
[Material.sequelize.fn('COUNT', Material.sequelize.col('id')), 'count'],
|
|
|
|
|
[Material.sequelize.fn('SUM', Material.sequelize.col('stockQuantity')), 'totalQuantity']
|
|
|
|
|
],
|
|
|
|
|
group: ['category'],
|
|
|
|
|
raw: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: {
|
|
|
|
|
totalCategories,
|
|
|
|
|
totalQuantity,
|
|
|
|
|
lowStockCount,
|
|
|
|
|
outOfStockCount,
|
|
|
|
|
categoryStats
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '获取统计信息失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-09-17 18:04:28 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|