1165 lines
28 KiB
JavaScript
1165 lines
28 KiB
JavaScript
const { SubscriptionPlan, PricingConfig, Order, Payment, Tenant, User, Apartment, Room, PaymentSetting } = require('../models');
|
||
const { Op } = require('sequelize');
|
||
const billingService = require('../services/billingService');
|
||
|
||
// 获取所有套餐列表
|
||
const getAllPlans = async (req, res) => {
|
||
try {
|
||
const plans = await SubscriptionPlan.findAll({
|
||
where: { isDeleted: 0 },
|
||
order: [['sort', 'ASC']]
|
||
});
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '获取套餐列表成功',
|
||
data: plans
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取套餐列表失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 创建套餐
|
||
const createPlan = async (req, res) => {
|
||
try {
|
||
const { name, description, maxApartments, maxRooms, maxUsers, monthlyPrice } = req.body;
|
||
|
||
// 验证必填字段
|
||
if (!name) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐名称不能为空'
|
||
});
|
||
}
|
||
|
||
// 检查是否是第一个套餐
|
||
const existingPlans = await SubscriptionPlan.count({ where: { isDeleted: 0 } });
|
||
const isDefault = existingPlans === 0;
|
||
|
||
// 创建套餐
|
||
const plan = await SubscriptionPlan.create({
|
||
name,
|
||
description,
|
||
maxApartments: maxApartments || 10,
|
||
maxRooms: maxRooms || 50,
|
||
maxUsers: maxUsers || 5,
|
||
monthlyPrice: monthlyPrice || 0,
|
||
isDefault
|
||
});
|
||
|
||
res.status(201).json({
|
||
code: 200,
|
||
message: '套餐创建成功',
|
||
data: plan
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '创建套餐失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 更新套餐
|
||
const updatePlan = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
const { name, description, maxApartments, maxRooms, maxUsers, monthlyPrice, status, sort } = req.body;
|
||
|
||
// 查找套餐
|
||
const plan = await SubscriptionPlan.findOne({
|
||
where: { id, isDeleted: 0 }
|
||
});
|
||
|
||
if (!plan) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐不存在'
|
||
});
|
||
}
|
||
|
||
// 更新套餐信息
|
||
await plan.update({
|
||
name: name !== undefined ? name : plan.name,
|
||
description: description !== undefined ? description : plan.description,
|
||
maxApartments: maxApartments !== undefined ? maxApartments : plan.maxApartments,
|
||
maxRooms: maxRooms !== undefined ? maxRooms : plan.maxRooms,
|
||
maxUsers: maxUsers !== undefined ? maxUsers : plan.maxUsers,
|
||
monthlyPrice: monthlyPrice !== undefined ? monthlyPrice : plan.monthlyPrice,
|
||
status: status !== undefined ? status : plan.status,
|
||
sort: sort !== undefined ? sort : plan.sort
|
||
});
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '套餐更新成功',
|
||
data: plan
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '更新套餐失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 删除套餐(软删除)
|
||
const deletePlan = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
// 查找套餐
|
||
const plan = await SubscriptionPlan.findOne({
|
||
where: { id, isDeleted: 0 }
|
||
});
|
||
|
||
if (!plan) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐不存在'
|
||
});
|
||
}
|
||
|
||
// 不能删除默认套餐
|
||
if (plan.isDefault) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '不能删除默认套餐'
|
||
});
|
||
}
|
||
|
||
// 软删除
|
||
await plan.update({ isDeleted: 1 });
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '套餐删除成功'
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '删除套餐失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 设置默认套餐
|
||
const setDefaultPlan = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
// 查找套餐
|
||
const plan = await SubscriptionPlan.findOne({
|
||
where: { id, isDeleted: 0 }
|
||
});
|
||
|
||
if (!plan) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐不存在'
|
||
});
|
||
}
|
||
|
||
// 将其他套餐 isDefault 设为 false
|
||
await SubscriptionPlan.update(
|
||
{ isDefault: false },
|
||
{ where: { isDeleted: 0 } }
|
||
);
|
||
|
||
// 将指定套餐 isDefault 设为 true
|
||
await plan.update({ isDefault: true });
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '设置默认套餐成功',
|
||
data: plan
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '设置默认套餐失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取当前价格配置
|
||
const getPricingConfig = async (req, res) => {
|
||
try {
|
||
// 查询 isActive=true 且最新的记录
|
||
const config = await PricingConfig.findOne({
|
||
where: { isActive: true, isDeleted: 0 },
|
||
order: [['createTime', 'DESC']]
|
||
});
|
||
|
||
// 如果没有则返回默认配置
|
||
if (!config) {
|
||
return res.status(200).json({
|
||
code: 200,
|
||
message: '获取价格配置成功',
|
||
data: {
|
||
overageApartmentPrice: 10.00,
|
||
overageRoomPrice: 2.00,
|
||
overageUserPrice: 5.00,
|
||
currency: 'CNY'
|
||
}
|
||
});
|
||
}
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '获取价格配置成功',
|
||
data: config
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取价格配置失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 更新价格配置
|
||
const updatePricingConfig = async (req, res) => {
|
||
try {
|
||
const { overageApartmentPrice, overageRoomPrice, overageUserPrice } = req.body;
|
||
|
||
// 将旧配置 isActive 设为 false
|
||
await PricingConfig.update(
|
||
{ isActive: false },
|
||
{ where: { isActive: true } }
|
||
);
|
||
|
||
// 创建新配置记录
|
||
const config = await PricingConfig.create({
|
||
overageApartmentPrice: overageApartmentPrice || 10.00,
|
||
overageRoomPrice: overageRoomPrice || 2.00,
|
||
overageUserPrice: overageUserPrice || 5.00,
|
||
isActive: true
|
||
});
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '价格配置更新成功',
|
||
data: config
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '更新价格配置失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 初始化默认套餐数据
|
||
const initDefaultPlans = async (req, res) => {
|
||
try {
|
||
// 检查是否已有套餐
|
||
const existingCount = await SubscriptionPlan.count({ where: { isDeleted: 0 } });
|
||
if (existingCount > 0) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐数据已存在,无需初始化'
|
||
});
|
||
}
|
||
|
||
// 默认套餐数据
|
||
const defaultPlans = [
|
||
{
|
||
name: '免费版',
|
||
description: '适合个人用户试用',
|
||
maxApartments: 2,
|
||
maxRooms: 10,
|
||
maxUsers: 2,
|
||
monthlyPrice: 0,
|
||
isDefault: true,
|
||
sort: 1
|
||
},
|
||
{
|
||
name: '基础版',
|
||
description: '适合小型公寓管理',
|
||
maxApartments: 5,
|
||
maxRooms: 50,
|
||
maxUsers: 5,
|
||
monthlyPrice: 99,
|
||
isDefault: false,
|
||
sort: 2
|
||
},
|
||
{
|
||
name: '专业版',
|
||
description: '适合中型公寓管理',
|
||
maxApartments: 20,
|
||
maxRooms: 200,
|
||
maxUsers: 20,
|
||
monthlyPrice: 299,
|
||
isDefault: false,
|
||
sort: 3
|
||
},
|
||
{
|
||
name: '旗舰版',
|
||
description: '适合大型公寓管理',
|
||
maxApartments: 100,
|
||
maxRooms: 1000,
|
||
maxUsers: 100,
|
||
monthlyPrice: 999,
|
||
isDefault: false,
|
||
sort: 4
|
||
}
|
||
];
|
||
|
||
// 批量创建套餐
|
||
const plans = await SubscriptionPlan.bulkCreate(defaultPlans);
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '默认套餐初始化成功',
|
||
data: plans
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '初始化默认套餐失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 初始化默认价格配置
|
||
const initDefaultPricing = async (req, res) => {
|
||
try {
|
||
// 检查是否已有活跃配置
|
||
const existingConfig = await PricingConfig.findOne({
|
||
where: { isActive: true, isDeleted: 0 }
|
||
});
|
||
|
||
if (existingConfig) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '价格配置已存在,无需初始化'
|
||
});
|
||
}
|
||
|
||
// 创建默认价格配置
|
||
const config = await PricingConfig.create({
|
||
overageApartmentPrice: 10.00,
|
||
overageRoomPrice: 2.00,
|
||
overageUserPrice: 5.00,
|
||
isActive: true
|
||
});
|
||
|
||
res.status(200).json({
|
||
code: 200,
|
||
message: '默认价格配置初始化成功',
|
||
data: config
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '初始化默认价格配置失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取订单列表
|
||
const getOrders = async (req, res) => {
|
||
try {
|
||
const { page = 1, pageSize = 10, tenantId, status } = req.query;
|
||
const offset = (page - 1) * pageSize;
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
const currentTenantId = req.tenantId;
|
||
|
||
const where = { isDeleted: 0 };
|
||
|
||
// 权限检查:普通租户只能查看自己的订单
|
||
if (!isSystemAdmin) {
|
||
where.tenantId = currentTenantId;
|
||
} else if (tenantId) {
|
||
// 系统管理员可以查看指定租户的订单
|
||
where.tenantId = tenantId;
|
||
}
|
||
|
||
if (status) {
|
||
where.status = status;
|
||
}
|
||
|
||
const { count, rows } = await Order.findAndCountAll({
|
||
where,
|
||
limit: parseInt(pageSize),
|
||
offset: parseInt(offset),
|
||
order: [['createTime', 'DESC']],
|
||
include: [
|
||
{
|
||
model: Tenant,
|
||
as: 'tenant',
|
||
attributes: ['id', 'code', 'contactName']
|
||
},
|
||
{
|
||
model: SubscriptionPlan,
|
||
as: 'subscriptionPlan',
|
||
attributes: ['id', 'name', 'monthlyPrice']
|
||
}
|
||
]
|
||
});
|
||
|
||
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 calculatePrice = async (req, res) => {
|
||
try {
|
||
const { planId, months } = req.body;
|
||
const tenantId = req.tenantId;
|
||
|
||
// 验证必填字段
|
||
if (!planId || !months) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐ID和购买月数不能为空'
|
||
});
|
||
}
|
||
|
||
if (months < 1 || months > 36) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '购买月数必须在1-36之间'
|
||
});
|
||
}
|
||
|
||
// 获取套餐信息
|
||
const plan = await SubscriptionPlan.findOne({
|
||
where: { id: planId, isDeleted: 0 }
|
||
});
|
||
|
||
if (!plan) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐不存在'
|
||
});
|
||
}
|
||
|
||
// 计算订单金额(含超额费用)
|
||
const amountInfo = await billingService.calculateRenewalAmount(tenantId, planId, months);
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '计算成功',
|
||
data: amountInfo
|
||
});
|
||
} catch (error) {
|
||
console.error('计算价格失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '计算价格失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 创建续费订单
|
||
const createOrder = async (req, res) => {
|
||
try {
|
||
const { planId, months } = req.body;
|
||
const tenantId = req.tenantId;
|
||
|
||
// 验证必填字段
|
||
if (!planId || !months) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐ID和购买月数不能为空'
|
||
});
|
||
}
|
||
|
||
if (months < 1 || months > 36) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '购买月数必须在1-36之间'
|
||
});
|
||
}
|
||
|
||
// 获取套餐信息
|
||
const plan = await SubscriptionPlan.findOne({
|
||
where: { id: planId, isDeleted: 0 }
|
||
});
|
||
|
||
if (!plan) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '套餐不存在'
|
||
});
|
||
}
|
||
|
||
// 计算订单金额
|
||
const amountInfo = await billingService.calculateRenewalAmount(tenantId, planId, months);
|
||
|
||
// 生成订单编号
|
||
const orderNo = `ORD${Date.now()}${Math.floor(Math.random() * 1000)}`;
|
||
|
||
// 计算过期时间(30分钟后过期)
|
||
const expireTime = new Date();
|
||
expireTime.setMinutes(expireTime.getMinutes() + 30);
|
||
|
||
// 创建订单
|
||
const order = await Order.create({
|
||
orderNo,
|
||
tenantId,
|
||
planId,
|
||
planName: plan.name,
|
||
months,
|
||
amount: amountInfo.totalAmount,
|
||
discountAmount: 0,
|
||
actualAmount: amountInfo.totalAmount,
|
||
status: 'pending',
|
||
expireTime,
|
||
createBy: req.user.id,
|
||
updateBy: req.user.id
|
||
});
|
||
|
||
// 重新查询订单以确保数据完整,包含关联数据
|
||
const createdOrder = await Order.findByPk(order.id, {
|
||
include: [
|
||
{
|
||
model: Tenant,
|
||
as: 'tenant',
|
||
attributes: ['id', 'code', 'contactName']
|
||
},
|
||
{
|
||
model: SubscriptionPlan,
|
||
as: 'subscriptionPlan',
|
||
attributes: ['id', 'name', 'monthlyPrice']
|
||
}
|
||
]
|
||
});
|
||
|
||
res.status(201).json({
|
||
code: 200,
|
||
message: '订单创建成功',
|
||
data: {
|
||
order: createdOrder.toJSON(),
|
||
details: amountInfo.details
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('创建订单失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '创建订单失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取订单详情
|
||
const getOrderDetail = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
const currentTenantId = req.tenantId;
|
||
|
||
const order = await Order.findOne({
|
||
where: { id, isDeleted: 0 },
|
||
include: [
|
||
{
|
||
model: Tenant,
|
||
as: 'tenant',
|
||
attributes: ['id', 'code', 'contactName']
|
||
},
|
||
{
|
||
model: SubscriptionPlan,
|
||
as: 'subscriptionPlan',
|
||
attributes: ['id', 'name', 'description', 'monthlyPrice', 'maxApartments', 'maxRooms', 'maxUsers']
|
||
},
|
||
{
|
||
model: Payment,
|
||
as: 'payments',
|
||
attributes: ['id', 'amount', 'paymentMethod', 'status', 'transactionId', 'paidAt', 'createTime']
|
||
}
|
||
]
|
||
});
|
||
|
||
if (!order) {
|
||
return res.status(404).json({
|
||
code: 404,
|
||
message: '订单不存在'
|
||
});
|
||
}
|
||
|
||
// 权限检查:普通租户只能查看自己的订单
|
||
if (!isSystemAdmin && order.tenantId !== currentTenantId) {
|
||
return res.status(403).json({
|
||
code: 403,
|
||
message: '无权查看此订单'
|
||
});
|
||
}
|
||
|
||
res.json({
|
||
code: 200,
|
||
data: order,
|
||
message: '获取订单详情成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取订单详情失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取订单详情失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 取消订单
|
||
const cancelOrder = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
const currentTenantId = req.tenantId;
|
||
|
||
const order = await Order.findOne({
|
||
where: { id, isDeleted: 0 }
|
||
});
|
||
|
||
if (!order) {
|
||
return res.status(404).json({
|
||
code: 404,
|
||
message: '订单不存在'
|
||
});
|
||
}
|
||
|
||
// 权限检查:普通租户只能取消自己的订单
|
||
if (!isSystemAdmin && order.tenantId !== currentTenantId) {
|
||
return res.status(403).json({
|
||
code: 403,
|
||
message: '无权取消此订单'
|
||
});
|
||
}
|
||
|
||
// 只能取消待支付的订单
|
||
if (order.status !== 'pending') {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '只能取消待支付的订单'
|
||
});
|
||
}
|
||
|
||
await order.update({ status: 'cancelled' });
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '订单取消成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('取消订单失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '取消订单失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 支付订单(管理员确认收款)
|
||
const payOrder = async (req, res) => {
|
||
try {
|
||
const { id: orderId } = req.params;
|
||
const { paymentMethod = 'other', amount, transactionId: customTransactionId, remark } = req.body || {};
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
const currentTenantId = req.tenantId;
|
||
|
||
if (!orderId) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '订单ID不能为空'
|
||
});
|
||
}
|
||
|
||
// 只有管理员可以确认支付
|
||
if (!isSystemAdmin) {
|
||
return res.status(403).json({
|
||
code: 403,
|
||
message: '无权操作,请联系管理员'
|
||
});
|
||
}
|
||
|
||
// 查询订单
|
||
const order = await Order.findOne({
|
||
where: { id: orderId, isDeleted: 0 }
|
||
});
|
||
|
||
if (!order) {
|
||
return res.status(404).json({
|
||
code: 404,
|
||
message: '订单不存在'
|
||
});
|
||
}
|
||
|
||
// 验证订单状态
|
||
if (order.status !== 'pending') {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '订单状态异常,无法支付'
|
||
});
|
||
}
|
||
|
||
// 验证金额
|
||
const paymentAmount = parseFloat(amount) || order.actualAmount;
|
||
if (paymentAmount <= 0) {
|
||
return res.status(400).json({
|
||
code: 400,
|
||
message: '支付金额必须大于0'
|
||
});
|
||
}
|
||
|
||
// 生成或使用自定义交易流水号
|
||
const transactionId = customTransactionId || `PAY${Date.now()}${Math.floor(Math.random() * 1000)}`;
|
||
|
||
// 创建支付记录
|
||
const payment = await Payment.create({
|
||
orderId,
|
||
tenantId: order.tenantId,
|
||
amount: paymentAmount,
|
||
paymentMethod: ['alipay', 'wechat', 'bank', 'other'].includes(paymentMethod) ? paymentMethod : 'other',
|
||
status: 'success',
|
||
transactionId,
|
||
paidAt: new Date(),
|
||
remark: remark || '管理员确认收款'
|
||
});
|
||
|
||
// 更新订单状态
|
||
const paidTime = new Date();
|
||
await order.update({
|
||
status: 'paid',
|
||
paidTime: paidTime
|
||
});
|
||
|
||
// 处理支付成功后的租户状态更新
|
||
// 使用订单的months字段和planId更新资源配置
|
||
const paymentResult = await billingService.processPaymentSuccess(order.tenantId, order.months, order.planId);
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '支付成功',
|
||
data: {
|
||
payment,
|
||
order: {
|
||
...order.toJSON(),
|
||
status: 'paid',
|
||
paidTime: paidTime
|
||
},
|
||
tenant: paymentResult
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('支付订单失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '支付订单失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取支付记录
|
||
const getPayments = async (req, res) => {
|
||
try {
|
||
const { page = 1, pageSize = 10, tenantId } = req.query;
|
||
const offset = (page - 1) * pageSize;
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
const currentTenantId = req.tenantId;
|
||
|
||
const where = { isDeleted: 0 };
|
||
|
||
// 权限检查:普通租户只能查看自己的支付记录
|
||
if (!isSystemAdmin) {
|
||
where.tenantId = currentTenantId;
|
||
} else if (tenantId) {
|
||
// 系统管理员可以查看指定租户的支付记录
|
||
where.tenantId = tenantId;
|
||
}
|
||
|
||
const { count, rows } = await Payment.findAndCountAll({
|
||
where,
|
||
limit: parseInt(pageSize),
|
||
offset: parseInt(offset),
|
||
order: [['createTime', 'DESC']],
|
||
include: [
|
||
{
|
||
model: Tenant,
|
||
as: 'tenant',
|
||
attributes: ['id', 'code', 'contactName']
|
||
},
|
||
{
|
||
model: Order,
|
||
as: 'order',
|
||
attributes: ['id', 'orderNo', 'planId', 'planName', 'months', 'amount', 'discountAmount', 'actualAmount']
|
||
}
|
||
]
|
||
});
|
||
|
||
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 getBillingInfo = async (req, res) => {
|
||
try {
|
||
const tenantId = req.tenantId;
|
||
|
||
const tenant = await Tenant.findOne({
|
||
where: { id: tenantId, isDeleted: 0 },
|
||
include: [
|
||
{
|
||
model: SubscriptionPlan,
|
||
as: 'subscriptionPlan',
|
||
attributes: ['id', 'name', 'description', 'monthlyPrice', 'maxApartments', 'maxRooms', 'maxUsers']
|
||
}
|
||
]
|
||
});
|
||
|
||
if (!tenant) {
|
||
return res.status(404).json({
|
||
code: 404,
|
||
message: '租户不存在'
|
||
});
|
||
}
|
||
|
||
// 获取当前使用量
|
||
const [apartmentCount, roomCount, userCount] = await Promise.all([
|
||
Apartment.count({ where: { tenantId, isDeleted: 0 } }),
|
||
Room.count({ where: { tenantId, isDeleted: 0 } }),
|
||
User.count({ where: { tenantId, isDeleted: 0 } })
|
||
]);
|
||
|
||
res.json({
|
||
code: 200,
|
||
data: {
|
||
tenant: {
|
||
id: tenant.id,
|
||
name: tenant.name,
|
||
code: tenant.code,
|
||
billingStatus: tenant.billingStatus,
|
||
trialStartDate: tenant.trialStartDate,
|
||
trialEndDate: tenant.trialEndDate,
|
||
paidStartDate: tenant.paidStartDate,
|
||
paidEndDate: tenant.paidEndDate,
|
||
currentPeriodStart: tenant.currentPeriodStart,
|
||
currentPeriodEnd: tenant.currentPeriodEnd
|
||
},
|
||
plan: tenant.subscriptionPlan,
|
||
usage: {
|
||
apartments: apartmentCount,
|
||
rooms: roomCount,
|
||
users: userCount
|
||
}
|
||
},
|
||
message: '获取计费信息成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取计费信息失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取计费信息失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取资源使用情况
|
||
const getUsageStats = async (req, res) => {
|
||
try {
|
||
const tenantId = req.tenantId;
|
||
|
||
const usageStats = await billingService.calculateOverage(tenantId);
|
||
|
||
res.json({
|
||
code: 200,
|
||
data: usageStats,
|
||
message: '获取资源使用情况成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取资源使用情况失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取资源使用情况失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取计费统计(仅系统管理员)
|
||
const getBillingStats = async (req, res) => {
|
||
try {
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
|
||
if (!isSystemAdmin) {
|
||
return res.status(403).json({
|
||
code: 403,
|
||
message: '无权访问此接口'
|
||
});
|
||
}
|
||
|
||
// 各计费状态租户数量
|
||
const billingStatusStats = await Tenant.findAll({
|
||
where: { isDeleted: 0 },
|
||
attributes: ['billingStatus', [require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count']],
|
||
group: ['billingStatus']
|
||
});
|
||
|
||
// 总收入
|
||
const totalRevenue = await Payment.sum('amount', {
|
||
where: { status: 'success', isDeleted: 0 }
|
||
});
|
||
|
||
// 本月收入
|
||
const now = new Date();
|
||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||
const monthRevenue = await Payment.sum('amount', {
|
||
where: {
|
||
status: 'success',
|
||
isDeleted: 0,
|
||
paidAt: {
|
||
[Op.gte]: monthStart
|
||
}
|
||
}
|
||
});
|
||
|
||
// 即将到期租户数(7天内)
|
||
const reminderDate = new Date();
|
||
reminderDate.setDate(reminderDate.getDate() + 7);
|
||
|
||
const upcomingExpiredCount = await Tenant.count({
|
||
where: {
|
||
isDeleted: 0,
|
||
[Op.or]: [
|
||
{
|
||
billingStatus: 'trial_active',
|
||
trialEndDate: {
|
||
[Op.lte]: reminderDate,
|
||
[Op.gte]: now
|
||
}
|
||
},
|
||
{
|
||
billingStatus: 'paid_active',
|
||
paidEndDate: {
|
||
[Op.lte]: reminderDate,
|
||
[Op.gte]: now
|
||
}
|
||
}
|
||
]
|
||
}
|
||
});
|
||
|
||
// 已过期租户数
|
||
const expiredCount = await Tenant.count({
|
||
where: {
|
||
isDeleted: 0,
|
||
billingStatus: {
|
||
[Op.in]: ['trial_expired', 'paid_expired']
|
||
}
|
||
}
|
||
});
|
||
|
||
// 待处理订单数
|
||
const pendingOrdersCount = await Order.count({
|
||
where: {
|
||
status: 'pending',
|
||
isDeleted: 0
|
||
}
|
||
});
|
||
|
||
res.json({
|
||
code: 200,
|
||
data: {
|
||
billingStatusStats: billingStatusStats.reduce((acc, item) => {
|
||
acc[item.billingStatus] = parseInt(item.get('count'));
|
||
return acc;
|
||
}, {}),
|
||
totalRevenue: totalRevenue || 0,
|
||
monthRevenue: monthRevenue || 0,
|
||
upcomingExpiredCount,
|
||
expiredCount,
|
||
pendingOrdersCount
|
||
},
|
||
message: '获取计费统计成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('获取计费统计失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取计费统计失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 获取支付设置
|
||
const getPaymentSettings = async (req, res) => {
|
||
try {
|
||
// 查询第一条记录(系统中只有一条支付设置记录)
|
||
let settings = await PaymentSetting.findOne({
|
||
where: { isDeleted: 0 }
|
||
});
|
||
|
||
// 如果没有记录,返回空对象
|
||
if (!settings) {
|
||
return res.json({
|
||
code: 200,
|
||
message: '获取支付设置成功',
|
||
data: {}
|
||
});
|
||
}
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '获取支付设置成功',
|
||
data: settings
|
||
});
|
||
} catch (error) {
|
||
console.error('获取支付设置失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '获取支付设置失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
// 更新支付设置
|
||
const updatePaymentSettings = async (req, res) => {
|
||
try {
|
||
const isSystemAdmin = req.user.userType === 'super_admin';
|
||
|
||
// 只有超级管理员可以修改支付设置
|
||
if (!isSystemAdmin) {
|
||
return res.status(403).json({
|
||
code: 403,
|
||
message: '无权操作,请联系管理员'
|
||
});
|
||
}
|
||
|
||
const {
|
||
alipayAccount,
|
||
alipayName,
|
||
wechatId,
|
||
wechatText,
|
||
bankName,
|
||
bankAccount,
|
||
bankHolder,
|
||
servicePhone,
|
||
serviceTime
|
||
} = req.body;
|
||
|
||
// 查询是否存在记录
|
||
let settings = await PaymentSetting.findOne({
|
||
where: { isDeleted: 0 }
|
||
});
|
||
|
||
if (settings) {
|
||
// 更新现有记录
|
||
await settings.update({
|
||
alipayAccount: alipayAccount !== undefined ? alipayAccount : settings.alipayAccount,
|
||
alipayName: alipayName !== undefined ? alipayName : settings.alipayName,
|
||
wechatId: wechatId !== undefined ? wechatId : settings.wechatId,
|
||
wechatText: wechatText !== undefined ? wechatText : settings.wechatText,
|
||
bankName: bankName !== undefined ? bankName : settings.bankName,
|
||
bankAccount: bankAccount !== undefined ? bankAccount : settings.bankAccount,
|
||
bankHolder: bankHolder !== undefined ? bankHolder : settings.bankHolder,
|
||
servicePhone: servicePhone !== undefined ? servicePhone : settings.servicePhone,
|
||
serviceTime: serviceTime !== undefined ? serviceTime : settings.serviceTime
|
||
});
|
||
} else {
|
||
// 创建新记录
|
||
settings = await PaymentSetting.create({
|
||
alipayAccount,
|
||
alipayName,
|
||
wechatId,
|
||
wechatText,
|
||
bankName,
|
||
bankAccount,
|
||
bankHolder,
|
||
servicePhone,
|
||
serviceTime
|
||
});
|
||
}
|
||
|
||
res.json({
|
||
code: 200,
|
||
message: '支付设置更新成功',
|
||
data: settings
|
||
});
|
||
} catch (error) {
|
||
console.error('更新支付设置失败:', error);
|
||
res.status(500).json({
|
||
code: 500,
|
||
message: '更新支付设置失败',
|
||
error: error.message
|
||
});
|
||
}
|
||
};
|
||
|
||
module.exports = {
|
||
getAllPlans,
|
||
createPlan,
|
||
updatePlan,
|
||
deletePlan,
|
||
setDefaultPlan,
|
||
getPricingConfig,
|
||
updatePricingConfig,
|
||
initDefaultPlans,
|
||
initDefaultPricing,
|
||
getOrders,
|
||
createOrder,
|
||
calculatePrice,
|
||
getOrderDetail,
|
||
cancelOrder,
|
||
payOrder,
|
||
getPayments,
|
||
getBillingInfo,
|
||
getUsageStats,
|
||
getBillingStats,
|
||
getPaymentSettings,
|
||
updatePaymentSettings
|
||
};
|