diff --git a/backend/routes/consumables.js b/backend/routes/consumables.js index 5a24a56..72a2237 100644 --- a/backend/routes/consumables.js +++ b/backend/routes/consumables.js @@ -8,6 +8,7 @@ const ConsumableRecord = require('../models/ConsumableRecord'); const ConsumableLog = require('../models/ConsumableLog'); const ConsumableLogArchive = require('../models/ConsumableLogArchive'); const { PAGINATION, RETRY } = require('../config'); +const { generateId } = require('../utils/idGenerator'); router.get('/', async (req, res) => { try { @@ -1045,6 +1046,78 @@ router.get('/logs/export', async (req, res) => { } }); +router.post('/logs', async (req, res) => { + const transaction = await sequelize.transaction(); + try { + const { + consumableId, + consumableName, + operationType, + quantity, + previousStock, + currentStock, + operator, + reason, + notes, + snList, + deviceId, + deviceName, + rackId, + rackName, + roomId, + roomName, + createdAt, + } = req.body; + + if (!consumableName || !operationType) { + await transaction.rollback(); + return res.status(400).json({ error: '缺少必填字段:耗材名称、操作类型' }); + } + + const validOperationTypes = ['in', 'out', 'create', 'update', 'delete', 'adjust', 'import']; + if (!validOperationTypes.includes(operationType)) { + await transaction.rollback(); + return res.status(400).json({ error: '无效的操作类型' }); + } + + const logData = { + consumableId: consumableId || generateId({ prefix: 'LOG' }), + consumableName, + operationType, + quantity: parseInt(quantity) || 0, + previousStock: parseInt(previousStock) || 0, + currentStock: parseInt(currentStock) || 0, + operator: operator || '', + reason: reason || '', + notes: notes || '', + snList: Array.isArray(snList) ? snList : [], + deviceId: deviceId || null, + deviceName: deviceName || null, + rackId: rackId || null, + rackName: rackName || null, + roomId: roomId || null, + roomName: roomName || null, + isEditable: true, + }; + + if (createdAt) { + const parsedDate = new Date(createdAt); + if (!isNaN(parsedDate.getTime())) { + logData.createdAt = parsedDate; + logData.updatedAt = parsedDate; + } + } + + const log = await ConsumableLog.create(logData, { transaction }); + + await transaction.commit(); + res.status(201).json({ message: '日志创建成功', log }); + } catch (error) { + await transaction.rollback(); + res.status(500).json({ error: error.message }); + } +}); + router.post('/logs/import', async (req, res) => { const transaction = await sequelize.transaction(); try { diff --git a/frontend/src/pages/ConsumableLogs.jsx b/frontend/src/pages/ConsumableLogs.jsx index 4ac614a..ef190f3 100644 --- a/frontend/src/pages/ConsumableLogs.jsx +++ b/frontend/src/pages/ConsumableLogs.jsx @@ -34,6 +34,7 @@ import { DownOutlined, EditOutlined, EyeOutlined, + PlusOutlined, } from '@ant-design/icons'; import axios from 'axios'; import CloseButton from '../components/CloseButton'; @@ -79,6 +80,11 @@ function ConsumableLogs() { const [currentArchive, setCurrentArchive] = useState(null); const [archiveLoading, setArchiveLoading] = useState(false); + // 手动添加日志弹窗状态 + const [addModalVisible, setAddModalVisible] = useState(false); + const [addLoading, setAddLoading] = useState(false); + const [addForm] = Form.useForm(); + const fetchLogs = async (page = 1, pageSize = 10, currentFilters = filters) => { try { setLoading(true); @@ -581,6 +587,43 @@ function ConsumableLogs() { } }; + // 手动添加日志 + const handleAddLog = () => { + addForm.resetFields(); + setAddModalVisible(true); + }; + + const handleAddSubmit = async values => { + setAddLoading(true); + try { + const submitData = { + consumableId: values.consumableId, + consumableName: values.consumableName, + operationType: values.operationType, + quantity: values.quantity || 0, + previousStock: values.previousStock || 0, + currentStock: values.currentStock || 0, + operator: values.operator || '', + reason: values.reason || '', + notes: values.notes || '', + }; + + if (values.createdAt) { + submitData.createdAt = values.createdAt.format('YYYY-MM-DD HH:mm:ss'); + } + + await axios.post('/api/consumables/logs', submitData); + + message.success('日志添加成功'); + setAddModalVisible(false); + fetchLogs(1, pagination.pageSize); + } catch (error) { + message.error(error.response?.data?.error || '添加失败'); + } finally { + setAddLoading(false); + } + }; + return (