467 lines
13 KiB
JavaScript
467 lines
13 KiB
JavaScript
const { Room, Apartment, Rental, Renter, Tenant } = require('../models');
|
||
const { Op } = require('sequelize');
|
||
const { logOperation } = require('../utils/logger');
|
||
|
||
// 格式化时间(考虑时区,转换为北京时间)
|
||
const formatDate = (date) => {
|
||
if (!date) return null;
|
||
// 确保 date 是 Date 对象
|
||
const dateObj = date instanceof Date ? date : new Date(date);
|
||
if (isNaN(dateObj.getTime())) return null;
|
||
// 创建一个新的Date对象,加上8小时的时区偏移
|
||
const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
|
||
return beijingDate.toISOString().split('T')[0];
|
||
};
|
||
|
||
// 格式化房间数据
|
||
const formatRoomData = (room) => {
|
||
const formattedRoom = {
|
||
...room.toJSON(),
|
||
createTime: formatDate(room.createTime),
|
||
updateTime: formatDate(room.updateTime)
|
||
};
|
||
|
||
// 格式化关联数据
|
||
if (formattedRoom.Apartment) {
|
||
formattedRoom.Apartment = {
|
||
...formattedRoom.Apartment,
|
||
createTime: formatDate(formattedRoom.Apartment.createTime),
|
||
updateTime: formatDate(formattedRoom.Apartment.updateTime)
|
||
};
|
||
}
|
||
|
||
// 格式化租房信息
|
||
if (formattedRoom.Rentals && formattedRoom.Rentals.length > 0) {
|
||
formattedRoom.Rentals = formattedRoom.Rentals.map(rental => {
|
||
const formattedRental = {
|
||
...rental,
|
||
startDate: formatDate(rental.startDate),
|
||
endDate: formatDate(rental.endDate),
|
||
createTime: formatDate(rental.createTime),
|
||
updateTime: formatDate(rental.updateTime)
|
||
};
|
||
|
||
return formattedRental;
|
||
});
|
||
}
|
||
|
||
return formattedRoom;
|
||
};
|
||
|
||
// 检查并更新租房状态
|
||
const checkAndUpdateRentalStatus = async () => {
|
||
try {
|
||
// 获取当前日期
|
||
const currentDate = new Date();
|
||
// 计算5天后的日期
|
||
const fiveDaysLater = new Date();
|
||
fiveDaysLater.setDate(currentDate.getDate() + 5);
|
||
|
||
// 查找所有在租的租房记录
|
||
const rentals = await Rental.findAll({
|
||
where: { status: 'active', isDeleted: 0 }
|
||
});
|
||
|
||
// 检查每个租房记录的状态
|
||
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 {
|
||
// 先检查并更新租房状态
|
||
await checkAndUpdateRentalStatus();
|
||
|
||
const { apartmentId, roomNumber, status, rentalStatus, page = 1, pageSize = 10 } = req.query;
|
||
|
||
// 构建查询条件
|
||
const where = { isDeleted: 0 };
|
||
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,
|
||
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;
|
||
}));
|
||
|
||
|
||
|
||
// 返回结果
|
||
res.status(200).json({
|
||
data: formattedRooms,
|
||
total: count,
|
||
page: parseInt(page),
|
||
pageSize: parseInt(pageSize)
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
};
|
||
|
||
// 获取单个房间
|
||
const getRoomById = async (req, res) => {
|
||
try {
|
||
// 先检查并更新租房状态
|
||
await checkAndUpdateRentalStatus();
|
||
|
||
const { id } = req.params;
|
||
const room = await Room.findOne({
|
||
where: { id, isDeleted: 0 },
|
||
include: [Apartment]
|
||
});
|
||
if (!room) {
|
||
return res.status(404).json({ error: '房间不存在' });
|
||
}
|
||
|
||
// 格式化房间数据
|
||
const formattedRoom = formatRoomData(room);
|
||
|
||
// 查询非过期且未删除的租房信息
|
||
const rentals = await Rental.findAll({
|
||
where: {
|
||
roomId: room.id,
|
||
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),
|
||
createTime: formatDate(rental.createTime),
|
||
// 兼容前端显示的字段
|
||
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
|
||
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''
|
||
};
|
||
|
||
return formattedRental;
|
||
});
|
||
} else {
|
||
formattedRoom.Rentals = [];
|
||
}
|
||
res.status(200).json({
|
||
code: 200,
|
||
data: formattedRoom,
|
||
message: 'success'
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ code: 500, error: error.message });
|
||
}
|
||
};
|
||
|
||
// 创建房间
|
||
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);
|
||
res.status(201).json({
|
||
code: 201,
|
||
data: room,
|
||
message: '创建成功',
|
||
warning: tenant && currentRoomCount >= tenant.maxRooms ? `当前已超出套餐限制(${tenant.maxRooms}间),续费时将收取超额费用` : null
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ code: 500, error: error.message });
|
||
}
|
||
};
|
||
|
||
// 更新房间
|
||
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({
|
||
where: { id, isDeleted: 0 }
|
||
});
|
||
if (!room) {
|
||
return res.status(404).json({ error: '房间不存在' });
|
||
}
|
||
// 处理空值
|
||
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);
|
||
res.status(200).json({
|
||
code: 200,
|
||
data: room,
|
||
message: '更新成功'
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ code: 500, error: error.message });
|
||
}
|
||
};
|
||
|
||
// 删除房间(软删除)
|
||
const deleteRoom = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
const room = await Room.findOne({
|
||
where: { id, isDeleted: 0 }
|
||
});
|
||
if (!room) {
|
||
return res.status(404).json({ error: '房间不存在' });
|
||
}
|
||
await room.update({
|
||
isDeleted: 1,
|
||
updateBy: req.user.id
|
||
});
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '房间删除成功'
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ code: 500, error: error.message });
|
||
}
|
||
};
|
||
|
||
// 获取所有房间(不分页)
|
||
const listRooms = async (req, res) => {
|
||
try {
|
||
// 先检查并更新租房状态
|
||
await checkAndUpdateRentalStatus();
|
||
|
||
const { apartmentId, roomNumber, status, rentalStatus } = req.query;
|
||
|
||
// 构建查询条件
|
||
const where = { isDeleted: 0 };
|
||
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,
|
||
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),
|
||
createTime: formatDate(rental.createTime),
|
||
// 兼容前端显示的字段
|
||
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
|
||
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''
|
||
};
|
||
}
|
||
|
||
return formattedRoom;
|
||
}));
|
||
|
||
// 返回结果
|
||
res.status(200).json({
|
||
code: 200,
|
||
data: formattedRooms,
|
||
message: 'success'
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ code: 500, error: error.message });
|
||
}
|
||
};
|
||
|
||
module.exports = {
|
||
getAllRooms,
|
||
listRooms,
|
||
getRoomById,
|
||
createRoom,
|
||
updateRoom,
|
||
deleteRoom
|
||
};
|