819 lines
22 KiB
JavaScript
819 lines
22 KiB
JavaScript
const { MonthlyDraw, ChitGroup, GroupMember, User, Payment } = require('../models');
|
|
const { Op } = require('sequelize');
|
|
const crypto = require('crypto');
|
|
|
|
// Generate provably fair lottery result
|
|
const generateProvablyFairResult = (serverSeed, clientSeed, nonce, eligibleMembers) => {
|
|
// Create combined seed
|
|
const combinedSeed = `${serverSeed}-${clientSeed}-${nonce}`;
|
|
const hash = crypto.createHash('sha256').update(combinedSeed).digest('hex');
|
|
|
|
// Use first 8 characters of hash as random number
|
|
const randomHex = hash.substring(0, 8);
|
|
const randomNumber = parseInt(randomHex, 16);
|
|
|
|
// Select winner from eligible members
|
|
const winnerIndex = randomNumber % eligibleMembers.length;
|
|
const winner = eligibleMembers[winnerIndex];
|
|
|
|
return {
|
|
winner,
|
|
hash,
|
|
randomNumber,
|
|
serverSeed,
|
|
clientSeed,
|
|
nonce
|
|
};
|
|
};
|
|
|
|
// Create a new monthly draw
|
|
const createMonthlyDraw = async (req, res) => {
|
|
try {
|
|
const {
|
|
group_id,
|
|
month,
|
|
year,
|
|
client_seed,
|
|
winner_id,
|
|
prize_amount,
|
|
is_past_draw,
|
|
animation_winner_user_id,
|
|
server_seed: bodyServerSeed,
|
|
nonce: bodyNonce
|
|
} = req.body;
|
|
|
|
// Convert month and year to integers
|
|
const monthInt = parseInt(month, 10);
|
|
const yearInt = parseInt(year, 10);
|
|
const managerId = req.user.id;
|
|
|
|
// Validate input
|
|
if (!group_id || !month || !year) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Group ID, month, and year are required'
|
|
});
|
|
}
|
|
|
|
// Validate month and year are valid numbers
|
|
if (isNaN(monthInt) || isNaN(yearInt)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Month and year must be valid numbers'
|
|
});
|
|
}
|
|
|
|
// Validate month range
|
|
if (monthInt < 1 || monthInt > 12) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Month must be between 1 and 12'
|
|
});
|
|
}
|
|
|
|
// Check if user is the manager of this group
|
|
const chitGroup = await ChitGroup.findOne({
|
|
where: { id: group_id, manager_id: managerId }
|
|
});
|
|
|
|
if (!chitGroup) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Chit group not found or you are not the manager'
|
|
});
|
|
}
|
|
|
|
// Check if group is active
|
|
if (chitGroup.status !== 'active') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Can only conduct draws for active chit groups'
|
|
});
|
|
}
|
|
|
|
// Check if draw already exists for this month/year
|
|
const existingDraw = await MonthlyDraw.findOne({
|
|
where: { group_id, month: monthInt, year: yearInt }
|
|
});
|
|
|
|
if (existingDraw) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Draw already exists for this month and year'
|
|
});
|
|
}
|
|
|
|
// Get eligible members (active members who haven't won yet)
|
|
const eligibleMembers = await GroupMember.findAll({
|
|
where: {
|
|
group_id,
|
|
status: 'active'
|
|
},
|
|
include: [
|
|
{
|
|
model: User,
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
}
|
|
]
|
|
});
|
|
|
|
// Filter out members who have already won
|
|
const wonMembers = await MonthlyDraw.findAll({
|
|
where: { group_id },
|
|
attributes: ['winner_id']
|
|
});
|
|
|
|
const wonMemberIds = wonMembers.map((draw) => draw.winner_id);
|
|
const availableMembers = eligibleMembers.filter(
|
|
(member) =>
|
|
!wonMemberIds.some((id) => id != null && String(id) === String(member.user_id))
|
|
);
|
|
|
|
const animationWinner =
|
|
animation_winner_user_id != null && String(animation_winner_user_id).trim() !== ''
|
|
? String(animation_winner_user_id).trim()
|
|
: null;
|
|
|
|
if (availableMembers.length === 0 && !is_past_draw && !animationWinner) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No eligible members available for draw'
|
|
});
|
|
}
|
|
|
|
const alreadyWon = (uid) =>
|
|
wonMemberIds.some((id) => id != null && String(id) === String(uid));
|
|
|
|
// Determine winner
|
|
let selectedWinnerId;
|
|
let selectedWinner;
|
|
let serverSeed;
|
|
let resultHash;
|
|
let nonce;
|
|
let fromAppAnimation = false;
|
|
|
|
if (animationWinner) {
|
|
// App showed a winner in the animation; persist that user (manager-authenticated).
|
|
// Without this branch the server re-rolls and WhatsApp/public links show the wrong name.
|
|
fromAppAnimation = true;
|
|
selectedWinnerId = animationWinner;
|
|
selectedWinner = eligibleMembers.find((m) => String(m.user_id) === selectedWinnerId);
|
|
if (!selectedWinner) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Winner is not an active member of this group'
|
|
});
|
|
}
|
|
if (alreadyWon(selectedWinnerId)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `${selectedWinner.User.full_name} has already won in a previous draw. Each member can only win once.`,
|
|
alreadyWon: true,
|
|
winnerName: selectedWinner.User.full_name
|
|
});
|
|
}
|
|
if (!availableMembers.some((m) => String(m.user_id) === selectedWinnerId)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Winner is not eligible for this draw (already won or not in pool)'
|
|
});
|
|
}
|
|
serverSeed =
|
|
bodyServerSeed && String(bodyServerSeed).trim() !== ''
|
|
? String(bodyServerSeed).trim()
|
|
: crypto.randomBytes(32).toString('hex');
|
|
nonce = bodyNonce != null ? parseInt(bodyNonce, 10) : Date.now();
|
|
if (Number.isNaN(nonce)) {
|
|
nonce = Date.now();
|
|
}
|
|
const cs = client_seed != null && String(client_seed).trim() !== '' ? String(client_seed) : 'animation';
|
|
resultHash = crypto
|
|
.createHash('sha256')
|
|
.update(`${serverSeed}-${cs}-${nonce}-${selectedWinnerId}`)
|
|
.digest('hex');
|
|
} else if (is_past_draw && winner_id) {
|
|
// Past draw - manual winner selection
|
|
selectedWinnerId = winner_id;
|
|
serverSeed = `PAST_DRAW_${Date.now()}`;
|
|
nonce = Date.now();
|
|
resultHash = crypto.createHash('sha256').update(`${serverSeed}_${winner_id}_${nonce}`).digest('hex');
|
|
|
|
// Get winner details
|
|
selectedWinner = eligibleMembers.find((m) => String(m.user_id) === String(winner_id));
|
|
if (!selectedWinner) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Selected winner is not a member of this group'
|
|
});
|
|
}
|
|
|
|
// Check if this member has already won
|
|
if (alreadyWon(winner_id)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `${selectedWinner.User.full_name} has already won in a previous draw. Each member can only win once.`,
|
|
alreadyWon: true,
|
|
winnerName: selectedWinner.User.full_name
|
|
});
|
|
}
|
|
} else {
|
|
// Regular draw - provably fair random selection (API-only / no animation winner)
|
|
serverSeed = crypto.randomBytes(32).toString('hex');
|
|
nonce = Date.now();
|
|
|
|
const result = generateProvablyFairResult(
|
|
serverSeed,
|
|
client_seed || 'default',
|
|
nonce,
|
|
availableMembers
|
|
);
|
|
|
|
selectedWinnerId = result.winner.user_id;
|
|
selectedWinner = result.winner;
|
|
resultHash = result.hash;
|
|
}
|
|
|
|
// Calculate prize amount
|
|
const totalMembers = eligibleMembers.length;
|
|
const calculatedPrizeAmount = prize_amount || chitGroup.total_value; // Use provided or default
|
|
|
|
// For past imports, use all active members; for animation + random draws, use not-yet-won pool
|
|
const membersForDraw = is_past_draw && !fromAppAnimation ? eligibleMembers : availableMembers;
|
|
|
|
// Create monthly draw
|
|
const monthlyDraw = await MonthlyDraw.create({
|
|
group_id,
|
|
month: monthInt,
|
|
year: yearInt,
|
|
draw_date: new Date(),
|
|
eligible_members: membersForDraw.map(member => ({
|
|
id: member.user_id,
|
|
name: member.User.full_name,
|
|
mobile: member.User.mobile_number
|
|
})),
|
|
winner_id: selectedWinnerId,
|
|
prize_amount: calculatedPrizeAmount,
|
|
server_seed: serverSeed,
|
|
server_seed_hash: crypto.createHash('sha256').update(serverSeed).digest('hex'),
|
|
client_seed:
|
|
client_seed || (fromAppAnimation ? 'animation' : is_past_draw ? 'PAST_DRAW' : 'default'),
|
|
nonce,
|
|
result_hash: resultHash,
|
|
status: 'completed',
|
|
notes: fromAppAnimation
|
|
? `App animation draw — ${selectedWinner.User.full_name}`
|
|
: is_past_draw
|
|
? `Past draw result (imported) - Winner: ${selectedWinner.User.full_name}`
|
|
: `Winner selected: ${selectedWinner.User.full_name}`
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Monthly draw completed successfully',
|
|
data: {
|
|
...monthlyDraw.toJSON(),
|
|
winner: selectedWinner.User,
|
|
eligible_count: membersForDraw.length
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Create monthly draw error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get all monthly draws for a group
|
|
const getGroupMonthlyDraws = async (req, res) => {
|
|
try {
|
|
const { group_id } = req.params;
|
|
const { status, page = 1, limit = 20 } = req.query;
|
|
|
|
// Check if user has access to this group
|
|
const chitGroup = await ChitGroup.findOne({
|
|
where: {
|
|
id: group_id,
|
|
[Op.or]: [
|
|
{ manager_id: req.user.id },
|
|
{ '$members.user_id$': req.user.id }
|
|
]
|
|
},
|
|
include: [
|
|
{
|
|
model: GroupMember,
|
|
as: 'members',
|
|
include: [
|
|
{
|
|
model: User,
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!chitGroup) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Chit group not found or access denied'
|
|
});
|
|
}
|
|
|
|
const whereClause = { group_id };
|
|
if (status) {
|
|
whereClause.status = status;
|
|
}
|
|
|
|
const offset = (page - 1) * limit;
|
|
|
|
const monthlyDraws = await MonthlyDraw.findAndCountAll({
|
|
where: whereClause,
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'winner',
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
}
|
|
],
|
|
order: [['created_at', 'DESC']],
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset)
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Monthly draws retrieved successfully',
|
|
data: {
|
|
monthlyDraws: monthlyDraws.rows,
|
|
total: monthlyDraws.count,
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
totalPages: Math.ceil(monthlyDraws.count / limit)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get group monthly draws error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get monthly draw details
|
|
const getMonthlyDrawDetails = async (req, res) => {
|
|
try {
|
|
const { draw_id } = req.params;
|
|
|
|
const monthlyDraw = await MonthlyDraw.findOne({
|
|
where: { id: draw_id },
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'winner',
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
},
|
|
{
|
|
model: ChitGroup,
|
|
attributes: ['id', 'name', 'monthly_installment', 'foreman_commission_amount']
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!monthlyDraw) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Monthly draw not found'
|
|
});
|
|
}
|
|
|
|
// Check if user has access to this draw
|
|
const chitGroup = await ChitGroup.findOne({
|
|
where: {
|
|
id: monthlyDraw.group_id,
|
|
[Op.or]: [
|
|
{ manager_id: req.user.id },
|
|
{ '$members.user_id$': req.user.id }
|
|
]
|
|
}
|
|
});
|
|
|
|
if (!chitGroup) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Access denied to this monthly draw'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Monthly draw details retrieved successfully',
|
|
data: monthlyDraw
|
|
});
|
|
} catch (error) {
|
|
console.error('Get monthly draw details error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Verify provably fair result
|
|
const verifyDrawResult = async (req, res) => {
|
|
try {
|
|
const { draw_id } = req.params;
|
|
const { client_seed } = req.body;
|
|
|
|
const monthlyDraw = await MonthlyDraw.findOne({
|
|
where: { id: draw_id },
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'winner',
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!monthlyDraw) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Monthly draw not found'
|
|
});
|
|
}
|
|
|
|
// Check if user has access to this draw
|
|
const chitGroup = await ChitGroup.findOne({
|
|
where: {
|
|
id: monthlyDraw.group_id,
|
|
[Op.or]: [
|
|
{ manager_id: req.user.id },
|
|
{ '$members.user_id$': req.user.id }
|
|
]
|
|
}
|
|
});
|
|
|
|
if (!chitGroup) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Access denied to this monthly draw'
|
|
});
|
|
}
|
|
|
|
// Verify the result
|
|
const verificationResult = generateProvablyFairResult(
|
|
monthlyDraw.server_seed,
|
|
client_seed || monthlyDraw.client_seed,
|
|
monthlyDraw.nonce,
|
|
monthlyDraw.eligible_members
|
|
);
|
|
|
|
const isVerified = verificationResult.hash === monthlyDraw.result_hash;
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Draw verification completed',
|
|
data: {
|
|
isVerified,
|
|
originalHash: monthlyDraw.result_hash,
|
|
verificationHash: verificationResult.hash,
|
|
serverSeed: monthlyDraw.server_seed,
|
|
clientSeed: client_seed || monthlyDraw.client_seed,
|
|
nonce: monthlyDraw.nonce,
|
|
winner: monthlyDraw.winner
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Verify draw result error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get draw statistics for a group
|
|
const getDrawStatistics = async (req, res) => {
|
|
try {
|
|
const { group_id } = req.params;
|
|
|
|
// Check if user has access to this group
|
|
const chitGroup = await ChitGroup.findOne({
|
|
where: {
|
|
id: group_id,
|
|
[Op.or]: [
|
|
{ manager_id: req.user.id },
|
|
{ '$members.user_id$': req.user.id }
|
|
]
|
|
}
|
|
});
|
|
|
|
if (!chitGroup) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Chit group not found or access denied'
|
|
});
|
|
}
|
|
|
|
// Get all draws for this group
|
|
const draws = await MonthlyDraw.findAll({
|
|
where: { group_id },
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'winner',
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
}
|
|
],
|
|
order: [['created_at', 'ASC']]
|
|
});
|
|
|
|
// Get total members
|
|
const totalMembers = await GroupMember.count({
|
|
where: { group_id, status: 'active' }
|
|
});
|
|
|
|
// Calculate statistics
|
|
const completedDraws = draws.filter(draw => draw.status === 'completed');
|
|
const totalPrizeAmount = completedDraws.reduce((sum, draw) => sum + parseFloat(draw.prize_amount), 0);
|
|
const averagePrizeAmount = completedDraws.length > 0 ? totalPrizeAmount / completedDraws.length : 0;
|
|
|
|
// Get winners by month
|
|
const winnersByMonth = completedDraws.map(draw => ({
|
|
month: draw.month,
|
|
year: draw.year,
|
|
winner: draw.winner,
|
|
prize_amount: draw.prize_amount,
|
|
draw_date: draw.draw_date
|
|
}));
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Draw statistics retrieved successfully',
|
|
data: {
|
|
totalDraws: draws.length,
|
|
completedDraws: completedDraws.length,
|
|
pendingDraws: draws.filter(draw => draw.status === 'pending').length,
|
|
totalMembers,
|
|
totalPrizeAmount,
|
|
averagePrizeAmount,
|
|
winnersByMonth,
|
|
remainingDraws: chitGroup.duration_months - completedDraws.length
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get draw statistics error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Delete monthly draw (manager only, for mistakes)
|
|
const deleteMonthlyDraw = async (req, res) => {
|
|
try {
|
|
const { draw_id } = req.params;
|
|
const managerId = req.user.id;
|
|
|
|
// Find the draw
|
|
const monthlyDraw = await MonthlyDraw.findOne({
|
|
where: { id: draw_id },
|
|
include: [
|
|
{
|
|
model: ChitGroup,
|
|
attributes: ['id', 'manager_id', 'name']
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!monthlyDraw) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Monthly draw not found'
|
|
});
|
|
}
|
|
|
|
// Check if user is the manager
|
|
if (monthlyDraw.ChitGroup.manager_id !== managerId) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Only the group manager can delete draws'
|
|
});
|
|
}
|
|
|
|
// Store draw info for response
|
|
const drawInfo = {
|
|
month: monthlyDraw.month,
|
|
year: monthlyDraw.year,
|
|
groupName: monthlyDraw.ChitGroup.name
|
|
};
|
|
|
|
// Delete the draw
|
|
await monthlyDraw.destroy();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Draw for ${drawInfo.month}/${drawInfo.year} deleted successfully`,
|
|
data: drawInfo
|
|
});
|
|
} catch (error) {
|
|
console.error('Delete monthly draw error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Update monthly draw (manager only, for corrections)
|
|
const updateMonthlyDraw = async (req, res) => {
|
|
try {
|
|
const { draw_id } = req.params;
|
|
const { winner_id, prize_amount, notes, month, year, draw_date } = req.body;
|
|
const managerId = req.user.id;
|
|
|
|
// Find the draw
|
|
const monthlyDraw = await MonthlyDraw.findOne({
|
|
where: { id: draw_id },
|
|
include: [
|
|
{
|
|
model: ChitGroup,
|
|
attributes: ['id', 'manager_id', 'name']
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!monthlyDraw) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Monthly draw not found'
|
|
});
|
|
}
|
|
|
|
// Check if user is the manager
|
|
if (monthlyDraw.ChitGroup.manager_id !== managerId) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Only the group manager can update draws'
|
|
});
|
|
}
|
|
|
|
const updates = {};
|
|
|
|
let nextMonth = monthlyDraw.month;
|
|
let nextYear = monthlyDraw.year;
|
|
const monthProvided = month !== undefined && month !== null && month !== '';
|
|
const yearProvided = year !== undefined && year !== null && year !== '';
|
|
if (monthProvided) {
|
|
nextMonth = parseInt(month, 10);
|
|
}
|
|
if (yearProvided) {
|
|
nextYear = parseInt(year, 10);
|
|
}
|
|
|
|
if (monthProvided || yearProvided) {
|
|
if (Number.isNaN(nextMonth) || nextMonth < 1 || nextMonth > 12) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Month must be between 1 and 12'
|
|
});
|
|
}
|
|
if (Number.isNaN(nextYear) || nextYear < 2020) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Year must be 2020 or later'
|
|
});
|
|
}
|
|
const conflict = await MonthlyDraw.findOne({
|
|
where: {
|
|
group_id: monthlyDraw.group_id,
|
|
month: nextMonth,
|
|
year: nextYear,
|
|
id: { [Op.ne]: draw_id }
|
|
}
|
|
});
|
|
if (conflict) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `Another draw already exists for ${nextMonth}/${nextYear}`
|
|
});
|
|
}
|
|
updates.month = nextMonth;
|
|
updates.year = nextYear;
|
|
}
|
|
|
|
if (draw_date !== undefined && draw_date !== null && draw_date !== '') {
|
|
const d = new Date(draw_date);
|
|
if (Number.isNaN(d.getTime())) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid draw_date'
|
|
});
|
|
}
|
|
updates.draw_date = d;
|
|
}
|
|
|
|
// Update winner if provided and different from current
|
|
if (winner_id) {
|
|
const winnerStr = String(winner_id);
|
|
const currentWinnerStr = monthlyDraw.winner_id != null ? String(monthlyDraw.winner_id) : '';
|
|
if (winnerStr !== currentWinnerStr) {
|
|
const member = await GroupMember.findOne({
|
|
where: {
|
|
group_id: monthlyDraw.ChitGroup.id,
|
|
user_id: winner_id,
|
|
status: 'active'
|
|
},
|
|
include: [{ model: User, attributes: ['id', 'full_name'] }]
|
|
});
|
|
|
|
if (!member) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Winner must be an active member of this group'
|
|
});
|
|
}
|
|
|
|
const duplicateWinner = await MonthlyDraw.findOne({
|
|
where: {
|
|
group_id: monthlyDraw.group_id,
|
|
winner_id,
|
|
id: { [Op.ne]: draw_id }
|
|
}
|
|
});
|
|
if (duplicateWinner) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `${member.User.full_name} has already won in another draw. Each member can only win once.`,
|
|
alreadyWon: true,
|
|
winnerName: member.User.full_name
|
|
});
|
|
}
|
|
|
|
updates.winner_id = winner_id;
|
|
if (notes === undefined || notes === null) {
|
|
updates.notes = `Winner updated to: ${member.User.full_name}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update prize amount if provided (including 0)
|
|
if (prize_amount != null && prize_amount !== '') {
|
|
updates.prize_amount = prize_amount;
|
|
}
|
|
|
|
// Update notes if provided
|
|
if (notes !== undefined && notes !== null) {
|
|
updates.notes = notes;
|
|
}
|
|
|
|
if (Object.keys(updates).length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No valid fields to update'
|
|
});
|
|
}
|
|
|
|
// Perform update
|
|
await monthlyDraw.update(updates);
|
|
|
|
// Fetch updated draw with winner details
|
|
const updatedDraw = await MonthlyDraw.findOne({
|
|
where: { id: draw_id },
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'winner',
|
|
attributes: ['id', 'full_name', 'mobile_number']
|
|
}
|
|
]
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Monthly draw updated successfully',
|
|
data: updatedDraw
|
|
});
|
|
} catch (error) {
|
|
console.error('Update monthly draw error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
createMonthlyDraw,
|
|
getGroupMonthlyDraws,
|
|
getMonthlyDrawDetails,
|
|
verifyDrawResult,
|
|
getDrawStatistics,
|
|
deleteMonthlyDraw,
|
|
updateMonthlyDraw
|
|
};
|