feat: 添加空闲设备状态支持并优化耗材导入功能

refactor(设备管理): 重构状态筛选和高级搜索界面

perf(机柜管理): 优化U位计算方式为基于设备高度

fix(耗材管理): 修复导入时库存计算不准确的问题

style(耗材统计): 调整表格样式和实时刷新功能

docs: 更新设备状态映射包含空闲状态
This commit is contained in:
zhang1106
2026-03-24 14:54:39 +08:00
parent f82b8202fe
commit 1b898814ed
13 changed files with 1385 additions and 456 deletions
+76 -21
View File
@@ -67,6 +67,9 @@ router.post('/', async (req, res) => {
...req.body,
consumableId: req.body.consumableId || `CON${Date.now()}`
};
if (Array.isArray(consumableData.snList)) {
consumableData.currentStock = consumableData.snList.length;
}
const consumable = await Consumable.create(consumableData, { transaction });
await ConsumableLog.create({
@@ -107,7 +110,7 @@ router.post('/', async (req, res) => {
router.post('/import', async (req, res) => {
const transaction = await sequelize.transaction();
try {
const { items, operator = '系统' } = req.body;
const { items, operator = '系统', mode = 'create' } = req.body;
if (!items || !Array.isArray(items) || items.length === 0) {
await transaction.rollback();
@@ -117,45 +120,93 @@ router.post('/import', async (req, res) => {
const results = {
success: 0,
failed: 0,
errors: []
updated: 0,
skipped: 0,
errors: [],
details: []
};
for (let i = 0; i < items.length; i++) {
const item = items[i];
const rowNumber = i + 1;
try {
let consumableId = item.耗材ID || item.consumableId;
const name = item.名称 || item.name;
const category = item.分类 || item.category;
if (!name || !category) {
results.failed++;
results.errors.push(`${rowNumber} 行: 名称和分类为必填项`);
results.details.push({ row: rowNumber, status: 'failed', error: '名称和分类为必填项' });
continue;
}
let snList = [];
if (item.SN序列号 || item.snList) {
const snStr = item.SN序列号 || item.snList;
if (typeof snStr === 'string') {
snList = snStr.split(/[,;\n]/).map(s => s.trim()).filter(Boolean);
} else if (Array.isArray(snStr)) {
snList = snStr;
}
}
const consumableData = {
consumableId: item.耗材ID || item.consumableId || `CON${Date.now()}${i}`,
name: item.名称 || item.name,
category: item.分类 || item.category,
consumableId: consumableId || `CON${Date.now()}${i}`,
name,
category,
unit: item.单位 || item.unit || '个',
currentStock: parseInt(item.当前库存 || item.currentStock) || 0,
currentStock: snList.length > 0 ? snList.length : (parseInt(item.当前库存 || item.currentStock) || 0),
minStock: parseInt(item.最小库存 || item.minStock) || 10,
maxStock: parseInt(item.最大库存 || item.maxStock) || 100,
maxStock: parseInt(item.最大库存 || item.maxStock) || 0,
unitPrice: parseFloat(item.单价 || item.unitPrice) || 0,
supplier: item.供应商 || item.supplier || '',
location: item.存放位置 || item.location || '',
description: item.描述 || item.description || '',
status: item.状态 || item.status || 'active'
status: item.状态 || item.status || 'active',
snList
};
if (!consumableData.name || !consumableData.category) {
results.failed++;
results.errors.push(`${i + 1} 行: 名称和分类为必填项`);
continue;
let existingConsumable = null;
if (consumableId) {
existingConsumable = await Consumable.findByPk(consumableId, { transaction });
}
const consumable = await Consumable.create(consumableData, { transaction });
let consumable;
let operationType;
let previousStock = 0;
if (existingConsumable) {
if (mode === 'update') {
previousStock = existingConsumable.currentStock;
await existingConsumable.update(consumableData, { transaction });
consumable = existingConsumable;
operationType = 'import_update';
results.updated++;
results.details.push({ row: rowNumber, status: 'updated', consumableId: consumable.consumableId, name: consumable.name });
} else {
results.skipped++;
results.details.push({ row: rowNumber, status: 'skipped', reason: '耗材已存在', consumableId: consumableId });
continue;
}
} else {
consumable = await Consumable.create(consumableData, { transaction });
operationType = 'import';
results.success++;
results.details.push({ row: rowNumber, status: 'created', consumableId: consumable.consumableId, name: consumable.name });
}
await ConsumableLog.create({
consumableId: consumable.consumableId,
consumableName: consumable.name,
operationType: 'import',
operationType,
quantity: consumable.currentStock,
previousStock: 0,
previousStock,
currentStock: consumable.currentStock,
operator,
reason: '批量导入',
notes: '',
notes: existingConsumable ? '更新现有耗材' : '',
consumableSnapshot: {
category: consumable.category,
unit: consumable.unit,
@@ -167,16 +218,16 @@ router.post('/import', async (req, res) => {
}
}, { transaction });
results.success++;
} catch (error) {
results.failed++;
results.errors.push(`${i + 1} 行: ${error.message}`);
results.errors.push(`${rowNumber} 行: ${error.message}`);
results.details.push({ row: rowNumber, status: 'failed', error: error.message });
}
}
await transaction.commit();
res.json({
message: `导入完成,成功 ${results.success} 条,失败 ${results.failed}`,
message: `导入完成,成功 ${results.success} 条,更新 ${results.updated} 条,跳过 ${results.skipped} 条,失败 ${results.failed}`,
results
});
} catch (error) {
@@ -872,9 +923,13 @@ router.put('/:id', async (req, res) => {
await transaction.rollback();
return res.status(404).json({ error: '耗材不存在' });
}
const oldData = consumable.toJSON();
await consumable.update(req.body, { transaction });
const updateData = { ...req.body };
if (Array.isArray(updateData.snList)) {
updateData.currentStock = updateData.snList.length;
}
await consumable.update(updateData, { transaction });
await ConsumableLog.create({
consumableId: consumable.consumableId,