diff --git a/config/db.js b/config/db.js index ebf73fc..65ea9d7 100644 --- a/config/db.js +++ b/config/db.js @@ -9,7 +9,7 @@ const dbConfig = { local: { host: 'localhost', user: 'root', - password: 'root', + password: '123456', database: 'rentease' }, development: { diff --git a/controllers/apartmentController.js b/controllers/apartmentController.js index bcc0d3e..8a33926 100644 --- a/controllers/apartmentController.js +++ b/controllers/apartmentController.js @@ -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 diff --git a/controllers/billingController.js b/controllers/billingController.js index e8c4df5..7cac342 100644 --- a/controllers/billingController.js +++ b/controllers/billingController.js @@ -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) diff --git a/controllers/menuController.js b/controllers/menuController.js index b424f1f..59d21e2 100644 --- a/controllers/menuController.js +++ b/controllers/menuController.js @@ -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) diff --git a/controllers/rentalController.js b/controllers/rentalController.js index ff05824..61e90fd 100644 --- a/controllers/rentalController.js +++ b/controllers/rentalController.js @@ -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; } diff --git a/controllers/renterController.js b/controllers/renterController.js index 43d10bb..3d69328 100644 --- a/controllers/renterController.js +++ b/controllers/renterController.js @@ -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' } diff --git a/controllers/roomController.js b/controllers/roomController.js index 17bc672..396a19c 100644 --- a/controllers/roomController.js +++ b/controllers/roomController.js @@ -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 : '' diff --git a/controllers/settingController.js b/controllers/settingController.js index 2fb2983..fe80fb3 100644 --- a/controllers/settingController.js +++ b/controllers/settingController.js @@ -1,4 +1,5 @@ const { Setting, Category } = require('../models'); +const { Op } = require('sequelize'); const response = require('../utils/response'); /** diff --git a/controllers/statisticsController.js b/controllers/statisticsController.js index 03a9763..7702ba3 100644 --- a/controllers/statisticsController.js +++ b/controllers/statisticsController.js @@ -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 + } }); // 构建按公寓分组的已到期房间数据 diff --git a/controllers/tenantController.js b/controllers/tenantController.js index 23b5216..bab9bf8 100644 --- a/controllers/tenantController.js +++ b/controllers/tenantController.js @@ -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, diff --git a/controllers/userController.js b/controllers/userController.js index 8ac0366..335833c 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -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); diff --git a/routes/settings.js b/routes/settings.js index 0b9f201..9b65718 100644 --- a/routes/settings.js +++ b/routes/settings.js @@ -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; diff --git a/routes/statistics.js b/routes/statistics.js index 923e46c..847bbc7 100644 --- a/routes/statistics.js +++ b/routes/statistics.js @@ -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);