2026-04-20 06:43:09 +00:00
|
|
|
const { Tenant, User, Apartment, Room, Category, Setting } = require('../models');
|
|
|
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 格式化租户数据
|
|
|
|
|
const formatTenantData = (tenant) => {
|
|
|
|
|
const data = tenant.toJSON ? tenant.toJSON() : tenant;
|
|
|
|
|
return {
|
|
|
|
|
...data,
|
|
|
|
|
createTime: formatDateTime(data.createTime),
|
|
|
|
|
updateTime: formatDateTime(data.updateTime)
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-20 06:43:09 +00:00
|
|
|
// 获取租户列表(超级管理员)
|
|
|
|
|
const getTenantList = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { page = 1, pageSize = 10, keyword, status } = req.query;
|
|
|
|
|
const offset = (page - 1) * pageSize;
|
|
|
|
|
|
|
|
|
|
const where = { isDeleted: 0 };
|
|
|
|
|
if (keyword) {
|
|
|
|
|
where.code = { [require('sequelize').Op.like]: `%${keyword}%` };
|
|
|
|
|
}
|
|
|
|
|
if (status) {
|
|
|
|
|
where.status = status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { count, rows } = await Tenant.findAndCountAll({
|
|
|
|
|
where,
|
|
|
|
|
limit: parseInt(pageSize),
|
|
|
|
|
offset: parseInt(offset),
|
|
|
|
|
order: [['createTime', 'DESC']]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: {
|
|
|
|
|
list: rows,
|
|
|
|
|
total: count,
|
|
|
|
|
page: parseInt(page),
|
|
|
|
|
pageSize: parseInt(pageSize)
|
|
|
|
|
},
|
|
|
|
|
message: '获取成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取租户列表失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '获取租户列表失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取租户详情
|
|
|
|
|
const getTenantDetail = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
const tenant = await Tenant.findOne({
|
|
|
|
|
where: { id, isDeleted: 0 }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!tenant) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '租户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取租户统计数据
|
|
|
|
|
const userCount = await User.count({ where: { tenantId: id, isDeleted: 0 } });
|
|
|
|
|
const apartmentCount = await Apartment.count({ where: { tenantId: id, isDeleted: 0 } });
|
|
|
|
|
const roomCount = await Room.count({ where: { tenantId: id, isDeleted: 0 } });
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: {
|
2026-05-09 09:01:41 +00:00
|
|
|
...formatTenantData(tenant),
|
2026-04-20 06:43:09 +00:00
|
|
|
stats: {
|
|
|
|
|
userCount,
|
|
|
|
|
apartmentCount,
|
|
|
|
|
roomCount
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
message: '获取成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取租户详情失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '获取租户详情失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 创建租户
|
|
|
|
|
const createTenant = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const {
|
|
|
|
|
code,
|
|
|
|
|
description,
|
|
|
|
|
contactName,
|
|
|
|
|
contactPhone,
|
|
|
|
|
contactEmail,
|
|
|
|
|
maxUsers,
|
|
|
|
|
maxApartments,
|
|
|
|
|
maxRooms,
|
|
|
|
|
adminUsername,
|
|
|
|
|
adminPassword
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
// 检查租户编码是否已存在
|
|
|
|
|
const existingTenant = await Tenant.findOne({ where: { code } });
|
|
|
|
|
if (existingTenant) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
code: 400,
|
|
|
|
|
message: '租户编码已存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建租户
|
|
|
|
|
const tenant = await Tenant.create({
|
|
|
|
|
code,
|
|
|
|
|
description,
|
|
|
|
|
contactName,
|
|
|
|
|
contactPhone,
|
|
|
|
|
contactEmail,
|
|
|
|
|
maxUsers: maxUsers || 10,
|
|
|
|
|
maxApartments: maxApartments || 5,
|
|
|
|
|
maxRooms: maxRooms || 50,
|
|
|
|
|
status: 'active',
|
|
|
|
|
createBy: req.user.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 创建租户管理员账号
|
|
|
|
|
if (adminUsername && adminPassword) {
|
|
|
|
|
const hashedPassword = await bcrypt.hash(adminPassword, 10);
|
|
|
|
|
await User.create({
|
|
|
|
|
username: adminUsername,
|
|
|
|
|
password: hashedPassword,
|
|
|
|
|
nickname: '管理员',
|
|
|
|
|
tenantId: tenant.id,
|
|
|
|
|
userType: 'tenant_admin',
|
|
|
|
|
status: 'active',
|
|
|
|
|
createBy: req.user.id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始化默认收支类目
|
|
|
|
|
const defaultCategories = [
|
|
|
|
|
// 收入类目
|
|
|
|
|
{ name: '租金', code: 'rent', type: 'income', sort: 1 },
|
|
|
|
|
{ name: '水费', code: 'water', type: 'income', sort: 2 },
|
|
|
|
|
{ name: '电费', code: 'electricity', type: 'income', sort: 3 },
|
|
|
|
|
{ name: '燃气费', code: 'gas', type: 'income', sort: 4 },
|
|
|
|
|
{ name: '押金', code: 'deposit', type: 'income', sort: 5 },
|
|
|
|
|
{ name: '物业费', code: 'property_fee', type: 'income', sort: 6 },
|
|
|
|
|
{ name: '违约金', code: 'penalty', type: 'income', sort: 7 },
|
|
|
|
|
{ name: '其他收入', code: 'other_income', type: 'income', sort: 8 },
|
|
|
|
|
// 支出类目
|
|
|
|
|
{ name: '维修费', code: 'maintenance', type: 'expense', sort: 1 },
|
|
|
|
|
{ name: '中介费', code: 'agency_fee', type: 'expense', sort: 2 },
|
|
|
|
|
{ name: '物业费', code: 'property_fee_expense', type: 'expense', sort: 3 },
|
|
|
|
|
{ name: '水费', code: 'water_expense', type: 'expense', sort: 4 },
|
|
|
|
|
{ name: '电费', code: 'electricity_expense', type: 'expense', sort: 5 },
|
|
|
|
|
{ name: '燃气费', code: 'gas_expense', type: 'expense', sort: 6 },
|
|
|
|
|
{ name: '装修费', code: 'renovation', type: 'expense', sort: 7 },
|
|
|
|
|
{ name: '其他支出', code: 'other_expense', type: 'expense', sort: 8 }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const category of defaultCategories) {
|
|
|
|
|
await Category.create({
|
|
|
|
|
...category,
|
|
|
|
|
tenantId: tenant.id,
|
|
|
|
|
status: 'active',
|
|
|
|
|
isDefault: 1,
|
|
|
|
|
createBy: req.user.id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始化默认系统设置
|
|
|
|
|
const defaultSettings = [
|
|
|
|
|
{ key: 'expireReminderDays', value: '30', description: '房间到期提前提醒天数' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const setting of defaultSettings) {
|
|
|
|
|
await Setting.create({
|
|
|
|
|
...setting,
|
|
|
|
|
tenantId: tenant.id,
|
|
|
|
|
createBy: req.user.id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: tenant,
|
|
|
|
|
message: '租户创建成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('创建租户失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '创建租户失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 更新租户
|
|
|
|
|
const updateTenant = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const updateData = req.body;
|
|
|
|
|
|
|
|
|
|
const tenant = await Tenant.findOne({
|
|
|
|
|
where: { id, isDeleted: 0 }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!tenant) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '租户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 不允许修改租户编码
|
|
|
|
|
delete updateData.code;
|
|
|
|
|
delete updateData.id;
|
|
|
|
|
|
|
|
|
|
updateData.updateBy = req.user.id;
|
|
|
|
|
|
|
|
|
|
await tenant.update(updateData);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: tenant,
|
|
|
|
|
message: '租户更新成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('更新租户失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '更新租户失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 删除租户
|
|
|
|
|
const deleteTenant = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
const tenant = await Tenant.findOne({
|
|
|
|
|
where: { id, isDeleted: 0 }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!tenant) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '租户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await tenant.update({
|
|
|
|
|
isDeleted: 1,
|
|
|
|
|
updateBy: req.user.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
message: '租户删除成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除租户失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '删除租户失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取当前租户信息
|
|
|
|
|
const getCurrentTenant = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const tenantId = req.tenantId;
|
|
|
|
|
|
|
|
|
|
const tenant = await Tenant.findOne({
|
|
|
|
|
where: { id: tenantId, isDeleted: 0 }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!tenant) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '租户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取租户统计数据
|
|
|
|
|
const userCount = await User.count({ where: { tenantId, isDeleted: 0 } });
|
|
|
|
|
const apartmentCount = await Apartment.count({ where: { tenantId, isDeleted: 0 } });
|
|
|
|
|
const roomCount = await Room.count({ where: { tenantId, isDeleted: 0 } });
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: {
|
2026-05-09 09:01:41 +00:00
|
|
|
...formatTenantData(tenant),
|
2026-04-20 06:43:09 +00:00
|
|
|
stats: {
|
|
|
|
|
userCount,
|
|
|
|
|
apartmentCount,
|
|
|
|
|
roomCount
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
message: '获取成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取当前租户信息失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '获取当前租户信息失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 更新当前租户信息
|
|
|
|
|
const updateCurrentTenant = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const tenantId = req.tenantId;
|
|
|
|
|
const updateData = req.body;
|
|
|
|
|
|
|
|
|
|
const tenant = await Tenant.findOne({
|
|
|
|
|
where: { id: tenantId, isDeleted: 0 }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!tenant) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
code: 404,
|
|
|
|
|
message: '租户不存在'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 只允许修改特定字段
|
|
|
|
|
const allowedFields = ['description', 'contactName', 'contactPhone', 'contactEmail', 'logo'];
|
|
|
|
|
const filteredData = {};
|
|
|
|
|
allowedFields.forEach(field => {
|
|
|
|
|
if (updateData[field] !== undefined) {
|
|
|
|
|
filteredData[field] = updateData[field];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
filteredData.updateBy = req.user.id;
|
|
|
|
|
|
|
|
|
|
await tenant.update(filteredData);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: tenant,
|
|
|
|
|
message: '租户信息更新成功'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('更新当前租户信息失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
code: 500,
|
|
|
|
|
message: '更新当前租户信息失败',
|
|
|
|
|
error: error.message
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
getTenantList,
|
|
|
|
|
getTenantDetail,
|
|
|
|
|
createTenant,
|
|
|
|
|
updateTenant,
|
|
|
|
|
deleteTenant,
|
|
|
|
|
getCurrentTenant,
|
|
|
|
|
updateCurrentTenant
|
|
|
|
|
};
|