2025-08-10 23:29:48 +02:00
|
|
|
// server/api/members/update-overdue-statuses.post.ts
|
2025-08-10 23:42:17 +02:00
|
|
|
|
|
|
|
|
// Helper function to calculate overdue duration
|
|
|
|
|
function calculateOverdueDuration(dueDate: Date, today: Date): {
|
|
|
|
|
years: number;
|
|
|
|
|
months: number;
|
|
|
|
|
totalMonths: number;
|
|
|
|
|
formattedDuration: string;
|
|
|
|
|
} {
|
|
|
|
|
const diffTime = today.getTime() - dueDate.getTime();
|
|
|
|
|
const diffMonths = Math.floor(diffTime / (1000 * 60 * 60 * 24 * 30.44)); // Average days per month
|
|
|
|
|
const years = Math.floor(diffMonths / 12);
|
|
|
|
|
const months = diffMonths % 12;
|
|
|
|
|
|
|
|
|
|
let formattedDuration = '';
|
|
|
|
|
if (years > 0) {
|
|
|
|
|
formattedDuration += `${years} year${years !== 1 ? 's' : ''}`;
|
|
|
|
|
if (months > 0) {
|
|
|
|
|
formattedDuration += ` ${months} month${months !== 1 ? 's' : ''}`;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
formattedDuration = `${diffMonths} month${diffMonths !== 1 ? 's' : ''}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
years,
|
|
|
|
|
months,
|
|
|
|
|
totalMonths: diffMonths,
|
|
|
|
|
formattedDuration: `${formattedDuration} overdue`
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 23:29:48 +02:00
|
|
|
export default defineEventHandler(async (event) => {
|
|
|
|
|
try {
|
|
|
|
|
const { getMembers, updateMember } = await import('~/server/utils/nocodb');
|
|
|
|
|
|
|
|
|
|
// Get all members
|
|
|
|
|
const allMembers = await getMembers();
|
|
|
|
|
|
|
|
|
|
if (!allMembers?.list) {
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: { updatedCount: 0 },
|
|
|
|
|
message: 'No members found'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const membersToUpdate: any[] = [];
|
2025-08-10 23:42:17 +02:00
|
|
|
const overdueDetails: any[] = [];
|
2025-08-10 23:29:48 +02:00
|
|
|
|
|
|
|
|
for (const member of allMembers.list) {
|
|
|
|
|
// Skip if already inactive
|
|
|
|
|
if (member.membership_status === 'Inactive') {
|
2025-08-10 23:42:17 +02:00
|
|
|
// Still check if this inactive member is overdue to include in details
|
|
|
|
|
let overdueDuration = null;
|
|
|
|
|
|
|
|
|
|
if (member.current_year_dues_paid === 'true' && member.membership_date_paid) {
|
|
|
|
|
const lastPaidDate = new Date(member.membership_date_paid);
|
|
|
|
|
const oneYearFromPayment = new Date(lastPaidDate);
|
|
|
|
|
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
|
|
|
|
|
|
|
|
|
if (today > oneYearFromPayment) {
|
|
|
|
|
overdueDuration = calculateOverdueDuration(oneYearFromPayment, today);
|
|
|
|
|
}
|
|
|
|
|
} else if (member.current_year_dues_paid !== 'true') {
|
|
|
|
|
let dueDate: Date;
|
|
|
|
|
|
|
|
|
|
if (member.payment_due_date) {
|
|
|
|
|
dueDate = new Date(member.payment_due_date);
|
|
|
|
|
} else if (member.member_since) {
|
|
|
|
|
dueDate = new Date(member.member_since);
|
|
|
|
|
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
|
|
|
|
} else {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const oneYearOverdue = new Date(dueDate);
|
|
|
|
|
oneYearOverdue.setFullYear(oneYearOverdue.getFullYear() + 1);
|
|
|
|
|
|
|
|
|
|
if (today > oneYearOverdue) {
|
|
|
|
|
overdueDuration = calculateOverdueDuration(oneYearOverdue, today);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (overdueDuration) {
|
|
|
|
|
overdueDetails.push({
|
|
|
|
|
id: member.Id,
|
|
|
|
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
|
|
|
|
status: 'Inactive',
|
|
|
|
|
overdueDuration: overdueDuration.formattedDuration,
|
|
|
|
|
totalMonthsOverdue: overdueDuration.totalMonths
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-10 23:29:48 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if member has overdue dues (more than 1 year)
|
|
|
|
|
let isOverdue = false;
|
2025-08-10 23:42:17 +02:00
|
|
|
let overdueDuration = null;
|
2025-08-10 23:29:48 +02:00
|
|
|
|
|
|
|
|
if (member.current_year_dues_paid === 'true' && member.membership_date_paid) {
|
|
|
|
|
// If dues are marked as paid, check if it's been more than 1 year since payment
|
|
|
|
|
const lastPaidDate = new Date(member.membership_date_paid);
|
|
|
|
|
const oneYearFromPayment = new Date(lastPaidDate);
|
|
|
|
|
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
|
|
|
|
|
|
|
|
|
if (today > oneYearFromPayment) {
|
|
|
|
|
isOverdue = true;
|
2025-08-10 23:42:17 +02:00
|
|
|
overdueDuration = calculateOverdueDuration(oneYearFromPayment, today);
|
2025-08-10 23:29:48 +02:00
|
|
|
}
|
|
|
|
|
} else if (member.current_year_dues_paid !== 'true') {
|
|
|
|
|
// If dues are not paid, check payment due date or member since date
|
|
|
|
|
let dueDate: Date;
|
|
|
|
|
|
|
|
|
|
if (member.payment_due_date) {
|
|
|
|
|
dueDate = new Date(member.payment_due_date);
|
|
|
|
|
} else if (member.member_since) {
|
|
|
|
|
// Fallback: 1 year from member since date
|
|
|
|
|
dueDate = new Date(member.member_since);
|
|
|
|
|
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
|
|
|
|
} else {
|
|
|
|
|
// Skip if we can't determine due date
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if more than 1 year overdue
|
|
|
|
|
const oneYearOverdue = new Date(dueDate);
|
|
|
|
|
oneYearOverdue.setFullYear(oneYearOverdue.getFullYear() + 1);
|
|
|
|
|
|
|
|
|
|
if (today > oneYearOverdue) {
|
|
|
|
|
isOverdue = true;
|
2025-08-10 23:42:17 +02:00
|
|
|
overdueDuration = calculateOverdueDuration(oneYearOverdue, today);
|
2025-08-10 23:29:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-10 23:42:17 +02:00
|
|
|
if (isOverdue && overdueDuration) {
|
2025-08-10 23:29:48 +02:00
|
|
|
membersToUpdate.push({
|
|
|
|
|
id: member.Id,
|
|
|
|
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
2025-08-10 23:42:17 +02:00
|
|
|
currentStatus: member.membership_status,
|
|
|
|
|
overdueDuration: overdueDuration.formattedDuration,
|
|
|
|
|
totalMonthsOverdue: overdueDuration.totalMonths
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
overdueDetails.push({
|
|
|
|
|
id: member.Id,
|
|
|
|
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
|
|
|
|
status: member.membership_status,
|
|
|
|
|
overdueDuration: overdueDuration.formattedDuration,
|
|
|
|
|
totalMonthsOverdue: overdueDuration.totalMonths
|
2025-08-10 23:29:48 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update all overdue members to inactive
|
|
|
|
|
let updatedCount = 0;
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
|
|
|
|
|
for (const memberInfo of membersToUpdate) {
|
|
|
|
|
try {
|
|
|
|
|
await updateMember(memberInfo.id, {
|
|
|
|
|
membership_status: 'Inactive'
|
|
|
|
|
});
|
|
|
|
|
updatedCount++;
|
2025-08-10 23:42:17 +02:00
|
|
|
console.log(`[API] Marked member ${memberInfo.name} (${memberInfo.id}) as inactive - ${memberInfo.overdueDuration}`);
|
2025-08-10 23:29:48 +02:00
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error(`[API] Failed to update member ${memberInfo.name} (${memberInfo.id}):`, error);
|
|
|
|
|
errors.push(`Failed to update ${memberInfo.name}: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[API] Updated ${updatedCount} of ${membersToUpdate.length} overdue members to inactive status`);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
updatedCount,
|
2025-08-10 23:42:17 +02:00
|
|
|
totalOverdue: overdueDetails.length,
|
|
|
|
|
overdueDetails: overdueDetails.sort((a, b) => b.totalMonthsOverdue - a.totalMonthsOverdue), // Sort by most overdue first
|
2025-08-10 23:29:48 +02:00
|
|
|
errors: errors.length > 0 ? errors : undefined
|
|
|
|
|
},
|
|
|
|
|
message: `Successfully updated ${updatedCount} overdue member${updatedCount !== 1 ? 's' : ''} to inactive status`
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error('[API] Error updating overdue member statuses:', error);
|
|
|
|
|
|
|
|
|
|
throw createError({
|
|
|
|
|
statusCode: error.statusCode || 500,
|
|
|
|
|
statusMessage: error.message || 'Failed to update overdue member statuses'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|