备份:PWA配置前的完整版本
包含所有最新开发的功能和修复,作为PWA正确配置前的备份点。
This commit is contained in:
+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) {
|
||||
|
||||
Reference in New Issue
Block a user