Fix critical member management issues: dues tracking, member IDs, and profile display
All checks were successful
Build And Push Image / docker (push) Successful in 2m20s
All checks were successful
Build And Push Image / docker (push) Successful in 2m20s
- Fix dues payment logic to automatically calculate payment_due_date as 1 year from payment date - Remove redundant dues_paid_until field and replace with payment_due_date throughout - Implement member ID generation system with format MUSA-YYYY-XXXX - Create migration endpoints for generating member IDs and fixing payment dates - Update admin members page to display actual member_id from database - Ensure ProfileAvatar components use correct member_id field - Add support for profile images in list and grid views with initials fallback - Fix countries export alias for backward compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
132
server/api/admin/fix-payment-dates.post.ts
Normal file
132
server/api/admin/fix-payment-dates.post.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
// server/api/admin/fix-payment-dates.post.ts
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const { getMembers, updateMember } = await import('~/server/utils/nocodb');
|
||||
|
||||
console.log('[api/admin/fix-payment-dates.post] Starting payment date migration...');
|
||||
|
||||
// Get all members
|
||||
const membersResponse = await getMembers();
|
||||
const members = membersResponse?.list || [];
|
||||
|
||||
if (members.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'No members found to process',
|
||||
stats: {
|
||||
total: 0,
|
||||
fixed: 0,
|
||||
skipped: 0,
|
||||
failed: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const results = {
|
||||
total: members.length,
|
||||
fixed: 0,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
errors: [] as any[]
|
||||
};
|
||||
|
||||
// Process each member
|
||||
for (const member of members) {
|
||||
try {
|
||||
// Check if member has membership_date_paid but no payment_due_date
|
||||
if (member.membership_date_paid && !member.payment_due_date) {
|
||||
const paymentDate = new Date(member.membership_date_paid);
|
||||
|
||||
// Calculate payment_due_date as 1 year from payment date
|
||||
const dueDate = new Date(paymentDate);
|
||||
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
||||
const dueDateStr = dueDate.toISOString().split('T')[0];
|
||||
|
||||
console.log(`[api/admin/fix-payment-dates.post] Fixing dates for ${member.first_name} ${member.last_name} (ID: ${member.Id})`);
|
||||
console.log(` Payment Date: ${member.membership_date_paid}`);
|
||||
console.log(` New Due Date: ${dueDateStr}`);
|
||||
|
||||
// Update the member
|
||||
await updateMember(member.Id, {
|
||||
payment_due_date: dueDateStr
|
||||
});
|
||||
|
||||
results.fixed++;
|
||||
console.log(`[api/admin/fix-payment-dates.post] ✅ Fixed payment dates for member ${member.Id}`);
|
||||
|
||||
} else if (member.membership_date_paid && member.payment_due_date) {
|
||||
// Member already has both dates, skip
|
||||
results.skipped++;
|
||||
console.log(`[api/admin/fix-payment-dates.post] Skipped member ${member.Id} - already has payment_due_date`);
|
||||
|
||||
} else if (!member.membership_date_paid && !member.payment_due_date) {
|
||||
// Member hasn't paid yet, check if they're new (within grace period)
|
||||
if (member.member_since) {
|
||||
const joinDate = new Date(member.member_since);
|
||||
const gracePeriodEnd = new Date(joinDate);
|
||||
gracePeriodEnd.setMonth(gracePeriodEnd.getMonth() + 1); // 1 month grace period
|
||||
|
||||
const now = new Date();
|
||||
if (now < gracePeriodEnd) {
|
||||
// Still in grace period, set payment_due_date to end of grace period
|
||||
const dueDateStr = gracePeriodEnd.toISOString().split('T')[0];
|
||||
|
||||
await updateMember(member.Id, {
|
||||
payment_due_date: dueDateStr
|
||||
});
|
||||
|
||||
results.fixed++;
|
||||
console.log(`[api/admin/fix-payment-dates.post] Set grace period due date for new member ${member.Id}`);
|
||||
} else {
|
||||
// Past grace period, set due date to 1 year from join date
|
||||
const dueDate = new Date(joinDate);
|
||||
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
||||
const dueDateStr = dueDate.toISOString().split('T')[0];
|
||||
|
||||
await updateMember(member.Id, {
|
||||
payment_due_date: dueDateStr
|
||||
});
|
||||
|
||||
results.fixed++;
|
||||
console.log(`[api/admin/fix-payment-dates.post] Set overdue date for member ${member.Id}`);
|
||||
}
|
||||
} else {
|
||||
results.skipped++;
|
||||
console.log(`[api/admin/fix-payment-dates.post] Skipped member ${member.Id} - no payment or join date`);
|
||||
}
|
||||
} else {
|
||||
results.skipped++;
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(`[api/admin/fix-payment-dates.post] ❌ Failed to fix dates for member ${member.Id}:`, error);
|
||||
results.failed++;
|
||||
results.errors.push({
|
||||
memberId: member.Id,
|
||||
name: `${member.first_name} ${member.last_name}`,
|
||||
error: error.message || 'Unknown error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const message = `Payment date migration complete!\n` +
|
||||
`Fixed: ${results.fixed} members\n` +
|
||||
`Skipped: ${results.skipped} members\n` +
|
||||
`Failed: ${results.failed} members`;
|
||||
|
||||
console.log(`[api/admin/fix-payment-dates.post] ${message}`);
|
||||
|
||||
return {
|
||||
success: results.failed === 0,
|
||||
message,
|
||||
stats: results
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/fix-payment-dates.post] Error:', error);
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || 'Failed to fix payment dates'
|
||||
});
|
||||
}
|
||||
});
|
||||
119
server/api/admin/generate-member-ids.post.ts
Normal file
119
server/api/admin/generate-member-ids.post.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
// server/api/admin/generate-member-ids.post.ts
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event).catch(() => ({}));
|
||||
const { forceRegenerate = false } = body;
|
||||
|
||||
const { getMembers, updateMember } = await import('~/server/utils/nocodb');
|
||||
const { generateUniqueMemberId } = await import('~/server/utils/member-id');
|
||||
|
||||
console.log('[api/admin/generate-member-ids.post] Starting member ID generation...');
|
||||
|
||||
// Get all members
|
||||
const membersResponse = await getMembers();
|
||||
const members = membersResponse?.list || [];
|
||||
|
||||
if (members.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'No members found to process',
|
||||
stats: {
|
||||
total: 0,
|
||||
generated: 0,
|
||||
skipped: 0,
|
||||
failed: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Filter members that need IDs (unless force regenerate)
|
||||
const membersToProcess = forceRegenerate
|
||||
? members
|
||||
: members.filter((member: any) => !member.member_id || member.member_id.trim() === '');
|
||||
|
||||
console.log(`[api/admin/generate-member-ids.post] Found ${membersToProcess.length} members to process (out of ${members.length} total)`);
|
||||
|
||||
const results = {
|
||||
total: members.length,
|
||||
generated: 0,
|
||||
skipped: members.length - membersToProcess.length,
|
||||
failed: 0,
|
||||
errors: [] as any[]
|
||||
};
|
||||
|
||||
// Process each member
|
||||
for (const member of membersToProcess) {
|
||||
try {
|
||||
// Generate unique member ID
|
||||
const memberID = await generateUniqueMemberId();
|
||||
|
||||
console.log(`[api/admin/generate-member-ids.post] Generated ID ${memberID} for ${member.first_name} ${member.last_name} (ID: ${member.Id})`);
|
||||
|
||||
// Update the member with the new ID
|
||||
await updateMember(member.Id, {
|
||||
member_id: memberID
|
||||
});
|
||||
|
||||
results.generated++;
|
||||
|
||||
console.log(`[api/admin/generate-member-ids.post] ✅ Successfully assigned ID ${memberID} to member ${member.Id}`);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(`[api/admin/generate-member-ids.post] ❌ Failed to generate ID for member ${member.Id}:`, error);
|
||||
results.failed++;
|
||||
results.errors.push({
|
||||
memberId: member.Id,
|
||||
name: `${member.first_name} ${member.last_name}`,
|
||||
error: error.message || 'Unknown error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate and update payment_due_date for all members with membership_date_paid
|
||||
console.log('[api/admin/generate-member-ids.post] Updating payment due dates for paid members...');
|
||||
|
||||
let duesDatesFixed = 0;
|
||||
for (const member of members) {
|
||||
if (member.membership_date_paid && !member.payment_due_date) {
|
||||
try {
|
||||
const paymentDate = new Date(member.membership_date_paid);
|
||||
const dueDate = new Date(paymentDate);
|
||||
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
||||
|
||||
await updateMember(member.Id, {
|
||||
payment_due_date: dueDate.toISOString().split('T')[0]
|
||||
});
|
||||
|
||||
duesDatesFixed++;
|
||||
console.log(`[api/admin/generate-member-ids.post] Fixed payment_due_date for member ${member.Id}`);
|
||||
} catch (error) {
|
||||
console.error(`[api/admin/generate-member-ids.post] Failed to fix payment_due_date for member ${member.Id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const message = `Member ID generation complete!\n` +
|
||||
`Generated: ${results.generated} IDs\n` +
|
||||
`Skipped: ${results.skipped} (already have IDs)\n` +
|
||||
`Failed: ${results.failed}\n` +
|
||||
`Payment Due Dates Fixed: ${duesDatesFixed}`;
|
||||
|
||||
console.log(`[api/admin/generate-member-ids.post] ${message}`);
|
||||
|
||||
return {
|
||||
success: results.failed === 0,
|
||||
message,
|
||||
stats: {
|
||||
...results,
|
||||
duesDatesFixed
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[api/admin/generate-member-ids.post] Error:', error);
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || 'Failed to generate member IDs'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -28,14 +28,16 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
// Determine payment date - use custom date if provided, otherwise today
|
||||
let paymentDate: string;
|
||||
let paymentDateObj: Date;
|
||||
|
||||
if (customPaymentDate) {
|
||||
try {
|
||||
// Validate the custom date
|
||||
const parsedDate = new Date(customPaymentDate);
|
||||
if (isNaN(parsedDate.getTime())) {
|
||||
paymentDateObj = new Date(customPaymentDate);
|
||||
if (isNaN(paymentDateObj.getTime())) {
|
||||
throw new Error('Invalid date format');
|
||||
}
|
||||
paymentDate = parsedDate.toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
paymentDate = paymentDateObj.toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
@@ -44,15 +46,21 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
} else {
|
||||
// Default to today if no custom date provided
|
||||
paymentDate = new Date().toISOString().split('T')[0];
|
||||
paymentDateObj = new Date();
|
||||
paymentDate = paymentDateObj.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// Calculate next payment due date (1 year from payment date)
|
||||
const nextDueDate = new Date(paymentDateObj);
|
||||
nextDueDate.setFullYear(nextDueDate.getFullYear() + 1);
|
||||
const nextDueDateStr = nextDueDate.toISOString().split('T')[0];
|
||||
|
||||
// Prepare update data
|
||||
const updateData = {
|
||||
current_year_dues_paid: 'true',
|
||||
membership_date_paid: paymentDate,
|
||||
membership_status: 'Active', // Ensure member is marked as active when dues are paid
|
||||
payment_due_date: undefined // Clear the due date since it's now paid
|
||||
payment_due_date: nextDueDateStr, // Set to 1 year from payment date
|
||||
membership_status: 'Active' // Ensure member is marked as active when dues are paid
|
||||
};
|
||||
|
||||
// Update the member
|
||||
|
||||
Reference in New Issue
Block a user