From 371620d228810ded7c8e7577ba60e03884e7b7c3 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Mon, 25 Aug 2025 13:23:32 +0530 Subject: [PATCH] Updated --- functions/src/index.ts | 2 +- .../src/notifications/filterMembersRequest.ts | 453 ------------------ functions/src/notifications/index.ts | 1 - 3 files changed, 1 insertion(+), 455 deletions(-) delete mode 100644 functions/src/notifications/filterMembersRequest.ts diff --git a/functions/src/index.ts b/functions/src/index.ts index 750b059..625c6d7 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -13,7 +13,7 @@ export * from './shared/config'; export { sendEmailSES } from './email'; export { sendSMSMessage } from './sms'; export { accessFile } from './storage'; -export { processNotificationOnCreate,checkExpiredMemberships,filterMembers } from './notifications'; +export { processNotificationOnCreate,checkExpiredMemberships } from './notifications'; export * from './payments'; export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './users'; diff --git a/functions/src/notifications/filterMembersRequest.ts b/functions/src/notifications/filterMembersRequest.ts deleted file mode 100644 index 0b4e610..0000000 --- a/functions/src/notifications/filterMembersRequest.ts +++ /dev/null @@ -1,453 +0,0 @@ -import { onCall } from "firebase-functions/v2/https"; -import { getLogger, getAdmin } from "../shared/config"; - -const app = getAdmin(); -const logger = getLogger(); - -interface FilterMembersRequest { - gymId: string; - filter: string; - sortBy: string; - sortAscending: boolean; - searchQuery?: string; - limit?: number; - lastDocumentId?: string; - role?: string; - trainerAssignedMembershipIds?: string[]; -} - -interface MembershipWithDetails { - membershipId: string; - memberData: any; - fields: { [key: string]: string }; - renewalDate?: string; - daysUntilExpiry?: number; - hasPersonalTraining: boolean; - trainerAssignments?: any[]; - timeSlots?: any[]; -} - -interface FilterMembersResponse { - members: MembershipWithDetails[]; - hasMore: boolean; - lastDocumentId?: string; - totalCount: number; -} - -export const filterMembers = onCall( - { - region: "#{SERVICES_RGN}#", - cors: true, - }, - async (request): Promise => { - try { - const { - gymId, - filter, - sortBy, - sortAscending, - searchQuery, - limit = 20, - lastDocumentId, - role, - trainerAssignedMembershipIds, - } = request.data as FilterMembersRequest; - - logger.info(`Filtering members for gym ${gymId} with filter: ${filter}`); - - if (!gymId) { - throw new Error("gymId is required"); - } - - let query = app - .firestore() - .collection("memberships") - .where("gymId", "==", gymId); - - if (filter === "Active") { - query = query.where("status", "==", "ACTIVE"); - } else if (filter === "Pending") { - query = query.where("status", "==", "PENDING"); - } else if (filter === "Expired") { - query = query.where("status", "==", "EXPIRED"); - } - - let orderByField = "createdAt"; - let orderByDirection: "asc" | "desc" = sortAscending ? "asc" : "desc"; - - switch (sortBy) { - case "Name": - orderByField = "createdAt"; - break; - case "Expiry Date": - orderByField = "expirationDate"; - break; - case "Join Date": - orderByField = "createdAt"; - break; - default: - orderByField = "createdAt"; - } - - query = query.orderBy(orderByField, orderByDirection); - - if (lastDocumentId) { - const lastDoc = await app - .firestore() - .collection("memberships") - .doc(lastDocumentId) - .get(); - if (lastDoc.exists) { - query = query.startAfter(lastDoc); - } - } - - const fetchLimit = ["All", "Active", "Pending", "Expired"].includes( - filter - ) - ? limit - : limit * 3; - query = query.limit(fetchLimit); - - const snapshot = await query.get(); - const allMembers: MembershipWithDetails[] = []; - - const batchSize = 10; - const docs = snapshot.docs; - - for (let i = 0; i < docs.length; i += batchSize) { - const batch = docs.slice(i, i + batchSize); - const batchResults = await Promise.allSettled( - batch.map(async (doc) => { - const memberData = doc.data(); - const membershipId = doc.id; - - const fields = await getClientFieldsByMembershipId(membershipId); - - let renewalDate: Date | undefined; - let daysUntilExpiry: number | undefined; - - if (memberData.subscription) { - const payments = await getPaymentsForMembership(membershipId); - if (payments.length > 0) { - const latestPayment = payments[0]; - renewalDate = calculateRenewalDateFromPayment( - memberData.subscription, - latestPayment.dateTimestamp - ); - - if (memberData.status === "ACTIVE") { - const now = new Date(); - const timeDiff = renewalDate.getTime() - now.getTime(); - daysUntilExpiry = Math.max( - 0, - Math.floor(timeDiff / (1000 * 3600 * 24)) - ); - } - } - } - - const hasPersonalTraining = - memberData.subscription?.hasPersonalTraining === true; - - let trainerAssignments: any[] = []; - let timeSlots: any[] = []; - - if (hasPersonalTraining) { - trainerAssignments = await getTrainerAssignmentsForMembership( - membershipId - ); - - if (trainerAssignments.length > 0) { - const latestAssignment = trainerAssignments[0]; - timeSlots = latestAssignment.timeSlot || []; - } - } - - return { - membershipId, - memberData, - fields, - renewalDate: renewalDate?.toISOString(), - daysUntilExpiry, - hasPersonalTraining, - trainerAssignments, - timeSlots, - }; - }) - ); - - batchResults.forEach((result) => { - if (result.status === "fulfilled" && result.value) { - allMembers.push(result.value); - } - }); - } - - let filteredMembers = allMembers; - if (role === "Trainer" && trainerAssignedMembershipIds) { - filteredMembers = allMembers.filter((member) => - trainerAssignedMembershipIds.includes(member.membershipId) - ); - } - - if (searchQuery && searchQuery.length >= 2) { - const searchLower = searchQuery.toLowerCase(); - filteredMembers = filteredMembers.filter((member) => { - const firstName = member.fields["first-name"]?.toLowerCase() || ""; - const lastName = member.fields["last-name"]?.toLowerCase() || ""; - const phone = member.fields["phone-number"]?.toLowerCase() || ""; - const email = member.fields["email"]?.toLowerCase() || ""; - - return ( - firstName.includes(searchLower) || - lastName.includes(searchLower) || - phone.includes(searchLower) || - email.includes(searchLower) - ); - }); - } - - if ( - filter !== "All" && - filter !== "Active" && - filter !== "Pending" && - filter !== "Expired" - ) { - filteredMembers = filteredMembers.filter((member) => { - switch (filter) { - case "Personal Training": - return member.hasPersonalTraining; - - case "Expiring in 10 Days": - return ( - member.memberData.status === "ACTIVE" && - member.daysUntilExpiry != null && - member.daysUntilExpiry > 0 && - member.daysUntilExpiry <= 10 - ); - - case "Expiring in 30 Days": - return ( - member.memberData.status === "ACTIVE" && - member.daysUntilExpiry != null && - member.daysUntilExpiry > 10 && - member.daysUntilExpiry <= 30 - ); - - case "Expired in 30 days": - return applyExpiredDateFilter(member.memberData, 30); - - case "Expired in 60 days": - return applyExpiredDateFilter(member.memberData, 60); - - default: - return true; - } - }); - } - - if (sortBy === "Name") { - filteredMembers.sort((a, b) => { - const aName = `${a.fields["first-name"] || ""} ${ - a.fields["last-name"] || "" - }`.trim(); - const bName = `${b.fields["first-name"] || ""} ${ - b.fields["last-name"] || "" - }`.trim(); - const comparison = aName.localeCompare(bName); - return sortAscending ? comparison : -comparison; - }); - } else if (sortBy === "Expiry Date") { - filteredMembers.sort((a, b) => { - const aDate = a.renewalDate ? new Date(a.renewalDate) : new Date(0); - const bDate = b.renewalDate ? new Date(b.renewalDate) : new Date(0); - const comparison = aDate.getTime() - bDate.getTime(); - return sortAscending ? comparison : -comparison; - }); - } - - const startIndex = 0; - const endIndex = Math.min(startIndex + limit, filteredMembers.length); - const paginatedMembers = filteredMembers.slice(startIndex, endIndex); - - const hasMore = endIndex < filteredMembers.length; - const lastMember = - paginatedMembers.length > 0 - ? paginatedMembers[paginatedMembers.length - 1] - : null; - - logger.info( - `Returning ${paginatedMembers.length} members out of ${filteredMembers.length} filtered` - ); - - return { - members: paginatedMembers, - hasMore, - lastDocumentId: lastMember?.membershipId, - totalCount: filteredMembers.length, - }; - } catch (error) { - logger.error("Error filtering members:", error); - throw new Error( - `Failed to filter members: ${ - error instanceof Error ? error.message : "Unknown error" - }` - ); - } - } -); - -async function getClientFieldsByMembershipId( - membershipId: string -): Promise<{ [key: string]: string }> { - try { - const membershipDoc = await app - .firestore() - .collection("memberships") - .doc(membershipId) - .get(); - - if (!membershipDoc.exists) { - return {}; - } - - const membershipData = membershipDoc.data(); - const userId = membershipData?.userId; - - if (!userId) { - return {}; - } - - const clientDoc = await app - .firestore() - .collection("client_profiles") - .doc(userId) - .get(); - - if (!clientDoc.exists) { - return {}; - } - - return clientDoc.data()?.fields || {}; - } catch (error) { - logger.error( - `Error getting client fields for membership ${membershipId}:`, - error - ); - return {}; - } -} - -async function getPaymentsForMembership(membershipId: string): Promise { - try { - const docSnapshot = await app - .firestore() - .collection("membership_payments") - .doc(membershipId) - .get(); - - if (!docSnapshot.exists) { - return []; - } - - const data = docSnapshot.data(); - const paymentsData = data?.payments || []; - - const payments = paymentsData.map((payment: any) => ({ - ...payment, - dateTimestamp: payment.dateTimestamp.toDate - ? payment.dateTimestamp.toDate() - : new Date(payment.dateTimestamp), - createdAt: payment.createdAt.toDate - ? payment.createdAt.toDate() - : new Date(payment.createdAt), - })); - - payments.sort( - (a: any, b: any) => b.createdAt.getTime() - a.createdAt.getTime() - ); - return payments; - } catch (error) { - logger.error( - `Error getting payments for membership ${membershipId}:`, - error - ); - return []; - } -} - -function calculateRenewalDateFromPayment( - subscription: any, - paymentDate: Date -): Date { - const renewalDate = new Date(paymentDate); - const frequency = subscription.frequency || "Monthly"; - - switch (frequency.toLowerCase()) { - case "monthly": - renewalDate.setMonth(renewalDate.getMonth() + 1); - break; - case "quarterly": - renewalDate.setMonth(renewalDate.getMonth() + 3); - break; - case "half-yearly": - renewalDate.setMonth(renewalDate.getMonth() + 6); - break; - case "yearly": - renewalDate.setFullYear(renewalDate.getFullYear() + 1); - break; - default: - renewalDate.setMonth(renewalDate.getMonth() + 1); - } - - return renewalDate; -} - -async function getTrainerAssignmentsForMembership( - membershipId: string -): Promise { - try { - const querySnapshot = await app - .firestore() - .collection("personal_trainer_assignments") - .where("membershipId", "==", membershipId) - .get(); - - return querySnapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })); - } catch (error) { - logger.error( - `Error getting trainer assignments for membership ${membershipId}:`, - error - ); - return []; - } -} - -function applyExpiredDateFilter(memberData: any, days: number): boolean { - const status = memberData.status; - const expirationDate = memberData.expirationDate; - - if (status !== "EXPIRED" || !expirationDate) { - return false; - } - - try { - const expiredDate = expirationDate.toDate - ? expirationDate.toDate() - : new Date(expirationDate); - const now = new Date(); - const targetDaysAgo = new Date(now.getTime() - days * 24 * 60 * 60 * 1000); - - return ( - expiredDate.getTime() < now.getTime() && - expiredDate.getTime() > targetDaysAgo.getTime() - ); - } catch (error) { - logger.error("Error parsing expiration date:", error); - return false; - } -} diff --git a/functions/src/notifications/index.ts b/functions/src/notifications/index.ts index 3f42ba1..9e40bc5 100644 --- a/functions/src/notifications/index.ts +++ b/functions/src/notifications/index.ts @@ -1,4 +1,3 @@ export { processNotificationOnCreate } from './processNotification'; export { checkExpiredMemberships } from "./membershipStatusNotifications"; -export { filterMembers } from "./filterMembersRequest";