备份:PWA配置前的完整版本
包含所有最新开发的功能和修复,作为PWA正确配置前的备份点。
This commit is contained in:
@@ -40,6 +40,7 @@ app.use('/api/products', require('./routes/products'));
|
||||
app.use('/api/customers', require('./routes/customers'));
|
||||
app.use('/api/suppliers', require('./routes/suppliers'));
|
||||
app.use('/api/subcontractors', require('./routes/subcontractors'));
|
||||
app.use('/api/project-receipts', require('./routes/project-receipts'));
|
||||
app.use('/api/projects', require('./routes/projects'));
|
||||
app.use('/api/upload', require('./routes/upload'));
|
||||
app.use('/api/construction', require('./routes/construction'));
|
||||
@@ -69,6 +70,7 @@ app.use('/api/process-templates', require('./routes/process-templates'));
|
||||
app.use('/api/receiving', require('./routes/receiving'));
|
||||
app.use('/api/expense-categories', require('./routes/expense-categories'));
|
||||
app.use('/api/financial-records', require('./routes/financial-records'));
|
||||
app.use('/api/cash-management', require('./routes/cash-management'));
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
|
||||
|
||||
Binary file not shown.
+215
-215
@@ -1,216 +1,216 @@
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 验证错误处理中间件
|
||||
const validate = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT a.*, u.name as user_name, p.name as project_name
|
||||
FROM advances a
|
||||
LEFT JOIN users u ON a.applicant_id = u.id
|
||||
LEFT JOIN projects p ON a.project_id = p.id
|
||||
ORDER BY a.created_at DESC
|
||||
`);
|
||||
|
||||
// 解析每个预支申请的 attachments 字段为数组
|
||||
const data = result.rows.map(item => {
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, 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.post('/', [
|
||||
body('amount').isFloat({ min: 0.01 }),
|
||||
body('reason').notEmpty()
|
||||
], validate, async (req, res) => {
|
||||
try {
|
||||
const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body;
|
||||
const user_id = 1; // 临时使用admin用户
|
||||
|
||||
// 生成预支编号
|
||||
const advanceCode = `ADV-${Date.now()}`;
|
||||
|
||||
const result = await db.query(
|
||||
'INSERT INTO advances (applicant_id, project_id, amount, currency, amount_cny, reason, advance_date, advance_code, status, applicant, attachments) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id',
|
||||
[applicant_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, advance_date, advanceCode, status || 'pending', applicant, JSON.stringify(attachments || [])]
|
||||
);
|
||||
|
||||
const data = { id: result.rows[0]?.id, applicant_id, project_id, amount, currency, reason, advance_date, advance_code: advanceCode, status, applicant };
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
console.error('创建预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM advances WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
// 解析 attachments 字段为数组
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body;
|
||||
|
||||
const result = await db.query(
|
||||
'UPDATE advances SET amount = $1, reason = $2, project_id = $3, currency = $4, advance_date = $5, attachments = $6, amount_cny = $7, applicant = $8, status = $9 WHERE id = $10',
|
||||
[amount, reason, project_id, currency, advance_date, JSON.stringify(attachments || []), amount_cny, applicant, status, id]
|
||||
);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('DELETE FROM advances WHERE id = $1', [id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1 WHERE id = $2', ['pending_edit', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 验证错误处理中间件
|
||||
const validate = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT a.*, u.name as user_name, p.name as project_name
|
||||
FROM advances a
|
||||
LEFT JOIN users u ON a.applicant_id = u.id
|
||||
LEFT JOIN projects p ON a.project_id = p.id
|
||||
ORDER BY a.created_at DESC
|
||||
`);
|
||||
|
||||
// 解析每个预支申请的 attachments 字段为数组
|
||||
const data = result.rows.map(item => {
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, 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.post('/', [
|
||||
body('amount').isFloat({ min: 0.01 }),
|
||||
body('reason').notEmpty()
|
||||
], validate, async (req, res) => {
|
||||
try {
|
||||
const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body;
|
||||
const user_id = 1; // 临时使用admin用户
|
||||
|
||||
// 生成预支编号
|
||||
const advanceCode = `ADV-${Date.now()}`;
|
||||
|
||||
const result = await db.query(
|
||||
'INSERT INTO advances (applicant_id, project_id, amount, currency, amount_cny, reason, advance_date, advance_code, status, applicant, attachments) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id',
|
||||
[applicant_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, advance_date, advanceCode, status || 'pending', applicant, JSON.stringify(attachments || [])]
|
||||
);
|
||||
|
||||
const data = { id: result.rows[0]?.id, applicant_id, project_id, amount, currency, reason, advance_date, advance_code: advanceCode, status, applicant };
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
console.error('创建预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM advances WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
// 解析 attachments 字段为数组
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body;
|
||||
|
||||
const result = await db.query(
|
||||
'UPDATE advances SET amount = $1, reason = $2, project_id = $3, currency = $4, advance_date = $5, attachments = $6, amount_cny = $7, applicant = $8, status = $9 WHERE id = $10',
|
||||
[amount, reason, project_id, currency, advance_date, JSON.stringify(attachments || []), amount_cny, applicant, status, id]
|
||||
);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('DELETE FROM advances WHERE id = $1', [id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE advances SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '预支申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回预支申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回预支申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -16,7 +16,7 @@ router.post('/login', async (req, res) => {
|
||||
}
|
||||
|
||||
const result = await db.query(
|
||||
'SELECT id, username, name, email, phone, role, password_hash FROM users WHERE username = $1',
|
||||
'SELECT id, username, name, email, phone, role, avatar, passport, driver_license, password_hash FROM users WHERE username = $1',
|
||||
[username]
|
||||
);
|
||||
|
||||
@@ -53,6 +53,9 @@ router.post('/login', async (req, res) => {
|
||||
phone: user.phone,
|
||||
role: user.role,
|
||||
department: '',
|
||||
avatar: user.avatar || null,
|
||||
passport: user.passport || null,
|
||||
driverLicense: user.driver_license || null,
|
||||
token: token
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function generateCode(prefix) {
|
||||
const now = new Date();
|
||||
const dateStr = now.getFullYear().toString() +
|
||||
(now.getMonth() + 1).toString().padStart(2, '0') +
|
||||
now.getDate().toString().padStart(2, '0');
|
||||
const rand = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||
return `${prefix}-${dateStr}-${rand}`;
|
||||
}
|
||||
|
||||
router.get('/summary', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { date_from, date_to } = req.query;
|
||||
let where = "WHERE status != 'voided'";
|
||||
const params = [];
|
||||
let idx = 1;
|
||||
|
||||
if (date_from) { params.push(date_from); where += ` AND record_date >= $${idx++}`; }
|
||||
if (date_to) { params.push(date_to); where += ` AND record_date <= $${idx++}`; }
|
||||
|
||||
const totalResult = await db.query(
|
||||
`SELECT
|
||||
COALESCE(SUM(CASE WHEN txn_type = 'income' THEN amount_cny ELSE 0 END), 0) as total_income,
|
||||
COALESCE(SUM(CASE WHEN txn_type = 'expense' THEN amount_cny ELSE 0 END), 0) as total_expense,
|
||||
COALESCE(SUM(CASE WHEN txn_type = 'income' THEN amount_cny ELSE -amount_cny END), 0) as net_amount
|
||||
FROM financial_records ${where}`,
|
||||
params
|
||||
);
|
||||
|
||||
const byCategory = await db.query(
|
||||
`SELECT category_level1, category_level2,
|
||||
COALESCE(SUM(amount_cny), 0) as total_amount,
|
||||
COUNT(*) as count
|
||||
FROM financial_records ${where}
|
||||
GROUP BY category_level1, category_level2
|
||||
ORDER BY category_level1, total_amount DESC`,
|
||||
params
|
||||
);
|
||||
|
||||
const recentResult = await db.query(
|
||||
`SELECT fr.*, p.name as project_name
|
||||
FROM financial_records fr
|
||||
LEFT JOIN projects p ON fr.project_id = p.id
|
||||
${where}
|
||||
ORDER BY fr.record_date DESC, fr.created_at DESC
|
||||
LIMIT 20`,
|
||||
params
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
totals: totalResult.rows[0],
|
||||
byCategory: byCategory.rows,
|
||||
recent: recentResult.rows
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取资金概览失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取资金概览失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/records', authenticate, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
txn_type, category_level1, category_level2,
|
||||
project_id, date_from, date_to,
|
||||
page = 1, pageSize = 20
|
||||
} = req.query;
|
||||
|
||||
let sql = `SELECT fr.*, p.name as project_name FROM financial_records fr LEFT JOIN projects p ON fr.project_id = p.id WHERE 1=1`;
|
||||
const params = [];
|
||||
let idx = 1;
|
||||
|
||||
if (txn_type) { params.push(txn_type); sql += ` AND fr.txn_type = $${idx++}`; }
|
||||
if (category_level1) { params.push(category_level1); sql += ` AND fr.category_level1 = $${idx++}`; }
|
||||
if (category_level2) { params.push(category_level2); sql += ` AND fr.category_level2 = $${idx++}`; }
|
||||
if (project_id) { params.push(project_id); sql += ` AND fr.project_id = $${idx++}`; }
|
||||
if (date_from) { params.push(date_from); sql += ` AND fr.record_date >= $${idx++}`; }
|
||||
if (date_to) { params.push(date_to); sql += ` AND fr.record_date <= $${idx++}`; }
|
||||
|
||||
const countResult = await db.query(`SELECT COUNT(*) as total FROM (${sql}) sub`, params);
|
||||
const total = parseInt(countResult.rows[0].total);
|
||||
|
||||
sql += ' ORDER BY fr.record_date DESC, fr.created_at DESC';
|
||||
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
params.push(parseInt(pageSize));
|
||||
sql += ` LIMIT $${idx++}`;
|
||||
params.push(offset);
|
||||
sql += ` OFFSET $${idx++}`;
|
||||
|
||||
const result = await db.query(sql, params);
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
total,
|
||||
totalPages: Math.ceil(total / parseInt(pageSize))
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('查询收支记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '查询收支记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', authenticate, async (req, res) => {
|
||||
const client = await db.pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const {
|
||||
txn_type, category_level1, category_level2,
|
||||
project_id, amount, currency, exchange_rate,
|
||||
record_date, counterparty_name, counterparty_type, counterparty_id,
|
||||
description, voucher_url
|
||||
} = req.body;
|
||||
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
|
||||
if (!txn_type || !category_level1 || !category_level2 || !amount || !record_date) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ success: false, message: '缺少必填字段' });
|
||||
}
|
||||
|
||||
const amt = parseFloat(amount) || 0;
|
||||
const rate = parseFloat(exchange_rate) || 1;
|
||||
const amountCny = parseFloat((amt * rate).toFixed(2));
|
||||
|
||||
const today = new Date();
|
||||
const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const codeResult = await client.query(
|
||||
"SELECT COUNT(*) as cnt FROM financial_records WHERE record_code LIKE $1",
|
||||
[`FIN-${dateStr}%`]
|
||||
);
|
||||
const seq = String(parseInt(codeResult.rows[0].cnt) + 1).padStart(4, '0');
|
||||
const recordCode = `FIN-${dateStr}-${seq}`;
|
||||
|
||||
const frResult = await client.query(
|
||||
`INSERT INTO financial_records
|
||||
(record_code, txn_type, category_level1, category_level2, project_id, user_id, user_name,
|
||||
amount_original, currency, exchange_rate, amount_cny, record_date,
|
||||
counterparty_name, counterparty_type, counterparty_id, source, source_code, description, attachments, status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, 'cash_management', $16, $17, $18, 'confirmed')
|
||||
RETURNING *`,
|
||||
[
|
||||
recordCode, txn_type, category_level1, category_level2,
|
||||
project_id || null, userId, req.user?.name || req.user?.username || '',
|
||||
amt, currency || 'CNY', rate, amountCny,
|
||||
record_date,
|
||||
counterparty_name || null, counterparty_type || null, counterparty_id || null,
|
||||
recordCode, description || null, voucher_url || null
|
||||
]
|
||||
);
|
||||
|
||||
if (txn_type === 'income' && project_id && ['contract_payment', 'customer_advance'].includes(category_level2)) {
|
||||
const msResult = await client.query(
|
||||
'SELECT id FROM project_milestones WHERE project_id = $1 ORDER BY id LIMIT 1',
|
||||
[project_id]
|
||||
);
|
||||
if (msResult.rows.length > 0) {
|
||||
const milestoneId = msResult.rows[0].id;
|
||||
const msData = await client.query('SELECT amount FROM project_milestones WHERE id = $1', [milestoneId]);
|
||||
if (msData.rows.length > 0 && parseFloat(msData.rows[0].amount) > 0) {
|
||||
const prResult = await client.query(
|
||||
`INSERT INTO project_receipts
|
||||
(project_id, receipt_type, milestone_id, amount, currency, exchange_rate, amount_cny,
|
||||
receipt_date, payer_name, description, voucher_url, financial_record_id, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
RETURNING *`,
|
||||
[
|
||||
project_id,
|
||||
category_level2 === 'customer_advance' ? 'advance' : 'node',
|
||||
milestoneId, amt, currency || 'CNY', rate, amountCny,
|
||||
record_date, counterparty_name || '', description || '', voucher_url || '',
|
||||
frResult.rows[0].id, userId
|
||||
]
|
||||
);
|
||||
|
||||
const totalReceived = await client.query(
|
||||
`SELECT COALESCE(SUM(amount), 0) as total FROM project_receipts
|
||||
WHERE milestone_id = $1 AND receipt_type = 'node'`,
|
||||
[milestoneId]
|
||||
);
|
||||
const progress = Math.min(100, Math.round((parseFloat(totalReceived.rows[0].total) / parseFloat(msData.rows[0].amount)) * 100));
|
||||
await client.query(
|
||||
`UPDATE project_milestones SET completion_progress = $1::numeric,
|
||||
status = CASE WHEN $1::numeric >= 100 THEN 'completed' ELSE 'in_progress' END,
|
||||
actual_date = CASE WHEN $1::numeric >= 100 THEN $2 ELSE actual_date END
|
||||
WHERE id = $3`,
|
||||
[progress, record_date, milestoneId]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
const fullResult = await db.query(
|
||||
`SELECT fr.*, p.name as project_name FROM financial_records fr LEFT JOIN projects p ON fr.project_id = p.id WHERE fr.id = $1`,
|
||||
[frResult.rows[0].id]
|
||||
);
|
||||
|
||||
res.json({ success: true, data: fullResult.rows[0] });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('新增收支记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '新增收支记录失败' });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:id', authenticate, async (req, res) => {
|
||||
const client = await db.pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const { id } = req.params;
|
||||
|
||||
const recordResult = await client.query(
|
||||
`SELECT * FROM financial_records WHERE id = $1 AND source = 'cash_management'`,
|
||||
[id]
|
||||
);
|
||||
if (recordResult.rowCount === 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(404).json({ success: false, message: '记录不存在或无权删除' });
|
||||
}
|
||||
|
||||
const record = recordResult.rows[0];
|
||||
|
||||
if (record.txn_type === 'income' && record.project_id) {
|
||||
const prResult = await client.query(
|
||||
'SELECT id FROM project_receipts WHERE financial_record_id = $1',
|
||||
[id]
|
||||
);
|
||||
if (prResult.rows.length > 0) {
|
||||
const receipt = prResult.rows[0];
|
||||
await client.query('DELETE FROM project_receipts WHERE id = $1', [receipt.id]);
|
||||
|
||||
if (receipt.milestone_id) {
|
||||
const msData = await client.query('SELECT amount FROM project_milestones WHERE id = $1', [receipt.milestone_id]);
|
||||
if (msData.rows.length > 0 && parseFloat(msData.rows[0].amount) > 0) {
|
||||
const totalReceived = await client.query(
|
||||
`SELECT COALESCE(SUM(amount), 0) as total FROM project_receipts
|
||||
WHERE milestone_id = $1 AND receipt_type = 'node'`,
|
||||
[receipt.milestone_id]
|
||||
);
|
||||
const progress = Math.min(100, Math.round((parseFloat(totalReceived.rows[0].total) / parseFloat(msData.rows[0].amount)) * 100));
|
||||
await client.query(
|
||||
`UPDATE project_milestones SET completion_progress = $1::numeric,
|
||||
status = CASE WHEN $1::numeric >= 100 THEN 'completed' WHEN $1::numeric > 0 THEN 'in_progress' ELSE 'pending' END
|
||||
WHERE id = $2`,
|
||||
[progress, receipt.milestone_id]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('DELETE FROM financial_records WHERE id = $1', [id]);
|
||||
|
||||
await client.query('COMMIT');
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('删除收支记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除收支记录失败' });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -4,6 +4,7 @@ const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 获取施工项目列表
|
||||
router.get('/my-projects', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
@@ -30,4 +31,298 @@ router.get('/my-projects', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 施工日志 ====================
|
||||
|
||||
// 获取项目施工日志
|
||||
router.get('/projects/:projectId/logs', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const result = await db.query(
|
||||
'SELECT * FROM construction_logs WHERE project_id = $1 ORDER BY log_date DESC, created_at DESC',
|
||||
[projectId]
|
||||
);
|
||||
res.json({ success: true, data: result.rows });
|
||||
} catch (error) {
|
||||
console.error('获取施工日志失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取施工日志失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 新增施工日志
|
||||
router.post('/projects/:projectId/logs', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { log_date, weather, work_content, next_plan, issues, photos } = req.body;
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
|
||||
const result = await db.query(
|
||||
`INSERT INTO construction_logs (project_id, log_date, weather, recorded_by, work_content, next_plan, issues, photos)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
|
||||
[projectId, log_date, weather || 'sunny', userId, work_content || '', next_plan || '', issues || '', photos || []]
|
||||
);
|
||||
res.json({ success: true, data: result.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('新增施工日志失败:', error);
|
||||
res.status(500).json({ success: false, message: '新增施工日志失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除施工日志
|
||||
router.delete('/logs/:logId', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { logId } = req.params;
|
||||
const result = await db.query('DELETE FROM construction_logs WHERE id = $1 RETURNING id', [logId]);
|
||||
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: '删除施工日志失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 施工进度 ====================
|
||||
|
||||
// 获取项目施工进度(包含phases和项目信息)
|
||||
router.get('/projects/:projectId/progress', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
|
||||
// 获取项目信息
|
||||
const projectResult = await db.query(
|
||||
`SELECT p.*, c.name as customer_name, u.name as manager_name
|
||||
FROM projects p
|
||||
LEFT JOIN customers c ON p.customer_id = c.id
|
||||
LEFT JOIN users u ON p.project_manager_id = u.id
|
||||
WHERE p.id = $1`,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
if (projectResult.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '项目不存在' });
|
||||
}
|
||||
|
||||
// 获取阶段
|
||||
const phasesResult = await db.query(
|
||||
'SELECT * FROM project_phases WHERE project_id = $1 ORDER BY phase_order',
|
||||
[projectId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
project: projectResult.rows[0],
|
||||
phases: phasesResult.rows
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取施工进度失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取施工进度失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 完成阶段
|
||||
router.post('/projects/:projectId/phases/:phaseId/complete', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { projectId, phaseId } = req.params;
|
||||
const { remark, photos } = req.body;
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
|
||||
// 标记阶段完成
|
||||
await db.query(
|
||||
`UPDATE project_phases SET status = 'completed', completed_at = CURRENT_TIMESTAMP, completed_by = $1, remark = COALESCE(remark, '') || $2, photos = COALESCE(photos, '{}') || $3 WHERE id = $4 AND project_id = $5`,
|
||||
[userId, remark ? '\n' + remark : '', photos || [], phaseId, projectId]
|
||||
);
|
||||
|
||||
// 计算总体进度
|
||||
const progressResult = await db.query(
|
||||
'SELECT COUNT(*) as total, COUNT(CASE WHEN status = $1 THEN 1 END) as completed FROM project_phases WHERE project_id = $2',
|
||||
['completed', projectId]
|
||||
);
|
||||
const total = parseInt(progressResult.rows[0].total);
|
||||
const completed = parseInt(progressResult.rows[0].completed);
|
||||
const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
|
||||
|
||||
// 找下一个待开始的阶段
|
||||
const nextPhaseResult = await db.query(
|
||||
`SELECT * FROM project_phases WHERE project_id = $1 AND status = 'pending' ORDER BY phase_order LIMIT 1`,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
if (nextPhaseResult.rows.length > 0) {
|
||||
await db.query(
|
||||
`UPDATE project_phases SET status = 'in_progress', started_at = CURRENT_TIMESTAMP WHERE id = $1`,
|
||||
[nextPhaseResult.rows[0].id]
|
||||
);
|
||||
// 更新项目当前阶段
|
||||
await db.query(
|
||||
`UPDATE projects SET current_phase = $1, phase_progress = $2 WHERE id = $3`,
|
||||
[nextPhaseResult.rows[0].phase_name, progress, projectId]
|
||||
);
|
||||
} else {
|
||||
// 所有阶段完成
|
||||
await db.query(
|
||||
`UPDATE projects SET current_phase = '已完成', phase_progress = 100, status = 'completed' WHERE id = $1`,
|
||||
[projectId]
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ success: true, progress });
|
||||
} catch (error) {
|
||||
console.error('完成阶段失败:', error);
|
||||
res.status(500).json({ success: false, message: '完成阶段失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 重新打开阶段
|
||||
router.put('/phases/:phaseId/reopen', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { phaseId } = req.params;
|
||||
|
||||
await db.query(
|
||||
`UPDATE project_phases SET status = 'in_progress', completed_at = NULL, completed_by = NULL WHERE id = $1`,
|
||||
[phaseId]
|
||||
);
|
||||
|
||||
// 重新计算进度
|
||||
const phaseResult = await db.query('SELECT project_id FROM project_phases WHERE id = $1', [phaseId]);
|
||||
if (phaseResult.rows.length > 0) {
|
||||
const projectId = phaseResult.rows[0].project_id;
|
||||
const progressResult = await db.query(
|
||||
'SELECT COUNT(*) as total, COUNT(CASE WHEN status = $1 THEN 1 END) as completed FROM project_phases WHERE project_id = $2',
|
||||
['completed', projectId]
|
||||
);
|
||||
const total = parseInt(progressResult.rows[0].total);
|
||||
const completed = parseInt(progressResult.rows[0].completed);
|
||||
const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
|
||||
|
||||
await db.query(
|
||||
`UPDATE projects SET phase_progress = $1, status = 'active' WHERE id = $2`,
|
||||
[progress, projectId]
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('重新打开阶段失败:', error);
|
||||
res.status(500).json({ success: false, message: '重新打开阶段失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新子项状态(支持完成时间、照片、与资料管理共享)
|
||||
router.put('/phases/:phaseId/sub-item', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { phaseId } = req.params;
|
||||
const { itemIndex, completed, completed_at, photos } = req.body;
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
|
||||
const phaseResult = await db.query('SELECT sub_items, project_id FROM project_phases WHERE id = $1', [phaseId]);
|
||||
if (phaseResult.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '阶段不存在' });
|
||||
}
|
||||
|
||||
const subItems = phaseResult.rows[0].sub_items || [];
|
||||
const projectId = phaseResult.rows[0].project_id;
|
||||
|
||||
if (itemIndex >= 0 && itemIndex < subItems.length) {
|
||||
subItems[itemIndex].completed = completed;
|
||||
if (completed) {
|
||||
subItems[itemIndex].completed_at = completed_at || new Date().toISOString();
|
||||
if (photos && photos.length > 0) {
|
||||
subItems[itemIndex].photos = photos;
|
||||
// 同步写入 project_documents 实现与资料管理共享
|
||||
for (const url of photos) {
|
||||
const fileName = url.split('/').pop() || 'photo.jpg';
|
||||
await db.query(
|
||||
`INSERT INTO project_documents (project_id, doc_type, file_name, file_url, description, uploaded_by)
|
||||
VALUES ($1, 'image', $2, $3, $4, $5)`,
|
||||
[projectId, fileName, url, `阶段子项: ${subItems[itemIndex].name}`, userId]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
subItems[itemIndex].completed_at = null;
|
||||
subItems[itemIndex].photos = [];
|
||||
}
|
||||
await db.query('UPDATE project_phases SET sub_items = $1 WHERE id = $2', [JSON.stringify(subItems), phaseId]);
|
||||
}
|
||||
|
||||
res.json({ success: true, subItems });
|
||||
} catch (error) {
|
||||
console.error('更新子项状态失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新子项状态失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 资料管理 ====================
|
||||
|
||||
// 获取项目资料列表
|
||||
router.get('/projects/:projectId/documents', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { doc_type } = req.query;
|
||||
|
||||
let query = 'SELECT d.*, u.name as uploader_name FROM project_documents d LEFT JOIN users u ON d.uploaded_by = u.id WHERE d.project_id = $1';
|
||||
const params = [projectId];
|
||||
|
||||
if (doc_type) {
|
||||
query += ' AND d.doc_type = $2';
|
||||
params.push(doc_type);
|
||||
}
|
||||
|
||||
query += ' ORDER BY d.created_at DESC';
|
||||
|
||||
const result = await db.query(query, params);
|
||||
res.json({ success: true, data: result.rows });
|
||||
} catch (error) {
|
||||
console.error('获取项目资料失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取项目资料失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 上传项目资料
|
||||
router.post('/projects/:projectId/documents', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { doc_type, file_name, file_url, description } = req.body;
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
|
||||
const result = await db.query(
|
||||
`INSERT INTO project_documents (project_id, doc_type, file_name, file_url, description, uploaded_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
|
||||
[projectId, doc_type || 'document', file_name, file_url, description || '', userId]
|
||||
);
|
||||
res.json({ success: true, data: result.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('上传项目资料失败:', error);
|
||||
res.status(500).json({ success: false, message: '上传项目资料失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除项目资料
|
||||
router.delete('/documents/:docId', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { docId } = req.params;
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
const isAdmin = req.user?.role === 'admin';
|
||||
|
||||
// 管理员或上传者可删除
|
||||
const docResult = await db.query('SELECT * FROM project_documents WHERE id = $1', [docId]);
|
||||
if (docResult.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '资料不存在' });
|
||||
}
|
||||
|
||||
if (!isAdmin && docResult.rows[0].uploaded_by !== userId) {
|
||||
return res.status(403).json({ success: false, message: '无权删除' });
|
||||
}
|
||||
|
||||
await db.query('DELETE FROM project_documents WHERE id = $1', [docId]);
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} catch (error) {
|
||||
console.error('删除项目资料失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除项目资料失败' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -85,7 +85,7 @@ router.post('/', async (req, res) => {
|
||||
|
||||
let status = action === 'execute' ? 'executed' : 'rejected';
|
||||
if (action === 'reject') {
|
||||
status = 'pending_edit';
|
||||
status = 'pending';
|
||||
}
|
||||
|
||||
const executeDate = new Date().toISOString().split('T')[0];
|
||||
|
||||
@@ -236,8 +236,14 @@ router.post('/batch', authenticate, async (req, res) => {
|
||||
for (let i = 0; i < records.length; i++) {
|
||||
const r = records[i];
|
||||
try {
|
||||
if (!r.txn_type || !r.category_level1 || !r.category_level2 || !r.amount_original || !r.record_date) {
|
||||
errors.push({ row: i + 1, message: '缺少必填字段' });
|
||||
const missingFields = [];
|
||||
if (!r.txn_type) missingFields.push('收支类型');
|
||||
if (!r.category_level1) missingFields.push('一级分类');
|
||||
if (!r.category_level2) missingFields.push('二级分类');
|
||||
if (!r.amount_original) missingFields.push('金额');
|
||||
if (!r.record_date) missingFields.push('日期');
|
||||
if (missingFields.length > 0) {
|
||||
errors.push({ row: i + 1, message: `缺少必填字段: ${missingFields.join(', ')}`, data: r });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -246,15 +252,34 @@ router.post('/batch', authenticate, async (req, res) => {
|
||||
|
||||
let projectId = r.project_id || null;
|
||||
if (!projectId && r.project_name) {
|
||||
// 先精确匹配
|
||||
const projResult = await db.query("SELECT id FROM projects WHERE name = $1", [r.project_name]);
|
||||
if (projResult.rows.length > 0) {
|
||||
projectId = projResult.rows[0].id;
|
||||
} else {
|
||||
// 模糊匹配:去除空格后比较
|
||||
const fuzzyResult = await db.query(
|
||||
"SELECT id, name FROM projects WHERE REPLACE(name, ' ', '') = REPLACE($1, ' ', '')",
|
||||
[r.project_name]
|
||||
);
|
||||
if (fuzzyResult.rows.length > 0) {
|
||||
projectId = fuzzyResult.rows[0].id;
|
||||
} else {
|
||||
// 包含匹配
|
||||
const likeResult = await db.query(
|
||||
"SELECT id, name FROM projects WHERE name LIKE '%' || $1 || '%' OR $1 LIKE '%' || name || '%'",
|
||||
[r.project_name]
|
||||
);
|
||||
if (likeResult.rows.length > 0) {
|
||||
projectId = likeResult.rows[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let userId = r.user_id || null;
|
||||
if (!userId && r.user_name) {
|
||||
const userResult = await db.query("SELECT id FROM users WHERE name = $1", [r.user_name]);
|
||||
const userResult = await db.query("SELECT id FROM users WHERE name = $1 OR username = $1", [r.user_name]);
|
||||
if (userResult.rows.length > 0) {
|
||||
userId = userResult.rows[0].id;
|
||||
}
|
||||
@@ -280,7 +305,7 @@ router.post('/batch', authenticate, async (req, res) => {
|
||||
);
|
||||
results.push(result.rows[0]);
|
||||
} catch (err) {
|
||||
errors.push({ row: i + 1, message: err.message });
|
||||
errors.push({ row: i + 1, message: err.message, data: { txn_type: r.txn_type, category_level1: r.category_level1, category_level2: r.category_level2, project_name: r.project_name, amount: r.amount_original } });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,32 @@ router.get('/summary', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/in', async (req, res) => {
|
||||
try {
|
||||
const { project_id, product_id, quantity, unit_price, total_amount, operator, remark } = req.body;
|
||||
|
||||
const result = await db.query(`
|
||||
INSERT INTO inventory_records
|
||||
(record_type, project_id, product_id, quantity, unit_price, total_amount, record_date, operator, remark)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_DATE, $7, $8)
|
||||
RETURNING id
|
||||
`, ['in', project_id || null, product_id, quantity, unit_price || null, total_amount || null, operator || '系统', remark || null]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '入库成功',
|
||||
data: { id: result.rows[0]?.id }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('入库失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '入库失败',
|
||||
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/out', async (req, res) => {
|
||||
try {
|
||||
const { project_id, product_id, quantity, unit_price, total_amount, operator, remark } = req.body;
|
||||
|
||||
+242
-242
@@ -4,256 +4,256 @@ const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT * FROM payment_requests
|
||||
ORDER BY created_at DESC
|
||||
`);
|
||||
|
||||
const data = result.rows.map(item => {
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
if (item.detail_items) {
|
||||
try {
|
||||
item.detail_items = JSON.parse(item.detail_items);
|
||||
} catch (error) {
|
||||
item.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
item.detail_items = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, data, count: data.length });
|
||||
} catch (error) {
|
||||
console.error('获取付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT * FROM payment_requests
|
||||
ORDER BY created_at DESC
|
||||
`);
|
||||
|
||||
const data = result.rows.map(item => {
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
if (item.detail_items) {
|
||||
try {
|
||||
item.detail_items = JSON.parse(item.detail_items);
|
||||
} catch (error) {
|
||||
item.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
item.detail_items = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, data, count: data.length });
|
||||
} catch (error) {
|
||||
console.error('获取付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
payment_date, payee, bank_account, bank_name, currency, reason,
|
||||
detail_items, attachments, applicant,
|
||||
payee_type, payee_id, expense_type, expense_category, project_id, amount
|
||||
} = req.body;
|
||||
|
||||
// 生成付款申请编号
|
||||
const requestCode = `PAY-${Date.now()}`;
|
||||
|
||||
// 使用默认值处理可选字段
|
||||
const finalBankAccount = bank_account || '';
|
||||
const finalBankName = bank_name || '';
|
||||
const finalAmount = amount || 0;
|
||||
|
||||
const result = await db.query(
|
||||
`INSERT INTO payment_requests (
|
||||
payee, bank_account, bank_name, amount, currency, reason, payment_date,
|
||||
request_code, status, applicant, detail_items, attachments,
|
||||
payee_type, payee_id, expense_type, expense_category, project_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17`,
|
||||
[
|
||||
payee, finalBankAccount, finalBankName, finalAmount, currency || 'CNY',
|
||||
reason, payment_date, requestCode, 'pending', applicant,
|
||||
JSON.stringify(detail_items || []), JSON.stringify(attachments || []),
|
||||
payee_type || 'other', payee_id || null, expense_type || 'company',
|
||||
expense_category || '', project_id || null
|
||||
]
|
||||
);
|
||||
|
||||
// SQLite不支持RETURNING,所以需要查询刚插入的数据
|
||||
const lastInsert = await db.query('SELECT * FROM payment_requests ORDER BY id DESC LIMIT 1');
|
||||
res.json({ success: true, data: lastInsert.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('创建付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
payment_date, payee, bank_account, bank_name, currency, reason,
|
||||
detail_items, attachments, applicant,
|
||||
payee_type, payee_id, expense_type, expense_category, project_id, amount
|
||||
} = req.body;
|
||||
|
||||
// 生成付款申请编号
|
||||
const requestCode = `PAY-${Date.now()}`;
|
||||
|
||||
// 使用默认值处理可选字段
|
||||
const finalBankAccount = bank_account || '';
|
||||
const finalBankName = bank_name || '';
|
||||
const finalAmount = amount || 0;
|
||||
|
||||
const result = await db.query(
|
||||
`INSERT INTO payment_requests (
|
||||
payee, bank_account, bank_name, amount, currency, reason, payment_date,
|
||||
request_code, status, applicant, detail_items, attachments,
|
||||
payee_type, payee_id, expense_type, expense_category, project_id
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`,
|
||||
[
|
||||
payee, finalBankAccount, finalBankName, finalAmount, currency || 'CNY',
|
||||
reason, payment_date, requestCode, 'pending', applicant,
|
||||
JSON.stringify(detail_items || []), JSON.stringify(attachments || []),
|
||||
payee_type || 'other', payee_id || null, expense_type || 'company',
|
||||
expense_category || '', project_id || null
|
||||
]
|
||||
);
|
||||
|
||||
// SQLite不支持RETURNING,所以需要查询刚插入的数据
|
||||
const lastInsert = await db.query('SELECT * FROM payment_requests ORDER BY id DESC LIMIT 1');
|
||||
res.json({ success: true, data: lastInsert.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('创建付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM payment_requests WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
if (data.detail_items) {
|
||||
try {
|
||||
data.detail_items = JSON.parse(data.detail_items);
|
||||
} catch (error) {
|
||||
data.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
data.detail_items = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM payment_requests WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
if (data.detail_items) {
|
||||
try {
|
||||
data.detail_items = JSON.parse(data.detail_items);
|
||||
} catch (error) {
|
||||
data.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
data.detail_items = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
payment_date, payee, bank_account, bank_name, currency, reason,
|
||||
detail_items, attachments, applicant, status,
|
||||
payee_type, payee_id, expense_type, expense_category, project_id, amount
|
||||
} = req.body;
|
||||
|
||||
// 构建动态更新SQL,只更新提供的字段
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
if (payment_date !== undefined) { updates.push('payment_date = $1'); params.push(payment_date); }
|
||||
if (payee !== undefined) { updates.push('payee = $1'); params.push(payee); }
|
||||
if (bank_account !== undefined) { updates.push('bank_account = $1'); params.push(bank_account); }
|
||||
if (bank_name !== undefined) { updates.push('bank_name = $1'); params.push(bank_name); }
|
||||
if (amount !== undefined) { updates.push('amount = $1'); params.push(amount); }
|
||||
if (currency !== undefined) { updates.push('currency = $1'); params.push(currency); }
|
||||
if (reason !== undefined) { updates.push('reason = $1'); params.push(reason); }
|
||||
if (detail_items !== undefined) { updates.push('detail_items = $1'); params.push(JSON.stringify(detail_items || [])); }
|
||||
if (attachments !== undefined) { updates.push('attachments = $1'); params.push(JSON.stringify(attachments || [])); }
|
||||
if (applicant !== undefined) { updates.push('applicant = $1'); params.push(applicant); }
|
||||
if (status !== undefined) { updates.push('status = $1'); params.push(status); }
|
||||
if (payee_type !== undefined) { updates.push('payee_type = $1'); params.push(payee_type); }
|
||||
if (payee_id !== undefined) { updates.push('payee_id = $1'); params.push(payee_id); }
|
||||
if (expense_type !== undefined) { updates.push('expense_type = $1'); params.push(expense_type); }
|
||||
if (expense_category !== undefined) { updates.push('expense_category = $1'); params.push(expense_category); }
|
||||
if (project_id !== undefined) { updates.push('project_id = $1'); params.push(project_id); }
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ success: false, message: '没有要更新的字段' });
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
|
||||
const result = await db.query(
|
||||
`UPDATE payment_requests SET ${updates.join(', ')} WHERE id = $1`,
|
||||
params
|
||||
);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
payment_date, payee, bank_account, bank_name, currency, reason,
|
||||
detail_items, attachments, applicant, status,
|
||||
payee_type, payee_id, expense_type, expense_category, project_id, amount
|
||||
} = req.body;
|
||||
|
||||
// 构建动态更新SQL,只更新提供的字段
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
if (payment_date !== undefined) { updates.push('payment_date = $1'); params.push(payment_date); }
|
||||
if (payee !== undefined) { updates.push('payee = $1'); params.push(payee); }
|
||||
if (bank_account !== undefined) { updates.push('bank_account = $1'); params.push(bank_account); }
|
||||
if (bank_name !== undefined) { updates.push('bank_name = $1'); params.push(bank_name); }
|
||||
if (amount !== undefined) { updates.push('amount = $1'); params.push(amount); }
|
||||
if (currency !== undefined) { updates.push('currency = $1'); params.push(currency); }
|
||||
if (reason !== undefined) { updates.push('reason = $1'); params.push(reason); }
|
||||
if (detail_items !== undefined) { updates.push('detail_items = $1'); params.push(JSON.stringify(detail_items || [])); }
|
||||
if (attachments !== undefined) { updates.push('attachments = $1'); params.push(JSON.stringify(attachments || [])); }
|
||||
if (applicant !== undefined) { updates.push('applicant = $1'); params.push(applicant); }
|
||||
if (status !== undefined) { updates.push('status = $1'); params.push(status); }
|
||||
if (payee_type !== undefined) { updates.push('payee_type = $1'); params.push(payee_type); }
|
||||
if (payee_id !== undefined) { updates.push('payee_id = $1'); params.push(payee_id); }
|
||||
if (expense_type !== undefined) { updates.push('expense_type = $1'); params.push(expense_type); }
|
||||
if (expense_category !== undefined) { updates.push('expense_category = $1'); params.push(expense_category); }
|
||||
if (project_id !== undefined) { updates.push('project_id = $1'); params.push(project_id); }
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ success: false, message: '没有要更新的字段' });
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
|
||||
const result = await db.query(
|
||||
`UPDATE payment_requests SET ${updates.join(', ')} WHERE id = $1`,
|
||||
params
|
||||
);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('DELETE FROM payment_requests WHERE id = $1', [id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('DELETE FROM payment_requests WHERE id = $1', [id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批付款申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批付款申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批付款申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1 WHERE id = $2', ['pending_edit', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE payment_requests SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '付款申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -106,7 +106,7 @@ router.post('/:id/copy', async (req, res) => {
|
||||
const src = existing.rows[0];
|
||||
const result = await db.query(
|
||||
'INSERT INTO project_type_templates (name, description, phases, is_system) VALUES ($1, $2, $3, false) RETURNING id',
|
||||
[name || `${src.name} (副本)`, src.description, src.phases]
|
||||
[name || `${src.name} (副本)`, src.description, JSON.stringify(src.phases)]
|
||||
);
|
||||
res.json({ success: true, message: '模板复制成功', data: { id: result.rows[0].id } });
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
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 { projectId } = req.query;
|
||||
if (!projectId) {
|
||||
return res.status(400).json({ success: false, message: '缺少项目ID' });
|
||||
}
|
||||
const result = await db.query(
|
||||
`SELECT r.*, m.milestone_name, m.percentage as milestone_percentage,
|
||||
u.name as creator_name
|
||||
FROM project_receipts r
|
||||
LEFT JOIN project_milestones m ON r.milestone_id = m.id
|
||||
LEFT JOIN users u ON r.created_by = u.id
|
||||
WHERE r.project_id = $1
|
||||
ORDER BY r.receipt_date DESC, r.created_at DESC`,
|
||||
[projectId]
|
||||
);
|
||||
res.json({ success: true, data: result.rows });
|
||||
} catch (error) {
|
||||
console.error('获取收款记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取收款记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取项目付款节点列表(用于收款时选择)
|
||||
router.get('/milestones', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.query;
|
||||
if (!projectId) {
|
||||
return res.status(400).json({ success: false, message: '缺少项目ID' });
|
||||
}
|
||||
const result = await db.query(
|
||||
`SELECT id, milestone_name, percentage, amount, status, completion_progress
|
||||
FROM project_milestones WHERE project_id = $1 ORDER BY id`,
|
||||
[projectId]
|
||||
);
|
||||
res.json({ success: true, data: result.rows });
|
||||
} catch (error) {
|
||||
console.error('获取付款节点失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取付款节点失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 新增收款记录(同时写入 financial_records 统一记账)
|
||||
router.post('/', authenticate, async (req, res) => {
|
||||
const client = await db.pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const { project_id, receipt_type, milestone_id, amount, currency, exchange_rate, receipt_date, payer_name, description, voucher_url, counterparty_id } = req.body;
|
||||
const userId = req.user?.id || req.user?.userId;
|
||||
|
||||
if (!project_id) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ success: false, message: '缺少项目ID' });
|
||||
}
|
||||
|
||||
const amt = parseFloat(amount) || 0;
|
||||
const rate = parseFloat(exchange_rate) || 1;
|
||||
const amountCny = parseFloat((amt * rate).toFixed(2));
|
||||
|
||||
// 1. 写入 financial_records(统一记账)
|
||||
const today = new Date();
|
||||
const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const codeResult = await client.query(
|
||||
"SELECT COUNT(*) as cnt FROM financial_records WHERE record_code LIKE $1",
|
||||
[`FIN-${dateStr}%`]
|
||||
);
|
||||
const seq = String(parseInt(codeResult.rows[0].cnt) + 1).padStart(4, '0');
|
||||
const recordCode = `FIN-${dateStr}-${seq}`;
|
||||
|
||||
// 确定分类
|
||||
let categoryLevel2 = 'contract_payment';
|
||||
if (receipt_type === 'advance') {
|
||||
categoryLevel2 = 'customer_advance';
|
||||
} else if (receipt_type === 'other') {
|
||||
categoryLevel2 = 'other_income';
|
||||
}
|
||||
|
||||
const frResult = await client.query(
|
||||
`INSERT INTO financial_records
|
||||
(record_code, txn_type, category_level1, category_level2, project_id, user_id, user_name,
|
||||
amount_original, currency, exchange_rate, amount_cny, record_date,
|
||||
counterparty_name, counterparty_type, counterparty_id, source, source_code, description, status)
|
||||
VALUES ($1, 'income', $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, 'customer', $13, 'receipt', $14, $15, 'confirmed')
|
||||
RETURNING id`,
|
||||
[
|
||||
recordCode, 'income', categoryLevel2, project_id,
|
||||
userId, req.user?.name || req.user?.username || '',
|
||||
amt, currency || 'CNY', rate, amountCny,
|
||||
receipt_date, payer_name || '',
|
||||
counterparty_id || null,
|
||||
recordCode, description || ''
|
||||
]
|
||||
);
|
||||
const financialRecordId = frResult.rows[0].id;
|
||||
|
||||
// 2. 写入 project_receipts
|
||||
const receiptResult = await client.query(
|
||||
`INSERT INTO project_receipts
|
||||
(project_id, receipt_type, milestone_id, amount, currency, exchange_rate, amount_cny,
|
||||
receipt_date, payer_name, description, voucher_url, financial_record_id, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
RETURNING *`,
|
||||
[project_id, receipt_type || 'node', milestone_id || null, amt, currency || 'CNY', rate, amountCny,
|
||||
receipt_date, payer_name || '', description || '', voucher_url || '', financialRecordId, userId]
|
||||
);
|
||||
|
||||
// 3. 如果是节点收款,更新里程碑完成进度
|
||||
if (receipt_type === 'node' && milestone_id) {
|
||||
const msResult = await client.query('SELECT amount FROM project_milestones WHERE id = $1', [milestone_id]);
|
||||
if (msResult.rows.length > 0 && parseFloat(msResult.rows[0].amount) > 0) {
|
||||
const totalReceived = await client.query(
|
||||
`SELECT COALESCE(SUM(amount), 0) as total FROM project_receipts
|
||||
WHERE milestone_id = $1 AND receipt_type = 'node'`,
|
||||
[milestone_id]
|
||||
);
|
||||
const progress = Math.min(100, Math.round((parseFloat(totalReceived.rows[0].total) / parseFloat(msResult.rows[0].amount)) * 100));
|
||||
await client.query(
|
||||
`UPDATE project_milestones SET completion_progress = $1::numeric, status = CASE WHEN $1::numeric >= 100 THEN 'completed' ELSE 'in_progress' END, actual_date = CASE WHEN $1::numeric >= 100 THEN $2 ELSE actual_date END WHERE id = $3`,
|
||||
[progress, receipt_date, milestone_id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
// 返回带里程碑名称的完整记录
|
||||
const fullResult = await db.query(
|
||||
`SELECT r.*, m.milestone_name FROM project_receipts r LEFT JOIN project_milestones m ON r.milestone_id = m.id WHERE r.id = $1`,
|
||||
[receiptResult.rows[0].id]
|
||||
);
|
||||
|
||||
res.json({ success: true, data: fullResult.rows[0] });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('新增收款记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '新增收款记录失败' });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
// 删除收款记录(同时删除 financial_records)
|
||||
router.delete('/:receiptId', authenticate, async (req, res) => {
|
||||
const client = await db.pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const { receiptId } = req.params;
|
||||
|
||||
const receiptResult = await client.query(
|
||||
'SELECT * FROM project_receipts WHERE id = $1',
|
||||
[receiptId]
|
||||
);
|
||||
if (receiptResult.rowCount === 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(404).json({ success: false, message: '收款记录不存在' });
|
||||
}
|
||||
|
||||
const receipt = receiptResult.rows[0];
|
||||
const financialRecordId = receipt.financial_record_id;
|
||||
|
||||
// 先删除 project_receipts(引用方),再删除 financial_records(被引用方)
|
||||
await client.query('DELETE FROM project_receipts WHERE id = $1', [receiptId]);
|
||||
|
||||
if (financialRecordId) {
|
||||
await client.query('DELETE FROM financial_records WHERE id = $1', [financialRecordId]);
|
||||
}
|
||||
|
||||
// 如果是节点收款,重新计算里程碑进度
|
||||
if (receipt.receipt_type === 'node' && receipt.milestone_id) {
|
||||
const msResult = await client.query('SELECT amount FROM project_milestones WHERE id = $1', [receipt.milestone_id]);
|
||||
if (msResult.rows.length > 0 && parseFloat(msResult.rows[0].amount) > 0) {
|
||||
const totalReceived = await client.query(
|
||||
`SELECT COALESCE(SUM(amount), 0) as total FROM project_receipts
|
||||
WHERE milestone_id = $1 AND receipt_type = 'node'`,
|
||||
[receipt.milestone_id]
|
||||
);
|
||||
const progress = Math.min(100, Math.round((parseFloat(totalReceived.rows[0].total) / parseFloat(msResult.rows[0].amount)) * 100));
|
||||
await client.query(
|
||||
`UPDATE project_milestones SET completion_progress = $1::numeric, status = CASE WHEN $1::numeric >= 100 THEN 'completed' WHEN $1::numeric > 0 THEN 'in_progress' ELSE 'pending' END WHERE id = $2`,
|
||||
[progress, receipt.milestone_id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('删除收款记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除收款记录失败' });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
+109
-35
@@ -19,10 +19,17 @@ router.get('/', async (req, res) => {
|
||||
p.status,
|
||||
p.location,
|
||||
c.name as customer_name,
|
||||
u.name as manager_name
|
||||
u.name as manager_name,
|
||||
COALESCE(expense_summary.total_expense, 0) as total_expense
|
||||
FROM projects p
|
||||
LEFT JOIN customers c ON p.customer_id = c.id
|
||||
LEFT JOIN users u ON p.project_manager_id = u.id
|
||||
LEFT JOIN (
|
||||
SELECT project_id, SUM(amount_cny::numeric) as total_expense
|
||||
FROM financial_records
|
||||
WHERE txn_type = 'expense' AND status = 'confirmed'
|
||||
GROUP BY project_id
|
||||
) expense_summary ON p.id = expense_summary.project_id
|
||||
ORDER BY p.created_at DESC
|
||||
LIMIT 50
|
||||
`);
|
||||
@@ -472,6 +479,14 @@ router.put('/:id', async (req, res) => {
|
||||
);
|
||||
}
|
||||
|
||||
// 合同金额变更时,自动按比例更新付款节点金额
|
||||
if (contract_amount !== undefined && contract_amount !== null) {
|
||||
await db.query(
|
||||
`UPDATE project_milestones SET amount = ROUND($1 * percentage / 100, 2) WHERE project_id = $2`,
|
||||
[contract_amount, id]
|
||||
);
|
||||
}
|
||||
|
||||
if (start_date && end_date) {
|
||||
const start = new Date(start_date);
|
||||
const end = new Date(end_date);
|
||||
@@ -553,6 +568,12 @@ router.put('/:id/contract', async (req, res) => {
|
||||
[id, node.name, node.condition || '', node.percentage, node.amount, 'pending']
|
||||
);
|
||||
}
|
||||
} else if (contract_total) {
|
||||
// 没有传付款节点但合同金额变了,按比例更新现有里程碑金额
|
||||
await db.query(
|
||||
`UPDATE project_milestones SET amount = ROUND($1 * percentage / 100, 2) WHERE project_id = $2`,
|
||||
[contract_total, id]
|
||||
);
|
||||
}
|
||||
|
||||
// 4. 处理单价项
|
||||
@@ -581,55 +602,108 @@ router.put('/:id/contract', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id/financial-details', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { txn_type, category_level1, category_level2, date_from, date_to, page = 1, pageSize = 20 } = req.query;
|
||||
|
||||
let sql = `SELECT fr.*, p.name as project_name FROM financial_records fr LEFT JOIN projects p ON fr.project_id = p.id WHERE fr.project_id = $1 AND fr.status != 'voided'`;
|
||||
const params = [id];
|
||||
let idx = 2;
|
||||
|
||||
if (txn_type) { params.push(txn_type); sql += ` AND fr.txn_type = $${idx++}`; }
|
||||
if (category_level1) { params.push(category_level1); sql += ` AND fr.category_level1 = $${idx++}`; }
|
||||
if (category_level2) { params.push(category_level2); sql += ` AND fr.category_level2 = $${idx++}`; }
|
||||
if (date_from) { params.push(date_from); sql += ` AND fr.record_date >= $${idx++}`; }
|
||||
if (date_to) { params.push(date_to); sql += ` AND fr.record_date <= $${idx++}`; }
|
||||
|
||||
const countResult = await db.query(`SELECT COUNT(*) as total FROM (${sql}) sub`, params);
|
||||
const total = parseInt(countResult.rows[0].total);
|
||||
|
||||
sql += ' ORDER BY fr.record_date DESC, fr.created_at DESC';
|
||||
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
params.push(parseInt(pageSize));
|
||||
sql += ` LIMIT $${idx++}`;
|
||||
params.push(offset);
|
||||
sql += ` OFFSET $${idx++}`;
|
||||
|
||||
const result = await db.query(sql, params);
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
pagination: { page: parseInt(page), pageSize: parseInt(pageSize), total, totalPages: Math.ceil(total / parseInt(pageSize)) }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取项目财务明细失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取项目财务明细失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id/cost-summary', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const purchaseResult = await db.query(`
|
||||
SELECT
|
||||
expense_category,
|
||||
SUM(total_amount) as total_amount
|
||||
FROM purchase_requests
|
||||
WHERE project_id = $1 AND status IN ('approved', 'executed')
|
||||
GROUP BY expense_category
|
||||
`, [id]);
|
||||
|
||||
const paymentResult = await db.query(`
|
||||
SELECT
|
||||
SUM(amount) as total_payment
|
||||
FROM payment_requests
|
||||
WHERE project_id = $1 AND status = 'approved' AND payment_type = 'company'
|
||||
`, [id]);
|
||||
|
||||
|
||||
const projectResult = await db.query('SELECT * FROM projects WHERE id = $1', [id]);
|
||||
|
||||
if (projectResult.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: '项目不存在' });
|
||||
}
|
||||
|
||||
const project = projectResult.rows[0];
|
||||
const purchaseByCategory = {};
|
||||
let totalPurchase = 0;
|
||||
|
||||
purchaseResult.rows.forEach(row => {
|
||||
purchaseByCategory[row.expense_category] = row.total_amount;
|
||||
totalPurchase += row.total_amount;
|
||||
|
||||
const incomeResult = await db.query(`
|
||||
SELECT category_level2, COALESCE(SUM(amount_cny), 0) as total_amount
|
||||
FROM financial_records
|
||||
WHERE project_id = $1 AND txn_type = 'income' AND status != 'voided'
|
||||
GROUP BY category_level2
|
||||
`, [id]);
|
||||
|
||||
const expenseResult = await db.query(`
|
||||
SELECT category_level1, category_level2, COALESCE(SUM(amount_cny), 0) as total_amount
|
||||
FROM financial_records
|
||||
WHERE project_id = $1 AND txn_type = 'expense' AND status != 'voided'
|
||||
GROUP BY category_level1, category_level2
|
||||
`, [id]);
|
||||
|
||||
const totalIncomeRow = await db.query(`
|
||||
SELECT COALESCE(SUM(amount_cny), 0) as total FROM financial_records
|
||||
WHERE project_id = $1 AND txn_type = 'income' AND status != 'voided'
|
||||
`, [id]);
|
||||
|
||||
const totalExpenseRow = await db.query(`
|
||||
SELECT COALESCE(SUM(amount_cny), 0) as total FROM financial_records
|
||||
WHERE project_id = $1 AND txn_type = 'expense' AND status != 'voided'
|
||||
`, [id]);
|
||||
|
||||
const incomeByCategory = {};
|
||||
let totalIncome = parseFloat(totalIncomeRow.rows[0].total);
|
||||
incomeResult.rows.forEach(row => {
|
||||
incomeByCategory[row.category_level2] = parseFloat(row.total_amount);
|
||||
});
|
||||
|
||||
const totalPayment = paymentResult.rows[0]?.total_payment || 0;
|
||||
|
||||
|
||||
const expenseByCategory = {};
|
||||
const expenseByLevel1 = {};
|
||||
let totalExpense = parseFloat(totalExpenseRow.rows[0].total);
|
||||
expenseResult.rows.forEach(row => {
|
||||
expenseByCategory[row.category_level2] = parseFloat(row.total_amount);
|
||||
if (!expenseByLevel1[row.category_level1]) expenseByLevel1[row.category_level1] = 0;
|
||||
expenseByLevel1[row.category_level1] += parseFloat(row.total_amount);
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
project_name: project.name,
|
||||
contract_amount: project.contract_amount || 0,
|
||||
purchase_cost: {
|
||||
total: totalPurchase,
|
||||
by_category: purchaseByCategory
|
||||
income: {
|
||||
total: totalIncome,
|
||||
by_category: incomeByCategory
|
||||
},
|
||||
payment_cost: totalPayment,
|
||||
total_cost: totalPurchase + totalPayment,
|
||||
profit: (project.contract_amount || 0) - (totalPurchase + totalPayment)
|
||||
expense: {
|
||||
total: totalExpense,
|
||||
by_category: expenseByCategory,
|
||||
by_level1: expenseByLevel1
|
||||
},
|
||||
total_cost: totalExpense,
|
||||
profit: totalIncome - totalExpense
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -203,7 +203,7 @@ router.put('/:id', async (req, res) => {
|
||||
WHERE id = ?
|
||||
`, [supplier_id, supplier_country, contract_url, quotation_url, remark, id]);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '采购订单不存在' });
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ router.post('/:id/cancel', async (req, res) => {
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '采购订单不存在' });
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ router.put('/:id/items/:itemId', async (req, res) => {
|
||||
WHERE id = ? AND purchase_order_id = ?
|
||||
`, [product_id, product_name, specification, unit, quantity, unit_price, total_price, itemId, id]);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '商品明细不存在' });
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ router.delete('/:id/items/:itemId', async (req, res) => {
|
||||
[itemId, id]
|
||||
);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '商品明细不存在' });
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ router.put('/:id/payment-plans/:planId', async (req, res) => {
|
||||
WHERE id = ? AND purchase_order_id = ?
|
||||
`, [stage, planned_date, planned_amount, planned_percentage, remark, planId, id]);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '付款计划不存在' });
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@ router.delete('/:id/payment-plans/:planId', async (req, res) => {
|
||||
[planId, id]
|
||||
);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '付款计划不存在' });
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ router.post('/', async (req, res) => {
|
||||
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_edit', remark]);
|
||||
'pending', remark]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -219,7 +219,7 @@ router.post('/:id/submit', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query(
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = datetime(\'now\') WHERE id = $2',
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
||||
['pending', id]
|
||||
);
|
||||
|
||||
@@ -256,7 +256,7 @@ router.post('/:id/approve', async (req, res) => {
|
||||
|
||||
try {
|
||||
await db.query(
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = datetime(\'now\') WHERE id = $2',
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
||||
['approved', id]
|
||||
);
|
||||
|
||||
@@ -297,8 +297,8 @@ router.post('/:id/reject', async (req, res) => {
|
||||
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]
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
||||
['pending', id]
|
||||
);
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
@@ -321,7 +321,7 @@ router.post('/:id/withdraw', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query(
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = datetime(\'now\') WHERE id = $2',
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
||||
['withdrawn', id]
|
||||
);
|
||||
|
||||
@@ -345,7 +345,7 @@ router.post('/:id/execute', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
await db.query(
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = datetime(\'now\') WHERE id = $2',
|
||||
'UPDATE purchase_requests SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
||||
['executed', id]
|
||||
);
|
||||
|
||||
|
||||
+209
-209
@@ -17,223 +17,223 @@ const validate = (req, res, next) => {
|
||||
next();
|
||||
};
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT r.*, u.name as user_name, p.name as project_name
|
||||
FROM reimbursements r
|
||||
LEFT JOIN users u ON r.applicant_id = u.id
|
||||
LEFT JOIN projects p ON r.project_id = p.id
|
||||
ORDER BY r.created_at DESC
|
||||
`);
|
||||
|
||||
// 解析每个报销申请的 attachments 和 detail_items 字段为数组
|
||||
const data = result.rows.map(item => {
|
||||
// 解析 attachments 字段
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
// 解析 detail_items 字段
|
||||
if (item.detail_items) {
|
||||
try {
|
||||
item.detail_items = JSON.parse(item.detail_items);
|
||||
} catch (error) {
|
||||
item.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
item.detail_items = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, 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('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT r.*, u.name as user_name, p.name as project_name
|
||||
FROM reimbursements r
|
||||
LEFT JOIN users u ON r.applicant_id = u.id
|
||||
LEFT JOIN projects p ON r.project_id = p.id
|
||||
ORDER BY r.created_at DESC
|
||||
`);
|
||||
|
||||
// 解析每个报销申请的 attachments 和 detail_items 字段为数组
|
||||
const data = result.rows.map(item => {
|
||||
// 解析 attachments 字段
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
// 解析 detail_items 字段
|
||||
if (item.detail_items) {
|
||||
try {
|
||||
item.detail_items = JSON.parse(item.detail_items);
|
||||
} catch (error) {
|
||||
item.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
item.detail_items = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, 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.post('/', [
|
||||
body('amount').isFloat({ min: 0.01 }),
|
||||
body('reason').notEmpty(),
|
||||
body('expense_type').notEmpty()
|
||||
], validate, async (req, res) => {
|
||||
try {
|
||||
const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items } = req.body;
|
||||
const user_id = 1; // 临时使用admin用户
|
||||
|
||||
// 生成报销编号
|
||||
const reimbursementCode = `REIMB-${Date.now()}`;
|
||||
|
||||
const result = await db.query(
|
||||
'INSERT INTO reimbursements (user_id, project_id, amount, currency, amount_cny, reason, reimbursement_date, reimbursement_code, status, applicant, expense_type, detail_items, attachments) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)',
|
||||
[user_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, reimbursement_date, reimbursementCode, 'pending', applicant, expense_type, JSON.stringify(detail_items || []), JSON.stringify(attachments || [])]
|
||||
);
|
||||
|
||||
// SQLite不支持RETURNING,所以需要查询刚插入的数据
|
||||
const lastInsert = await db.query('SELECT * FROM reimbursements ORDER BY id DESC LIMIT 1');
|
||||
res.json({ success: true, data: lastInsert.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('创建报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/', [
|
||||
body('amount').isFloat({ min: 0.01 }),
|
||||
body('reason').notEmpty(),
|
||||
body('expense_type').notEmpty()
|
||||
], validate, async (req, res) => {
|
||||
try {
|
||||
const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items } = req.body;
|
||||
const user_id = 1; // 临时使用admin用户
|
||||
|
||||
// 生成报销编号
|
||||
const reimbursementCode = `REIMB-${Date.now()}`;
|
||||
|
||||
const result = await db.query(
|
||||
'INSERT INTO reimbursements (user_id, project_id, amount, currency, amount_cny, reason, reimbursement_date, reimbursement_code, status, applicant, expense_type, detail_items, attachments) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)',
|
||||
[user_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, reimbursement_date, reimbursementCode, 'pending', applicant, expense_type, JSON.stringify(detail_items || []), JSON.stringify(attachments || [])]
|
||||
);
|
||||
|
||||
// SQLite不支持RETURNING,所以需要查询刚插入的数据
|
||||
const lastInsert = await db.query('SELECT * FROM reimbursements ORDER BY id DESC LIMIT 1');
|
||||
res.json({ success: true, data: lastInsert.rows[0] });
|
||||
} catch (error) {
|
||||
console.error('创建报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM reimbursements WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
// 解析 attachments 字段为数组
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
// 解析 detail_items 字段为数组
|
||||
if (data.detail_items) {
|
||||
try {
|
||||
data.detail_items = JSON.parse(data.detail_items);
|
||||
} catch (error) {
|
||||
data.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
data.detail_items = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM reimbursements WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
// 解析 attachments 字段为数组
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
// 解析 detail_items 字段为数组
|
||||
if (data.detail_items) {
|
||||
try {
|
||||
data.detail_items = JSON.parse(data.detail_items);
|
||||
} catch (error) {
|
||||
data.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
data.detail_items = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items, status } = req.body;
|
||||
|
||||
const result = await db.query(
|
||||
'UPDATE reimbursements SET amount = $1, reason = $2, project_id = $3, currency = $4, reimbursement_date = $5, attachments = $6, amount_cny = $7, applicant = $8, expense_type = $9, detail_items = $10, status = $11 WHERE id = $12',
|
||||
[amount, reason, project_id, currency, reimbursement_date, JSON.stringify(attachments || []), amount_cny, applicant, expense_type, JSON.stringify(detail_items || []), status, id]
|
||||
);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items, status } = req.body;
|
||||
|
||||
const result = await db.query(
|
||||
'UPDATE reimbursements SET amount = $1, reason = $2, project_id = $3, currency = $4, reimbursement_date = $5, attachments = $6, amount_cny = $7, applicant = $8, expense_type = $9, detail_items = $10, status = $11 WHERE id = $12',
|
||||
[amount, reason, project_id, currency, reimbursement_date, JSON.stringify(attachments || []), amount_cny, applicant, expense_type, JSON.stringify(detail_items || []), status, id]
|
||||
);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('DELETE FROM reimbursements WHERE id = $1', [id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('DELETE FROM reimbursements WHERE id = $1', [id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1 WHERE id = $2', ['pending_edit', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回报销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE reimbursements SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '报销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回报销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回报销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -248,7 +248,7 @@ router.post('/:id/reject', async (req, res) => {
|
||||
WHERE id = ?
|
||||
`, [reason || '无', id]);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '退库单不存在' });
|
||||
}
|
||||
|
||||
|
||||
+64
-6
@@ -4,6 +4,34 @@ const db = require('../db');
|
||||
const { hashPassword, verifyPassword } = require('../utils/auth');
|
||||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
router.get('/me', authenticate, async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT id, username, name, email, phone, role, avatar, passport, driver_license, is_active, created_at, updated_at FROM users WHERE id = $1', [req.user.id]);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: '用户不存在' });
|
||||
}
|
||||
const u = result.rows[0];
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
name: u.name,
|
||||
email: u.email,
|
||||
phone: u.phone,
|
||||
role: u.role,
|
||||
avatar: u.avatar,
|
||||
passport: u.passport,
|
||||
driverLicense: u.driver_license,
|
||||
is_active: u.is_active
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取用户信息失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', authenticate, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const usersResult = await db.query('SELECT id, username, name, email, phone, role, is_active, created_at, updated_at FROM users ORDER BY id');
|
||||
@@ -45,25 +73,55 @@ router.post('/', authenticate, requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id/profile', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = parseInt(id);
|
||||
|
||||
if (req.user.id !== userId && req.user.role !== 'admin') {
|
||||
return res.status(403).json({ success: false, message: '只能修改自己的个人信息' });
|
||||
}
|
||||
|
||||
const { name, email, phone, avatar, passport, driver_license } = req.body;
|
||||
|
||||
await db.query(
|
||||
'UPDATE users SET name = $1, email = $2, phone = $3, avatar = $4, passport = $5, driver_license = $6, updated_at = NOW() WHERE id = $7',
|
||||
[name, email || null, phone || null, avatar || null, passport || null, driver_license || null, userId]
|
||||
);
|
||||
|
||||
const updatedUserResult = await db.query('SELECT id, username, name, email, phone, role, avatar, passport, driver_license, is_active, created_at, updated_at FROM users WHERE id = $1', [userId]);
|
||||
const updatedUser = updatedUserResult.rows[0];
|
||||
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ success: false, message: '用户不存在' });
|
||||
}
|
||||
|
||||
res.json({ success: true, data: updatedUser });
|
||||
} catch (error) {
|
||||
console.error('更新个人信息失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新个人信息失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id', authenticate, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, email, phone, role, password } = req.body;
|
||||
const { name, email, phone, role, password, avatar, passport, driver_license } = req.body;
|
||||
|
||||
if (password) {
|
||||
const passwordHash = hashPassword(password);
|
||||
await db.query(
|
||||
'UPDATE users SET name = $1, email = $2, phone = $3, role = $4, password_hash = $5, updated_at = NOW() WHERE id = $6',
|
||||
[name, email || null, phone || null, role, passwordHash, id]
|
||||
'UPDATE users SET name = $1, email = $2, phone = $3, role = $4, avatar = $5, passport = $6, driver_license = $7, password_hash = $8, updated_at = NOW() WHERE id = $9',
|
||||
[name, email || null, phone || null, role, avatar || null, passport || null, driver_license || null, passwordHash, id]
|
||||
);
|
||||
} else {
|
||||
await db.query(
|
||||
'UPDATE users SET name = $1, email = $2, phone = $3, role = $4, updated_at = NOW() WHERE id = $5',
|
||||
[name, email || null, phone || null, role, id]
|
||||
'UPDATE users SET name = $1, email = $2, phone = $3, role = $4, avatar = $5, passport = $6, driver_license = $7, updated_at = NOW() WHERE id = $8',
|
||||
[name, email || null, phone || null, role, avatar || null, passport || null, driver_license || null, id]
|
||||
);
|
||||
}
|
||||
|
||||
const updatedUserResult = await db.query('SELECT id, username, name, email, phone, role, is_active, created_at, updated_at FROM users WHERE id = $1', [id]);
|
||||
const updatedUserResult = await db.query('SELECT id, username, name, email, phone, role, avatar, passport, driver_license, is_active, created_at, updated_at FROM users WHERE id = $1', [id]);
|
||||
const updatedUser = updatedUserResult.rows[0];
|
||||
|
||||
if (!updatedUser) {
|
||||
|
||||
@@ -284,7 +284,7 @@ router.post('/:id/reject', async (req, res) => {
|
||||
WHERE id = ?
|
||||
`, [reason || '无', id]);
|
||||
|
||||
if (result.changes === 0) {
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ success: false, message: '验收单不存在' });
|
||||
}
|
||||
|
||||
|
||||
+348
-348
@@ -4,362 +4,362 @@ const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { advance_id } = req.query;
|
||||
let query = `
|
||||
SELECT v.*, a.advance_code, a.applicant_id as advance_applicant_id
|
||||
FROM verifications v
|
||||
LEFT JOIN advances a ON v.advance_id = a.id
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
if (advance_id) {
|
||||
query += ` WHERE v.advance_id = $1`;
|
||||
params.push(advance_id);
|
||||
}
|
||||
|
||||
query += ` ORDER BY v.created_at DESC`;
|
||||
|
||||
const result = await db.query(query, params);
|
||||
|
||||
const data = result.rows.map(item => {
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
if (item.detail_items) {
|
||||
try {
|
||||
item.detail_items = JSON.parse(item.detail_items);
|
||||
} catch (error) {
|
||||
item.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
item.detail_items = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, data, count: data.length });
|
||||
} catch (error) {
|
||||
console.error('获取核销记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取核销记录失败' });
|
||||
}
|
||||
});
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { advance_id } = req.query;
|
||||
let query = `
|
||||
SELECT v.*, a.advance_code, a.applicant_id as advance_applicant_id
|
||||
FROM verifications v
|
||||
LEFT JOIN advances a ON v.advance_id = a.id
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
if (advance_id) {
|
||||
query += ` WHERE v.advance_id = $1`;
|
||||
params.push(advance_id);
|
||||
}
|
||||
|
||||
query += ` ORDER BY v.created_at DESC`;
|
||||
|
||||
const result = await db.query(query, params);
|
||||
|
||||
const data = result.rows.map(item => {
|
||||
if (item.attachments) {
|
||||
try {
|
||||
item.attachments = JSON.parse(item.attachments);
|
||||
} catch (error) {
|
||||
item.attachments = [];
|
||||
}
|
||||
} else {
|
||||
item.attachments = [];
|
||||
}
|
||||
if (item.detail_items) {
|
||||
try {
|
||||
item.detail_items = JSON.parse(item.detail_items);
|
||||
} catch (error) {
|
||||
item.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
item.detail_items = [];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
res.json({ success: true, data, count: data.length });
|
||||
} catch (error) {
|
||||
console.error('获取核销记录失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取核销记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, expense_type, project_id, settlement, settlement_amount } = req.body;
|
||||
|
||||
// 生成核销编号
|
||||
const verificationCode = `VER-${Date.now()}`;
|
||||
const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0;
|
||||
|
||||
// 验证关联预支单
|
||||
if (!advance_id && !advance_code) {
|
||||
return res.status(400).json({ success: false, message: '关联预支单是必填项' });
|
||||
}
|
||||
|
||||
let finalAdvanceCode = advance_code;
|
||||
let finalAdvanceId = advance_id;
|
||||
|
||||
// 如果advance_code为空,根据advance_id查询预支单的advance_code
|
||||
if (!finalAdvanceCode && finalAdvanceId) {
|
||||
const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = $1', [finalAdvanceId]);
|
||||
if (advanceResult.rows.length > 0) {
|
||||
finalAdvanceCode = advanceResult.rows[0].advance_code;
|
||||
} else {
|
||||
return res.status(400).json({ success: false, message: '关联的预支单不存在' });
|
||||
}
|
||||
}
|
||||
|
||||
// 如果advance_id为空,根据advance_code查询预支单的id
|
||||
if (!finalAdvanceId && finalAdvanceCode) {
|
||||
const advanceResult = await db.query('SELECT id FROM advances WHERE advance_code = $1', [finalAdvanceCode]);
|
||||
if (advanceResult.rows.length > 0) {
|
||||
finalAdvanceId = advanceResult.rows[0].id;
|
||||
} else {
|
||||
return res.status(400).json({ success: false, message: '关联的预支单不存在' });
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然为空,返回错误
|
||||
if (!finalAdvanceCode || !finalAdvanceId) {
|
||||
return res.status(400).json({ success: false, message: '关联预支单不存在' });
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 插入核销申请
|
||||
const result = await db.query(
|
||||
'INSERT INTO verifications (advance_id, amount, currency, reason, verification_date, verification_code, status, applicant, advance_code, advance_amount, detail_items, attachments, expense_type, project_id, settlement, settlement_amount) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)',
|
||||
[finalAdvanceId, amount, currency || 'CNY', reason, verification_date, verificationCode, 'pending', applicant, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0]
|
||||
);
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
// SQLite不支持RETURNING,所以需要查询刚插入的数据
|
||||
const lastInsert = await db.query('SELECT * FROM verifications ORDER BY id DESC LIMIT 1');
|
||||
res.json({ success: true, data: lastInsert.rows[0] });
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, expense_type, project_id, settlement, settlement_amount } = req.body;
|
||||
|
||||
// 生成核销编号
|
||||
const verificationCode = `VER-${Date.now()}`;
|
||||
const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0;
|
||||
|
||||
// 验证关联预支单
|
||||
if (!advance_id && !advance_code) {
|
||||
return res.status(400).json({ success: false, message: '关联预支单是必填项' });
|
||||
}
|
||||
|
||||
let finalAdvanceCode = advance_code;
|
||||
let finalAdvanceId = advance_id;
|
||||
|
||||
// 如果advance_code为空,根据advance_id查询预支单的advance_code
|
||||
if (!finalAdvanceCode && finalAdvanceId) {
|
||||
const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = $1', [finalAdvanceId]);
|
||||
if (advanceResult.rows.length > 0) {
|
||||
finalAdvanceCode = advanceResult.rows[0].advance_code;
|
||||
} else {
|
||||
return res.status(400).json({ success: false, message: '关联的预支单不存在' });
|
||||
}
|
||||
}
|
||||
|
||||
// 如果advance_id为空,根据advance_code查询预支单的id
|
||||
if (!finalAdvanceId && finalAdvanceCode) {
|
||||
const advanceResult = await db.query('SELECT id FROM advances WHERE advance_code = $1', [finalAdvanceCode]);
|
||||
if (advanceResult.rows.length > 0) {
|
||||
finalAdvanceId = advanceResult.rows[0].id;
|
||||
} else {
|
||||
return res.status(400).json({ success: false, message: '关联的预支单不存在' });
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然为空,返回错误
|
||||
if (!finalAdvanceCode || !finalAdvanceId) {
|
||||
return res.status(400).json({ success: false, message: '关联预支单不存在' });
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 插入核销申请
|
||||
const result = await db.query(
|
||||
'INSERT INTO verifications (advance_id, amount, currency, reason, verification_date, verification_code, status, applicant, advance_code, advance_amount, detail_items, attachments, expense_type, project_id, settlement, settlement_amount) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)',
|
||||
[finalAdvanceId, amount, currency || 'CNY', reason, verification_date, verificationCode, 'pending', applicant, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0]
|
||||
);
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
// SQLite不支持RETURNING,所以需要查询刚插入的数据
|
||||
const lastInsert = await db.query('SELECT * FROM verifications ORDER BY id DESC LIMIT 1');
|
||||
res.json({ success: true, data: lastInsert.rows[0] });
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '创建核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM verifications WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
if (data.detail_items) {
|
||||
try {
|
||||
data.detail_items = JSON.parse(data.detail_items);
|
||||
} catch (error) {
|
||||
data.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
data.detail_items = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('SELECT * FROM verifications WHERE id = $1', [id]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
const data = result.rows[0];
|
||||
if (data.attachments) {
|
||||
try {
|
||||
data.attachments = JSON.parse(data.attachments);
|
||||
} catch (error) {
|
||||
data.attachments = [];
|
||||
}
|
||||
} else {
|
||||
data.attachments = [];
|
||||
}
|
||||
if (data.detail_items) {
|
||||
try {
|
||||
data.detail_items = JSON.parse(data.detail_items);
|
||||
} catch (error) {
|
||||
data.detail_items = [];
|
||||
}
|
||||
} else {
|
||||
data.detail_items = [];
|
||||
}
|
||||
res.json({ success: true, data });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '获取核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, status, expense_type, project_id, settlement, settlement_amount } = req.body;
|
||||
const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0;
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取原核销金额
|
||||
const oldVerification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = $1', [id]);
|
||||
const oldAmount = oldVerification.rows[0]?.amount || 0;
|
||||
const oldAdvanceId = oldVerification.rows[0]?.advance_id;
|
||||
|
||||
let finalAdvanceCode = advance_code;
|
||||
|
||||
// 如果advance_code为空,根据advance_id查询预支单的advance_code
|
||||
if (!finalAdvanceCode && advance_id) {
|
||||
const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = $1', [advance_id]);
|
||||
if (advanceResult.rows.length > 0) {
|
||||
finalAdvanceCode = advanceResult.rows[0].advance_code;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然为空,使用默认值
|
||||
if (!finalAdvanceCode) {
|
||||
finalAdvanceCode = 'UNKNOWN';
|
||||
}
|
||||
|
||||
// 更新核销申请
|
||||
const result = await db.query(
|
||||
'UPDATE verifications SET verification_date = $1, advance_id = $2, amount = $3, currency = $4, reason = $5, advance_code = $6, advance_amount = $7, detail_items = $8, attachments = $9, applicant = $10, status = $11, expense_type = $12, project_id = $13, settlement = $14, settlement_amount = $15 WHERE id = $16',
|
||||
[verification_date, advance_id, amount, currency, reason, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), applicant, status, expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0, id]
|
||||
);
|
||||
|
||||
// 不在这里更新预支单已核销金额,而是在执行核销时更新
|
||||
// if (oldAdvanceId) {
|
||||
// const amountDiff = amount - oldAmount;
|
||||
// if (amountDiff !== 0) {
|
||||
// await db.query(
|
||||
// 'UPDATE advances SET total_reimbursed = total_reimbursed + $1 WHERE id = $2',
|
||||
// [amountDiff, oldAdvanceId]
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, status, expense_type, project_id, settlement, settlement_amount } = req.body;
|
||||
const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0;
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取原核销金额
|
||||
const oldVerification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = $1', [id]);
|
||||
const oldAmount = oldVerification.rows[0]?.amount || 0;
|
||||
const oldAdvanceId = oldVerification.rows[0]?.advance_id;
|
||||
|
||||
let finalAdvanceCode = advance_code;
|
||||
|
||||
// 如果advance_code为空,根据advance_id查询预支单的advance_code
|
||||
if (!finalAdvanceCode && advance_id) {
|
||||
const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = $1', [advance_id]);
|
||||
if (advanceResult.rows.length > 0) {
|
||||
finalAdvanceCode = advanceResult.rows[0].advance_code;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然为空,使用默认值
|
||||
if (!finalAdvanceCode) {
|
||||
finalAdvanceCode = 'UNKNOWN';
|
||||
}
|
||||
|
||||
// 更新核销申请
|
||||
const result = await db.query(
|
||||
'UPDATE verifications SET verification_date = $1, advance_id = $2, amount = $3, currency = $4, reason = $5, advance_code = $6, advance_amount = $7, detail_items = $8, attachments = $9, applicant = $10, status = $11, expense_type = $12, project_id = $13, settlement = $14, settlement_amount = $15 WHERE id = $16',
|
||||
[verification_date, advance_id, amount, currency, reason, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), applicant, status, expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0, id]
|
||||
);
|
||||
|
||||
// 不在这里更新预支单已核销金额,而是在执行核销时更新
|
||||
// if (oldAdvanceId) {
|
||||
// const amountDiff = amount - oldAmount;
|
||||
// if (amountDiff !== 0) {
|
||||
// await db.query(
|
||||
// 'UPDATE advances SET total_reimbursed = total_reimbursed + $1 WHERE id = $2',
|
||||
// [amountDiff, oldAdvanceId]
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '更新成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '更新核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取核销金额和预支单ID
|
||||
const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = $1', [id]);
|
||||
const amount = verification.rows[0]?.amount || 0;
|
||||
const advanceId = verification.rows[0]?.advance_id;
|
||||
|
||||
// 删除核销申请
|
||||
const result = await db.query('DELETE FROM verifications WHERE id = $1', [id]);
|
||||
|
||||
// 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额
|
||||
// if (advanceId && amount > 0) {
|
||||
// await db.query(
|
||||
// 'UPDATE advances SET total_reimbursed = total_reimbursed - $1 WHERE id = $2',
|
||||
// [amount, advanceId]
|
||||
// );
|
||||
// }
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取核销金额和预支单ID
|
||||
const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = $1', [id]);
|
||||
const amount = verification.rows[0]?.amount || 0;
|
||||
const advanceId = verification.rows[0]?.advance_id;
|
||||
|
||||
// 删除核销申请
|
||||
const result = await db.query('DELETE FROM verifications WHERE id = $1', [id]);
|
||||
|
||||
// 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额
|
||||
// if (advanceId && amount > 0) {
|
||||
// await db.query(
|
||||
// 'UPDATE advances SET total_reimbursed = total_reimbursed - $1 WHERE id = $2',
|
||||
// [amount, advanceId]
|
||||
// );
|
||||
// }
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '删除成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '删除核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE verifications SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/submit', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE verifications SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '提交成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '提交核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE verifications SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/withdraw', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query('UPDATE verifications SET status = $1 WHERE id = $2', ['withdrawn', id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '撤回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '撤回核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE verifications SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/approve', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { remark } = req.body;
|
||||
|
||||
const result = await db.query('UPDATE verifications SET status = $1, approval_remark = $2 WHERE id = $3', ['approved', remark, id]);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '审批通过成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审批核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '审批核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取核销金额和预支单ID
|
||||
const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = $1', [id]);
|
||||
const amount = verification.rows[0]?.amount || 0;
|
||||
const advanceId = verification.rows[0]?.advance_id;
|
||||
|
||||
// 退回核销申请
|
||||
const result = await db.query('UPDATE verifications SET status = $1 WHERE id = $2', ['pending_edit', id]);
|
||||
|
||||
// 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额
|
||||
// if (advanceId && amount > 0) {
|
||||
// await db.query(
|
||||
// 'UPDATE advances SET total_reimbursed = total_reimbursed - $1 WHERE id = $2',
|
||||
// [amount, advanceId]
|
||||
// );
|
||||
// }
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (result.changes > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回核销申请失败' });
|
||||
}
|
||||
});
|
||||
router.post('/:id/reject', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rejectReason } = req.body;
|
||||
|
||||
// 开始事务
|
||||
await db.query('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取核销金额和预支单ID
|
||||
const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = $1', [id]);
|
||||
const amount = verification.rows[0]?.amount || 0;
|
||||
const advanceId = verification.rows[0]?.advance_id;
|
||||
|
||||
// 退回核销申请
|
||||
const result = await db.query('UPDATE verifications SET status = $1 WHERE id = $2', ['pending', id]);
|
||||
|
||||
// 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额
|
||||
// if (advanceId && amount > 0) {
|
||||
// await db.query(
|
||||
// 'UPDATE advances SET total_reimbursed = total_reimbursed - $1 WHERE id = $2',
|
||||
// [amount, advanceId]
|
||||
// );
|
||||
// }
|
||||
|
||||
// 提交事务
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
res.json({ success: true, message: '退回成功' });
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: '核销申请不存在' });
|
||||
}
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await db.query('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退回核销申请失败:', error);
|
||||
res.status(500).json({ success: false, message: '退回核销申请失败' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user