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:
@@ -31,14 +31,14 @@ export interface DuesPayment {
|
||||
|
||||
/**
|
||||
* Calculate dues status for a member
|
||||
* Uses actual dues_paid_until field from database when available
|
||||
* Uses actual payment_due_date field from database when available
|
||||
*/
|
||||
export async function calculateDuesStatus(member: any): Promise<DuesStatus> {
|
||||
const now = new Date();
|
||||
|
||||
// First check if member has dues_paid_until field
|
||||
if (member.dues_paid_until) {
|
||||
const paidUntil = new Date(member.dues_paid_until);
|
||||
// First check if member has payment_due_date field
|
||||
if (member.payment_due_date) {
|
||||
const paidUntil = new Date(member.payment_due_date);
|
||||
const isDue = paidUntil < now;
|
||||
const daysUntilDue = isDue ? null : Math.floor((paidUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||
const daysOverdue = isDue ? Math.floor((now.getTime() - paidUntil.getTime()) / (1000 * 60 * 60 * 24)) : null;
|
||||
@@ -174,7 +174,7 @@ export async function recordDuesPayment(payment: DuesPayment): Promise<{ success
|
||||
// Update member record with payment information
|
||||
await updateMember(payment.memberId, {
|
||||
last_dues_paid: payment.paymentDate.toISOString(),
|
||||
dues_paid_until: payment.paidUntil.toISOString(),
|
||||
payment_due_date: payment.paidUntil.toISOString(),
|
||||
dues_amount: payment.amount,
|
||||
last_payment_method: payment.paymentMethod,
|
||||
last_transaction_id: payment.transactionId
|
||||
@@ -309,7 +309,7 @@ export async function bulkUpdateDuesDates(
|
||||
for (const memberId of memberIds) {
|
||||
try {
|
||||
await updateMember(memberId, {
|
||||
dues_paid_until: paidUntil.toISOString()
|
||||
payment_due_date: paidUntil.toISOString()
|
||||
});
|
||||
results.success++;
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -2,24 +2,44 @@ import { getMembers, updateMember } from './nocodb';
|
||||
import type { Member } from '~/utils/types';
|
||||
|
||||
/**
|
||||
* Generates a unique member ID in the format MUSA-{unique 6-digit number}
|
||||
* Generates a unique member ID in the format MUSA-YYYY-XXXX
|
||||
* where YYYY is the current year and XXXX is a sequential number
|
||||
* Checks against existing member IDs to ensure uniqueness
|
||||
* @returns Promise<string> - The unique member ID
|
||||
*/
|
||||
export async function generateMemberID(): Promise<string> {
|
||||
console.log('[member-id] Generating new member ID...');
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
let memberID: string;
|
||||
let isUnique = false;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100; // Prevent infinite loops
|
||||
const maxAttempts = 1000; // Prevent infinite loops
|
||||
|
||||
// Get all existing member IDs for this year to find the highest number
|
||||
const members = await getMembers();
|
||||
const memberList = Array.isArray(members) ? members : members?.list || [];
|
||||
|
||||
// Filter member IDs that match the current year format
|
||||
const yearPrefix = `MUSA-${currentYear}-`;
|
||||
const existingYearIds = memberList
|
||||
.filter((member: Member) => member.member_id?.startsWith(yearPrefix))
|
||||
.map((member: Member) => {
|
||||
const parts = member.member_id!.split('-');
|
||||
return parts.length === 3 ? parseInt(parts[2], 10) : 0;
|
||||
})
|
||||
.filter((num: number) => !isNaN(num));
|
||||
|
||||
// Find the highest number used this year
|
||||
const highestNumber = existingYearIds.length > 0 ? Math.max(...existingYearIds) : 0;
|
||||
let nextNumber = highestNumber + 1;
|
||||
|
||||
while (!isUnique && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
|
||||
// Generate a 6-digit number (100000 to 999999)
|
||||
const uniqueNumber = Math.floor(Math.random() * 900000) + 100000;
|
||||
memberID = `MUSA-${uniqueNumber}`;
|
||||
// Format with leading zeros (4 digits)
|
||||
const formattedNumber = String(nextNumber).padStart(4, '0');
|
||||
memberID = `MUSA-${currentYear}-${formattedNumber}`;
|
||||
|
||||
console.log(`[member-id] Attempt ${attempts}: Checking uniqueness of ${memberID}`);
|
||||
|
||||
@@ -28,7 +48,8 @@ export async function generateMemberID(): Promise<string> {
|
||||
isUnique = !existingMember;
|
||||
|
||||
if (!isUnique) {
|
||||
console.log(`[member-id] ID ${memberID} already exists, generating new one...`);
|
||||
console.log(`[member-id] ID ${memberID} already exists, trying next number...`);
|
||||
nextNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +62,13 @@ export async function generateMemberID(): Promise<string> {
|
||||
return memberID!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for generateMemberID to match the import in other files
|
||||
*/
|
||||
export async function generateUniqueMemberId(): Promise<string> {
|
||||
return generateMemberID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a member ID already exists in the database
|
||||
* @param memberID - The member ID to check
|
||||
@@ -103,8 +131,8 @@ export function isValidMemberIDFormat(memberID: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check format: MUSA-{6 digits}
|
||||
const memberIDRegex = /^MUSA-\d{6}$/;
|
||||
// Check format: MUSA-YYYY-XXXX (year and 4 digits)
|
||||
const memberIDRegex = /^MUSA-\d{4}-\d{4}$/;
|
||||
return memberIDRegex.test(memberID);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user