209 lines
9.0 KiB
JavaScript
209 lines
9.0 KiB
JavaScript
const express = require('express');
|
|
const { getDb } = require('../db');
|
|
const { authenticate, requireRole, optionalAuth } = require('../middleware/auth');
|
|
|
|
const router = express.Router();
|
|
|
|
router.get('/', optionalAuth, (req, res) => {
|
|
const db = getDb();
|
|
let query = `SELECT c.*,
|
|
p.display_name as planner_name, i.display_name as implementer_name, r.display_name as reviewer_name
|
|
FROM complaints c
|
|
LEFT JOIN users p ON c.assigned_planner_id = p.id
|
|
LEFT JOIN users i ON c.assigned_implementer_id = i.id
|
|
LEFT JOIN users r ON c.assigned_reviewer_id = r.id`;
|
|
let conditions = [];
|
|
let params = [];
|
|
|
|
const { status, priority, category, search } = req.query;
|
|
|
|
if (!req.user) {
|
|
conditions.push("c.status != 'pending'");
|
|
}
|
|
|
|
if (status) {
|
|
conditions.push('c.status = ?');
|
|
params.push(status);
|
|
}
|
|
if (priority) {
|
|
conditions.push('c.priority = ?');
|
|
params.push(priority);
|
|
}
|
|
if (category) {
|
|
conditions.push('c.category = ?');
|
|
params.push(category);
|
|
}
|
|
if (search) {
|
|
conditions.push("(c.title LIKE ? OR c.description LIKE ? OR c.player_name LIKE ?)");
|
|
const s = `%${search}%`;
|
|
params.push(s, s, s);
|
|
}
|
|
|
|
if (req.user && req.user.role === 'planner') {
|
|
conditions.push('(c.assigned_planner_id = ? OR c.assigned_planner_id IS NULL)');
|
|
params.push(req.user.id);
|
|
}
|
|
if (req.user && req.user.role === 'implementer') {
|
|
conditions.push('c.assigned_implementer_id = ?');
|
|
params.push(req.user.id);
|
|
}
|
|
if (req.user && req.user.role === 'reviewer') {
|
|
conditions.push('c.assigned_reviewer_id = ?');
|
|
params.push(req.user.id);
|
|
}
|
|
|
|
if (conditions.length > 0) {
|
|
query += ' WHERE ' + conditions.join(' AND ');
|
|
}
|
|
query += ' ORDER BY c.updated_at DESC';
|
|
|
|
const complaints = db.prepare(query).all(...params);
|
|
res.json(complaints);
|
|
});
|
|
|
|
router.get('/stats', authenticate, (req, res) => {
|
|
const db = getDb();
|
|
const total = db.prepare('SELECT COUNT(*) as count FROM complaints').get();
|
|
const byStatus = db.prepare('SELECT status, COUNT(*) as count FROM complaints GROUP BY status').all();
|
|
const byPriority = db.prepare('SELECT priority, COUNT(*) as count FROM complaints GROUP BY priority').all();
|
|
const today = db.prepare("SELECT COUNT(*) as count FROM complaints WHERE date(created_at) = date('now','localtime')").get();
|
|
|
|
let myStats = {};
|
|
if (['planner', 'implementer', 'reviewer'].includes(req.user.role)) {
|
|
const roleMap = { planner: 'assigned_planner_id', implementer: 'assigned_implementer_id', reviewer: 'assigned_reviewer_id' };
|
|
const col = roleMap[req.user.role];
|
|
myStats.total = db.prepare(`SELECT COUNT(*) as count FROM complaints WHERE ${col} = ?`).get(req.user.id).count;
|
|
myStats.pending = db.prepare(`SELECT COUNT(*) as count FROM complaints WHERE ${col} = ? AND status NOT IN ('resolved','closed','rejected')`).get(req.user.id).count;
|
|
myStats.completed = db.prepare(`SELECT COUNT(*) as count FROM complaints WHERE ${col} = ? AND status IN ('resolved','closed')`).get(req.user.id).count;
|
|
}
|
|
|
|
res.json({ total: total.count, byStatus, byPriority, today: today.count, myStats });
|
|
});
|
|
|
|
router.get('/:id', optionalAuth, (req, res) => {
|
|
const db = getDb();
|
|
const complaint = db.prepare(`SELECT c.*,
|
|
p.display_name as planner_name, i.display_name as implementer_name, r.display_name as reviewer_name
|
|
FROM complaints c
|
|
LEFT JOIN users p ON c.assigned_planner_id = p.id
|
|
LEFT JOIN users i ON c.assigned_implementer_id = i.id
|
|
LEFT JOIN users r ON c.assigned_reviewer_id = r.id
|
|
WHERE c.id = ?`).get(req.params.id);
|
|
if (!complaint) return res.status(404).json({ error: '投诉不存在' });
|
|
|
|
const responses = db.prepare(`SELECT r.*, u.display_name as user_name, u.role as user_role
|
|
FROM responses r LEFT JOIN users u ON r.user_id = u.id
|
|
WHERE r.complaint_id = ? ORDER BY r.created_at ASC`).all(req.params.id);
|
|
complaint.responses = responses;
|
|
|
|
if (complaint.plan_id) {
|
|
const plan = db.prepare(`SELECT p.*, u.display_name as planner_name FROM plans p
|
|
LEFT JOIN users u ON p.planner_id = u.id WHERE p.id = ?`).get(complaint.plan_id);
|
|
complaint.plan = plan;
|
|
}
|
|
if (complaint.implementation_id) {
|
|
const impl = db.prepare(`SELECT i.*, u.display_name as implementer_name FROM implementations i
|
|
LEFT JOIN users u ON i.implementer_id = u.id WHERE i.id = ?`).get(complaint.implementation_id);
|
|
complaint.implementation = impl;
|
|
}
|
|
if (complaint.review_id) {
|
|
const review = db.prepare(`SELECT r.*, u.display_name as reviewer_name FROM reviews r
|
|
LEFT JOIN users u ON r.reviewer_id = u.id WHERE r.id = ?`).get(complaint.review_id);
|
|
complaint.review = review;
|
|
}
|
|
|
|
res.json(complaint);
|
|
});
|
|
|
|
router.post('/', optionalAuth, (req, res) => {
|
|
const { title, description, category, priority, player_name, player_email, player_contact } = req.body;
|
|
if (!title || !description || !player_name) {
|
|
return res.status(400).json({ error: '标题、描述和玩家名称为必填项' });
|
|
}
|
|
const db = getDb();
|
|
const result = db.prepare(`INSERT INTO complaints(title, description, category, priority, player_name, player_email, player_contact, status)
|
|
VALUES (?,?,?,?,?,?,?,'pending')`).run(
|
|
title, description, category || 'other', priority || 'medium', player_name, player_email || '', player_contact || ''
|
|
);
|
|
const complaintId = result.lastInsertRowid;
|
|
|
|
db.prepare(`INSERT INTO responses(complaint_id, user_id, response_type, content) VALUES (?,?,?,?)`).run(
|
|
complaintId, req.user?.id || null, 'system', '投诉已提交,等待处理'
|
|
);
|
|
db.prepare(`INSERT INTO audit_logs(user_id, username, action, entity_type, entity_id, details) VALUES (?,?,?,?,?,?)`).run(
|
|
req.user?.id || null, req.user?.username || player_name, 'create', 'complaint', complaintId,
|
|
`投诉已创建: ${title}`
|
|
);
|
|
res.status(201).json({ id: complaintId, message: '投诉提交成功' });
|
|
});
|
|
|
|
router.put('/:id', authenticate, (req, res) => {
|
|
const db = getDb();
|
|
const complaint = db.prepare('SELECT * FROM complaints WHERE id = ?').get(req.params.id);
|
|
if (!complaint) return res.status(404).json({ error: '投诉不存在' });
|
|
|
|
const allowedFields = {};
|
|
if (req.body.title) allowedFields.title = req.body.title;
|
|
if (req.body.description) allowedFields.description = req.body.description;
|
|
if (req.body.category) allowedFields.category = req.body.category;
|
|
if (req.body.priority) allowedFields.priority = req.body.priority;
|
|
if (req.body.status) allowedFields.status = req.body.status;
|
|
|
|
if (Object.keys(allowedFields).length === 0) {
|
|
return res.status(400).json({ error: '没有可更新的字段' });
|
|
}
|
|
|
|
const assignments = {};
|
|
if (req.body.assigned_planner_id !== undefined) assignments.assigned_planner_id = req.body.assigned_planner_id;
|
|
if (req.body.assigned_implementer_id !== undefined) assignments.assigned_implementer_id = req.body.assigned_implementer_id;
|
|
if (req.body.assigned_reviewer_id !== undefined) assignments.assigned_reviewer_id = req.body.assigned_reviewer_id;
|
|
|
|
const allFields = { ...allowedFields, ...assignments };
|
|
const setClauses = Object.keys(allFields).map(k => `${k} = ?`).join(', ');
|
|
const values = Object.values(allFields);
|
|
|
|
db.prepare(`UPDATE complaints SET ${setClauses}, updated_at = datetime('now','localtime') WHERE id = ?`).run(...values, req.params.id);
|
|
|
|
if (req.body.status && req.body.status !== complaint.status) {
|
|
db.prepare(`INSERT INTO responses(complaint_id, user_id, response_type, content) VALUES (?,?,?,?)`).run(
|
|
req.params.id, req.user.id, 'system', `状态变更为: ${getStatusLabel(req.body.status)}`
|
|
);
|
|
db.prepare(`INSERT INTO audit_logs(user_id, username, action, entity_type, entity_id, details) VALUES (?,?,?,?,?,?)`).run(
|
|
req.user.id, req.user.username, 'update_status', 'complaint', req.params.id,
|
|
`状态从 ${complaint.status} 变更为 ${req.body.status}`
|
|
);
|
|
}
|
|
|
|
db.prepare(`INSERT INTO audit_logs(user_id, username, action, entity_type, entity_id, details) VALUES (?,?,?,?,?,?)`).run(
|
|
req.user.id, req.user.username, 'update', 'complaint', req.params.id, '更新投诉信息'
|
|
);
|
|
|
|
res.json({ message: '更新成功' });
|
|
});
|
|
|
|
router.post('/:id/response', authenticate, (req, res) => {
|
|
const { content, response_type } = req.body;
|
|
if (!content) return res.status(400).json({ error: '内容不能为空' });
|
|
const db = getDb();
|
|
const complaint = db.prepare('SELECT * FROM complaints WHERE id = ?').get(req.params.id);
|
|
if (!complaint) return res.status(404).json({ error: '投诉不存在' });
|
|
|
|
db.prepare(`INSERT INTO responses(complaint_id, user_id, response_type, content) VALUES (?,?,?,?)`).run(
|
|
req.params.id, req.user.id, response_type || 'comment', content
|
|
);
|
|
db.prepare(`UPDATE complaints SET updated_at = datetime('now','localtime') WHERE id = ?`).run(req.params.id);
|
|
|
|
res.status(201).json({ message: '回复添加成功' });
|
|
});
|
|
|
|
function getStatusLabel(status) {
|
|
const labels = {
|
|
pending: '待处理', under_review: '审查中', planned: '已计划',
|
|
in_progress: '实施中', in_review: '审核中', resolved: '已解决',
|
|
rejected: '已驳回', closed: '已关闭'
|
|
};
|
|
return labels[status] || status;
|
|
}
|
|
|
|
module.exports = router;
|