Files
yunhaifinance/backend/routes/construction.js
T
a273825743 706dcc24eb 备份:PWA配置前的完整版本
包含所有最新开发的功能和修复,作为PWA正确配置前的备份点。
2026-06-13 12:44:48 +08:00

329 lines
12 KiB
JavaScript

const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const router = express.Router();
// 获取施工项目列表
router.get('/my-projects', async (req, res) => {
try {
const result = 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.status IN ('active', 'in_progress', 'planning', 'pending', 'completed')
ORDER BY p.created_at DESC
`);
const projects = result.rows.map(project => ({
...project,
latest_log: null,
progress: 0
}));
res.json({ success: true, data: projects, count: projects.length });
} catch (error) {
console.error('获取施工项目失败:', error);
res.status(500).json({ success: false, message: '操作失败' });
}
});
// ==================== 施工日志 ====================
// 获取项目施工日志
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;