feat: Enhanced dues overdue system with detailed time tracking
Build And Push Image / docker (push) Successful in 2m42s
Details
Build And Push Image / docker (push) Successful in 2m42s
Details
- Enhanced update-overdue-statuses API to calculate and return specific overdue durations (years/months) - Updated overdue-count API to provide detailed member information with overdue durations - Enhanced DuesOverdueBanner component to display expandable list of overdue members with their specific overdue time - Added automatic marking of members as inactive when dues are over 1 year overdue - Improved UI to show 'Dues Overdue - X Members Affected' with detailed breakdown - Members with overdue dues now display exact time overdue (e.g., '2 years 3 months overdue') - Added proper TypeScript interfaces for overdue member data - Enhanced banner shows inactive status and overdue duration for each affected member
This commit is contained in:
parent
ff85d1c722
commit
d9ef5bbdeb
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<v-alert
|
<v-alert
|
||||||
v-if="overdueCount > 0"
|
v-if="overdueCount > 0 && !dismissed"
|
||||||
type="warning"
|
type="warning"
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
class="dues-overdue-banner mb-6"
|
class="dues-overdue-banner mb-6"
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="text-h6 font-weight-bold">
|
<span class="text-h6 font-weight-bold">
|
||||||
{{ overdueCount }} Member{{ overdueCount > 1 ? 's' : '' }} with Overdue Dues
|
Dues Overdue - {{ overdueCount }} Member{{ overdueCount > 1 ? 's' : '' }} Affected
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -23,6 +23,70 @@
|
||||||
These accounts have been automatically marked as inactive.
|
These accounts have been automatically marked as inactive.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<!-- Detailed Overdue List -->
|
||||||
|
<v-expansion-panels
|
||||||
|
v-if="overdueMembers && overdueMembers.length > 0"
|
||||||
|
class="mb-4"
|
||||||
|
variant="accordion"
|
||||||
|
>
|
||||||
|
<v-expansion-panel
|
||||||
|
title="View Overdue Details"
|
||||||
|
:text="`Click to see all ${overdueCount} overdue members and their specific overdue durations`"
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
<v-list class="pa-0">
|
||||||
|
<v-list-item
|
||||||
|
v-for="member in overdueMembers"
|
||||||
|
:key="member.id"
|
||||||
|
class="overdue-member-item"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-avatar
|
||||||
|
:color="member.isInactive ? 'grey' : 'warning'"
|
||||||
|
size="32"
|
||||||
|
class="mr-3"
|
||||||
|
>
|
||||||
|
<v-icon color="white" size="16">
|
||||||
|
{{ member.isInactive ? 'mdi-account-off' : 'mdi-account-alert' }}
|
||||||
|
</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-list-item-title class="font-weight-medium">
|
||||||
|
{{ member.name }}
|
||||||
|
</v-list-item-title>
|
||||||
|
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
{{ member.email }}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
|
||||||
|
<template #append>
|
||||||
|
<div class="text-right">
|
||||||
|
<v-chip
|
||||||
|
:color="member.isInactive ? 'grey' : 'error'"
|
||||||
|
size="small"
|
||||||
|
variant="flat"
|
||||||
|
class="mb-1"
|
||||||
|
>
|
||||||
|
<v-icon start size="12">mdi-clock-alert</v-icon>
|
||||||
|
{{ member.overdueDuration }}
|
||||||
|
</v-chip>
|
||||||
|
<br>
|
||||||
|
<v-chip
|
||||||
|
:color="member.isInactive ? 'grey' : 'warning'"
|
||||||
|
size="x-small"
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
|
{{ member.isInactive ? 'Inactive' : member.status }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-2 align-center">
|
<div class="d-flex flex-wrap gap-2 align-center">
|
||||||
<v-btn
|
<v-btn
|
||||||
color="warning"
|
color="warning"
|
||||||
|
|
@ -73,6 +137,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
interface OverdueMember {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
status: string;
|
||||||
|
overdueDuration: string;
|
||||||
|
totalMonthsOverdue: number;
|
||||||
|
isInactive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
overdueCount: number;
|
overdueCount: number;
|
||||||
canUpdateStatuses?: boolean;
|
canUpdateStatuses?: boolean;
|
||||||
|
|
@ -97,6 +171,27 @@ const emit = defineEmits<Emits>();
|
||||||
// State
|
// State
|
||||||
const dismissed = ref(false);
|
const dismissed = ref(false);
|
||||||
const updatingStatuses = ref(false);
|
const updatingStatuses = ref(false);
|
||||||
|
const overdueMembers = ref<OverdueMember[]>([]);
|
||||||
|
|
||||||
|
// Load overdue member details
|
||||||
|
const loadOverdueDetails = async () => {
|
||||||
|
try {
|
||||||
|
const response = await $fetch<{
|
||||||
|
success: boolean;
|
||||||
|
data: {
|
||||||
|
count: number;
|
||||||
|
overdueMembers: OverdueMember[];
|
||||||
|
};
|
||||||
|
}>('/api/members/overdue-count');
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
overdueMembers.value = response.data.overdueMembers || [];
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error loading overdue details:', error);
|
||||||
|
overdueMembers.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Update overdue member statuses
|
// Update overdue member statuses
|
||||||
const updateOverdueStatuses = async () => {
|
const updateOverdueStatuses = async () => {
|
||||||
|
|
@ -114,6 +209,9 @@ const updateOverdueStatuses = async () => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
emit('statuses-updated', response.data.updatedCount);
|
emit('statuses-updated', response.data.updatedCount);
|
||||||
console.log(`Updated ${response.data.updatedCount} overdue member statuses`);
|
console.log(`Updated ${response.data.updatedCount} overdue member statuses`);
|
||||||
|
|
||||||
|
// Refresh overdue details after update
|
||||||
|
await loadOverdueDetails();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || 'Failed to update statuses');
|
throw new Error(response.message || 'Failed to update statuses');
|
||||||
}
|
}
|
||||||
|
|
@ -128,12 +226,21 @@ const updateOverdueStatuses = async () => {
|
||||||
// Reset dismissed state when refresh trigger changes
|
// Reset dismissed state when refresh trigger changes
|
||||||
watch(() => props.refreshTrigger, () => {
|
watch(() => props.refreshTrigger, () => {
|
||||||
dismissed.value = false;
|
dismissed.value = false;
|
||||||
|
loadOverdueDetails(); // Refresh data
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for overdueCount changes and reset dismissed
|
// Watch for overdueCount changes and reset dismissed
|
||||||
watch(() => props.overdueCount, (newCount, oldCount) => {
|
watch(() => props.overdueCount, (newCount, oldCount) => {
|
||||||
if (newCount > oldCount) {
|
if (newCount > oldCount) {
|
||||||
dismissed.value = false;
|
dismissed.value = false;
|
||||||
|
loadOverdueDetails(); // Load details when count changes
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load details on component mount
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.overdueCount > 0) {
|
||||||
|
loadOverdueDetails();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -572,7 +572,13 @@ const createPortalAccount = async (member: Member) => {
|
||||||
// Overdue dues handlers
|
// Overdue dues handlers
|
||||||
const loadOverdueCount = async () => {
|
const loadOverdueCount = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await $fetch<{ success: boolean; data: { count: number } }>('/api/members/overdue-count');
|
const response = await $fetch<{
|
||||||
|
success: boolean;
|
||||||
|
data: {
|
||||||
|
count: number;
|
||||||
|
overdueMembers: any[];
|
||||||
|
}
|
||||||
|
}>('/api/members/overdue-count');
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
overdueCount.value = response.data.count;
|
overdueCount.value = response.data.count;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,35 @@
|
||||||
// server/api/members/overdue-count.get.ts
|
// 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) => {
|
export default defineEventHandler(async (event) => {
|
||||||
try {
|
try {
|
||||||
const { getMembers } = await import('~/server/utils/nocodb');
|
const { getMembers } = await import('~/server/utils/nocodb');
|
||||||
|
|
@ -9,16 +40,19 @@ export default defineEventHandler(async (event) => {
|
||||||
if (!allMembers?.list) {
|
if (!allMembers?.list) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: { count: 0 }
|
data: {
|
||||||
|
count: 0,
|
||||||
|
overdueMembers: []
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
let severelyOverdueCount = 0;
|
const overdueMembers: any[] = [];
|
||||||
|
|
||||||
for (const member of allMembers.list) {
|
for (const member of allMembers.list) {
|
||||||
// Check for severely overdue members (more than 1 year past due date)
|
// Check for severely overdue members (more than 1 year past due date)
|
||||||
let isSeverelyOverdue = false;
|
let overdueDuration = null;
|
||||||
|
|
||||||
if (member.current_year_dues_paid === 'true' && member.membership_date_paid) {
|
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
|
// If dues are marked as paid, check if it's been more than 1 year since payment
|
||||||
|
|
@ -27,7 +61,7 @@ export default defineEventHandler(async (event) => {
|
||||||
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
||||||
|
|
||||||
if (today > oneYearFromPayment) {
|
if (today > oneYearFromPayment) {
|
||||||
isSeverelyOverdue = true;
|
overdueDuration = calculateOverdueDuration(oneYearFromPayment, today);
|
||||||
}
|
}
|
||||||
} else if (member.current_year_dues_paid !== 'true') {
|
} else if (member.current_year_dues_paid !== 'true') {
|
||||||
// If dues are not paid, check payment due date or member since date
|
// If dues are not paid, check payment due date or member since date
|
||||||
|
|
@ -49,18 +83,32 @@ export default defineEventHandler(async (event) => {
|
||||||
oneYearOverdue.setFullYear(oneYearOverdue.getFullYear() + 1);
|
oneYearOverdue.setFullYear(oneYearOverdue.getFullYear() + 1);
|
||||||
|
|
||||||
if (today > oneYearOverdue) {
|
if (today > oneYearOverdue) {
|
||||||
isSeverelyOverdue = true;
|
overdueDuration = calculateOverdueDuration(oneYearOverdue, today);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSeverelyOverdue) {
|
if (overdueDuration) {
|
||||||
severelyOverdueCount++;
|
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,
|
||||||
|
isInactive: member.membership_status === 'Inactive'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort by most overdue first
|
||||||
|
overdueMembers.sort((a, b) => b.totalMonthsOverdue - a.totalMonthsOverdue);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: { count: severelyOverdueCount }
|
data: {
|
||||||
|
count: overdueMembers.length,
|
||||||
|
overdueMembers: overdueMembers
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,35 @@
|
||||||
// server/api/members/update-overdue-statuses.post.ts
|
// server/api/members/update-overdue-statuses.post.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) => {
|
export default defineEventHandler(async (event) => {
|
||||||
try {
|
try {
|
||||||
const { getMembers, updateMember } = await import('~/server/utils/nocodb');
|
const { getMembers, updateMember } = await import('~/server/utils/nocodb');
|
||||||
|
|
@ -16,15 +47,57 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const membersToUpdate: any[] = [];
|
const membersToUpdate: any[] = [];
|
||||||
|
const overdueDetails: any[] = [];
|
||||||
|
|
||||||
for (const member of allMembers.list) {
|
for (const member of allMembers.list) {
|
||||||
// Skip if already inactive
|
// Skip if already inactive
|
||||||
if (member.membership_status === 'Inactive') {
|
if (member.membership_status === 'Inactive') {
|
||||||
|
// Still check if this inactive member is overdue to include in details
|
||||||
|
let overdueDuration = null;
|
||||||
|
|
||||||
|
if (member.current_year_dues_paid === 'true' && member.membership_date_paid) {
|
||||||
|
const lastPaidDate = new Date(member.membership_date_paid);
|
||||||
|
const oneYearFromPayment = new Date(lastPaidDate);
|
||||||
|
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
||||||
|
|
||||||
|
if (today > oneYearFromPayment) {
|
||||||
|
overdueDuration = calculateOverdueDuration(oneYearFromPayment, today);
|
||||||
|
}
|
||||||
|
} else if (member.current_year_dues_paid !== 'true') {
|
||||||
|
let dueDate: Date;
|
||||||
|
|
||||||
|
if (member.payment_due_date) {
|
||||||
|
dueDate = new Date(member.payment_due_date);
|
||||||
|
} else if (member.member_since) {
|
||||||
|
dueDate = new Date(member.member_since);
|
||||||
|
dueDate.setFullYear(dueDate.getFullYear() + 1);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oneYearOverdue = new Date(dueDate);
|
||||||
|
oneYearOverdue.setFullYear(oneYearOverdue.getFullYear() + 1);
|
||||||
|
|
||||||
|
if (today > oneYearOverdue) {
|
||||||
|
overdueDuration = calculateOverdueDuration(oneYearOverdue, today);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overdueDuration) {
|
||||||
|
overdueDetails.push({
|
||||||
|
id: member.Id,
|
||||||
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
||||||
|
status: 'Inactive',
|
||||||
|
overdueDuration: overdueDuration.formattedDuration,
|
||||||
|
totalMonthsOverdue: overdueDuration.totalMonths
|
||||||
|
});
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if member has overdue dues (more than 1 year)
|
// Check if member has overdue dues (more than 1 year)
|
||||||
let isOverdue = false;
|
let isOverdue = false;
|
||||||
|
let overdueDuration = null;
|
||||||
|
|
||||||
if (member.current_year_dues_paid === 'true' && member.membership_date_paid) {
|
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
|
// If dues are marked as paid, check if it's been more than 1 year since payment
|
||||||
|
|
@ -34,6 +107,7 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
if (today > oneYearFromPayment) {
|
if (today > oneYearFromPayment) {
|
||||||
isOverdue = true;
|
isOverdue = true;
|
||||||
|
overdueDuration = calculateOverdueDuration(oneYearFromPayment, today);
|
||||||
}
|
}
|
||||||
} else if (member.current_year_dues_paid !== 'true') {
|
} else if (member.current_year_dues_paid !== 'true') {
|
||||||
// If dues are not paid, check payment due date or member since date
|
// If dues are not paid, check payment due date or member since date
|
||||||
|
|
@ -56,14 +130,25 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
if (today > oneYearOverdue) {
|
if (today > oneYearOverdue) {
|
||||||
isOverdue = true;
|
isOverdue = true;
|
||||||
|
overdueDuration = calculateOverdueDuration(oneYearOverdue, today);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOverdue) {
|
if (isOverdue && overdueDuration) {
|
||||||
membersToUpdate.push({
|
membersToUpdate.push({
|
||||||
id: member.Id,
|
id: member.Id,
|
||||||
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
||||||
currentStatus: member.membership_status
|
currentStatus: member.membership_status,
|
||||||
|
overdueDuration: overdueDuration.formattedDuration,
|
||||||
|
totalMonthsOverdue: overdueDuration.totalMonths
|
||||||
|
});
|
||||||
|
|
||||||
|
overdueDetails.push({
|
||||||
|
id: member.Id,
|
||||||
|
name: member.FullName || `${member.first_name} ${member.last_name}`,
|
||||||
|
status: member.membership_status,
|
||||||
|
overdueDuration: overdueDuration.formattedDuration,
|
||||||
|
totalMonthsOverdue: overdueDuration.totalMonths
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +163,7 @@ export default defineEventHandler(async (event) => {
|
||||||
membership_status: 'Inactive'
|
membership_status: 'Inactive'
|
||||||
});
|
});
|
||||||
updatedCount++;
|
updatedCount++;
|
||||||
console.log(`[API] Marked member ${memberInfo.name} (${memberInfo.id}) as inactive due to overdue dues`);
|
console.log(`[API] Marked member ${memberInfo.name} (${memberInfo.id}) as inactive - ${memberInfo.overdueDuration}`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`[API] Failed to update member ${memberInfo.name} (${memberInfo.id}):`, error);
|
console.error(`[API] Failed to update member ${memberInfo.name} (${memberInfo.id}):`, error);
|
||||||
errors.push(`Failed to update ${memberInfo.name}: ${error.message}`);
|
errors.push(`Failed to update ${memberInfo.name}: ${error.message}`);
|
||||||
|
|
@ -91,7 +176,8 @@ export default defineEventHandler(async (event) => {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
updatedCount,
|
updatedCount,
|
||||||
totalOverdue: membersToUpdate.length,
|
totalOverdue: overdueDetails.length,
|
||||||
|
overdueDetails: overdueDetails.sort((a, b) => b.totalMonthsOverdue - a.totalMonthsOverdue), // Sort by most overdue first
|
||||||
errors: errors.length > 0 ? errors : undefined
|
errors: errors.length > 0 ? errors : undefined
|
||||||
},
|
},
|
||||||
message: `Successfully updated ${updatedCount} overdue member${updatedCount !== 1 ? 's' : ''} to inactive status`
|
message: `Successfully updated ${updatedCount} overdue member${updatedCount !== 1 ? 's' : ''} to inactive status`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue