This commit is contained in:
wangxiaoxian 2026-05-09 17:01:41 +08:00
parent ed4ff56082
commit a4ed46d10a
13 changed files with 330 additions and 170 deletions

View File

@ -9,7 +9,7 @@ const dbConfig = {
local: {
host: 'localhost',
user: 'root',
password: 'root',
password: '123456',
database: 'rentease'
},
development: {

View File

@ -6,19 +6,25 @@ const response = require('../utils/response');
// 格式化时间(考虑时区,转换为北京时间)
const formatDate = (date) => {
if (!date) return null;
// 确保date是Date对象
const dateObj = date instanceof Date ? date : new Date(date);
// 创建一个新的Date对象加上8小时的时区偏移
const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
return beijingDate.toISOString().split('T')[0];
};
// 格式化日期时间(年月日时分秒)
const formatDateTime = (date) => {
if (!date) return null;
const dateObj = date instanceof Date ? date : new Date(date);
const beijingDate = new Date(dateObj.getTime() + 8 * 60 * 60 * 1000);
return beijingDate.toISOString().replace('T', ' ').slice(0, 19);
};
// 格式化公寓数据
const formatApartmentData = (apartment) => {
return {
...apartment.toJSON(),
createTime: formatDate(apartment.createTime),
updateTime: formatDate(apartment.updateTime)
createTime: formatDateTime(apartment.createTime),
updateTime: formatDateTime(apartment.updateTime)
};
};
@ -28,7 +34,7 @@ const getAllApartments = async (req, res) => {
const { regionId, name, page = 1, pageSize = 10 } = req.query;
// 构建查询条件
const where = { isDeleted: 0 };
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
if (regionId) {
where.regionId = regionId;
}
@ -37,10 +43,10 @@ const getAllApartments = async (req, res) => {
[Op.like]: `%${name}%`
};
}
// 计算偏移量
const offset = (page - 1) * pageSize;
// 查询公寓数据
const { count, rows } = await Apartment.findAndCountAll({
where,
@ -68,7 +74,7 @@ const getApartmentById = async (req, res) => {
try {
const { id } = req.params;
const apartment = await Apartment.findOne({
where: { id, isDeleted: 0 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!apartment) {
return response.notFound(res, '公寓不存在');
@ -135,7 +141,7 @@ const updateApartment = async (req, res) => {
const { id } = req.params;
const { name, address, description } = req.body;
const apartment = await Apartment.findOne({
where: { id, isDeleted: 0 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!apartment) {
return response.notFound(res, '公寓不存在');
@ -157,7 +163,7 @@ const deleteApartment = async (req, res) => {
try {
const { id } = req.params;
const apartment = await Apartment.findOne({
where: { id, isDeleted: 0 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!apartment) {
return response.notFound(res, '公寓不存在');
@ -178,7 +184,7 @@ const listApartments = async (req, res) => {
const { regionId, name } = req.query;
// 构建查询条件
const where = { isDeleted: 0 };
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
if (regionId) {
where.regionId = regionId;
}
@ -187,7 +193,7 @@ const listApartments = async (req, res) => {
[Op.like]: `%${name}%`
};
}
// 查询公寓数据
const apartments = await Apartment.findAll({
where

View File

@ -3,6 +3,26 @@ const { Op } = require('sequelize');
const billingService = require('../services/billingService');
const response = require('../utils/response');
// 格式化日期时间(年月日时分秒)
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);
};
// 格式化订单数据
const formatOrderData = (order) => {
const data = order.toJSON ? order.toJSON() : order;
return {
...data,
createTime: formatDateTime(data.createTime),
updateTime: formatDateTime(data.updateTime),
paidTime: formatDateTime(data.paidTime)
};
};
// 获取所有套餐列表
const getAllPlans = async (req, res) => {
try {
@ -318,7 +338,7 @@ const getOrders = async (req, res) => {
});
response.success(res, '获取订单列表成功', {
list: rows,
list: rows.map(formatOrderData),
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
@ -430,7 +450,7 @@ const createOrder = async (req, res) => {
});
response.created(res, '订单创建成功', {
order: createdOrder.toJSON(),
order: formatOrderData(createdOrder),
details: amountInfo.details
});
} catch (error) {
@ -476,7 +496,7 @@ const getOrderDetail = async (req, res) => {
return response.forbidden(res, '无权查看此订单');
}
response.success(res, '获取订单详情成功', order);
response.success(res, '获取订单详情成功', formatOrderData(order));
} catch (error) {
console.error('获取订单详情失败:', error);
response.serverError(res, '获取订单详情失败', error);
@ -582,9 +602,9 @@ const payOrder = async (req, res) => {
response.success(res, '支付成功', {
payment,
order: {
...order.toJSON(),
...formatOrderData(order),
status: 'paid',
paidTime: paidTime
paidTime: formatDateTime(paidTime)
},
tenant: paymentResult
});
@ -632,7 +652,11 @@ const getPayments = async (req, res) => {
});
response.success(res, '获取支付记录成功', {
list: rows,
list: rows.map(r => ({
...(r.toJSON ? r.toJSON() : r),
createTime: formatDateTime(r.createTime),
paidAt: formatDateTime(r.paidAt)
})),
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)

View File

@ -4,6 +4,29 @@ const RoleMenu = require('../models/RoleMenu');
const { logOperation } = require('../utils/logger');
const response = require('../utils/response');
// 格式化日期时间(年月日时分秒)
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);
};
// 格式化菜单数据递归处理children
const formatMenuData = (menu) => {
const data = menu.toJSON ? menu.toJSON() : menu;
const result = {
...data,
createTime: formatDateTime(data.createTime),
updateTime: formatDateTime(data.updateTime)
};
if (data.children && Array.isArray(data.children)) {
result.children = data.children.map(formatMenuData);
}
return result;
};
// 获取菜单树
exports.getMenuTree = async (req, res) => {
try {
@ -61,7 +84,7 @@ exports.getMenuList = async (req, res) => {
});
response.success(res, '获取成功', {
list: rows,
list: rows.map(formatMenuData),
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
@ -85,7 +108,7 @@ exports.getMenuById = async (req, res) => {
return response.notFound(res, '菜单不存在');
}
response.success(res, '获取成功', menu);
response.success(res, '获取成功', formatMenuData(menu));
} catch (error) {
console.error('获取菜单详情失败:', error);
response.serverError(res, '获取菜单详情失败', error);
@ -149,7 +172,7 @@ exports.createMenu = async (req, res) => {
status: 'success'
});
response.success(res, '创建成功', menu);
response.success(res, '创建成功', formatMenuData(menu));
} catch (error) {
console.error('创建菜单失败:', error);
response.serverError(res, '创建菜单失败', error);
@ -221,7 +244,7 @@ exports.updateMenu = async (req, res) => {
status: 'success'
});
response.success(res, '更新成功', menu);
response.success(res, '更新成功', formatMenuData(menu));
} catch (error) {
console.error('更新菜单失败:', error);
response.serverError(res, '更新菜单失败', error);
@ -306,7 +329,7 @@ exports.getRoleMenus = async (req, res) => {
const menus = roleData ? roleData.menus : [];
response.success(res, '获取成功', menus);
response.success(res, '获取成功', menus.map(formatMenuData));
} catch (error) {
console.error('获取角色菜单失败:', error);
response.serverError(res, '获取角色菜单失败', error);
@ -446,7 +469,7 @@ function buildTree(menus, parentId = null) {
.filter(menu => menu.parentId === parentId)
.map(menu => {
// 获取菜单的原始数据
const menuData = menu.toJSON ? menu.toJSON() : menu;
const menuData = formatMenuData(menu);
return {
...menuData,
children: buildTree(menus, menu.id)

View File

@ -5,29 +5,36 @@ const response = require('../utils/response');
// 格式化时间(考虑时区,转换为北京时间)
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 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);
};
// 格式化租房数据
const formatRentalData = (rental) => {
const formattedRental = {
...rental.toJSON(),
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
createTime: formatDate(rental.createTime),
updateTime: formatDate(rental.updateTime)
createTime: formatDateTime(rental.createTime),
updateTime: formatDateTime(rental.updateTime)
};
return formattedRental;
};
// 检查并更新租房状态
const checkAndUpdateRentalStatus = async () => {
const checkAndUpdateRentalStatus = async (tenantId) => {
try {
// 获取当前日期
const currentDate = new Date();
@ -35,9 +42,9 @@ const checkAndUpdateRentalStatus = async () => {
const fiveDaysLater = new Date();
fiveDaysLater.setDate(currentDate.getDate() + 5);
// 查找所有活跃的租房记录
// 查找该租户下所有活跃的租房记录
const rentals = await Rental.findAll({
where: { status: 'active', isDeleted: 0 },
where: { tenantId, status: 'active', isDeleted: 0 },
include: [
{
model: Room,
@ -82,7 +89,7 @@ const checkAndUpdateRentalStatus = async () => {
const getAllRentals = async (req, res) => {
try {
// 先检查并更新租房状态
await checkAndUpdateRentalStatus();
await checkAndUpdateRentalStatus(req.user.tenantId);
const {
apartmentId,
@ -98,7 +105,7 @@ const getAllRentals = async (req, res) => {
} = req.query;
// 构建查询条件
const where = { isDeleted: 0 };
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
if (status) {
where.status = status;
}
@ -166,7 +173,7 @@ const getRentalById = async (req, res) => {
try {
const { id } = req.params;
const rental = await Rental.findOne({
where: { id, isDeleted: 0 },
where: { id, tenantId: req.user.tenantId, isDeleted: 0 },
include: [
{
model: Room,
@ -269,6 +276,7 @@ const createRental = async (req, res) => {
electricityMeterStart: body.electricityMeterStart || null,
status: body.status || 'active',
remark: body.remark,
tenantId: req.user.tenantId,
createBy: req.user.id,
updateBy: req.user.id
});
@ -280,16 +288,17 @@ const createRental = async (req, res) => {
// 自动生成租金账单
await Bill.create({
billNo: 'B' + Date.now(),
rentalId: rental.id,
roomId: parsedRoomId,
renterId: parsedRenterId,
type: 'income',
category: 'rent',
amount: body.rent,
paidAmount: 0,
status: 'pending',
dueDate: body.endDate,
periodStart: body.startDate,
periodEnd: body.endDate,
receivableAmount: body.rent,
receivedAmount: 0,
status: 'unpaid',
billDate: body.startDate,
billMonth: body.startDate.substring(0, 7),
remark: `租约租金 - ${renterName}`,
tenantId: req.user.tenantId,
createBy: req.user.id,
@ -299,14 +308,17 @@ const createRental = async (req, res) => {
// 自动生成押金账单(如果有押金)
if (deposit > 0) {
await Bill.create({
billNo: 'B' + Date.now() + '1',
rentalId: rental.id,
roomId: parsedRoomId,
renterId: parsedRenterId,
type: 'income',
category: 'deposit',
amount: deposit,
paidAmount: 0,
status: 'pending',
dueDate: body.startDate,
receivableAmount: deposit,
receivedAmount: 0,
status: 'unpaid',
billDate: body.startDate,
billMonth: body.startDate.substring(0, 7),
remark: `租约押金 - ${renterName}`,
tenantId: req.user.tenantId,
createBy: req.user.id,
@ -327,7 +339,7 @@ const updateRental = async (req, res) => {
const { id } = req.params;
const { roomId, renterId, startDate, endDate, paymentType, rent, deposit, operator, waterMeterStart, electricityMeterStart, waterMeterEnd, electricityMeterEnd, status, remark } = req.body;
const rental = await Rental.findOne({
where: { id, isDeleted: 0 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!rental) {
return response.notFound(res, '租房记录不存在');
@ -362,7 +374,7 @@ const deleteRental = async (req, res) => {
try {
const { id } = req.params;
const rental = await Rental.findOne({
where: { id, isDeleted: 0 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!rental) {
return response.notFound(res, '租房记录不存在');
@ -384,7 +396,7 @@ const terminateRental = async (req, res) => {
const { waterMeterEnd, electricityMeterEnd, remark } = req.body;
const rental = await Rental.findOne({
where: { id, isDeleted: 0 },
where: { id, tenantId: req.user.tenantId, isDeleted: 0 },
include: [
{
model: Room,
@ -429,13 +441,16 @@ const terminateRental = async (req, res) => {
if (waterUsage > 0 && apartment.waterPrice) {
const waterAmount = waterUsage * parseFloat(apartment.waterPrice);
await Bill.create({
billNo: 'B' + Date.now(),
rentalId: rental.id,
roomId: room.id,
renterId: rental.renterId,
type: 'income',
category: 'water',
amount: waterAmount,
paidAmount: 0,
status: 'pending',
receivableAmount: waterAmount,
receivedAmount: 0,
status: 'unpaid',
billDate: new Date(),
remark: `退租水费 - ${renterName}(用量:${waterUsage.toFixed(2)}吨)`,
tenantId: req.user.tenantId,
createBy: req.user.id,
@ -449,13 +464,16 @@ const terminateRental = async (req, res) => {
if (electricityUsage > 0 && apartment.electricityPrice) {
const electricityAmount = electricityUsage * parseFloat(apartment.electricityPrice);
await Bill.create({
billNo: 'B' + Date.now() + '1',
rentalId: rental.id,
roomId: room.id,
renterId: rental.renterId,
type: 'income',
category: 'electricity',
amount: electricityAmount,
paidAmount: 0,
status: 'pending',
receivableAmount: electricityAmount,
receivedAmount: 0,
status: 'unpaid',
billDate: new Date(),
remark: `退租电费 - ${renterName}(用量:${electricityUsage.toFixed(2)}度)`,
tenantId: req.user.tenantId,
createBy: req.user.id,
@ -481,7 +499,7 @@ const terminateRental = async (req, res) => {
const listRentals = async (req, res) => {
try {
// 先检查并更新租房状态
await checkAndUpdateRentalStatus();
await checkAndUpdateRentalStatus(req.user.tenantId);
const {
apartmentId,
@ -495,7 +513,7 @@ const listRentals = async (req, res) => {
} = req.query;
// 构建查询条件
const where = { isDeleted: 0 };
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
if (status) {
where.status = status;
}

View File

@ -2,6 +2,25 @@ const { Renter, Rental, Room, Apartment } = require('../models');
const { Op } = require('sequelize');
const response = require('../utils/response');
// 格式化日期时间(年月日时分秒)
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);
};
// 格式化租客数据
const formatRenterData = (renter) => {
const data = renter.toJSON ? renter.toJSON() : renter;
return {
...data,
createTime: formatDateTime(data.createTime),
updateTime: formatDateTime(data.updateTime)
};
};
// 获取租客列表(分页)
const getRenters = async (req, res) => {
try {
@ -30,7 +49,7 @@ const getRenters = async (req, res) => {
});
response.success(res, '获取成功', {
list: rows,
list: rows.map(formatRenterData),
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
@ -66,7 +85,7 @@ const getRenterList = async (req, res) => {
order: [['createTime', 'DESC']]
});
response.success(res, '获取成功', rows);
response.success(res, '获取成功', rows.map(formatRenterData));
} catch (error) {
console.error('获取租客列表失败:', error);
response.serverError(res, '获取租客列表失败', error);
@ -107,7 +126,13 @@ const getRenterById = async (req, res) => {
response.success(res, '获取成功', {
...renter.toJSON(),
rentals
createTime: formatDateTime(renter.createTime),
updateTime: formatDateTime(renter.updateTime),
rentals: rentals.map(r => ({
...r.toJSON(),
createTime: formatDateTime(r.createTime),
updateTime: formatDateTime(r.updateTime)
}))
});
} catch (error) {
console.error('获取租客详情失败:', error);
@ -167,7 +192,7 @@ const createRenter = async (req, res) => {
status: 'active'
});
response.created(res, '创建成功', renter);
response.created(res, '创建成功', formatRenterData(renter));
} catch (error) {
console.error('创建租客失败:', error);
response.serverError(res, '创建租客失败', error);
@ -230,7 +255,7 @@ const updateRenter = async (req, res) => {
updateBy
});
response.success(res, '更新成功', renter);
response.success(res, '更新成功', formatRenterData(renter));
} catch (error) {
console.error('更新租客失败:', error);
response.serverError(res, '更新租客失败', error);
@ -254,7 +279,7 @@ const deleteRenter = async (req, res) => {
// 检查是否有进行中的租赁
const activeRentals = await Rental.count({
where: {
tenantName: renter.name,
renterId: id,
tenantId,
status: 'active'
}

View File

@ -6,28 +6,35 @@ const response = require('../utils/response');
// 格式化时间(考虑时区,转换为北京时间)
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 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);
};
// 格式化房间数据
const formatRoomData = (room) => {
const formattedRoom = {
...room.toJSON(),
createTime: formatDate(room.createTime),
updateTime: formatDate(room.updateTime)
createTime: formatDateTime(room.createTime),
updateTime: formatDateTime(room.updateTime)
};
// 格式化关联数据
if (formattedRoom.Apartment) {
formattedRoom.Apartment = {
...formattedRoom.Apartment,
createTime: formatDate(formattedRoom.Apartment.createTime),
updateTime: formatDate(formattedRoom.Apartment.updateTime)
createTime: formatDateTime(formattedRoom.Apartment.createTime),
updateTime: formatDateTime(formattedRoom.Apartment.updateTime)
};
}
@ -38,8 +45,8 @@ const formatRoomData = (room) => {
...rental,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
createTime: formatDate(rental.createTime),
updateTime: formatDate(rental.updateTime)
createTime: formatDateTime(rental.createTime),
updateTime: formatDateTime(rental.updateTime)
};
return formattedRental;
@ -50,7 +57,7 @@ const formatRoomData = (room) => {
};
// 检查并更新租房状态
const checkAndUpdateRentalStatus = async () => {
const checkAndUpdateRentalStatus = async (tenantId) => {
try {
// 获取当前日期
const currentDate = new Date();
@ -58,9 +65,9 @@ const checkAndUpdateRentalStatus = async () => {
const fiveDaysLater = new Date();
fiveDaysLater.setDate(currentDate.getDate() + 5);
// 查找所有在租的租房记录
// 查找该租户下所有在租的租房记录
const rentals = await Rental.findAll({
where: { status: 'active', isDeleted: 0 }
where: { tenantId, status: 'active', isDeleted: 0 }
});
// 检查每个租房记录的状态
@ -99,12 +106,12 @@ const checkAndUpdateRentalStatus = async () => {
const getAllRooms = async (req, res) => {
try {
// 先检查并更新租房状态
await checkAndUpdateRentalStatus();
await checkAndUpdateRentalStatus(req.user.tenantId);
const { apartmentId, roomNumber, status, rentalStatus, page = 1, pageSize = 10 } = req.query;
// 构建查询条件
const where = { isDeleted: 0 };
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
if (apartmentId) {
where.apartmentId = apartmentId;
}
@ -142,6 +149,7 @@ const getAllRooms = async (req, res) => {
const rentals = await Rental.findAll({
where: {
roomId: room.id,
tenantId: req.user.tenantId,
status: { [Op.ne]: 'expired' },
isDeleted: 0
},
@ -195,11 +203,11 @@ const getAllRooms = async (req, res) => {
const getRoomById = async (req, res) => {
try {
// 先检查并更新租房状态
await checkAndUpdateRentalStatus();
await checkAndUpdateRentalStatus(req.user.tenantId);
const { id } = req.params;
const room = await Room.findOne({
where: { id, isDeleted: 0 },
where: { id, tenantId: req.user.tenantId, isDeleted: 0 },
include: [Apartment]
});
if (!room) {
@ -213,6 +221,7 @@ const getRoomById = async (req, res) => {
const rentals = await Rental.findAll({
where: {
roomId: room.id,
tenantId: req.user.tenantId,
status: { [Op.ne]: 'expired' },
isDeleted: 0
},
@ -232,7 +241,7 @@ const getRoomById = async (req, res) => {
...rentalData,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
createTime: formatDate(rental.createTime),
createTime: formatDateTime(rental.createTime),
// 兼容前端显示的字段
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''
@ -314,7 +323,7 @@ const updateRoom = async (req, res) => {
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 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!room) {
return response.notFound(res, '房间不存在');
@ -346,7 +355,7 @@ const deleteRoom = async (req, res) => {
try {
const { id } = req.params;
const room = await Room.findOne({
where: { id, isDeleted: 0 }
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!room) {
return response.notFound(res, '房间不存在');
@ -365,12 +374,12 @@ const deleteRoom = async (req, res) => {
const listRooms = async (req, res) => {
try {
// 先检查并更新租房状态
await checkAndUpdateRentalStatus();
await checkAndUpdateRentalStatus(req.user.tenantId);
const { apartmentId, roomNumber, status, rentalStatus } = req.query;
// 构建查询条件
const where = { isDeleted: 0 };
const where = { tenantId: req.user.tenantId, isDeleted: 0 };
if (apartmentId) {
where.apartmentId = apartmentId;
}
@ -403,6 +412,7 @@ const listRooms = async (req, res) => {
const rentals = await Rental.findAll({
where: {
roomId: room.id,
tenantId: req.user.tenantId,
status: { [Op.ne]: 'expired' },
isDeleted: 0
},
@ -423,7 +433,7 @@ const listRooms = async (req, res) => {
...rentalData,
startDate: formatDate(rental.startDate),
endDate: formatDate(rental.endDate),
createTime: formatDate(rental.createTime),
createTime: formatDateTime(rental.createTime),
// 兼容前端显示的字段
tenantName: rentalData.Renter ? rentalData.Renter.name : '',
tenantPhone: rentalData.Renter ? rentalData.Renter.phone : ''

View File

@ -1,4 +1,5 @@
const { Setting, Category } = require('../models');
const { Op } = require('sequelize');
const response = require('../utils/response');
/**

View File

@ -1,4 +1,4 @@
const { Room, Rental, Apartment, Bill } = require('../models');
const { Room, Rental, Apartment, Bill, Renter } = require('../models');
const { Op } = require('sequelize');
const response = require('../utils/response');
@ -27,9 +27,8 @@ const getRentStatistics = async (req, res) => {
try {
rentals = await Rental.findAll({
where: {
createTime: {
[Op.gte]: startDate
},
tenantId: req.user.tenantId,
createTime: { [Op.gte]: startDate },
isDeleted: 0
}
});
@ -66,10 +65,9 @@ const getRentStatistics = async (req, res) => {
try {
const expenses = await Bill.findAll({
where: {
tenantId: req.user.tenantId,
type: 'expense',
billDate: {
[Op.gte]: startDate
},
billDate: { [Op.gte]: startDate },
isDeleted: 0
}
});
@ -117,7 +115,7 @@ const getRentStatistics = async (req, res) => {
const getRoomStatusStatistics = async (req, res) => {
try {
// 获取所有房间(排除已删除的)
const rooms = await Room.findAll({ where: { isDeleted: 0 } });
const rooms = await Room.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 初始化统计数据
let empty = 0;
@ -162,40 +160,35 @@ const getRoomStatusStatistics = async (req, res) => {
// Dashboard统计数据
const getDashboardStatistics = async (req, res) => {
try {
// 导入所有需要的模型
const { Apartment, Room, WaterBill, Rental, Bill } = require('../models');
const tenantId = req.user.tenantId;
const baseWhere = { tenantId, isDeleted: 0 };
// 并行查询所有统计数据
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 } })
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' } })
]);
// 安全地获取sum统计,处理表不存在或为空的情况
// 安全地获取sum统计
let collectedRentAmount = 0;
let collectedWaterAmount = 0;
try {
const rentResult = await Rental.sum('rent', { where: { isDeleted: 0 } });
const rentResult = await Rental.sum('rent', { where: { tenantId, 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
}
const waterResult = await Bill.sum('receivedAmount', {
where: { tenantId, category: 'water', status: 'paid', isDeleted: 0 }
});
collectedWaterAmount = waterResult || 0;
} catch (e) {
@ -227,10 +220,10 @@ const getDashboardStatistics = async (req, res) => {
const getApartmentRoomStatusStatistics = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 获取所有房间(排除已删除的)
const rooms = await Room.findAll({ where: { isDeleted: 0 } });
const rooms = await Room.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 构建公寓房间状态分布数据
const apartmentRoomStatusStatistics = apartments.map(apartment => {
@ -280,14 +273,15 @@ const getApartmentRoomStatusStatistics = async (req, res) => {
const getEmptyRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 获取所有空房(排除已删除的)
const emptyRooms = await Room.findAll({
where: {
status: 'empty',
isDeleted: 0
}
const emptyRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
status: 'empty',
isDeleted: 0
}
});
// 构建按公寓分组的空房数据
@ -319,14 +313,15 @@ const getEmptyRoomsByApartment = async (req, res) => {
const getRentedRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 获取所有在租房(排除已删除的)
const rentedRooms = await Room.findAll({
where: {
status: 'rented',
isDeleted: 0
}
const rentedRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
status: 'rented',
isDeleted: 0
}
});
// 构建按公寓分组的在租数据
@ -357,26 +352,32 @@ const getRentedRoomsByApartment = async (req, res) => {
// 租客在租统计
const getTenantRentalStats = async (req, res) => {
try {
const tenantId = req.user.tenantId;
// 获取所有在租的租赁记录(排除已删除的)
const rentals = await Rental.findAll({
where: {
tenantId,
status: 'active',
isDeleted: 0
},
include: [{
model: Room,
where: { isDeleted: 0 }
}, {
model: Renter,
attributes: ['id', 'name']
}]
});
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
const apartments = await Apartment.findAll({ where: { tenantId, isDeleted: 0 } });
// 按租客分组
const tenantMap = new Map();
rentals.forEach(rental => {
const tenantName = rental.tenantName;
const tenantName = rental.Renter ? rental.Renter.name : '未知租客';
const room = rental.Room;
if (!tenantMap.has(tenantName)) {
@ -425,15 +426,16 @@ const getTenantRentalStats = async (req, res) => {
const getSoonExpireRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 获取所有即将到期的房间(排除已删除的)
const soonExpireRooms = await Room.findAll({
where: {
const soonExpireRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
status: 'rented',
rentalStatus: 'soon_expire',
isDeleted: 0
}
isDeleted: 0
}
});
// 构建按公寓分组的即将到期房间数据
@ -465,15 +467,16 @@ const getSoonExpireRoomsByApartment = async (req, res) => {
const getExpiredRoomsByApartment = async (req, res) => {
try {
// 获取所有公寓(排除已删除的)
const apartments = await Apartment.findAll({ where: { isDeleted: 0 } });
const apartments = await Apartment.findAll({ where: { tenantId: req.user.tenantId, isDeleted: 0 } });
// 获取所有已到期的房间(排除已删除的)
const expiredRooms = await Room.findAll({
where: {
const expiredRooms = await Room.findAll({
where: {
tenantId: req.user.tenantId,
status: 'rented',
rentalStatus: 'expired',
isDeleted: 0
}
isDeleted: 0
}
});
// 构建按公寓分组的已到期房间数据

View File

@ -1,6 +1,25 @@
const { Tenant, User, Apartment, Room, Category, Setting } = require('../models');
const bcrypt = require('bcryptjs');
// 格式化日期时间(年月日时分秒)
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);
};
// 格式化租户数据
const formatTenantData = (tenant) => {
const data = tenant.toJSON ? tenant.toJSON() : tenant;
return {
...data,
createTime: formatDateTime(data.createTime),
updateTime: formatDateTime(data.updateTime)
};
};
// 获取租户列表(超级管理员)
const getTenantList = async (req, res) => {
try {
@ -66,7 +85,7 @@ const getTenantDetail = async (req, res) => {
res.json({
code: 200,
data: {
...tenant.toJSON(),
...formatTenantData(tenant),
stats: {
userCount,
apartmentCount,
@ -297,7 +316,7 @@ const getCurrentTenant = async (req, res) => {
res.json({
code: 200,
data: {
...tenant.toJSON(),
...formatTenantData(tenant),
stats: {
userCount,
apartmentCount,

View File

@ -6,13 +6,32 @@ const Tenant = require('../models/Tenant');
const { logOperation } = require('../utils/logger');
const response = require('../utils/response');
// 格式化日期时间(年月日时分秒)
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);
};
// 格式化用户数据
const formatUserData = (user) => {
const data = user.toJSON ? user.toJSON() : user;
return {
...data,
createTime: formatDateTime(data.createTime),
updateTime: formatDateTime(data.updateTime)
};
};
// 获取用户列表
exports.getUserList = async (req, res) => {
try {
const { page = 1, pageSize = 10, username, roleId, status } = req.query;
// 构建查询条件 - 只查询普通用户(不包括系统管理员)
const where = { isDeleted: 0, userType: 'user' };
const where = { tenantId: req.user.tenantId, isDeleted: 0, userType: 'user' };
if (username) {
where.username = { [Op.like]: `%${username}%` };
}
@ -38,7 +57,7 @@ exports.getUserList = async (req, res) => {
});
response.success(res, '获取成功', {
list: rows,
list: rows.map(formatUserData),
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
@ -54,8 +73,8 @@ exports.getUserById = async (req, res) => {
try {
const { id } = req.params;
const user = await User.findByPk(id, {
where: { isDeleted: 0 },
const user = await User.findOne({
where: { id, tenantId: req.user.tenantId, isDeleted: 0 },
attributes: ['id', 'username', 'nickname', 'roleId', 'status', 'createTime', 'updateTime'],
include: [{
model: Role,
@ -68,7 +87,7 @@ exports.getUserById = async (req, res) => {
return response.notFound(res, '用户不存在');
}
response.success(res, '获取成功', user);
response.success(res, '获取成功', formatUserData(user));
// 记录操作日志
await logOperation({
@ -147,8 +166,8 @@ exports.createUser = async (req, res) => {
return response.badRequest(res, '角色不存在或已禁用');
}
// 检查用户名是否已存在
const existingUser = await User.findOne({ where: { username, isDeleted: 0 } });
// 检查用户名是否已存在(同一租户下)
const existingUser = await User.findOne({ where: { username, tenantId: req.user.tenantId, isDeleted: 0 } });
if (existingUser) {
return response.badRequest(res, '用户名已存在');
}
@ -172,7 +191,7 @@ exports.createUser = async (req, res) => {
username: user.username,
nickname: user.nickname,
roleId: user.roleId,
createTime: user.createTime,
createTime: formatDateTime(user.createTime),
warning: isOverage && tenant ? `当前已超出套餐限制(${tenant.maxUsers}人),续费时将收取超额费用` : null
});
@ -201,8 +220,8 @@ exports.updateUser = async (req, res) => {
const { nickname, roleId, status } = req.body;
// 查找用户
const user = await User.findByPk(id, {
where: { isDeleted: 0 }
const user = await User.findOne({
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!user) {
return response.notFound(res, '用户不存在');
@ -235,7 +254,7 @@ exports.updateUser = async (req, res) => {
nickname: user.nickname,
roleId: user.roleId,
status: user.status,
updateTime: user.updateTime
updateTime: formatDateTime(user.updateTime)
});
// 记录操作日志
@ -267,8 +286,8 @@ exports.deleteUser = async (req, res) => {
}
// 查找用户
const user = await User.findByPk(id, {
where: { isDeleted: 0 }
const user = await User.findOne({
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!user) {
return response.notFound(res, '用户不存在');
@ -307,8 +326,8 @@ exports.resetUserPassword = async (req, res) => {
const defaultPassword = '123456';
// 查找用户
const user = await User.findByPk(id, {
where: { isDeleted: 0 }
const user = await User.findOne({
where: { id, tenantId: req.user.tenantId, isDeleted: 0 }
});
if (!user) {
return response.notFound(res, '用户不存在');
@ -349,11 +368,11 @@ exports.resetUserPassword = async (req, res) => {
exports.getAllUsers = async (req, res) => {
try {
const users = await User.findAll({
where: { isDeleted: 0, status: 'active' },
where: { tenantId: req.user.tenantId, isDeleted: 0, status: 'active' },
attributes: ['id', 'username', 'nickname']
});
response.success(res, '获取用户列表成功', users);
response.success(res, '获取用户列表成功', users.map(formatUserData));
} catch (error) {
console.error('获取用户列表错误:', error);
response.serverError(res, '获取用户列表失败', error);
@ -376,7 +395,7 @@ exports.getCurrentUserInfo = async (req, res) => {
return response.notFound(res, '用户不存在');
}
response.success(res, '获取用户信息成功', user);
response.success(res, '获取用户信息成功', formatUserData(user));
} catch (error) {
console.error('获取用户信息失败:', error);
response.serverError(res, '获取用户信息失败', error);
@ -422,8 +441,8 @@ exports.updateUserProfile = async (req, res) => {
username: user.username,
nickname: user.nickname,
status: user.status,
createTime: user.createTime,
updateTime: user.updateTime
createTime: formatDateTime(user.createTime),
updateTime: formatDateTime(user.updateTime)
});
} catch (error) {
console.error('更新个人资料失败:', error);

View File

@ -1,15 +1,23 @@
const express = require('express');
const router = express.Router();
const settingController = require('../controllers/settingController');
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
// 设置相关接口
// 所有设置接口都需要认证
router.use(authMiddleware);
// 设置相关接口(读取)
router.get('/', settingController.getSettings);
router.put('/', settingController.updateSettings);
// 类目相关接口
// 设置相关接口(写入需要管理员权限)
router.put('/', adminMiddleware, settingController.updateSettings);
// 类目相关接口(读取)
router.get('/categories', settingController.getCategories);
router.post('/categories', settingController.createCategory);
router.put('/categories/:id', settingController.updateCategory);
router.delete('/categories/:id', settingController.deleteCategory);
// 类目相关接口(写入需要管理员权限)
router.post('/categories', adminMiddleware, settingController.createCategory);
router.put('/categories/:id', adminMiddleware, settingController.updateCategory);
router.delete('/categories/:id', adminMiddleware, settingController.deleteCategory);
module.exports = router;

View File

@ -1,6 +1,10 @@
const express = require('express');
const router = express.Router();
const statisticsController = require('../controllers/statisticsController');
const { authMiddleware } = require('../middleware/auth');
// 所有统计接口都需要认证
router.use(authMiddleware);
// 路由
router.get('/rent', statisticsController.getRentStatistics);