152 lines
5.1 KiB
TypeScript
152 lines
5.1 KiB
TypeScript
import { getMembers, updateMember } from './nocodb';
|
|
import type { Member } from '~/utils/types';
|
|
|
|
/**
|
|
* 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 = 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++;
|
|
|
|
// 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}`);
|
|
|
|
// Check if ID already exists in database
|
|
const existingMember = await checkMemberIDExists(memberID);
|
|
isUnique = !existingMember;
|
|
|
|
if (!isUnique) {
|
|
console.log(`[member-id] ID ${memberID} already exists, trying next number...`);
|
|
nextNumber++;
|
|
}
|
|
}
|
|
|
|
if (attempts >= maxAttempts) {
|
|
console.error('[member-id] Failed to generate unique member ID after maximum attempts');
|
|
throw new Error('Failed to generate unique member ID after maximum attempts');
|
|
}
|
|
|
|
console.log(`[member-id] ✅ Generated unique member ID: ${memberID!} (attempts: ${attempts})`);
|
|
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
|
|
* @returns Promise<boolean> - True if the member ID exists, false otherwise
|
|
*/
|
|
export async function checkMemberIDExists(memberID: string): Promise<boolean> {
|
|
try {
|
|
console.log(`[member-id] Checking if member ID exists: ${memberID}`);
|
|
|
|
// Get all members and check for duplicate member_id
|
|
const members = await getMembers();
|
|
const memberList = Array.isArray(members) ? members : members?.list || [];
|
|
|
|
const existingMember = memberList.find((member: Member) => member.member_id === memberID);
|
|
const exists = !!existingMember;
|
|
|
|
console.log(`[member-id] Member ID ${memberID} exists: ${exists}`);
|
|
return exists;
|
|
|
|
} catch (error: any) {
|
|
console.error('[member-id] Error checking member ID existence:', error);
|
|
// In case of error, assume it doesn't exist to allow generation to continue
|
|
// The actual creation will fail if there's a real database issue
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds all members without a member_id field
|
|
* Used for migration purposes
|
|
* @returns Promise<Member[]> - Array of members without member IDs
|
|
*/
|
|
export async function findMembersWithoutMemberID(): Promise<Member[]> {
|
|
try {
|
|
console.log('[member-id] Finding members without member IDs for migration...');
|
|
|
|
const members = await getMembers();
|
|
const memberList = Array.isArray(members) ? members : members?.list || [];
|
|
|
|
const membersWithoutId = memberList.filter((member: Member) =>
|
|
!member.member_id || member.member_id.trim() === ''
|
|
);
|
|
|
|
console.log(`[member-id] Found ${membersWithoutId.length} members without member IDs`);
|
|
return membersWithoutId;
|
|
|
|
} catch (error: any) {
|
|
console.error('[member-id] Error finding members without member IDs:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates a member ID format
|
|
* @param memberID - The member ID to validate
|
|
* @returns boolean - True if valid format, false otherwise
|
|
*/
|
|
export function isValidMemberIDFormat(memberID: string): boolean {
|
|
if (!memberID || typeof memberID !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
// Check format: MUSA-YYYY-XXXX (year and 4 digits)
|
|
const memberIDRegex = /^MUSA-\d{4}-\d{4}$/;
|
|
return memberIDRegex.test(memberID);
|
|
}
|
|
|
|
/**
|
|
* Extracts the numeric part from a member ID
|
|
* @param memberID - The member ID (e.g., "MUSA-123456")
|
|
* @returns number - The numeric part or null if invalid
|
|
*/
|
|
export function extractMemberIDNumber(memberID: string): number | null {
|
|
if (!isValidMemberIDFormat(memberID)) {
|
|
return null;
|
|
}
|
|
|
|
const numericPart = memberID.replace('MUSA-', '');
|
|
return parseInt(numericPart, 10);
|
|
}
|