189 lines
4.8 KiB
JavaScript
189 lines
4.8 KiB
JavaScript
const express = require('express');
|
|||
|
|
const router = express.Router();
|
||
|
|
const { Op } = require('sequelize');
|
||
|
|
const { sequelize } = require('../db');
|
||
|
|
const Consumable = require('../models/Consumable');
|
||
|
|
const ConsumableRecord = require('../models/ConsumableRecord');
|
||
|
|
const ConsumableLog = require('../models/ConsumableLog');
|
||
|
|
|
||
|
|
router.get('/', async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { consumableId, type, startDate, endDate, page = 1, pageSize = 10 } = req.query;
|
||
|
|
const offset = (page - 1) * pageSize;
|
||
|
|
|
||
|
|
const where = {};
|
||
|
|
|
||
|
|
if (consumableId) {
|
||
|
|
where.consumableId = consumableId;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type && type !== 'all') {
|
||
|
|
where.type = type;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (startDate && endDate) {
|
||
|
|
where.createdAt = {
|
||
|
|
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||
|
|
};
|
||
|
|
} else if (startDate) {
|
||
|
|
where.createdAt = { [Op.gte]: new Date(startDate) };
|
||
|
|
} else if (endDate) {
|
||
|
|
where.createdAt = { [Op.lte]: new Date(endDate) };
|
||
|
|
}
|
||
|
|
|
||
|
|
const { count, rows } = await ConsumableRecord.findAndCountAll({
|
||
|
|
where,
|
||
|
|
include: [
|
||
|
|
{ model: Consumable, attributes: ['name', 'category', 'unit'] }
|
||
|
|
],
|
||
|
|
offset,
|
||
|
|
limit: parseInt(pageSize),
|
||
|
|
order: [['createdAt', 'DESC']]
|
||
|
|
});
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
total: count,
|
||
|
|
records: rows,
|
||
|
|
page: parseInt(page),
|
||
|
|
pageSize: parseInt(pageSize)
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
res.status(500).json({ error: error.message });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/', async (req, res) => {
|
||
|
|
const transaction = await sequelize.transaction();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { consumableId, type, quantity, operator, reason, recipient, notes } = req.body;
|
||
|
|
|
||
|
|
const consumable = await Consumable.findByPk(consumableId);
|
||
|
|
if (!consumable) {
|
||
|
|
await transaction.rollback();
|
||
|
|
return res.status(404).json({ error: '耗材不存在' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const previousStock = consumable.currentStock;
|
||
|
|
let newStock;
|
||
|
|
|
||
|
|
if (type === 'in') {
|
||
|
|
newStock = previousStock + quantity;
|
||
|
|
} else if (type === 'out') {
|
||
|
|
if (previousStock < quantity) {
|
||
|
|
await transaction.rollback();
|
||
|
|
return res.status(400).json({ error: '库存不足' });
|
||
|
|
}
|
||
|
|
newStock = previousStock - quantity;
|
||
|
|
} else {
|
||
|
|
await transaction.rollback();
|
||
|
|
return res.status(400).json({ error: '操作类型无效' });
|
||
|
|
}
|
||
|
|
|
||
|
|
await consumable.update({ currentStock: newStock }, { transaction });
|
||
|
|
|
||
|
|
const record = await ConsumableRecord.create({
|
||
|
|
consumableId,
|
||
|
|
type,
|
||
|
|
quantity,
|
||
|
|
previousStock,
|
||
|
|
currentStock: newStock,
|
||
|
|
operator,
|
||
|
|
reason,
|
||
|
|
recipient,
|
||
|
|
notes
|
||
|
|
}, { transaction });
|
||
|
|
|
||
|
|
await ConsumableLog.create({
|
||
|
|
consumableId,
|
||
|
|
consumableName: consumable.name,
|
||
|
|
operationType: type,
|
||
|
|
quantity: type === 'in' ? parseFloat(quantity) : -parseFloat(quantity),
|
||
|
|
previousStock,
|
||
|
|
currentStock: newStock,
|
||
|
|
operator,
|
||
|
|
reason,
|
||
|
|
notes
|
||
|
|
}, { transaction });
|
||
|
|
|
||
|
|
await transaction.commit();
|
||
|
|
|
||
|
|
res.status(201).json({
|
||
|
|
record,
|
||
|
|
consumable: {
|
||
|
|
previousStock,
|
||
|
|
currentStock: newStock
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
await transaction.rollback();
|
||
|
|
res.status(400).json({ error: error.message });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/statistics', async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { startDate, endDate } = req.query;
|
||
|
|
|
||
|
|
const dateWhere = {};
|
||
|
|
if (startDate && endDate) {
|
||
|
|
dateWhere.createdAt = {
|
||
|
|
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const inCount = await ConsumableRecord.count({
|
||
|
|
where: { ...dateWhere, type: 'in' }
|
||
|
|
});
|
||
|
|
|
||
|
|
const outCount = await ConsumableRecord.count({
|
||
|
|
where: { ...dateWhere, type: 'out' }
|
||
|
|
});
|
||
|
|
|
||
|
|
const inQuantity = await ConsumableRecord.sum('quantity', {
|
||
|
|
where: { ...dateWhere, type: 'in' }
|
||
|
|
}) || 0;
|
||
|
|
|
||
|
|
const outQuantity = await ConsumableRecord.sum('quantity', {
|
||
|
|
where: { ...dateWhere, type: 'out' }
|
||
|
|
}) || 0;
|
||
|
|
|
||
|
|
const byType = await ConsumableRecord.findAll({
|
||
|
|
where: dateWhere,
|
||
|
|
attributes: [
|
||
|
|
'type',
|
||
|
|
[sequelize.fn('SUM', sequelize.col('quantity')), 'totalQuantity'],
|
||
|
|
[sequelize.fn('COUNT', '*'), 'count']
|
||
|
|
],
|
||
|
|
group: ['type']
|
||
|
|
});
|
||
|
|
|
||
|
|
const recentRecords = await ConsumableRecord.findAll({
|
||
|
|
where: dateWhere,
|
||
|
|
include: [
|
||
|
|
{ model: Consumable, attributes: ['name', 'category'] }
|
||
|
|
],
|
||
|
|
order: [['createdAt', 'DESC']],
|
||
|
|
limit: 10
|
||
|
|
});
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
inCount,
|
||
|
|
outCount,
|
||
|
|
inQuantity,
|
||
|
|
outQuantity,
|
||
|
|
netQuantity: inQuantity - outQuantity,
|
||
|
|
byType: byType.map(item => ({
|
||
|
|
type: item.type,
|
||
|
|
totalQuantity: item.dataValues.totalQuantity,
|
||
|
|
count: item.dataValues.count
|
||
|
|
})),
|
||
|
|
recentRecords
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
res.status(500).json({ error: error.message });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
module.exports = router;
|