109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
// server/api/members/overdue-count.get.ts
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
const { getMembers } = await import('~/server/utils/nocodb');
|
|
const { calculateDuesStatus } = await import('~/server/utils/dues-calculator');
|
|
|
|
// Get all members
|
|
const allMembers = await getMembers();
|
|
|
|
if (!allMembers?.list) {
|
|
return {
|
|
success: true,
|
|
data: {
|
|
count: 0,
|
|
overdueMembers: [],
|
|
dataSource: 'unavailable'
|
|
}
|
|
};
|
|
}
|
|
|
|
const today = new Date();
|
|
const overdueMembers: any[] = [];
|
|
|
|
for (const member of allMembers.list) {
|
|
// Use dues-calculator for proper dues status calculation
|
|
const duesStatus = await calculateDuesStatus(member);
|
|
|
|
// Only include members who are overdue
|
|
if (duesStatus.isOverdue && duesStatus.daysOverdue !== null) {
|
|
// Format overdue duration
|
|
const totalMonths = Math.floor(duesStatus.daysOverdue / 30.44);
|
|
const years = Math.floor(totalMonths / 12);
|
|
const months = totalMonths % 12;
|
|
|
|
let formattedDuration = '';
|
|
if (years > 0) {
|
|
formattedDuration = `${years} year${years !== 1 ? 's' : ''}`;
|
|
if (months > 0) {
|
|
formattedDuration += ` ${months} month${months !== 1 ? 's' : ''}`;
|
|
}
|
|
} else if (totalMonths > 0) {
|
|
formattedDuration = `${totalMonths} month${totalMonths !== 1 ? 's' : ''}`;
|
|
} else {
|
|
const days = duesStatus.daysOverdue;
|
|
formattedDuration = `${days} day${days !== 1 ? 's' : ''}`;
|
|
}
|
|
formattedDuration += ' overdue';
|
|
|
|
overdueMembers.push({
|
|
id: member.Id,
|
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
|
email: member.email,
|
|
status: member.membership_status,
|
|
overdueDuration: formattedDuration,
|
|
daysOverdue: duesStatus.daysOverdue,
|
|
totalMonthsOverdue: totalMonths,
|
|
dueDate: duesStatus.dueDate,
|
|
amount: duesStatus.amount,
|
|
currency: duesStatus.currency,
|
|
dataSource: duesStatus.source,
|
|
confidence: duesStatus.confidence,
|
|
isInactive: member.membership_status === 'Inactive'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Sort by most overdue first (by days)
|
|
overdueMembers.sort((a, b) => b.daysOverdue - a.daysOverdue);
|
|
|
|
// Log data quality metrics
|
|
const highConfidenceCount = overdueMembers.filter(m => m.confidence === 'high').length;
|
|
const mediumConfidenceCount = overdueMembers.filter(m => m.confidence === 'medium').length;
|
|
const lowConfidenceCount = overdueMembers.filter(m => m.confidence === 'low').length;
|
|
|
|
console.log(`[overdue-count] Found ${overdueMembers.length} overdue members:`, {
|
|
highConfidence: highConfidenceCount,
|
|
mediumConfidence: mediumConfidenceCount,
|
|
lowConfidence: lowConfidenceCount
|
|
});
|
|
|
|
// Alert if many low confidence results
|
|
if (lowConfidenceCount > overdueMembers.length * 0.3) {
|
|
console.warn(`[overdue-count] High percentage of low confidence dues data (${lowConfidenceCount}/${overdueMembers.length}). Data quality review recommended.`);
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
count: overdueMembers.length,
|
|
overdueMembers: overdueMembers,
|
|
dataQuality: {
|
|
highConfidence: highConfidenceCount,
|
|
mediumConfidence: mediumConfidenceCount,
|
|
lowConfidence: lowConfidenceCount
|
|
}
|
|
}
|
|
};
|
|
|
|
} catch (error: any) {
|
|
console.error('[API] Error fetching overdue count:', error);
|
|
|
|
throw createError({
|
|
statusCode: error.statusCode || 500,
|
|
statusMessage: error.message || 'Failed to fetch overdue count'
|
|
});
|
|
}
|
|
});
|