Replace all mock/placeholder data with real data systems
All checks were successful
Build And Push Image / docker (push) Successful in 2m13s
All checks were successful
Build And Push Image / docker (push) Successful in 2m13s
- Added getUserCount() method to Keycloak admin for real user statistics - Replaced hardcoded userCount (25) with live Keycloak data in admin stats - Fixed board meeting API to query real events, removed Jan 15 2025 fallback - Updated board stats to count real events instead of hardcoded 3 - Created member-tiers service for proper tier determination - Created dues-calculator service for accurate dues tracking - Updated auth callback to use member-tiers service - Updated overdue-count API to use dues-calculator - Added data quality tracking with confidence levels - Added proper error handling - returns null/0 instead of fake data - Included source tracking for all data (live/calculated/fallback) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,38 +1,9 @@
|
||||
// server/api/members/overdue-count.get.ts
|
||||
|
||||
// Helper function to calculate overdue duration
|
||||
function calculateOverdueDuration(dueDate: Date, today: Date): {
|
||||
years: number;
|
||||
months: number;
|
||||
totalMonths: number;
|
||||
formattedDuration: string;
|
||||
} {
|
||||
const diffTime = today.getTime() - dueDate.getTime();
|
||||
const diffMonths = Math.floor(diffTime / (1000 * 60 * 60 * 24 * 30.44)); // Average days per month
|
||||
const years = Math.floor(diffMonths / 12);
|
||||
const months = diffMonths % 12;
|
||||
|
||||
let formattedDuration = '';
|
||||
if (years > 0) {
|
||||
formattedDuration += `${years} year${years !== 1 ? 's' : ''}`;
|
||||
if (months > 0) {
|
||||
formattedDuration += ` ${months} month${months !== 1 ? 's' : ''}`;
|
||||
}
|
||||
} else {
|
||||
formattedDuration = `${diffMonths} month${diffMonths !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
return {
|
||||
years,
|
||||
months,
|
||||
totalMonths: diffMonths,
|
||||
formattedDuration: `${formattedDuration} overdue`
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -42,7 +13,8 @@ export default defineEventHandler(async (event) => {
|
||||
success: true,
|
||||
data: {
|
||||
count: 0,
|
||||
overdueMembers: []
|
||||
overdueMembers: [],
|
||||
dataSource: 'unavailable'
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -51,63 +23,77 @@ export default defineEventHandler(async (event) => {
|
||||
const overdueMembers: any[] = [];
|
||||
|
||||
for (const member of allMembers.list) {
|
||||
// Check for severely overdue members (more than 1 year past due date)
|
||||
let overdueDuration = null;
|
||||
// Use dues-calculator for proper dues status calculation
|
||||
const duesStatus = await calculateDuesStatus(member);
|
||||
|
||||
if (member.current_year_dues_paid === 'true' && member.membership_date_paid) {
|
||||
// If dues are marked as paid, check if it's been more than 1 year since payment
|
||||
const lastPaidDate = new Date(member.membership_date_paid);
|
||||
const oneYearFromPayment = new Date(lastPaidDate);
|
||||
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
||||
// 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;
|
||||
|
||||
if (today > oneYearFromPayment) {
|
||||
overdueDuration = calculateOverdueDuration(oneYearFromPayment, today);
|
||||
}
|
||||
} else if (member.current_year_dues_paid !== 'true') {
|
||||
// If dues are not paid, check payment due date or member since date
|
||||
let dueDate: Date;
|
||||
|
||||
if (member.payment_due_date) {
|
||||
dueDate = new Date(member.payment_due_date);
|
||||
} else if (member.member_since) {
|
||||
// Fallback: 1 year from member since date
|
||||
dueDate = new Date(member.member_since);
|
||||
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
||||
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 {
|
||||
// Skip if we can't determine due date
|
||||
continue;
|
||||
const days = duesStatus.daysOverdue;
|
||||
formattedDuration = `${days} day${days !== 1 ? 's' : ''}`;
|
||||
}
|
||||
formattedDuration += ' overdue';
|
||||
|
||||
// Check if more than 1 year overdue
|
||||
const oneYearOverdue = new Date(dueDate);
|
||||
oneYearOverdue.setFullYear(oneYearOverdue.getFullYear() + 1);
|
||||
|
||||
if (today > oneYearOverdue) {
|
||||
overdueDuration = calculateOverdueDuration(oneYearOverdue, today);
|
||||
}
|
||||
}
|
||||
|
||||
if (overdueDuration) {
|
||||
overdueMembers.push({
|
||||
id: member.Id,
|
||||
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
||||
email: member.email,
|
||||
status: member.membership_status,
|
||||
overdueDuration: overdueDuration.formattedDuration,
|
||||
totalMonthsOverdue: overdueDuration.totalMonths,
|
||||
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
|
||||
overdueMembers.sort((a, b) => b.totalMonthsOverdue - a.totalMonthsOverdue);
|
||||
// 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
|
||||
overdueMembers: overdueMembers,
|
||||
dataQuality: {
|
||||
highConfidence: highConfidenceCount,
|
||||
mediumConfidence: mediumConfidenceCount,
|
||||
lowConfidence: lowConfidenceCount
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user