feat(consumable): 解耦耗材日志与耗材关联并支持耗材删除后日志保留
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
const { sequelize } = require('../db');
|
const { sequelize } = require('../db');
|
||||||
const Consumable = require('./Consumable');
|
|
||||||
|
|
||||||
const ConsumableLog = sequelize.define('ConsumableLog', {
|
const ConsumableLog = sequelize.define('ConsumableLog', {
|
||||||
id: {
|
id: {
|
||||||
@@ -78,6 +77,16 @@ const ConsumableLog = sequelize.define('ConsumableLog', {
|
|||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
comment: '修改原因'
|
comment: '修改原因'
|
||||||
|
},
|
||||||
|
isConsumableDeleted: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
comment: '关联耗材是否已被删除'
|
||||||
|
},
|
||||||
|
consumableSnapshot: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
comment: '耗材快照信息(分类、单位、供应商等),用于耗材删除后追溯'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
tableName: 'consumable_logs',
|
tableName: 'consumable_logs',
|
||||||
@@ -89,13 +98,9 @@ const ConsumableLog = sequelize.define('ConsumableLog', {
|
|||||||
{ fields: ['createdAt'] },
|
{ fields: ['createdAt'] },
|
||||||
{ fields: ['consumableId', 'createdAt'] },
|
{ fields: ['consumableId', 'createdAt'] },
|
||||||
{ fields: ['originalLogId'] },
|
{ fields: ['originalLogId'] },
|
||||||
{ fields: ['isEditable'] }
|
{ fields: ['isEditable'] },
|
||||||
|
{ fields: ['isConsumableDeleted'] }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
ConsumableLog.belongsTo(Consumable, {
|
|
||||||
foreignKey: 'consumableId',
|
|
||||||
onDelete: 'CASCADE'
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = ConsumableLog;
|
module.exports = ConsumableLog;
|
||||||
|
|||||||
@@ -70,7 +70,16 @@ router.post('/', async (req, res) => {
|
|||||||
currentStock: consumable.currentStock,
|
currentStock: consumable.currentStock,
|
||||||
operator: req.body.operator || req.body.operatorName || '系统',
|
operator: req.body.operator || req.body.operatorName || '系统',
|
||||||
reason: '新建耗材',
|
reason: '新建耗材',
|
||||||
notes: req.body.description || ''
|
notes: req.body.description || '',
|
||||||
|
consumableSnapshot: {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location,
|
||||||
|
minStock: consumable.minStock,
|
||||||
|
maxStock: consumable.maxStock
|
||||||
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
@@ -138,7 +147,16 @@ router.post('/import', async (req, res) => {
|
|||||||
currentStock: consumable.currentStock,
|
currentStock: consumable.currentStock,
|
||||||
operator,
|
operator,
|
||||||
reason: '批量导入',
|
reason: '批量导入',
|
||||||
notes: ''
|
notes: '',
|
||||||
|
consumableSnapshot: {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location,
|
||||||
|
minStock: consumable.minStock,
|
||||||
|
maxStock: consumable.maxStock
|
||||||
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
results.success++;
|
results.success++;
|
||||||
@@ -337,7 +355,14 @@ router.post('/quick-inout', async (req, res) => {
|
|||||||
operator,
|
operator,
|
||||||
reason,
|
reason,
|
||||||
notes,
|
notes,
|
||||||
isEditable: false
|
isEditable: false,
|
||||||
|
consumableSnapshot: {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location
|
||||||
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
@@ -432,7 +457,14 @@ router.post('/inout', async (req, res) => {
|
|||||||
operator,
|
operator,
|
||||||
reason,
|
reason,
|
||||||
notes,
|
notes,
|
||||||
isEditable: false
|
isEditable: false,
|
||||||
|
consumableSnapshot: {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location
|
||||||
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
@@ -515,7 +547,6 @@ router.post('/adjust', async (req, res) => {
|
|||||||
|
|
||||||
const changeQuantity = newStock - previousStock;
|
const changeQuantity = newStock - previousStock;
|
||||||
|
|
||||||
// 系统生成的调整记录不可编辑
|
|
||||||
await ConsumableLog.create({
|
await ConsumableLog.create({
|
||||||
consumableId,
|
consumableId,
|
||||||
consumableName: consumable.name,
|
consumableName: consumable.name,
|
||||||
@@ -526,7 +557,14 @@ router.post('/adjust', async (req, res) => {
|
|||||||
operator,
|
operator,
|
||||||
reason: reason || (adjustType === 'set' ? '库存调整为 ' + newStock : reason),
|
reason: reason || (adjustType === 'set' ? '库存调整为 ' + newStock : reason),
|
||||||
notes: adjustType === 'set' ? `库存从 ${previousStock} 调整为 ${newStock}` : notes,
|
notes: adjustType === 'set' ? `库存从 ${previousStock} 调整为 ${newStock}` : notes,
|
||||||
isEditable: false
|
isEditable: false,
|
||||||
|
consumableSnapshot: {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location
|
||||||
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
@@ -618,7 +656,7 @@ router.get('/logs/export', async (req, res) => {
|
|||||||
order: [['createdAt', 'DESC']]
|
order: [['createdAt', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|
||||||
const csvHeader = 'ID,耗材ID,耗材名称,操作类型,变动数量,操作前库存,操作后库存,操作人,原因,备注,创建时间,更新时间\n';
|
const csvHeader = 'ID,耗材ID,耗材名称,操作类型,变动数量,操作前库存,操作后库存,操作人,原因,备注,耗材状态,分类,单位,单价,创建时间,更新时间\n';
|
||||||
const csvRows = logs.map(log => {
|
const csvRows = logs.map(log => {
|
||||||
const operationTypeMap = {
|
const operationTypeMap = {
|
||||||
'in': '入库',
|
'in': '入库',
|
||||||
@@ -629,6 +667,7 @@ router.get('/logs/export', async (req, res) => {
|
|||||||
'adjust': '调整',
|
'adjust': '调整',
|
||||||
'import': '导入'
|
'import': '导入'
|
||||||
};
|
};
|
||||||
|
const snapshot = log.consumableSnapshot || {};
|
||||||
return [
|
return [
|
||||||
log.id,
|
log.id,
|
||||||
log.consumableId,
|
log.consumableId,
|
||||||
@@ -640,6 +679,10 @@ router.get('/logs/export', async (req, res) => {
|
|||||||
log.operator,
|
log.operator,
|
||||||
log.reason || '',
|
log.reason || '',
|
||||||
log.notes || '',
|
log.notes || '',
|
||||||
|
log.isConsumableDeleted ? '已删除' : '正常',
|
||||||
|
snapshot.category || '',
|
||||||
|
snapshot.unit || '',
|
||||||
|
snapshot.unitPrice || '',
|
||||||
dayjs(log.createdAt).format('YYYY-MM-DD HH:mm:ss'),
|
dayjs(log.createdAt).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
dayjs(log.updatedAt).format('YYYY-MM-DD HH:mm:ss')
|
dayjs(log.updatedAt).format('YYYY-MM-DD HH:mm:ss')
|
||||||
].map(v => `"${String(v).replace(/"/g, '""')}"`).join(',');
|
].map(v => `"${String(v).replace(/"/g, '""')}"`).join(',');
|
||||||
@@ -754,7 +797,14 @@ router.put('/:id', async (req, res) => {
|
|||||||
currentStock: consumable.currentStock,
|
currentStock: consumable.currentStock,
|
||||||
operator: req.body.operator || req.body.operatorName || '系统',
|
operator: req.body.operator || req.body.operatorName || '系统',
|
||||||
reason: '信息更新',
|
reason: '信息更新',
|
||||||
notes: `更新字段: ${Object.keys(req.body).join(', ')}`
|
notes: `更新字段: ${Object.keys(req.body).join(', ')}`,
|
||||||
|
consumableSnapshot: {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location
|
||||||
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
@@ -777,17 +827,51 @@ router.delete('/:id', async (req, res) => {
|
|||||||
const consumableId = consumable.consumableId;
|
const consumableId = consumable.consumableId;
|
||||||
const consumableName = consumable.name;
|
const consumableName = consumable.name;
|
||||||
const currentStock = consumable.currentStock;
|
const currentStock = consumable.currentStock;
|
||||||
|
const operator = req.body.operator || req.query.operator || '系统';
|
||||||
|
|
||||||
|
const consumableSnapshot = {
|
||||||
|
category: consumable.category,
|
||||||
|
unit: consumable.unit,
|
||||||
|
unitPrice: consumable.unitPrice,
|
||||||
|
supplier: consumable.supplier,
|
||||||
|
location: consumable.location,
|
||||||
|
description: consumable.description,
|
||||||
|
minStock: consumable.minStock,
|
||||||
|
maxStock: consumable.maxStock,
|
||||||
|
status: consumable.status
|
||||||
|
};
|
||||||
|
|
||||||
|
await ConsumableLog.create({
|
||||||
|
consumableId,
|
||||||
|
consumableName,
|
||||||
|
operationType: 'delete',
|
||||||
|
quantity: -currentStock,
|
||||||
|
previousStock: currentStock,
|
||||||
|
currentStock: 0,
|
||||||
|
operator,
|
||||||
|
reason: '删除耗材',
|
||||||
|
notes: `删除耗材:${consumableName},删除时库存为 ${currentStock}`,
|
||||||
|
isEditable: false,
|
||||||
|
isConsumableDeleted: true,
|
||||||
|
consumableSnapshot
|
||||||
|
}, { transaction });
|
||||||
|
|
||||||
|
await ConsumableLog.update(
|
||||||
|
{
|
||||||
|
isConsumableDeleted: true,
|
||||||
|
consumableSnapshot
|
||||||
|
},
|
||||||
|
{
|
||||||
|
where: { consumableId },
|
||||||
|
transaction
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
await ConsumableRecord.destroy({
|
await ConsumableRecord.destroy({
|
||||||
where: { consumableId },
|
where: { consumableId },
|
||||||
transaction
|
transaction
|
||||||
});
|
});
|
||||||
|
|
||||||
await ConsumableLog.destroy({
|
|
||||||
where: { consumableId },
|
|
||||||
transaction
|
|
||||||
});
|
|
||||||
|
|
||||||
await consumable.destroy({ transaction });
|
await consumable.destroy({ transaction });
|
||||||
|
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* 耗材操作日志解耦迁移脚本
|
||||||
|
* 添加 isConsumableDeleted 和 consumableSnapshot 字段
|
||||||
|
* 实现耗材删除后日志仍可保留
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { sequelize, DB_TYPE } = require('../db');
|
||||||
|
const Consumable = require('../models/Consumable');
|
||||||
|
const ConsumableLog = require('../models/ConsumableLog');
|
||||||
|
|
||||||
|
async function migrate() {
|
||||||
|
try {
|
||||||
|
console.log('开始迁移耗材操作日志解耦...');
|
||||||
|
console.log(`数据库类型: ${DB_TYPE}`);
|
||||||
|
|
||||||
|
const isMySQL = DB_TYPE === 'mysql';
|
||||||
|
|
||||||
|
if (isMySQL) {
|
||||||
|
await migrateMySQL();
|
||||||
|
} else {
|
||||||
|
await migrateSQLite();
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateExistingLogs();
|
||||||
|
|
||||||
|
console.log('\n迁移完成!耗材删除后日志将被保留。');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('迁移失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function migrateMySQL() {
|
||||||
|
console.log('\n执行 MySQL 迁移...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelize.query(`
|
||||||
|
ALTER TABLE consumable_logs
|
||||||
|
ADD COLUMN isConsumableDeleted TINYINT(1) DEFAULT 0 COMMENT '关联耗材是否已被删除'
|
||||||
|
`);
|
||||||
|
console.log('✓ 添加 isConsumableDeleted 字段成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('Duplicate column')) {
|
||||||
|
console.log('✓ isConsumableDeleted 字段已存在');
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelize.query(`
|
||||||
|
ALTER TABLE consumable_logs
|
||||||
|
ADD COLUMN consumableSnapshot JSON DEFAULT NULL COMMENT '耗材快照信息'
|
||||||
|
`);
|
||||||
|
console.log('✓ 添加 consumableSnapshot 字段成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('Duplicate column')) {
|
||||||
|
console.log('✓ consumableSnapshot 字段已存在');
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelize.query(`
|
||||||
|
CREATE INDEX idx_consumable_logs_is_deleted ON consumable_logs(isConsumableDeleted)
|
||||||
|
`);
|
||||||
|
console.log('✓ 创建 isConsumableDeleted 索引成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('Duplicate key name')) {
|
||||||
|
console.log('✓ isConsumableDeleted 索引已存在');
|
||||||
|
} else {
|
||||||
|
console.log('! isConsumableDeleted 索引创建失败:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function migrateSQLite() {
|
||||||
|
console.log('\n执行 SQLite 迁移...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelize.query(`
|
||||||
|
ALTER TABLE consumable_logs ADD COLUMN isConsumableDeleted BOOLEAN DEFAULT 0
|
||||||
|
`);
|
||||||
|
console.log('✓ 添加 isConsumableDeleted 字段成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('duplicate column name')) {
|
||||||
|
console.log('✓ isConsumableDeleted 字段已存在');
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelize.query(`
|
||||||
|
ALTER TABLE consumable_logs ADD COLUMN consumableSnapshot TEXT
|
||||||
|
`);
|
||||||
|
console.log('✓ 添加 consumableSnapshot 字段成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('duplicate column name')) {
|
||||||
|
console.log('✓ consumableSnapshot 字段已存在');
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelize.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_consumable_logs_is_deleted ON consumable_logs(isConsumableDeleted)
|
||||||
|
`);
|
||||||
|
console.log('✓ 创建 isConsumableDeleted 索引成功');
|
||||||
|
} catch (err) {
|
||||||
|
console.log('! isConsumableDeleted 索引创建失败:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateExistingLogs() {
|
||||||
|
console.log('\n更新现有日志数据...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const consumables = await Consumable.findAll({
|
||||||
|
attributes: ['consumableId', 'category', 'unit', 'unitPrice', 'supplier', 'location', 'minStock', 'maxStock', 'status']
|
||||||
|
});
|
||||||
|
|
||||||
|
const consumableMap = new Map();
|
||||||
|
consumables.forEach(c => {
|
||||||
|
consumableMap.set(c.consumableId, {
|
||||||
|
category: c.category,
|
||||||
|
unit: c.unit,
|
||||||
|
unitPrice: c.unitPrice,
|
||||||
|
supplier: c.supplier,
|
||||||
|
location: c.location,
|
||||||
|
minStock: c.minStock,
|
||||||
|
maxStock: c.maxStock,
|
||||||
|
status: c.status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const logs = await ConsumableLog.findAll({
|
||||||
|
where: {
|
||||||
|
consumableSnapshot: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedCount = 0;
|
||||||
|
for (const log of logs) {
|
||||||
|
const snapshot = consumableMap.get(log.consumableId);
|
||||||
|
if (snapshot) {
|
||||||
|
await log.update({ consumableSnapshot: snapshot });
|
||||||
|
updatedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✓ 更新了 ${updatedCount} 条日志的快照信息`);
|
||||||
|
|
||||||
|
const deletedLogsCount = await ConsumableLog.count({
|
||||||
|
where: { operationType: 'delete' }
|
||||||
|
});
|
||||||
|
console.log(`✓ 当前有 ${deletedLogsCount} 条删除类型日志`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('! 更新现有日志数据时出错:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate();
|
||||||
@@ -121,13 +121,39 @@ function ConsumableLogs() {
|
|||||||
dataIndex: 'consumableId',
|
dataIndex: 'consumableId',
|
||||||
key: 'consumableId',
|
key: 'consumableId',
|
||||||
width: 150,
|
width: 150,
|
||||||
render: value => <code>{value}</code>,
|
render: (value, record) => (
|
||||||
|
<Space>
|
||||||
|
<code>{value}</code>
|
||||||
|
{record.isConsumableDeleted && (
|
||||||
|
<Tooltip title="该耗材已被删除">
|
||||||
|
<Tag color="red">已删除</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '耗材名称',
|
title: '耗材名称',
|
||||||
dataIndex: 'consumableName',
|
dataIndex: 'consumableName',
|
||||||
key: 'consumableName',
|
key: 'consumableName',
|
||||||
width: 150,
|
width: 150,
|
||||||
|
render: (value, record) => (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
record.consumableSnapshot ? (
|
||||||
|
<div>
|
||||||
|
<div>分类: {record.consumableSnapshot.category || '-'}</div>
|
||||||
|
<div>单位: {record.consumableSnapshot.unit || '-'}</div>
|
||||||
|
<div>单价: {record.consumableSnapshot.unitPrice || '-'}</div>
|
||||||
|
<div>供应商: {record.consumableSnapshot.supplier || '-'}</div>
|
||||||
|
<div>位置: {record.consumableSnapshot.location || '-'}</div>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>{value}</span>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作类型',
|
title: '操作类型',
|
||||||
@@ -277,6 +303,10 @@ function ConsumableLogs() {
|
|||||||
操作人: log.operator,
|
操作人: log.operator,
|
||||||
原因: log.reason || '',
|
原因: log.reason || '',
|
||||||
备注: log.notes || '',
|
备注: log.notes || '',
|
||||||
|
耗材状态: log.isConsumableDeleted ? '已删除' : '正常',
|
||||||
|
分类: log.consumableSnapshot?.category || '',
|
||||||
|
单位: log.consumableSnapshot?.unit || '',
|
||||||
|
单价: log.consumableSnapshot?.unitPrice || '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const ws = XLSX.utils.json_to_sheet(exportData);
|
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||||
@@ -646,6 +676,7 @@ function ConsumableLogs() {
|
|||||||
<div style={{ fontSize: 12, color: '#666' }}>
|
<div style={{ fontSize: 12, color: '#666' }}>
|
||||||
<p>
|
<p>
|
||||||
<strong>耗材:</strong> {item.consumableName} ({item.consumableId})
|
<strong>耗材:</strong> {item.consumableName} ({item.consumableId})
|
||||||
|
{item.isConsumableDeleted && <Tag color="red" style={{ marginLeft: 8 }}>已删除</Tag>}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>操作人:</strong> {item.operator}
|
<strong>操作人:</strong> {item.operator}
|
||||||
@@ -660,6 +691,13 @@ function ConsumableLogs() {
|
|||||||
<strong>备注:</strong> {item.notes}
|
<strong>备注:</strong> {item.notes}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{item.consumableSnapshot && (
|
||||||
|
<p>
|
||||||
|
<strong>快照信息:</strong> 分类:{item.consumableSnapshot.category || '-'} |
|
||||||
|
单位:{item.consumableSnapshot.unit || '-'} |
|
||||||
|
单价:{item.consumableSnapshot.unitPrice || '-'}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{item.modifiedBy && (
|
{item.modifiedBy && (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
Reference in New Issue
Block a user