rentease-backend-new/controllers/statisticsController.js

515 lines
16 KiB
JavaScript
Raw Normal View History

2026-04-20 06:43:09 +00:00
const { Room, Rental, Apartment, Bill } = require('../models');
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: {
createTime: {
[Op.gte]: startDate
},
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: {
type: 'expense',
billDate: {
[Op.gte]: startDate
},
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 {
// 获取所有房间(排除已删除的)
const rooms = await Room.findAll({ where: { isDeleted: 0 } });
// 初始化统计数据
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 {
// 导入所有需要的模型
const { Apartment, Room, WaterBill, Rental, Bill } = require('../models');
// 并行查询所有统计数据
const [apartmentCount, roomCount, emptyRoomCount, reservedRoomCount, rentedRoomCount, soonExpireRoomCount, expiredRoomCount] = await Promise.all([
Apartment.count({ where: { isDeleted: 0 } }),
Room.count({ where: { isDeleted: 0 } }),
Room.count({ where: { status: 'empty', isDeleted: 0 } }),
Room.count({ where: { status: 'reserved', isDeleted: 0 } }),
Room.count({ where: { status: 'rented', isDeleted: 0 } }),
Room.count({ where: { status: 'rented', rentalStatus: 'soon_expire', isDeleted: 0 } }),
Room.count({ where: { status: 'rented', rentalStatus: 'expired', isDeleted: 0 } })
]);
// 安全地获取sum统计处理表不存在或为空的情况
let collectedRentAmount = 0;
let collectedWaterAmount = 0;
try {
const rentResult = await Rental.sum('rent', { where: { isDeleted: 0 } });
collectedRentAmount = rentResult || 0;
} catch (e) {
console.warn('统计租金失败:', e.message);
collectedRentAmount = 0;
}
try {
// 尝试从Bill表统计水费收入
const waterResult = await Bill.sum('receivedAmount', {
where: {
category: 'water',
status: 'paid',
isDeleted: 0
}
});
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 {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
// 获取所有房间(排除已删除的)
const rooms = await Room.findAll({ where: { isDeleted: 0 } });
// 构建公寓房间状态分布数据
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 {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
// 获取所有空房(排除已删除的)
const emptyRooms = await Room.findAll({
where: {
status: 'empty',
isDeleted: 0
}
});
// 构建按公寓分组的空房数据
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 {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
// 获取所有在租房(排除已删除的)
const rentedRooms = await Room.findAll({
where: {
status: 'rented',
isDeleted: 0
}
});
// 构建按公寓分组的在租数据
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 {
// 获取所有在租的租赁记录(排除已删除的)
const rentals = await Rental.findAll({
where: {
status: 'active',
isDeleted: 0
},
include: [{
model: Room,
where: { isDeleted: 0 }
}]
});
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
// 按租客分组
const tenantMap = new Map();
rentals.forEach(rental => {
const tenantName = rental.tenantName;
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 {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
// 获取所有即将到期的房间(排除已删除的)
const soonExpireRooms = await Room.findAll({
where: {
status: 'rented',
rentalStatus: 'soon_expire',
isDeleted: 0
}
});
// 构建按公寓分组的即将到期房间数据
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 {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
// 获取所有已到期的房间(排除已删除的)
const expiredRooms = await Room.findAll({
where: {
status: 'rented',
rentalStatus: 'expired',
isDeleted: 0
}
});
// 构建按公寓分组的已到期房间数据
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
};