150 lines
5.1 KiB
JavaScript
150 lines
5.1 KiB
JavaScript
const express = require('express');
|
||
const router = express.Router();
|
||
const Payment = require('../models/Payment');
|
||
const { authMiddleware, requireRole } = require('../middleware/auth');
|
||
|
||
// 获取收支明细(仅 admin)
|
||
router.get('/', authMiddleware, requireRole('admin'), async (req, res) => {
|
||
try {
|
||
const { type, startDate, endDate, party } = req.query;
|
||
const filter = {};
|
||
if (type) filter.type = type;
|
||
if (party) filter.party = new RegExp(party, 'i');
|
||
if (startDate || endDate) {
|
||
filter.createdAt = {};
|
||
if (startDate) filter.createdAt.$gte = new Date(startDate);
|
||
if (endDate) filter.createdAt.$lte = new Date(endDate);
|
||
}
|
||
|
||
const payments = await Payment.find(filter).sort('-createdAt').limit(100);
|
||
|
||
let totalIncome = 0;
|
||
let totalExpense = 0;
|
||
const incomeMap = {};
|
||
const expenseMap = {};
|
||
|
||
const allPayments = await Payment.find(filter);
|
||
for (const p of allPayments) {
|
||
if (['income', '收入', '租金收入', '押金退还', '租金'].includes(p.type)) {
|
||
totalIncome += p.amount;
|
||
incomeMap[p.category || '其他'] = (incomeMap[p.category || '其他'] || 0) + p.amount;
|
||
} else {
|
||
totalExpense += p.amount;
|
||
expenseMap[p.category || '其他'] = (expenseMap[p.category || '其他'] || 0) + p.amount;
|
||
}
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
list: payments,
|
||
summary: {
|
||
totalIncome,
|
||
totalExpense,
|
||
balance: totalIncome - totalExpense
|
||
},
|
||
incomeByCategory: incomeMap,
|
||
expenseByCategory: expenseMap
|
||
}
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, message: "服务器内部错误" });
|
||
}
|
||
});
|
||
|
||
// 创建收支记录(仅 admin)
|
||
router.post('/', authMiddleware, requireRole('admin'), async (req, res) => {
|
||
try {
|
||
const payment = new Payment(req.body);
|
||
await payment.save();
|
||
res.json({ success: true, data: payment });
|
||
} catch (error) {
|
||
res.status(400).json({ success: false, message: "服务器内部错误" });
|
||
}
|
||
});
|
||
|
||
// 更新收支记录(仅 admin)
|
||
router.put('/:id', authMiddleware, requireRole('admin'), async (req, res) => {
|
||
try {
|
||
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||
res.json({ success: true, data: payment });
|
||
} catch (error) {
|
||
res.status(400).json({ success: false, message: "服务器内部错误" });
|
||
}
|
||
});
|
||
|
||
// 删除收支记录(仅 admin)
|
||
router.delete('/:id', authMiddleware, requireRole('admin'), async (req, res) => {
|
||
try {
|
||
await Payment.findByIdAndDelete(req.params.id);
|
||
res.json({ success: true });
|
||
} catch (error) {
|
||
res.status(400).json({ success: false, message: "服务器内部错误" });
|
||
}
|
||
});
|
||
|
||
// 清空所有收支记录(仅 admin)
|
||
router.post('/delete-all', authMiddleware, requireRole('admin'), async (req, res) => {
|
||
try {
|
||
await Payment.deleteMany({});
|
||
res.json({ success: true, message: 'All payments deleted' });
|
||
} catch (error) {
|
||
res.status(400).json({ success: false, message: "服务器内部错误" });
|
||
}
|
||
});
|
||
|
||
// 收支统计(仅 admin)
|
||
router.get('/stats', authMiddleware, requireRole('admin'), async (req, res) => {
|
||
try {
|
||
const { period = 'month' } = req.query;
|
||
let startDate;
|
||
const now = new Date();
|
||
switch (period) {
|
||
case 'week': startDate = new Date(now - 7 * 24 * 60 * 60 * 1000); break;
|
||
case 'month': startDate = new Date(now.getFullYear(), now.getMonth(), 1); break;
|
||
case 'quarter': startDate = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1); break;
|
||
case 'year': startDate = new Date(now.getFullYear(), 0, 1); break;
|
||
default: startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||
}
|
||
|
||
const incomeAgg = await Payment.aggregate([
|
||
{ $match: { type: '收入', createdAt: { $gte: startDate } } },
|
||
{ $group: { _id: null, total: { $sum: '$amount' } } }
|
||
]);
|
||
const expenseAgg = await Payment.aggregate([
|
||
{ $match: { type: '支出', createdAt: { $gte: startDate } } },
|
||
{ $group: { _id: null, total: { $sum: '$amount' } } }
|
||
]);
|
||
const incomeByCategory = await Payment.aggregate([
|
||
{ $match: { type: '收入', createdAt: { $gte: startDate } } },
|
||
{ $group: { _id: '$category', total: { $sum: '$amount' } } }
|
||
]);
|
||
const expenseByCategory = await Payment.aggregate([
|
||
{ $match: { type: '支出', createdAt: { $gte: startDate } } },
|
||
{ $group: { _id: '$category', total: { $sum: '$amount' } } }
|
||
]);
|
||
|
||
const totalIncome = incomeAgg[0]?.total || 0;
|
||
const totalExpense = expenseAgg[0]?.total || 0;
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
income: totalIncome,
|
||
expense: totalExpense,
|
||
balance: totalIncome - totalExpense,
|
||
incomeByCategory: incomeByCategory.reduce((acc, item) => {
|
||
acc[item._id || '其他'] = item.total; return acc;
|
||
}, {}),
|
||
expenseByCategory: expenseByCategory.reduce((acc, item) => {
|
||
acc[item._id || '其他'] = item.total; return acc;
|
||
}, {})
|
||
}
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, message: "服务器内部错误" });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|