Implement dues reminder system with monthly payment cycle
Some checks failed
Build And Push Image / docker (push) Failing after 1m10s
Some checks failed
Build And Push Image / docker (push) Failing after 1m10s
- Add API endpoint and email templates for dues reminders - Change due date calculation from yearly to monthly billing - Add visual status indicators for overdue and due-soon members - Enhance member cards with status stripes and styling
This commit is contained in:
177
server/api/members/[id]/send-dues-reminder.post.ts
Normal file
177
server/api/members/[id]/send-dues-reminder.post.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { sendEmail } from '~/server/utils/email';
|
||||
import { createNocoDBClient } from '~/server/utils/nocodb';
|
||||
import { calculateDaysUntilDue, calculateOverdueDays, getNextDuesDate, isPaymentOverOneYear, isDuesActuallyCurrent } from '~/utils/dues-calculations';
|
||||
import { getRegistrationConfig } from '~/server/utils/admin-config';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const memberId = getRouterParam(event, 'id');
|
||||
const { reminderType } = await readBody(event);
|
||||
|
||||
if (!memberId) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Member ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
if (!['due-soon', 'overdue'].includes(reminderType)) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid reminder type. Must be "due-soon" or "overdue"'
|
||||
});
|
||||
}
|
||||
|
||||
// Get member data
|
||||
const nocodb = createNocoDBClient();
|
||||
const member = await nocodb.findOne('nc_members', memberId);
|
||||
|
||||
if (!member) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Member not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (!member.email) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Member does not have an email address'
|
||||
});
|
||||
}
|
||||
|
||||
// Get current admin configuration for payment details
|
||||
const registrationConfig = getRegistrationConfig();
|
||||
|
||||
// Calculate dues status
|
||||
const memberName = member.FullName || `${member.first_name} ${member.last_name}`.trim() || 'Member';
|
||||
const nextDueDate = getNextDuesDate(member);
|
||||
const membershipFee = `€${registrationConfig.membershipFee}`;
|
||||
const paymentIban = registrationConfig.iban;
|
||||
const accountHolder = registrationConfig.accountHolder;
|
||||
const portalUrl = `${process.env.NUXT_PUBLIC_DOMAIN || 'https://monacousa.org'}/dashboard`;
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
// Format dates
|
||||
const formatDate = (dateString: string | Date | null | undefined): string => {
|
||||
if (!dateString) return '';
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch {
|
||||
return String(dateString);
|
||||
}
|
||||
};
|
||||
|
||||
let emailData;
|
||||
let subject;
|
||||
let template;
|
||||
|
||||
if (reminderType === 'due-soon') {
|
||||
// Check if this member actually has dues coming due
|
||||
const daysUntilDue = calculateDaysUntilDue(member);
|
||||
|
||||
if (!daysUntilDue || daysUntilDue.daysUntilDue <= 0) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Member does not have dues coming due soon'
|
||||
});
|
||||
}
|
||||
|
||||
subject = `🏎️ MonacoUSA - Annual Dues Due in ${daysUntilDue.daysUntilDue} Days`;
|
||||
template = 'dues-due-soon';
|
||||
|
||||
emailData = {
|
||||
memberName,
|
||||
memberId: member.member_id || member.Id,
|
||||
memberEmail: member.email,
|
||||
memberSince: formatDate(member.member_since),
|
||||
amount: membershipFee,
|
||||
dueDate: formatDate(daysUntilDue.nextDueDate),
|
||||
daysUntilDue: daysUntilDue.daysUntilDue,
|
||||
paymentIban,
|
||||
accountHolder,
|
||||
portalUrl,
|
||||
currentYear
|
||||
};
|
||||
|
||||
} else if (reminderType === 'overdue') {
|
||||
// Check if this member is actually overdue
|
||||
const isDuesCurrent = isDuesActuallyCurrent(member);
|
||||
|
||||
if (isDuesCurrent) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Member does not have overdue dues'
|
||||
});
|
||||
}
|
||||
|
||||
const overdueDays = calculateOverdueDays(member);
|
||||
const originalDueDate = member.payment_due_date || nextDueDate;
|
||||
|
||||
subject = `🚨 MonacoUSA - URGENT: Overdue Dues Notice (${overdueDays} days overdue)`;
|
||||
template = 'dues-overdue';
|
||||
|
||||
emailData = {
|
||||
memberName,
|
||||
memberId: member.member_id || member.Id,
|
||||
memberEmail: member.email,
|
||||
memberSince: formatDate(member.member_since),
|
||||
amount: membershipFee,
|
||||
originalDueDate: formatDate(originalDueDate),
|
||||
daysOverdue: overdueDays,
|
||||
paymentIban,
|
||||
accountHolder,
|
||||
portalUrl,
|
||||
currentYear
|
||||
};
|
||||
}
|
||||
|
||||
// Send the email
|
||||
console.log(`[Dues Reminder] Sending ${reminderType} reminder to ${member.email}`, {
|
||||
memberId,
|
||||
memberName,
|
||||
reminderType,
|
||||
emailData
|
||||
});
|
||||
|
||||
await sendEmail({
|
||||
to: member.email,
|
||||
subject,
|
||||
template,
|
||||
data: emailData
|
||||
});
|
||||
|
||||
// Log the reminder sent (could store in database for tracking)
|
||||
console.log(`[Dues Reminder] Successfully sent ${reminderType} reminder to ${memberName} (${member.email})`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `${reminderType === 'due-soon' ? 'Due soon' : 'Overdue'} reminder sent successfully`,
|
||||
data: {
|
||||
memberId,
|
||||
memberName,
|
||||
memberEmail: member.email,
|
||||
reminderType,
|
||||
sentAt: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[Dues Reminder] Error sending reminder:', error);
|
||||
|
||||
// Handle specific error cases
|
||||
if (error.statusCode) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Failed to send dues reminder'
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user