const { ChitGroup, GroupMember, User, Payment, MonthlyDraw } = require('../models'); const { Op } = require('sequelize'); // Create a new chit group const createChitGroup = async (req, res) => { try { const { name, total_value, monthly_installment, duration_months, max_members, foreman_commission_amount, foreman_commission_type = 'fixed', foreman_commission_rate, draw_date, start_date, // Optional: for importing existing groups current_month, // Optional: current month number for existing groups is_import, // Flag to indicate this is an existing group status // Optional: can be 'active' for imported groups } = req.body; const managerId = req.user.id; // Validate input if (!name || !total_value || !monthly_installment || !duration_months || !max_members || !foreman_commission_amount || !draw_date) { return res.status(400).json({ success: false, message: 'All fields are required' }); } // Validate business rules based on commission type let expectedTotalValue; let actualCommission; if (foreman_commission_type === 'percentage') { // For percentage commission: commission = (total_value / duration_months) * (rate / 100) // So: monthly_installment = (total_value / duration_months) + (total_value / duration_months) * (rate / 100) // monthly_installment = (total_value / duration_months) * (1 + rate/100) // total_value = monthly_installment * duration_months / (1 + rate/100) const rate = foreman_commission_rate || 5; expectedTotalValue = (monthly_installment * duration_months) / (1 + rate/100); actualCommission = (expectedTotalValue / duration_months) * (rate / 100); } else { // For fixed commission: total_value = (monthly_installment - foreman_commission_amount) × duration_months expectedTotalValue = (monthly_installment - foreman_commission_amount) * duration_months; actualCommission = foreman_commission_amount; } if (Math.abs(total_value - expectedTotalValue) > 1) { // Allow small rounding differences return res.status(400).json({ success: false, message: `Total value calculation mismatch. Expected: ₹${expectedTotalValue.toFixed(0)}, Provided: ₹${total_value}. Commission type: ${foreman_commission_type}` }); } // Validate commission based on type if (foreman_commission_type === 'percentage') { const rate = foreman_commission_rate || 5; if (rate < 0 || rate > 100) { return res.status(400).json({ success: false, message: 'Commission rate must be between 0 and 100%' }); } } else { if (foreman_commission_amount < 0 || foreman_commission_amount > total_value * 0.1) { return res.status(400).json({ success: false, message: 'Foreman commission must be between 0 and 10% of total value' }); } } if (draw_date < 1 || draw_date > 31) { return res.status(400).json({ success: false, message: 'Draw date must be between 1 and 31' }); } // Determine start date and status const groupStartDate = is_import && start_date ? new Date(start_date) : new Date(); const groupStatus = is_import && status === 'active' ? 'active' : 'forming'; // Calculate end date from start date const endDate = new Date(groupStartDate); endDate.setMonth(endDate.getMonth() + duration_months); // Create chit group const chitGroup = await ChitGroup.create({ name, total_value, monthly_installment, duration_months, max_members, foreman_commission_amount: actualCommission, // Use calculated commission foreman_commission_type, foreman_commission_rate, draw_date, manager_id: managerId, status: groupStatus, start_date: groupStartDate, end_date: endDate }); const message = is_import ? `Existing group imported successfully. Started: ${groupStartDate.toLocaleDateString()}. Current month: ${current_month || 1}` : 'Chit group created successfully'; res.status(201).json({ success: true, message, data: { ...chitGroup.toJSON(), current_month: current_month || 1, is_imported: is_import || false } }); } catch (error) { console.error('Create chit group error:', error); res.status(500).json({ success: false, message: 'Internal server error', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; // Get all chit groups for the manager const getManagerChitGroups = async (req, res) => { try { const managerId = req.user.id; const { status, page = 1, limit = 10 } = req.query; const whereClause = { manager_id: managerId }; if (status) { whereClause.status = status; } const offset = (page - 1) * limit; const chitGroups = await ChitGroup.findAndCountAll({ where: whereClause, include: [ { model: GroupMember, as: 'members', include: [ { model: User, attributes: ['id', 'full_name', 'mobile_number'] } ] } ], limit: parseInt(limit), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: { chitGroups: chitGroups.rows, pagination: { currentPage: parseInt(page), totalPages: Math.ceil(chitGroups.count / limit), totalItems: chitGroups.count, itemsPerPage: parseInt(limit) } } }); } catch (error) { console.error('Get manager chit groups error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Get chit groups for a member const getMemberChitGroups = async (req, res) => { try { const memberId = req.user.id; const { status, page = 1, limit = 10 } = req.query; const whereClause = { user_id: memberId }; if (status) { whereClause.status = status; } const offset = (page - 1) * limit; const groupMemberships = await GroupMember.findAndCountAll({ where: whereClause, include: [ { model: ChitGroup, include: [ { model: User, as: 'manager', attributes: ['id', 'full_name', 'mobile_number'] } ] } ], limit: parseInt(limit), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: { chitGroups: groupMemberships.rows.map(membership => ({ ...membership.ChitGroup.toJSON(), membership_status: membership.status, total_paid: membership.total_paid, total_won: membership.total_won })), pagination: { currentPage: parseInt(page), totalPages: Math.ceil(groupMemberships.count / limit), totalItems: groupMemberships.count, itemsPerPage: parseInt(limit) } } }); } catch (error) { console.error('Get member chit groups error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Get single chit group details const getChitGroupDetails = async (req, res) => { try { const { groupId } = req.params; const userId = req.user.id; const chitGroup = await ChitGroup.findByPk(groupId, { include: [ { model: User, as: 'manager', attributes: ['id', 'full_name', 'mobile_number'] }, { model: GroupMember, as: 'members', include: [ { model: User, attributes: ['id', 'full_name', 'mobile_number'] } ] }, { model: MonthlyDraw, order: [['created_at', 'DESC']], limit: 5 } ] }); if (!chitGroup) { return res.status(404).json({ success: false, message: 'Chit group not found' }); } // Check if user has access to this group const isManager = chitGroup.manager_id === userId; const isMember = chitGroup.members.some(member => member.user_id === userId); if (!isManager && !isMember) { return res.status(403).json({ success: false, message: 'Access denied' }); } res.json({ success: true, data: chitGroup }); } catch (error) { console.error('Get chit group details error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Update chit group const updateChitGroup = async (req, res) => { try { const { groupId } = req.params; const managerId = req.user.id; const updateData = req.body; const chitGroup = await ChitGroup.findOne({ where: { id: groupId, manager_id: managerId } }); if (!chitGroup) { return res.status(404).json({ success: false, message: 'Chit group not found or access denied' }); } // Only allow updates if group is still forming if (chitGroup.status !== 'forming') { return res.status(400).json({ success: false, message: 'Cannot update chit group after it has started' }); } // Validate business rules if updating financial fields if (updateData.monthly_installment && updateData.duration_months) { const commissionType = updateData.foreman_commission_type || chitGroup.foreman_commission_type || 'fixed'; let expectedTotalValue; let actualCommission; if (commissionType === 'percentage') { const rate = updateData.foreman_commission_rate || chitGroup.foreman_commission_rate || 5; expectedTotalValue = (updateData.monthly_installment * updateData.duration_months) / (1 + rate/100); actualCommission = (expectedTotalValue / updateData.duration_months) * (rate / 100); } else { const commission = updateData.foreman_commission_amount || chitGroup.foreman_commission_amount; expectedTotalValue = (updateData.monthly_installment - commission) * updateData.duration_months; actualCommission = commission; } if (updateData.total_value && Math.abs(updateData.total_value - expectedTotalValue) > 1) { return res.status(400).json({ success: false, message: `Total value calculation mismatch. Expected: ₹${expectedTotalValue.toFixed(0)}, Provided: ₹${updateData.total_value}. Commission type: ${commissionType}` }); } updateData.total_value = expectedTotalValue; updateData.foreman_commission_amount = actualCommission; } await chitGroup.update(updateData); res.json({ success: true, message: 'Chit group updated successfully', data: chitGroup }); } catch (error) { console.error('Update chit group error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Delete chit group (only if forming and no active members) const deleteChitGroup = async (req, res) => { try { const { groupId } = req.params; const managerId = req.user.id; const chitGroup = await ChitGroup.findOne({ where: { id: groupId, manager_id: managerId } }); if (!chitGroup) { return res.status(404).json({ success: false, message: 'Chit group not found or access denied' }); } if (chitGroup.status !== 'forming') { return res.status(400).json({ success: false, message: 'Cannot delete chit group after it has started' }); } // Count only ACTIVE members (not removed or inactive) const activeMemberCount = await GroupMember.count({ where: { group_id: groupId, status: 'active' } }); if (activeMemberCount > 0) { return res.status(400).json({ success: false, message: `Cannot delete chit group with ${activeMemberCount} active member(s). Please remove all active members first.` }); } await chitGroup.destroy(); res.json({ success: true, message: 'Chit group deleted successfully' }); } catch (error) { console.error('Delete chit group error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Start chit group (activate it) const startChitGroup = async (req, res) => { try { const { groupId } = req.params; const managerId = req.user.id; const chitGroup = await ChitGroup.findOne({ where: { id: groupId, manager_id: managerId }, include: [ { model: GroupMember, as: 'members' } ] }); if (!chitGroup) { return res.status(404).json({ success: false, message: 'Chit group not found or access denied' }); } if (chitGroup.status !== 'forming') { return res.status(400).json({ success: false, message: 'Chit group is already active or completed' }); } if (chitGroup.members.length < 2) { return res.status(400).json({ success: false, message: 'Need at least 2 members to start the chit group' }); } await chitGroup.update({ status: 'active', start_date: new Date() }); res.json({ success: true, message: 'Chit group activated successfully', data: chitGroup }); } catch (error) { console.error('Start chit group error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Get chit group statistics const getChitGroupStats = async (req, res) => { try { const { groupId } = req.params; const userId = req.user.id; console.log('Getting stats for group:', groupId, 'user:', userId); // First, try to find the chit group without includes const chitGroup = await ChitGroup.findByPk(groupId); if (!chitGroup) { console.log('Chit group not found:', groupId); return res.status(404).json({ success: false, message: 'Chit group not found' }); } console.log('Chit group found:', chitGroup.name, 'Manager:', chitGroup.manager_id); // Check access - user must be the manager const isManager = chitGroup.manager_id === userId; if (!isManager) { console.log('Access denied - user is not manager'); return res.status(403).json({ success: false, message: 'Access denied - only group manager can view stats' }); } // Get related data separately to avoid complex query issues const members = await GroupMember.findAll({ where: { group_id: groupId }, include: [{ model: User, attributes: ['id', 'full_name', 'mobile_number'] }] }); const payments = await Payment.findAll({ where: { group_id: groupId, status: 'success' } }); const draws = await MonthlyDraw.findAll({ where: { group_id: groupId, status: 'completed' } }); // Calculate statistics const totalMembers = members.length; const totalPayments = payments.length; const totalDraws = draws.length; const totalCollection = totalPayments * parseFloat(chitGroup.monthly_installment); const foremanCommission = parseFloat(chitGroup.foreman_commission_amount); const totalPrizePool = totalCollection - foremanCommission; const stats = { totalMembers, totalPayments, totalDraws, totalCollection, foremanCommission, totalPrizePool, averagePrizePerDraw: totalDraws > 0 ? totalPrizePool / totalDraws : 0, completionPercentage: (totalDraws / chitGroup.duration_months) * 100 }; console.log('Stats calculated:', stats); res.json({ success: true, data: stats }); } catch (error) { console.error('Get chit group stats error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Get chit group financial data const getChitGroupFinancialData = async (req, res) => { try { const { groupId } = req.params; const userId = req.user.id; console.log('Getting financial data for group:', groupId, 'user:', userId); const chitGroup = await ChitGroup.findByPk(groupId); if (!chitGroup) { console.log('Chit group not found:', groupId); return res.status(404).json({ success: false, message: 'Chit group not found' }); } // Check access const isManager = chitGroup.manager_id === userId; const isMember = await GroupMember.findOne({ where: { group_id: groupId, user_id: userId } }); if (!isManager && !isMember) { return res.status(403).json({ success: false, message: 'Access denied' }); } // Generate financial data based on group parameters const financialData = generateFinancialData(chitGroup); res.json({ success: true, data: { financial_data: financialData } }); } catch (error) { console.error('Get chit group financial data error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }; // Helper function to generate financial data const generateFinancialData = (chitGroup) => { const financialData = []; const totalMonths = chitGroup.duration_months; const chitValue = chitGroup.total_value; const monthlyInstallment = chitGroup.monthly_installment; const commission = chitGroup.foreman_commission_amount; // Use actual start_date if valid; invalid/legacy DB values → today (avoids NaN → "undefined-N" labels). let startDate = chitGroup.start_date ? new Date(chitGroup.start_date) : new Date(); if (Number.isNaN(startDate.getTime())) { startDate = new Date(); } const startMonth = startDate.getMonth(); // 0-11 (0 = January) const startYear = startDate.getFullYear(); // Calculate starting bid amount (typically 85-90% of chit value) let currentBidAmount = chitGroup.total_value * 0.8765; // Starting at 87.65% for (let i = 0; i < totalMonths; i++) { // Calculate the actual calendar month and year for this cycle month const totalMonthsFromStart = startMonth + i; const currentMonth = totalMonthsFromStart % 12; // 0-11 const currentYear = startYear + Math.floor(totalMonthsFromStart / 12); const monthName = getMonthName(currentMonth); const monthYear = `${monthName}-${currentYear.toString().substring(2)}`; // Calculate dividend (difference between chit value and bid amount) const dividend = chitValue - currentBidAmount; financialData.push({ month_year: monthYear, chit_value: chitValue, bid_amount: Math.round(currentBidAmount), subscription_amount: monthlyInstallment, commission_installment: commission, total_payable_installment: parseFloat(monthlyInstallment) + parseFloat(commission), dividend_amount: Math.round(dividend) }); // Increase bid amount for next month currentBidAmount += 2600; // Increment by ₹2600 per month } // Add total row const totalChitValue = chitValue * totalMonths; const totalBidAmount = financialData.reduce((sum, entry) => sum + entry.bid_amount, 0); const totalSubscription = monthlyInstallment * totalMonths; const totalCommission = commission * totalMonths; const totalPayable = (monthlyInstallment + commission) * totalMonths; const totalDividend = financialData.reduce((sum, entry) => sum + entry.dividend_amount, 0); financialData.push({ month_year: 'Total', chit_value: totalChitValue, bid_amount: totalBidAmount, subscription_amount: totalSubscription, commission_installment: totalCommission, total_payable_installment: totalPayable, dividend_amount: totalDividend }); return financialData; }; const getMonthName = (monthIndex) => { const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; if (typeof monthIndex !== 'number' || Number.isNaN(monthIndex) || monthIndex < 0 || monthIndex > 11) { return '?'; } return months[monthIndex]; }; module.exports = { createChitGroup, getManagerChitGroups, getMemberChitGroups, getChitGroupDetails, updateChitGroup, deleteChitGroup, startChitGroup, getChitGroupStats, getChitGroupFinancialData };