const { Tenant, SubscriptionPlan, TenantSubscription, Apartment, Room, User } = require('../models'); const { Op } = require('sequelize'); const { logOperation } = require('../utils/logger'); const addCalendarMonths = (date, months) => { const source = new Date(date); const day = source.getDate(); const target = new Date(source); target.setDate(1); target.setMonth(target.getMonth() + Number(months)); const lastDayOfTargetMonth = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate(); target.setDate(Math.min(day, lastDayOfTargetMonth)); return target; }; const getBillingCycle = (months) => { if (Number(months) === 12) return 'yearly'; if (Number(months) === 1) return 'monthly'; return 'custom'; }; const normalizeAmount = (value) => Number.parseFloat(Number(value || 0).toFixed(2)); const checkTenantExpiration = async () => { const now = new Date(); const trialExpiredTenants = await Tenant.findAll({ where: { billingStatus: 'trial_active', trialEndDate: { [Op.lt]: now } } }); for (const tenant of trialExpiredTenants) { await tenant.update({ billingStatus: 'trial_expired' }); await logOperation({ tenantId: tenant.id, module: '计费管理', action: '试用期到期', description: `租户 ${tenant.code} 试用期已过期`, status: 'success' }); } const paidExpiredTenants = await Tenant.findAll({ where: { billingStatus: 'paid_active', paidEndDate: { [Op.lt]: now } } }); for (const tenant of paidExpiredTenants) { await tenant.update({ billingStatus: 'paid_expired' }); await TenantSubscription.update( { status: 'expired' }, { where: { tenantId: tenant.id, status: 'active', endDate: { [Op.lt]: now } } } ); await logOperation({ tenantId: tenant.id, module: '计费管理', action: '付费期到期', description: `租户 ${tenant.code} 付费期已过期`, status: 'success' }); } return { trialExpired: trialExpiredTenants.length, paidExpired: paidExpiredTenants.length, longExpired: 0 }; }; const getUpcomingExpiredTenants = async (days = 7) => { const reminderDate = new Date(); reminderDate.setDate(reminderDate.getDate() + days); return Tenant.findAll({ where: { [Op.or]: [ { billingStatus: 'trial_active', trialEndDate: { [Op.lte]: reminderDate, [Op.gte]: new Date() } }, { billingStatus: 'paid_active', paidEndDate: { [Op.lte]: reminderDate, [Op.gte]: new Date() } } ] }, include: [{ model: SubscriptionPlan, as: 'subscriptionPlan' }] }); }; const calculateOverage = async (tenantId) => { const tenant = await Tenant.findByPk(tenantId, { include: [{ model: SubscriptionPlan, as: 'subscriptionPlan' }] }); if (!tenant) { throw new Error('租户不存在'); } 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 } }) ]); return { usage: { apartments: apartmentCount, rooms: roomCount, users: userCount }, limits: { apartments: tenant.maxApartments, rooms: tenant.maxRooms, users: tenant.maxUsers }, overage: { apartments: Math.max(0, apartmentCount - tenant.maxApartments), rooms: Math.max(0, roomCount - tenant.maxRooms), users: Math.max(0, userCount - tenant.maxUsers) } }; }; const calculateRenewalAmount = async (tenantId, planId, months) => { const plan = await SubscriptionPlan.findOne({ where: { id: planId, status: 'active', isDeleted: 0 } }); if (!plan) { throw new Error('套餐不存在或已停用'); } const normalizedMonths = Number.parseInt(months, 10); if (!Number.isInteger(normalizedMonths) || normalizedMonths < 1 || normalizedMonths > 36) { throw new Error('购买月数必须在1-36之间'); } const billingCycle = getBillingCycle(normalizedMonths); const monthlyPrice = Number(plan.monthlyPrice || 0); const yearlyPrice = Number(plan.yearlyPrice || 0); const baseAmount = billingCycle === 'yearly' && yearlyPrice > 0 ? yearlyPrice : monthlyPrice * normalizedMonths; return { baseAmount: normalizeAmount(baseAmount), overageAmount: 0, discountAmount: 0, totalAmount: normalizeAmount(baseAmount), billingCycle, details: { planName: plan.name, monthlyPrice: normalizeAmount(monthlyPrice), yearlyPrice: normalizeAmount(yearlyPrice), months: normalizedMonths, billingCycle } }; }; const processPaymentSuccess = async ({ order, payment, operatorId, transaction }) => { const tenant = await Tenant.findByPk(order.tenantId, { transaction }); if (!tenant) { throw new Error('租户不存在'); } const plan = await SubscriptionPlan.findByPk(order.planId, { transaction }); if (!plan) { throw new Error('套餐不存在'); } const now = new Date(); const currentEnd = tenant.paidEndDate ? new Date(tenant.paidEndDate) : null; const startDate = tenant.billingStatus === 'paid_active' && currentEnd && currentEnd > now ? currentEnd : now; const endDate = addCalendarMonths(startDate, order.months); const billingCycle = order.billingCycle || getBillingCycle(order.months); await TenantSubscription.update( { status: 'expired' }, { where: { tenantId: tenant.id, status: 'active', isDeleted: 0 }, transaction } ); const subscription = await TenantSubscription.create({ tenantId: tenant.id, planId: plan.id, orderId: order.id, status: 'active', billingCycle, months: order.months, startDate, endDate, amount: order.actualAmount, createBy: operatorId, updateBy: operatorId }, { transaction }); await tenant.update({ planId: plan.id, billingStatus: 'paid_active', paidStartDate: startDate, paidEndDate: endDate, currentPeriodStart: startDate, currentPeriodEnd: endDate, maxApartments: plan.maxApartments, maxRooms: plan.maxRooms, maxUsers: plan.maxUsers, updateBy: operatorId }, { transaction }); await order.update({ subscriptionId: subscription.id, periodStart: startDate, periodEnd: endDate }, { transaction }); await logOperation({ tenantId: tenant.id, userId: operatorId, module: '计费管理', action: '订阅生效', description: `租户订阅 ${plan.name} ${order.months} 个月,有效期至 ${endDate.toLocaleDateString()}`, status: 'success' }); return { subscription, billingStatus: 'paid_active', paidStartDate: startDate, paidEndDate: endDate, paymentId: payment ? payment.id : null }; }; module.exports = { checkTenantExpiration, getUpcomingExpiredTenants, calculateOverage, calculateRenewalAmount, processPaymentSuccess };