Files
yunhaifinance/backend/routes/projects.js
T

586 lines
19 KiB
JavaScript
Raw Normal View History

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 result = await db.query(`
SELECT
p.id,
p.name,
p.customer_id,
p.project_manager_id,
p.contract_amount,
p.start_date,
p.end_date,
p.description,
p.status,
p.location,
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
ORDER BY p.created_at DESC
LIMIT 50
`);
res.json({
success: true,
data: result.rows,
count: result.rows.length
});
} catch (error) {
console.error('获取项目失败:', error);
res.status(500).json({
success: false,
message: '获取项目失败',
error: error.message
});
}
});
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
// 获取项目基本信息
const 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
`, [id]);
if (projectResult.rows.length > 0) {
const project = projectResult.rows[0];
// 获取项目合同信息
const contractResult = await db.query(`
SELECT * FROM project_contracts
WHERE project_id = $1
ORDER BY created_at DESC
LIMIT 1
`, [id]);
const contract = contractResult.rows[0];
// 从合同表读取质保金数据,如果没有则使用默认值
const warrantyPercent = contract?.warranty_deposit_percentage || 5;
const warrantyMonths = contract?.warranty_period || 12;
const contractAmount = parseFloat(project.contract_amount || 0);
// 计算质保金金额:合同金额 * 质保比例 / 100
const warrantyAmount = Math.round(contractAmount * warrantyPercent / 100);
// 计算质保期结束日期
const warrantyStartDate = project.end_date;
const warrantyEndDate = warrantyStartDate
? new Date(new Date(warrantyStartDate).getTime() + warrantyMonths * 30 * 24 * 60 * 60 * 1000).toISOString()
: null;
res.json({
success: true,
data: {
id: project.id,
project_code: project.code || `PROJ-${String(project.id).padStart(4, '0')}`,
name: project.name,
customer_id: project.customer_id,
customer_name: project.customer_name || '未知客户',
status: project.status || 'planning',
budget: '0',
spent: '0',
start_date: project.start_date,
end_date: project.end_date,
description: project.description,
contract_type: 'lump_sum',
contract_amount: project.contract_amount?.toString() || '0',
currency: 'CNY',
contract_days: contract?.contract_period || 180,
project_manager_id: project.project_manager_id,
manager_id: project.project_manager_id,
manager_name: project.manager_name || '未知经理',
location: project.location || '',
work_quantity: '',
project_situation: project.description || '',
settlement_type: contract?.settlement_method || 'lump_sum',
has_warranty: true,
warranty_amount: warrantyAmount.toString(),
warranty_percent: warrantyPercent.toString(),
warranty_months: warrantyMonths,
warranty_start_date: warrantyStartDate,
warranty_end_date: warrantyEndDate,
warranty_status: 'pending'
}
});
} else {
res.status(404).json({
success: false,
message: '项目不存在'
});
}
} catch (error) {
console.error('获取项目详情失败:', error);
res.status(500).json({
success: false,
message: '获取项目详情失败',
error: error.message
});
}
});
router.get('/:id/contracts', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM project_contracts
WHERE project_id = $1
ORDER BY created_at DESC
`, [id]);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('获取项目合同失败:', error);
res.status(500).json({
success: false,
message: '获取项目合同失败',
error: error.message
});
}
});
router.get('/:id/subcontracts', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM subcontracts
WHERE project_id = $1
ORDER BY created_at DESC
`, [id]);
// 解析unit_price_items字段
const subcontracts = result.rows.map(subcontract => {
if (subcontract.unit_price_items) {
try {
subcontract.unit_price_items = JSON.parse(subcontract.unit_price_items);
} catch (error) {
subcontract.unit_price_items = [];
}
} else {
subcontract.unit_price_items = [];
}
return subcontract;
});
res.json({
success: true,
data: subcontracts
});
} catch (error) {
console.error('获取项目分包失败:', error);
res.status(500).json({
success: false,
message: '获取项目分包失败',
error: error.message
});
}
});
router.post('/:id/subcontracts', async (req, res) => {
try {
const { id } = req.params;
const { subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, status } = req.body;
const unitPriceItemsJson = unit_price_items ? JSON.stringify(unit_price_items) : null;
const result = await db.query(
`INSERT INTO subcontracts (project_id, subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, paid_amount, status, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, subcontractor_id, subcontractor_name, contract_amount, currency || 'CNY', settlement_type || 'lump_sum', other_terms, payment_description, unitPriceItemsJson, start_date, end_date, work_days, 0, status || 'active']
);
const subcontractId = (result.rows[0]?.id || result.rows?.[0]?.id);
res.json({
success: true,
message: '新增分包成功',
data: {
id: subcontractId,
project_id: id,
subcontractor_id,
subcontractor_name,
contract_amount,
currency: currency || 'CNY',
settlement_type: settlement_type || 'lump_sum',
other_terms,
payment_description,
unit_price_items,
start_date,
end_date,
work_days,
paid_amount: 0,
status: status || 'active',
created_at: new Date().toISOString()
}
});
} catch (error) {
console.error('新增项目分包失败:', error);
res.status(500).json({
success: false,
message: '新增项目分包失败',
error: error.message
});
}
});
router.get('/:id/materials', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM project_materials
WHERE project_id = $1
ORDER BY created_at DESC
`, [id]);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('获取项目材料失败:', error);
res.status(500).json({
success: false,
message: '获取项目材料失败',
error: error.message
});
}
});
router.get('/:id/milestones', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM project_milestones
WHERE project_id = $1
ORDER BY expected_date ASC
`, [id]);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('获取项目施工节点失败:', error);
res.status(500).json({
success: false,
message: '获取项目施工节点失败',
error: error.message
});
}
});
router.get('/:id/finances', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM project_finances
WHERE project_id = $1
ORDER BY payment_date DESC
`, [id]);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('获取项目财务失败:', error);
res.status(500).json({
success: false,
message: '获取项目财务失败',
error: error.message
});
}
});
router.get('/:id/warranty-deposits', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM warranty_deposits
WHERE project_id = $1
ORDER BY created_at DESC
`, [id]);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('获取项目质保金失败:', error);
res.status(500).json({
success: false,
message: '获取项目质保金失败',
error: error.message
});
}
});
router.get('/:id/construction-logs', async (req, res) => {
try {
const { id } = req.params;
// 由于施工日志表可能不存在,返回空数组
res.json({
success: true,
data: []
});
} catch (error) {
console.error('获取项目施工日志失败:', error);
res.status(500).json({
success: false,
message: '获取项目施工日志失败',
error: error.message
});
}
});
router.post('/', async (req, res) => {
try {
const { name, code, customer_id, manager_id, contract_amount, start_date, end_date, description, status, location } = req.body;
const projectCode = code || 'PROJ' + new Date().toISOString().slice(0, 10).replace(/-/g, '') +
String(Math.floor(Math.random() * 10000)).padStart(4, '0');
const result = await db.query(
`INSERT INTO projects (name, code, customer_id, manager_id, contract_amount, start_date, end_date, description, status, location, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[name, projectCode, customer_id || null, manager_id || null, contract_amount || 0, start_date || '', end_date || '', description || '', status || 'planning', location || '']
);
res.json({
success: true,
message: '项目创建成功',
data: { id: (result.rows[0]?.id || result.rows?.[0]?.id), code: projectCode }
});
} catch (error) {
console.error('创建项目失败:', error);
res.status(500).json({
success: false,
message: '创建项目失败',
error: error.message
});
}
});
router.delete('/:id', authenticate, requireAdmin, async (req, res) => {
try {
const { id } = req.params;
await db.query('DELETE FROM projects WHERE id = $1', [id]);
res.json({ success: true, message: '项目已删除' });
} catch (error) {
console.error('删除项目失败:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, manager_id, location, start_date, end_date, description, status, contract_amount } = req.body;
console.log('收到项目更新请求:', { id, name, manager_id, location, start_date, end_date, description });
// 更新项目信息
await db.query(
'UPDATE projects SET name = CASE WHEN $1 IS NOT NULL THEN $2 ELSE name END, manager_id = CASE WHEN $3 IS NOT NULL THEN $4 ELSE manager_id END, location = CASE WHEN $5 IS NOT NULL THEN $6 ELSE location END, start_date = CASE WHEN $7 IS NOT NULL THEN $8 ELSE start_date END, end_date = CASE WHEN $9 IS NOT NULL THEN $10 ELSE end_date END, description = CASE WHEN $11 IS NOT NULL THEN $12 ELSE description END, status = CASE WHEN $13 IS NOT NULL THEN $14 ELSE status END, contract_amount = CASE WHEN $15 IS NOT NULL THEN $16 ELSE contract_amount END, updated_at = CURRENT_TIMESTAMP WHERE id = $17',
[name, name, manager_id, manager_id, location, location, start_date, start_date, end_date, end_date, description, description, status, status, contract_amount, contract_amount, id]
);
// 如果提供了开始和结束日期,更新合同的工期信息
if (start_date && end_date) {
const start = new Date(start_date);
const end = new Date(end_date);
const contractPeriod = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
// 更新合同信息
await db.query(
'UPDATE project_contracts SET start_date = $1, end_date = $2, contract_period = $3 WHERE project_id = $4',
[start_date, end_date, contractPeriod, id]
);
}
// 查询更新后的数据
const updatedResult = await db.query('SELECT * FROM projects WHERE id = $1', [id]);
res.json({ success: true, data: updatedResult.rows[0] });
} catch (error) {
console.error('更新项目失败:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.put('/:id/contract', async (req, res) => {
try {
const { id } = req.params;
const {
project_overview,
settlement_type,
contract_total,
tax_included,
unit_price_items,
payment_nodes,
other_info,
contract_file
} = req.body;
console.log('收到合同细节保存请求:', { id, project_overview, settlement_type, contract_total, tax_included, contract_file });
// 1. 更新项目基本信息
await db.query(
`UPDATE projects
SET description = $1, contract_amount = $2
WHERE id = $3`,
[project_overview, contract_total, id]
);
// 2. 更新或创建项目合同
const contractResult = await db.query(
`SELECT * FROM project_contracts WHERE project_id = $1`,
[id]
);
if (contractResult.rows.length > 0) {
// 更新现有合同
await db.query(
`UPDATE project_contracts
SET settlement_method = $1, contract_amount = $2, contract_file = $3, other_info = $4, tax_included = $5
WHERE project_id = $6`,
[settlement_type, contract_total, contract_file, other_info, tax_included, id]
);
} else {
// 创建新合同
const contractCode = `CONTRACT-${Date.now()}`;
await db.query(
`INSERT INTO project_contracts (project_id, contract_code, contract_amount, settlement_method, contract_file, other_info, tax_included, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, contractCode, contract_total, settlement_type, contract_file, other_info, tax_included]
);
}
// 3. 处理付款节点
if (payment_nodes && Array.isArray(payment_nodes)) {
// 删除旧的付款节点
await db.query(`DELETE FROM project_milestones WHERE project_id = $1`, [id]);
// 创建新的付款节点
for (const node of payment_nodes) {
await db.query(
`INSERT INTO project_milestones (project_id, milestone_name, condition, percentage, amount, status, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, node.name, node.condition || '', node.percentage, node.amount, 'pending']
);
}
}
// 4. 处理单价项
if (unit_price_items && Array.isArray(unit_price_items) && settlement_type === 'unit_price') {
// 删除旧的材料项
await db.query(`DELETE FROM project_materials WHERE project_id = $1`, [id]);
// 创建新的材料项
for (const item of unit_price_items) {
await db.query(
`INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, item.name, item.unit, item.quantity, item.price, item.total]
);
}
}
res.json({
success: true,
message: '合同细节保存成功'
});
} catch (error) {
console.error('保存合同细节失败:', error);
res.status(500).json({ success: false, message: error.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 totalPayment = paymentResult.rows[0]?.total_payment || 0;
res.json({
success: true,
data: {
project_name: project.name,
contract_amount: project.contract_amount || 0,
purchase_cost: {
total: totalPurchase,
by_category: purchaseByCategory
},
payment_cost: totalPayment,
total_cost: totalPurchase + totalPayment,
profit: (project.contract_amount || 0) - (totalPurchase + totalPayment)
}
});
} catch (error) {
console.error('获取项目成本统计失败:', error);
res.status(500).json({
success: false,
message: '获取项目成本统计失败',
error: error.message
});
}
});
module.exports = router;