rentease-backend-new/controllers/statisticsController.js

518 lines
16 KiB
JavaScript
Raw Normal View History

2026-05-09 09:01:41 +00:00
const { Room, Rental, Apartment, Bill, Renter } = require('../models');
2026-04-20 06:43:09 +00:00
const { Op } = require('sequelize');
2026-04-22 06:48:32 +00:00
const response = require('../utils/response');
2026-04-20 06:43:09 +00:00
// 租金统计
const getRentStatistics = async (req, res) => {
try {
// 计算过去12个月的开始日期
const now = new Date();
const startDate = new Date(now.getFullYear(), now.getMonth() - 11, 1); // 12个月前的第一天
// 初始化过去12个月的数据
const monthlyRent = {};
for (let i = 0; i < 12; i++) {
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
const monthKey = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`;
monthlyRent[monthKey] = {
amount: 0,
depositReceived: 0,
depositRefunded: 0,
expense: 0
};
}
// 安全地查询租赁记录
let rentals = [];
try {
rentals = await Rental.findAll({
where: {
2026-05-09 09:01:41 +00:00
tenantId: req.user.tenantId,
createTime: { [Op.gte]: startDate },
2026-04-20 06:43:09 +00:00
isDeleted: 0
}
});
} catch (e) {
console.warn('查询租赁记录失败:', e.message);
rentals = [];
}
// 按月份统计已收租金和押金
rentals.forEach(rental => {
if (rental.createTime) {
// 解析创建时间
const createDate = new Date(rental.createTime);
const monthKey = `${createDate.getFullYear()}-${(createDate.getMonth() + 1).toString().padStart(2, '0')}`;
// 如果该月份在我们的统计范围内
if (monthlyRent.hasOwnProperty(monthKey)) {
// 统计租金
const rentAmount = parseFloat(rental.rent) || 0;
if (rentAmount > 0) {
monthlyRent[monthKey].amount += rentAmount;
}
// 统计已收押金(创建租赁记录时的押金)
const depositReceived = parseFloat(rental.deposit) || 0;
if (depositReceived > 0) {
monthlyRent[monthKey].depositReceived += depositReceived;
}
}
}
});
// 安全地查询支出记录从Bill表中的expense类型
try {
const expenses = await Bill.findAll({
where: {
2026-05-09 09:01:41 +00:00
tenantId: req.user.tenantId,
2026-04-20 06:43:09 +00:00
type: 'expense',
2026-05-09 09:01:41 +00:00
billDate: { [Op.gte]: startDate },
2026-04-20 06:43:09 +00:00
isDeleted: 0
}
});
// 按月份统计费用支出
expenses.forEach(expense => {
if (expense.billDate) {
// 解析费用日期
const expenseDate = new Date(expense.billDate);
const monthKey = `${expenseDate.getFullYear()}-${(expenseDate.getMonth() + 1).toString().padStart(2, '0')}`;
// 如果该月份在我们的统计范围内
if (monthlyRent.hasOwnProperty(monthKey)) {
// 统计费用支出
const expenseAmount = parseFloat(expense.receivedAmount) || 0;
if (expenseAmount > 0) {
monthlyRent[monthKey].expense += expenseAmount;
}
}
}
});
} catch (e) {
console.warn('查询支出记录失败:', e.message);
}
// 转换为数组格式并按月份倒序排序
const rentStatistics = Object.entries(monthlyRent)
.map(([month, data]) => ({
month,
amount: Math.round(data.amount * 100) / 100, // 保留两位小数
depositReceived: Math.round(data.depositReceived * 100) / 100, // 保留两位小数
depositRefunded: Math.round(data.depositRefunded * 100) / 100, // 保留两位小数
expense: Math.round(data.expense * 100) / 100 // 保留两位小数
}))
.sort((a, b) => b.month.localeCompare(a.month));
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', rentStatistics);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取租金统计数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取租金统计数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 房间状态统计
const getRoomStatusStatistics = async (req, res) => {
try {
// 获取所有房间(排除已删除的)
2026-05-09 09:01:41 +00:00
const rooms = await Room.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 初始化统计数据
let empty = 0;
let reserved = 0;
let rented = 0;
let soon_expire = 0;
let expired = 0;
// 统计各状态房间数量
rooms.forEach(room => {
if (room.status === 'empty') {
empty++;
} else if (room.status === 'reserved') {
reserved++;
} else if (room.status === 'rented') {
rented++;
// 统计附属状态
if (room.rentalStatus === 'soon_expire') {
soon_expire++;
} else if (room.rentalStatus === 'expired') {
expired++;
}
}
});
// 返回前端期望的数据格式
const roomStatusStatistics = {
total: rooms.length,
empty: empty,
reserved: reserved,
rented: rented,
soonExpire: soon_expire,
expired: expired
};
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', roomStatusStatistics);
2026-04-20 06:43:09 +00:00
} catch (error) {
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取房间状态统计失败', error);
2026-04-20 06:43:09 +00:00
}
};
// Dashboard统计数据
const getDashboardStatistics = async (req, res) => {
try {
2026-05-09 09:01:41 +00:00
const tenantId = req.user.tenantId;
const baseWhere = { tenantId, isDeleted: 0 };
2026-04-20 06:43:09 +00:00
// 并行查询所有统计数据
const [apartmentCount, roomCount, emptyRoomCount, reservedRoomCount, rentedRoomCount, soonExpireRoomCount, expiredRoomCount] = await Promise.all([
2026-05-09 09:01:41 +00:00
Apartment.count({ where: baseWhere }),
Room.count({ where: baseWhere }),
Room.count({ where: { ...baseWhere, status: 'empty' } }),
Room.count({ where: { ...baseWhere, status: 'reserved' } }),
Room.count({ where: { ...baseWhere, status: 'rented' } }),
Room.count({ where: { ...baseWhere, status: 'rented', rentalStatus: 'soon_expire' } }),
Room.count({ where: { ...baseWhere, status: 'rented', rentalStatus: 'expired' } })
2026-04-20 06:43:09 +00:00
]);
2026-05-09 09:01:41 +00:00
// 安全地获取sum统计
2026-04-20 06:43:09 +00:00
let collectedRentAmount = 0;
let collectedWaterAmount = 0;
2026-05-09 09:01:41 +00:00
2026-04-20 06:43:09 +00:00
try {
2026-05-09 09:01:41 +00:00
const rentResult = await Rental.sum('rent', { where: { tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
collectedRentAmount = rentResult || 0;
} catch (e) {
console.warn('统计租金失败:', e.message);
collectedRentAmount = 0;
}
2026-05-09 09:01:41 +00:00
2026-04-20 06:43:09 +00:00
try {
2026-05-09 09:01:41 +00:00
const waterResult = await Bill.sum('receivedAmount', {
where: { tenantId, category: 'water', status: 'paid', isDeleted: 0 }
2026-04-20 06:43:09 +00:00
});
collectedWaterAmount = waterResult || 0;
} catch (e) {
console.warn('统计水费失败:', e.message);
collectedWaterAmount = 0;
}
// 构造响应数据
const dashboardStatistics = {
apartmentCount: apartmentCount || 0,
roomCount: roomCount || 0,
emptyRoomCount: emptyRoomCount || 0,
reservedRoomCount: reservedRoomCount || 0,
rentedRoomCount: rentedRoomCount || 0,
soonExpireRoomCount: soonExpireRoomCount || 0,
expiredRoomCount: expiredRoomCount || 0,
collectedRentAmount: parseFloat(collectedRentAmount) || 0,
collectedWaterAmount: parseFloat(collectedWaterAmount) || 0
};
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', dashboardStatistics);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取Dashboard统计数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取Dashboard统计数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 公寓房间状态分布统计
const getApartmentRoomStatusStatistics = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
2026-05-09 09:01:41 +00:00
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 获取所有房间(排除已删除的)
2026-05-09 09:01:41 +00:00
const rooms = await Room.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 构建公寓房间状态分布数据
const apartmentRoomStatusStatistics = apartments.map(apartment => {
const apartmentRooms = rooms.filter(room => room.apartmentId === apartment.id);
let empty = 0;
let reserved = 0;
let rented = 0;
let soon_expire = 0;
let expired = 0;
apartmentRooms.forEach(room => {
if (room.status === 'empty') {
empty++;
} else if (room.status === 'reserved') {
reserved++;
} else if (room.status === 'rented') {
rented++;
if (room.rentalStatus === 'soon_expire') {
soon_expire++;
} else if (room.rentalStatus === 'expired') {
expired++;
}
}
});
return {
apartmentId: apartment.id,
apartment: apartment.name,
empty,
reserved,
rented,
soon_expire,
expired,
total: empty + reserved + rented
};
});
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', apartmentRoomStatusStatistics);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取公寓房间状态分布数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取公寓房间状态分布数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 空房分布统计(按公寓分组)
const getEmptyRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
2026-05-09 09:01:41 +00:00
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 获取所有空房(排除已删除的)
2026-05-09 09:01:41 +00:00
const emptyRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
status: 'empty',
isDeleted: 0
}
2026-04-20 06:43:09 +00:00
});
// 构建按公寓分组的空房数据
const emptyRoomsByApartment = apartments.map(apartment => {
const apartmentEmptyRooms = emptyRooms
.filter(room => room.apartmentId === apartment.id)
.map(room => ({
id: room.id,
roomNumber: room.roomNumber,
type: room.type,
area: room.area
}));
return {
apartmentId: apartment.id,
apartmentName: apartment.name,
emptyRooms: apartmentEmptyRooms
};
});
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', emptyRoomsByApartment);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取空房分布数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取空房分布数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 在租分布统计(按公寓分组)
const getRentedRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
2026-05-09 09:01:41 +00:00
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 获取所有在租房(排除已删除的)
2026-05-09 09:01:41 +00:00
const rentedRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
status: 'rented',
isDeleted: 0
}
2026-04-20 06:43:09 +00:00
});
// 构建按公寓分组的在租数据
const rentedRoomsByApartment = apartments.map(apartment => {
const apartmentRentedRooms = rentedRooms
.filter(room => room.apartmentId === apartment.id)
.map(room => ({
id: room.id,
roomNumber: room.roomNumber,
type: room.type,
area: room.area
}));
return {
apartmentId: apartment.id,
apartmentName: apartment.name,
rentedRooms: apartmentRentedRooms
};
});
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', rentedRoomsByApartment);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取在租分布数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取在租分布数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 租客在租统计
const getTenantRentalStats = async (req, res) => {
try {
2026-05-09 09:01:41 +00:00
const tenantId = req.user.tenantId;
2026-04-20 06:43:09 +00:00
// 获取所有在租的租赁记录(排除已删除的)
const rentals = await Rental.findAll({
where: {
2026-05-09 09:01:41 +00:00
tenantId,
2026-04-20 06:43:09 +00:00
status: 'active',
isDeleted: 0
},
include: [{
model: Room,
where: { isDeleted: 0 }
2026-05-09 09:01:41 +00:00
}, {
model: Renter,
attributes: ['id', 'name']
2026-04-20 06:43:09 +00:00
}]
});
2026-05-09 09:01:41 +00:00
2026-04-20 06:43:09 +00:00
// 获取所有公寓(排除已删除的)
2026-05-09 09:01:41 +00:00
const apartments = await Apartment.findAll({ where: { tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 按租客分组
const tenantMap = new Map();
2026-05-09 09:01:41 +00:00
2026-04-20 06:43:09 +00:00
rentals.forEach(rental => {
2026-05-09 09:01:41 +00:00
const tenantName = rental.Renter ? rental.Renter.name : '未知租客';
2026-04-20 06:43:09 +00:00
const room = rental.Room;
if (!tenantMap.has(tenantName)) {
tenantMap.set(tenantName, {
tenantName,
count: 0,
apartments: []
});
}
const tenantData = tenantMap.get(tenantName);
tenantData.count++;
// 查找房间所属的公寓
const apartment = apartments.find(apt => apt.id === room.apartmentId);
if (apartment) {
let apartmentData = tenantData.apartments.find(apt => apt.apartmentId === apartment.id);
if (!apartmentData) {
apartmentData = {
apartmentId: apartment.id,
apartmentName: apartment.name,
rooms: []
};
tenantData.apartments.push(apartmentData);
}
apartmentData.rooms.push({
id: room.id,
roomNumber: room.roomNumber,
type: room.type,
area: room.area
});
}
});
// 转换为数组
const tenantRentalStats = Array.from(tenantMap.values());
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', tenantRentalStats);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取租客在租统计数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取租客在租统计数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 即将到期房间分布统计(按公寓分组)
const getSoonExpireRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
2026-05-09 09:01:41 +00:00
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 获取所有即将到期的房间(排除已删除的)
2026-05-09 09:01:41 +00:00
const soonExpireRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
2026-04-20 06:43:09 +00:00
status: 'rented',
rentalStatus: 'soon_expire',
2026-05-09 09:01:41 +00:00
isDeleted: 0
}
2026-04-20 06:43:09 +00:00
});
// 构建按公寓分组的即将到期房间数据
const soonExpireRoomsByApartment = apartments.map(apartment => {
const apartmentSoonExpireRooms = soonExpireRooms
.filter(room => room.apartmentId === apartment.id)
.map(room => ({
id: room.id,
roomNumber: room.roomNumber,
type: room.type,
area: room.area
}));
return {
apartmentId: apartment.id,
apartmentName: apartment.name,
soonExpireRooms: apartmentSoonExpireRooms
};
});
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', soonExpireRoomsByApartment);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取即将到期分布数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取即将到期分布数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 已到期房间分布统计(按公寓分组)
const getExpiredRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
2026-05-09 09:01:41 +00:00
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
2026-04-20 06:43:09 +00:00
// 获取所有已到期的房间(排除已删除的)
2026-05-09 09:01:41 +00:00
const expiredRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
2026-04-20 06:43:09 +00:00
status: 'rented',
rentalStatus: 'expired',
2026-05-09 09:01:41 +00:00
isDeleted: 0
}
2026-04-20 06:43:09 +00:00
});
// 构建按公寓分组的已到期房间数据
const expiredRoomsByApartment = apartments.map(apartment => {
const apartmentExpiredRooms = expiredRooms
.filter(room => room.apartmentId === apartment.id)
.map(room => ({
id: room.id,
roomNumber: room.roomNumber,
type: room.type,
area: room.area
}));
return {
apartmentId: apartment.id,
apartmentName: apartment.name,
expiredRooms: apartmentExpiredRooms
};
});
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', expiredRoomsByApartment);
2026-04-20 06:43:09 +00:00
} catch (error) {
console.error('获取已到期分布数据时出错:', error);
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取已到期分布数据失败', error);
2026-04-20 06:43:09 +00:00
}
};
module.exports = {
getRentStatistics,
getRoomStatusStatistics,
getDashboardStatistics,
getApartmentRoomStatusStatistics,
getEmptyRoomsByApartment,
getRentedRoomsByApartment,
getSoonExpireRoomsByApartment,
getExpiredRoomsByApartment,
getTenantRentalStats
2026-04-22 06:48:32 +00:00
};