feat(耗材管理): 新增创建耗材同时入库功能并优化库存编辑逻辑

添加创建耗材时同时入库的功能,包含入库数量、操作人和原因等字段
强制初始化新建耗材的库存为0和空SN列表
禁止通过编辑接口直接修改库存和SN列表
前端新增同时入库选项及相关表单字段
优化编辑模式下库存信息的展示方式
This commit is contained in:
zhang1106
2026-04-09 17:11:11 +08:00
parent 6a7e52312e
commit 9421e34147
2 changed files with 632 additions and 326 deletions
+131 -10
View File
@@ -122,14 +122,9 @@ router.post('/', async (req, res) => {
const consumableData = {
...req.body,
consumableId: req.body.consumableId || `CON${Date.now()}`,
currentStock: 0, // 强制设为0
snList: [], // 强制设为空数组
};
// SN 列表非空时,取手动填写的库存和 SN 数量中的较大值
if (Array.isArray(consumableData.snList) && consumableData.snList.length > 0) {
consumableData.currentStock = Math.max(
Number(consumableData.currentStock) || 0,
consumableData.snList.length
);
}
const consumable = await Consumable.create(consumableData, { transaction });
await ConsumableLog.create(
@@ -170,6 +165,132 @@ router.post('/', async (req, res) => {
}
});
// 创建耗材并同时入库
router.post('/create-with-inbound', async (req, res) => {
const transaction = await sequelize.transaction();
try {
const {
consumableId,
name,
category,
unit,
currentStock,
minStock,
maxStock,
unitPrice,
supplier,
location,
description,
status,
// 入库相关字段
inboundQuantity,
inboundOperator,
inboundReason,
inboundNotes,
inboundSnList,
} = req.body;
// 创建耗材
const consumable = await Consumable.create({
consumableId: consumableId || `CON${Date.now()}`,
name,
category,
unit: unit || '个',
currentStock: 0,
minStock: minStock || 10,
maxStock: maxStock || 0,
unitPrice: unitPrice || 0,
supplier: supplier || '',
location: location || '',
description: description || '',
status: status || 'active',
snList: [],
}, { transaction });
// 记录创建日志
await ConsumableLog.create({
consumableId: consumable.consumableId,
consumableName: consumable.name,
operationType: 'create',
quantity: 0,
previousStock: 0,
currentStock: 0,
operator: inboundOperator || '系统',
reason: '新建耗材',
notes: description || '',
consumableSnapshot: {
category: consumable.category,
unit: consumable.unit,
unitPrice: consumable.unitPrice,
supplier: consumable.supplier,
location: consumable.location,
minStock: consumable.minStock,
maxStock: consumable.maxStock,
},
}, { transaction });
// 执行入库操作
if (inboundQuantity && inboundQuantity > 0) {
const previousStock = 0;
const newStock = inboundQuantity;
let updatedSnList = [...(inboundSnList || [])];
await Consumable.update({
currentStock: newStock,
snList: updatedSnList,
version: sequelize.literal('version + 1'),
}, {
where: { consumableId: consumable.consumableId },
transaction,
});
await ConsumableRecord.create({
consumableId: consumable.consumableId,
type: 'in',
quantity: inboundQuantity,
previousStock,
currentStock: newStock,
operator: inboundOperator || '系统',
reason: inboundReason || '初始入库',
notes: inboundNotes || '',
snList: updatedSnList,
}, { transaction });
await ConsumableLog.create({
consumableId: consumable.consumableId,
consumableName: consumable.name,
operationType: 'in',
quantity: inboundQuantity,
previousStock,
currentStock: newStock,
operator: inboundOperator || '系统',
reason: inboundReason || '初始入库',
notes: inboundNotes || '',
snList: updatedSnList,
consumableSnapshot: {
category: consumable.category,
unit: consumable.unit,
unitPrice: consumable.unitPrice,
supplier: consumable.supplier,
location: consumable.location,
},
}, { transaction });
}
await transaction.commit();
res.status(201).json(consumable);
} catch (error) {
await transaction.rollback();
console.error('创建耗材并入库错误:', error);
if (error.name === 'SequelizeValidationError') {
const messages = error.errors.map(e => `${e.path}: ${e.message}`).join(', ');
res.status(400).json({ error: `Validation error: ${messages}` });
} else {
res.status(400).json({ error: error.message });
}
}
});
router.post('/import', async (req, res) => {
const transaction = await sequelize.transaction();
try {
@@ -1272,9 +1393,9 @@ router.put('/:id', async (req, res) => {
const oldData = consumable.toJSON();
const updateData = { ...req.body };
if (Array.isArray(updateData.snList)) {
updateData.currentStock = updateData.snList.length;
}
// 禁止通过编辑接口修改库存和SN列表
delete updateData.currentStock;
delete updateData.snList;
await consumable.update(updateData, { transaction });
await ConsumableLog.create(