const User = require('../models/User'); const Invitation = require('../models/Invitation'); const Appointment = require('../models/Appointment'); const { sequelize } = require('../config/database'); const { generateInvitationCode } = require('./authService'); /** * Invitation Service * Handles invitation code generation, tracking, and rewards */ /** * Generate invitation code for user * @param {string} userId - User ID * @returns {Object} User with invitation code */ const generateInvitationCodeForUser = async (userId) => { const user = await User.findByPk(userId); if (!user) { throw new Error('User not found'); } // Check if user already has an invitation code if (user.invitationCode) { return user; } // Generate unique invitation code let code; let exists = true; while (exists) { code = generateInvitationCode(); const existingUser = await User.findOne({ where: { invitationCode: code } }); exists = !!existingUser; } // Update user with invitation code user.invitationCode = code; await user.save(); return user; }; /** * Get invitation statistics for user * @param {string} userId - User ID * @returns {Object} Invitation statistics */ const getInvitationStats = async (userId) => { const user = await User.findByPk(userId); if (!user) { throw new Error('User not found'); } // Get all invitations where user is the inviter const invitations = await Invitation.findAll({ where: { inviterId: userId }, }); // Calculate statistics const totalInvites = invitations.length; const paidInvites = invitations.filter(inv => inv.firstPaymentAt != null).length; const totalRewards = invitations .filter(inv => inv.rewardStatus === 'credited') .reduce((sum, inv) => sum + parseFloat(inv.rewardAmount || 0), 0); return { invitationCode: user.invitationCode, totalInvites, paidInvites, totalRewards: totalRewards.toFixed(2), availableBalance: parseFloat(user.balance).toFixed(2), }; }; /** * Get invitation records for user * @param {string} userId - User ID * @param {Object} options - Query options (page, limit) * @returns {Object} Invitation records with pagination */ const getInvitationRecords = async (userId, options = {}) => { const { page = 1, limit = 20 } = options; const offset = (page - 1) * limit; const user = await User.findByPk(userId); if (!user) { throw new Error('User not found'); } // Get invitations with invitee information const { count, rows } = await Invitation.findAndCountAll({ where: { inviterId: userId }, include: [ { model: User, as: 'invitee', attributes: ['id', 'uid', 'nickname', 'avatar', 'createdAt'], }, ], order: [['createdAt', 'DESC']], limit: parseInt(limit), offset: parseInt(offset), }); // Format records const records = rows.map(invitation => ({ id: invitation.id, invitee: { id: invitation.invitee.id, uid: invitation.invitee.uid, nickname: invitation.invitee.nickname, avatar: invitation.invitee.avatar, registeredAt: invitation.registeredAt, }, firstPaymentAt: invitation.firstPaymentAt, firstPaymentAmount: invitation.firstPaymentAmount ? parseFloat(invitation.firstPaymentAmount).toFixed(2) : null, rewardAmount: invitation.rewardAmount ? parseFloat(invitation.rewardAmount).toFixed(2) : null, rewardStatus: invitation.rewardStatus, createdAt: invitation.createdAt, })); return { records, pagination: { page: parseInt(page), limit: parseInt(limit), total: count, totalPages: Math.ceil(count / limit), }, }; }; /** * Record invitation relationship when user registers * @param {string} inviteeId - New user ID * @param {string} invitationCode - Invitation code used * @returns {Object} Invitation record */ const recordInvitationRelationship = async (inviteeId, invitationCode) => { // Find inviter by invitation code const inviter = await User.findOne({ where: { invitationCode } }); if (!inviter) { throw new Error('Invalid invitation code'); } // Check if invitation relationship already exists const existingInvitation = await Invitation.findOne({ where: { inviteeId }, }); if (existingInvitation) { return existingInvitation; } // Create invitation record const invitation = await Invitation.create({ inviterId: inviter.id, inviteeId, invitationCode, registeredAt: new Date(), rewardStatus: 'pending', }); return invitation; }; /** * Process first payment and calculate reward * @deprecated This function is deprecated. Commission calculation is now handled by * commissionService.calculateCommission() when payment orders are created in admin panel. * The new system calculates commission for EVERY payment (not just first), and uses * configurable commission rate (default 2%) instead of fixed 10%. * * @param {string} userId - User ID who made payment * @param {string} appointmentId - Appointment ID * @returns {Object} Updated invitation record or null */ const processFirstPayment = async (userId, appointmentId) => { // DEPRECATED: Commission calculation is now handled by commissionService // when admin creates payment orders. This function is kept for backward // compatibility but no longer calculates or credits commissions. // Check if user was invited const invitation = await Invitation.findOne({ where: { inviteeId: userId }, }); if (!invitation) { return null; // User was not invited } // Just return the invitation record without processing commission // Commission is now calculated via PaymentOrder creation in admin panel return invitation; }; module.exports = { generateInvitationCodeForUser, getInvitationStats, getInvitationRecords, recordInvitationRelationship, processFirstPayment, };