rentease-backend-new/controllers/roomController.js

461 lines
14 KiB
JavaScript
Raw Permalink Normal View History

2026-04-20 06:43:09 +00:00
const { Room, Apartment, Rental, Renter, Tenant } = require('../models');
const { Op } = require('sequelize');
const { logOperation } = require('../utils/logger');
2026-04-22 06:48:32 +00:00
const response = require('../utils/response');
2026-04-20 06:43:09 +00:00
// 格式化时间(考虑时区,转换为北京时间)
const formatDate = (date) => {
if (!date) return null;
const dateObj = date instanceof Date ? date : new Date(date);
if (isNaN(dateObj.getTime())) return null;
const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
return beijingDate.toISOString().split('T')[0];
};
2026-05-09 09:01:41 +00:00
// 格式化日期时间(年月日时分秒)
const formatDateTime = (date) => {
if (!date) return null;
const dateObj = date instanceof Date ? date : new Date(date);
if (isNaN(dateObj.getTime())) return null;
const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
return beijingDate.toISOString().replace('T', ' ').slice(0, 19);
};
2026-04-20 06:43:09 +00:00
// 格式化房间数据
const formatRoomData = (room) => {
const formattedRoom = {
...room.toJSON(),
2026-05-09 09:01:41 +00:00
createTime: formatDateTime(room.createTime),
updateTime: formatDateTime(room.updateTime)
2026-04-20 06:43:09 +00:00
};
// 格式化关联数据
if (formattedRoom.Apartment) {
formattedRoom.Apartment = {
...formattedRoom.Apartment,
2026-05-09 09:01:41 +00:00
createTime: formatDateTime(formattedRoom.Apartment.createTime),
updateTime: formatDateTime(formattedRoom.Apartment.updateTime)
2026-04-20 06:43:09 +00:00
};
}
// 格式化租房信息
if (formattedRoom.Rentals && formattedRoom.Rentals.length > 0) {
formattedRoom.Rentals = formattedRoom.Rentals.map(rental => {
const formattedRental = {
...rental,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
2026-05-09 09:01:41 +00:00
createTime: formatDateTime(rental.createTime),
updateTime: formatDateTime(rental.updateTime)
2026-04-20 06:43:09 +00:00
};
return formattedRental;
});
}
return formattedRoom;
};
// 检查并更新租房状态
2026-05-09 09:01:41 +00:00
const checkAndUpdateRentalStatus = async (tenantId) => {
2026-04-20 06:43:09 +00:00
try {
// 获取当前日期
const currentDate = new Date();
// 计算5天后的日期
const fiveDaysLater = new Date();
fiveDaysLater.setDate(currentDate.getDate() + 5);
2026-05-09 09:01:41 +00:00
// 查找该租户下所有在租的租房记录
2026-04-20 06:43:09 +00:00
const rentals = await Rental.findAll({
2026-05-09 09:01:41 +00:00
where: { tenantId, status: 'active', isDeleted: 0 }
2026-04-20 06:43:09 +00:00
});
// 检查每个租房记录的状态
for (const rental of rentals) {
const endDate = new Date(rental.endDate);
// 检查是否已到期
if (endDate < currentDate) {
// 更新房间租房标识为已到期
const room = await Room.findByPk(rental.roomId);
if (room && room.status === 'rented') {
await room.update({ rentalStatus: 'expired' });
}
} else if (endDate <= fiveDaysLater) {
// 更新房间租房标识为即将到期
const room = await Room.findByPk(rental.roomId);
if (room && room.status === 'rented') {
await room.update({ rentalStatus: 'soon_expire' });
}
} else {
// 更新房间租房标识为正常
const room = await Room.findByPk(rental.roomId);
if (room && room.status === 'rented') {
await room.update({ rentalStatus: 'normal' });
}
}
}
console.log('租房状态检查和更新完成');
} catch (error) {
console.error('检查和更新租房状态时出错:', error);
}
};
// 获取所有房间(支持搜索和分页)
const getAllRooms = async (req, res) => {
try {
// 先检查并更新租房状态
2026-05-09 09:01:41 +00:00
await checkAndUpdateRentalStatus(req.user.tenantId);
2026-04-20 06:43:09 +00:00
const { apartmentId, roomNumber, status, rentalStatus, page = 1, pageSize = 10 } = req.query;
// 构建查询条件
2026-05-09 09:01:41 +00:00
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
2026-04-20 06:43:09 +00:00
if (apartmentId) {
where.apartmentId = apartmentId;
}
if (roomNumber) {
where.roomNumber = {
[Op.like]: `%${roomNumber}%`
};
}
if (status) {
where.status = status;
}
if (rentalStatus) {
where.rentalStatus = rentalStatus;
}
// 计算偏移量
const offset = (page - 1) * pageSize;
// 查询房间数据
const { count, rows } = await Room.findAndCountAll({
where,
include: [
Apartment
],
limit: parseInt(pageSize),
offset: parseInt(offset),
order: [['sortOrder', 'ASC'], ['id', 'DESC']]
});
// 为每个房间获取非过期的租房信息
const formattedRooms = await Promise.all(rows.map(async (room) => {
const formattedRoom = formatRoomData(room);
// 查询非过期且未删除的租房信息,按创建时间倒序排列
const rentals = await Rental.findAll({
where: {
roomId: room.id,
2026-05-09 09:01:41 +00:00
tenantId: req.user.tenantId,
2026-04-20 06:43:09 +00:00
status: { [Op.ne]: 'expired' },
isDeleted: 0
},
include: [
{
model: Renter,
attributes: ['id', 'name', 'phone']
}
],
order: [['createTime', 'DESC']]
});
// 格式化租房信息
if (rentals.length > 0) {
formattedRoom.Rentals = rentals.map(rental => {
const rentalData = rental.toJSON();
const formattedRental = {
...rentalData,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
createTime: formatDate(rental.createTime),
// 兼容前端显示的字段
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''
};
return formattedRental;
});
} else {
formattedRoom.Rentals = [];
}
return formattedRoom;
}));
// 返回结果
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', {
list: formattedRooms,
2026-04-20 06:43:09 +00:00
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
});
} catch (error) {
2026-04-22 06:48:32 +00:00
response.serverError(res, '获取房间列表失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 获取单个房间
const getRoomById = async (req, res) => {
try {
// 先检查并更新租房状态
2026-05-09 09:01:41 +00:00
await checkAndUpdateRentalStatus(req.user.tenantId);
2026-04-20 06:43:09 +00:00
const { id } = req.params;
const room = await Room.findOne({
2026-05-09 09:01:41 +00:00
where: { id, tenantId: req.user.tenantId, isDeleted: 0 },
2026-04-20 06:43:09 +00:00
include: [Apartment]
});
if (!room) {
2026-04-22 06:48:32 +00:00
return response.notFound(res, '房间不存在');
2026-04-20 06:43:09 +00:00
}
// 格式化房间数据
const formattedRoom = formatRoomData(room);
// 查询非过期且未删除的租房信息
const rentals = await Rental.findAll({
where: {
roomId: room.id,
2026-05-09 09:01:41 +00:00
tenantId: req.user.tenantId,
2026-04-20 06:43:09 +00:00
status: { [Op.ne]: 'expired' },
isDeleted: 0
},
include: [
{
model: Renter,
attributes: ['id', 'name', 'phone']
}
]
});
// 格式化租房信息
if (rentals.length > 0) {
formattedRoom.Rentals = rentals.map(rental => {
const rentalData = rental.toJSON();
const formattedRental = {
...rentalData,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
2026-05-09 09:01:41 +00:00
createTime: formatDateTime(rental.createTime),
2026-04-20 06:43:09 +00:00
// 兼容前端显示的字段
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''
};
return formattedRental;
});
} else {
formattedRoom.Rentals = [];
}
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', formattedRoom);
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
}
};
// 创建房间
const createRoom = async (req, res) => {
try {
const { apartmentId, roomNumber, floor, roomType, area, monthlyPrice, yearlyPrice, deposit, sortOrder, status, rentalStatus } = req.body;
// 检查租户资源使用情况(仅记录日志,不阻止创建)
const tenant = await Tenant.findByPk(req.user.tenantId);
let currentRoomCount = 0;
if (tenant) {
// 获取当前房间数量
currentRoomCount = await Room.count({
where: { tenantId: req.user.tenantId, isDeleted: 0 }
});
// 检查是否超出限制
if (currentRoomCount >= tenant.maxRooms) {
// 记录超额使用日志
await logOperation({
userId: req.user.id,
username: req.user.username,
tenantId: req.user.tenantId,
module: '房间管理',
action: '超额创建',
description: `创建房间"${roomNumber}",当前已使用 ${currentRoomCount + 1}/${tenant.maxRooms} 间(超出限制)`,
method: req.method,
path: req.path,
ip: req.ip,
status: 'success'
});
}
}
// 处理空值
const processedData = {
apartmentId,
roomNumber,
floor: floor === '' ? null : floor,
roomType: roomType === '' ? null : roomType,
area: area === '' ? null : area,
monthlyPrice: monthlyPrice === '' ? null : monthlyPrice,
yearlyPrice: yearlyPrice === '' ? null : yearlyPrice,
deposit: deposit === '' ? null : deposit,
sortOrder: sortOrder === '' ? 0 : sortOrder,
status,
rentalStatus: rentalStatus || 'normal',
tenantId: req.user.tenantId,
createBy: req.user.id,
updateBy: req.user.id
};
const room = await Room.create(processedData);
2026-04-22 06:48:32 +00:00
response.created(res, '创建成功', {
room,
2026-04-20 06:43:09 +00:00
warning: tenant && currentRoomCount >= tenant.maxRooms ? `当前已超出套餐限制(${tenant.maxRooms}间),续费时将收取超额费用` : null
});
} catch (error) {
2026-04-22 06:48:32 +00:00
response.serverError(res, '创建房间失败', error);
2026-04-20 06:43:09 +00:00
}
};
// 更新房间
const updateRoom = async (req, res) => {
try {
const { id } = req.params;
const { apartmentId, roomNumber, floor, roomType, area, monthlyPrice, yearlyPrice, deposit, sortOrder, status, rentalStatus } = req.body;
const room = await Room.findOne({
2026-05-09 09:01:41 +00:00
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
2026-04-20 06:43:09 +00:00
});
if (!room) {
2026-04-22 06:48:32 +00:00
return response.notFound(res, '房间不存在');
2026-04-20 06:43:09 +00:00
}
// 处理空值
const processedData = {
apartmentId,
roomNumber,
floor: floor === '' ? null : floor,
roomType: roomType === '' ? null : roomType,
area: area === '' ? null : area,
monthlyPrice: monthlyPrice === '' ? null : monthlyPrice,
yearlyPrice: yearlyPrice === '' ? null : yearlyPrice,
deposit: deposit === '' ? null : deposit,
sortOrder: sortOrder === '' ? 0 : sortOrder,
status,
rentalStatus: rentalStatus || 'normal',
updateBy: req.user.id
};
await room.update(processedData);
2026-04-22 06:48:32 +00:00
response.success(res, '更新成功', room);
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
}
};
// 删除房间(软删除)
const deleteRoom = async (req, res) => {
try {
const { id } = req.params;
const room = await Room.findOne({
2026-05-09 09:01:41 +00:00
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
2026-04-20 06:43:09 +00:00
});
if (!room) {
2026-04-22 06:48:32 +00:00
return response.notFound(res, '房间不存在');
2026-04-20 06:43:09 +00:00
}
await room.update({
isDeleted: 1,
updateBy: req.user.id
});
2026-04-22 06:48:32 +00:00
response.success(res, '房间删除成功');
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
}
};
// 获取所有房间(不分页)
const listRooms = async (req, res) => {
try {
// 先检查并更新租房状态
2026-05-09 09:01:41 +00:00
await checkAndUpdateRentalStatus(req.user.tenantId);
2026-04-20 06:43:09 +00:00
const { apartmentId, roomNumber, status, rentalStatus } = req.query;
// 构建查询条件
2026-05-09 09:01:41 +00:00
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
2026-04-20 06:43:09 +00:00
if (apartmentId) {
where.apartmentId = apartmentId;
}
if (roomNumber) {
where.roomNumber = {
[Op.like]: `%${roomNumber}%`
};
}
if (status) {
where.status = status;
}
if (rentalStatus) {
where.rentalStatus = rentalStatus;
}
// 查询房间数据
const rooms = await Room.findAll({
where,
include: [
Apartment
],
order: [['sortOrder', 'ASC'], ['id', 'DESC']]
});
// 为每个房间获取非过期的租房信息
const formattedRooms = await Promise.all(rooms.map(async (room) => {
const formattedRoom = formatRoomData(room);
// 查询非过期且未删除的租房信息,按创建时间倒序排列
const rentals = await Rental.findAll({
where: {
roomId: room.id,
2026-05-09 09:01:41 +00:00
tenantId: req.user.tenantId,
2026-04-20 06:43:09 +00:00
status: { [Op.ne]: 'expired' },
isDeleted: 0
},
include: [
{
model: Renter,
attributes: ['id', 'name', 'phone']
}
],
order: [['createTime', 'DESC']]
});
// 格式化租房信息,取第一条作为当前租赁信息
if (rentals.length > 0) {
const rental = rentals[0];
const rentalData = rental.toJSON();
formattedRoom.rental = {
...rentalData,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
2026-05-09 09:01:41 +00:00
createTime: formatDateTime(rental.createTime),
2026-04-20 06:43:09 +00:00
// 兼容前端显示的字段
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''
};
}
return formattedRoom;
}));
// 返回结果
2026-04-22 06:48:32 +00:00
response.success(res, '获取成功', formattedRooms);
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
}
};
module.exports = {
getAllRooms,
listRooms,
getRoomById,
createRoom,
updateRoom,
deleteRoom
};