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

- 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:
2025-09-04 18:43:57 +02:00
parent 67bb9e32ac
commit d34d16fda1
8 changed files with 326 additions and 24 deletions

View 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'
});
}
});

View 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'
});
}
});

View File

@@ -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