375 lines
12 KiB
JavaScript
375 lines
12 KiB
JavaScript
/**
|
|
* 采购申请路由
|
|
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.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.code
|
|
}));
|
|
|
|
res.json({
|
|
success: true,
|
|
data: data,
|
|
count: data.length
|
|
});
|
|
} catch (error) {
|
|
console.error('获取采购申请列表失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取采购申请列表失败',
|
|
error: 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 = ?
|
|
`, [id]);
|
|
|
|
if (requestResult.rows.length === 0) {
|
|
return res.status(404).json({ success: false, message: '采购申请不存在' });
|
|
}
|
|
|
|
const purchaseRequest = requestResult.rows[0];
|
|
purchaseRequest.request_code = purchaseRequest.code;
|
|
|
|
if (purchaseRequest.attachments) {
|
|
if (typeof purchaseRequest.attachments === 'string') {
|
|
purchaseRequest.attachments = purchaseRequest.attachments.split(',').map((url) => ({
|
|
url: url,
|
|
name: url.split('/').pop() || '',
|
|
uid: url,
|
|
status: 'done'
|
|
}));
|
|
}
|
|
} else {
|
|
purchaseRequest.attachments = [];
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: purchaseRequest
|
|
});
|
|
} catch (error) {
|
|
console.error('获取采购申请详情失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取采购申请详情失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 创建采购申请
|
|
* 遵循设计方案:二、采购申请页面改造
|
|
* 简化后的字段:
|
|
* - purchase_type: 采购类型(项目采购/库存采购)
|
|
* - project_id: 关联项目(项目采购必填)
|
|
* - brief_description: 事由描述
|
|
* - total_amount: 预计金额
|
|
* - currency: 币种
|
|
* - expected_date: 需求日期
|
|
* - remark: 备注
|
|
* - attachments: 附件
|
|
*/
|
|
router.post('/', async (req, res) => {
|
|
try {
|
|
const {
|
|
project_id, applicant, request_date,
|
|
expense_category, total_amount, currency, remark, attachments,
|
|
purchase_type, brief_description, expected_date
|
|
} = 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
|
|
(code, title, project_id, applicant, request_date, expense_category, total_amount, currency,
|
|
status, purchase_type, brief_description, expected_date, remark, attachments, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
`, [requestCode, brief_description || '采购申请', project_id, applicant || '系统管理员',
|
|
request_date, expense_category || 'material', total_amount || 0, currency || 'CNY',
|
|
'pending_edit', purchase_type || 'inventory', brief_description, expected_date, remark, attachments || '']);
|
|
|
|
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: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 更新采购申请
|
|
* 遵循设计方案:简化后不再处理商品明细
|
|
*/
|
|
router.put('/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const {
|
|
project_id, applicant, request_date,
|
|
expense_category, total_amount, currency, remark, attachments,
|
|
purchase_type, brief_description, expected_date
|
|
} = req.body;
|
|
|
|
const result = await db.query(`
|
|
UPDATE purchase_requests
|
|
SET project_id = ?, applicant = ?, request_date = ?, expense_category = ?,
|
|
total_amount = ?, currency = ?, purchase_type = ?, brief_description = ?,
|
|
expected_date = ?, remark = ?, attachments = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`, [project_id, applicant, request_date, expense_category, total_amount, currency || 'CNY',
|
|
purchase_type || 'inventory', brief_description, expected_date, remark, attachments || '', id]);
|
|
|
|
if (result.changes === 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: 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.changes === 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: 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 = datetime(\'now\') WHERE id = $2',
|
|
['pending', id]
|
|
);
|
|
|
|
if (result.changes === 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: error.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 = datetime(\'now\') 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 (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
`, [orderCode, id, purchaseRequest.project_id, purchaseRequest.total_amount,
|
|
purchaseRequest.currency || 'CNY', 'draft', purchaseRequest.applicant || '系统']);
|
|
|
|
await db.query('COMMIT');
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '审批通过成功,已自动生成采购订单草稿',
|
|
data: { order_id: orderResult.lastID, order_code: orderCode }
|
|
});
|
|
} catch (innerError) {
|
|
await db.query('ROLLBACK');
|
|
throw innerError;
|
|
}
|
|
} catch (error) {
|
|
console.error('审批采购申请失败:', error);
|
|
res.status(500).json({ success: false, message: '审批采购申请失败', error: error.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 = datetime(\'now\') WHERE id = $2',
|
|
['pending_edit', id]
|
|
);
|
|
|
|
if (result.changes === 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: error.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 = datetime(\'now\') WHERE id = $2',
|
|
['withdrawn', id]
|
|
);
|
|
|
|
if (result.changes === 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: error.message });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 执行(已废弃,保留向后兼容)
|
|
* 遵循设计方案:简化后不再使用此接口
|
|
*/
|
|
router.post('/:id/execute', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
await db.query(
|
|
'UPDATE purchase_requests SET status = $1, updated_at = datetime(\'now\') WHERE id = $2',
|
|
['executed', id]
|
|
);
|
|
|
|
res.json({ success: true, message: '执行成功' });
|
|
} catch (error) {
|
|
console.error('执行采购申请失败:', error);
|
|
res.status(500).json({ success: false, message: '执行采购申请失败', error: error.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|