/** * 采购申请路由 * 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md * 章节:二、采购申请页面改造 * * 简化后的采购申请: * - 不再录入供应商(询价前未知) * - 不再录入商品明细(询价后确定) * - 仅填写需求描述和预计金额 * - 审批通过后自动生成订单草稿 */ const express = require('express'); const db = require('../db'); const { authenticate, requireAdmin } = require('../middleware/auth'); const router = express.Router(); /** * 获取采购申请列表 * 遵循设计方案:支持按项目、状态筛选 */ router.get('/', async (req, res) => { try { const { project_id, status } = req.query; let query = ` SELECT pr.*, p.name as project_name FROM purchase_requests pr LEFT JOIN projects p ON pr.project_id = p.id `; const params = []; if (project_id) { query += ' WHERE pr.project_id = $1'; params.push(project_id); } if (status) { query += project_id ? ' AND pr.status = $1' : ' WHERE pr.status = $2'; params.push(status); } query += ' ORDER BY pr.created_at DESC'; const result = await db.query(query, params); const data = result.rows.map(row => ({ ...row, request_code: row.request_code })); res.json({ success: true, data: data, count: data.length }); } catch (error) { console.error('获取采购申请列表失败:', error); res.status(500).json({ success: false, message: '获取采购申请列表失败', error: process.env.NODE_ENV === 'development' ? error.message : '操作失败' }); } }); /** * 获取采购申请详情 * 遵循设计方案:简化后不再返回商品明细 */ router.get('/:id', async (req, res) => { try { const { id } = req.params; const requestResult = await db.query(` SELECT pr.*, p.name as project_name FROM purchase_requests pr LEFT JOIN projects p ON pr.project_id = p.id WHERE pr.id = $1 `, [id]); if (requestResult.rows.length === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } const purchaseRequest = requestResult.rows[0]; // request_code is already in the table, no need to alias from code res.json({ success: true, data: purchaseRequest }); } catch (error) { console.error('获取采购申请详情失败:', error); res.status(500).json({ success: false, message: '获取采购申请详情失败', error: process.env.NODE_ENV === 'development' ? error.message : '操作失败' }); } }); /** * 创建采购申请 * 遵循设计方案:二、采购申请页面改造 * 简化后的字段: * - purchase_type: 采购类型(项目采购/库存采购) * - project_id: 关联项目(项目采购必填) * - brief_description: 事由描述 * - total_amount: 预计金额 * - currency: 币种 * - expected_date: 需求日期 * - remark: 备注 * - attachments: 附件 */ router.post('/', async (req, res) => { try { const { project_id, total_amount, currency, purchase_type, remark } = req.body; const date = new Date(); const requestCode = `PUR-${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}-${String(Math.floor(Math.random() * 1000)).padStart(3, '0')}`; const result = await db.query(` INSERT INTO purchase_requests (request_code, project_id, purchase_type, total_amount, currency, status, remarks, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id`, [requestCode, project_id, purchase_type || 'inventory', total_amount || 0, currency || 'CNY', 'pending', remark]); res.json({ success: true, message: '采购申请创建成功', data: { id: (result.rows[0]?.id || result.rows?.[0]?.id), request_code: requestCode } }); } catch (error) { console.error('创建采购申请失败:', error); res.status(500).json({ success: false, message: '创建采购申请失败', error: process.env.NODE_ENV === 'development' ? error.message : '操作失败' }); } }); /** * 更新采购申请 * 遵循设计方案:简化后不再处理商品明细 */ router.put('/:id', async (req, res) => { try { const { id } = req.params; const { project_id, total_amount, currency, purchase_type, remark } = req.body; const result = await db.query(` UPDATE purchase_requests SET project_id = $1, total_amount = $2, currency = $3, purchase_type = $4, remarks = $5, updated_at = CURRENT_TIMESTAMP WHERE id = $6 `, [project_id, total_amount, currency || 'CNY', purchase_type || 'inventory', remark, id]); if (result.rowCount === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '采购申请更新成功' }); } catch (error) { console.error('更新采购申请失败:', error); res.status(500).json({ success: false, message: '更新采购申请失败', error: process.env.NODE_ENV === 'development' ? error.message : '操作失败' }); } }); /** * 删除采购申请 */ router.delete('/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('DELETE FROM purchase_requests WHERE id = $1', [id]); if (result.rowCount === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '采购申请删除成功' }); } catch (error) { console.error('删除采购申请失败:', error); res.status(500).json({ success: false, message: '删除采购申请失败', error: process.env.NODE_ENV === 'development' ? error.message : '操作失败' }); } }); /** * 提交审批 * 遵循设计方案:状态从 pending_edit 变为 pending */ router.post('/:id/submit', async (req, res) => { try { const { id } = req.params; const result = await db.query( 'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2', ['pending', id] ); if (result.rowCount === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '提交成功' }); } catch (error) { console.error('提交采购申请失败:', error); res.status(500).json({ success: false, message: '提交采购申请失败' }); } }); /** * 审批通过 * 遵循设计方案:一、流程设计 - 审批通过后自动生成订单草稿 * 章节:1.2 流程节点说明 * - 状态变为 approved * - 自动创建采购订单草稿(purchase_orders表) */ router.post('/:id/approve', async (req, res) => { try { const { id } = req.params; const requestResult = await db.query('SELECT * FROM purchase_requests WHERE id = $1', [id]); if (requestResult.rows.length === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } const purchaseRequest = requestResult.rows[0]; await db.query('BEGIN TRANSACTION'); try { await db.query( 'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2', ['approved', id] ); const orderCode = 'PO' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + String(Math.floor(Math.random() * 10000)).padStart(4, '0'); const orderResult = await db.query(` INSERT INTO purchase_orders (code, purchase_request_id, project_id, estimated_amount, currency, status, created_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id`, [orderCode, id, purchaseRequest.project_id, purchaseRequest.total_amount, purchaseRequest.currency || 'CNY', 'draft', purchaseRequest.created_by || 1]); await db.query('COMMIT'); res.json({ success: true, message: '审批通过成功,已自动生成采购订单草稿', data: { order_id: orderResult.rows[0]?.id, order_code: orderCode } }); } catch (innerError) { await db.query('ROLLBACK'); throw innerError; } } catch (error) { console.error('审批采购申请失败:', error); res.status(500).json({ success: false, message: '审批采购申请失败' }); } }); /** * 驳回 * 遵循设计方案:状态从 pending 变为 pending_edit */ router.post('/:id/reject', async (req, res) => { try { const { id } = req.params; const result = await db.query( 'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2', ['pending', id] ); if (result.rowCount === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '驳回成功' }); } catch (error) { console.error('驳回采购申请失败:', error); res.status(500).json({ success: false, message: '驳回采购申请失败' }); } }); /** * 撤回 * 遵循设计方案:状态从 pending 变为 withdrawn */ router.post('/:id/withdraw', async (req, res) => { try { const { id } = req.params; const result = await db.query( 'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2', ['withdrawn', id] ); if (result.rowCount === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '撤回成功' }); } catch (error) { console.error('撤回采购申请失败:', error); res.status(500).json({ success: false, message: '撤回采购申请失败' }); } }); /** * 执行(已废弃,保留向后兼容) * 遵循设计方案:简化后不再使用此接口 */ router.post('/:id/execute', async (req, res) => { try { const { id } = req.params; await db.query( 'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2', ['executed', id] ); res.json({ success: true, message: '执行成功' }); } catch (error) { console.error('执行采购申请失败:', error); res.status(500).json({ success: false, message: '执行采购申请失败' }); } }); module.exports = router;