From b2bb8891c374c43abfa90cd1a3edf34037c08675 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Mon, 4 Aug 2025 16:54:19 +0530 Subject: [PATCH 1/2] Changes updated --- .../membershipStatusNotifications.ts | 181 +++++++++++++++--- 1 file changed, 150 insertions(+), 31 deletions(-) diff --git a/functions/src/notifications/membershipStatusNotifications.ts b/functions/src/notifications/membershipStatusNotifications.ts index 797e0fb..85aa32a 100644 --- a/functions/src/notifications/membershipStatusNotifications.ts +++ b/functions/src/notifications/membershipStatusNotifications.ts @@ -13,7 +13,7 @@ interface MembershipData { subscription?: { name: string; frequency: string; - assignedAt: admin.firestore.Timestamp; + assignedAt?: admin.firestore.Timestamp; }; } @@ -23,6 +23,17 @@ interface ClientFields { "last-name"?: string; } +interface PaymentData { + id: string; + date: string; + amount: number; + paymentMethod: string; + referenceNumber: string; + dateTimestamp: Date; + createdAt: Date; + discount?: number; +} + export const checkExpiredMemberships = onSchedule( { schedule: "0 8,14,20 * * *", @@ -72,11 +83,28 @@ async function findExpiredMemberships(): Promise< const expired: Array<{ id: string; data: MembershipData }> = []; - snapshot.docs.forEach((doc) => { - const data = doc.data() as MembershipData; - const isExpired = checkIfMembershipExpired(data); - if (isExpired) expired.push({ id: doc.id, data }); - }); + 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 data = doc.data() as MembershipData; + const isExpired = await checkIfMembershipExpired(doc.id, data); + if (isExpired) { + return { id: doc.id, data }; + } + return null; + }) + ); + + batchResults.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + expired.push(result.value); + } + }); + } return expired; } catch (error) { @@ -85,35 +113,85 @@ async function findExpiredMemberships(): Promise< } } -function checkIfMembershipExpired(data: MembershipData): boolean { +async function checkIfMembershipExpired(membershipId: string, data: MembershipData): Promise { try { - if ( - !data.subscription || - !data.subscription.frequency || - !data.subscription.assignedAt - ) { + if (!data.subscription || !data.subscription.frequency) { logger.warn( - `Skipping expiry check for membership ${data.id} with missing subscription data.` + `Skipping expiry check for membership ${membershipId} with missing subscription data.` ); return false; } - const startDate = ( - data.subscription.assignedAt as admin.firestore.Timestamp - ).toDate(); - const expiryDate = calculateExpiryDate( - startDate, - data.subscription.frequency - ); + let startDate: Date; + + if (data.subscription.assignedAt) { + startDate = (data.subscription.assignedAt as admin.firestore.Timestamp).toDate(); + } else { + logger.info(`Using payment logic for membership ${membershipId} as assignedAt is missing`); + + const payments = await getPaymentsForMembership(membershipId); + if (payments.length === 0) { + logger.warn(`No payments found for membership ${membershipId}, cannot determine expiry`); + return false; + } + + const latestPayment = payments[0]; + startDate = latestPayment.dateTimestamp; + + logger.info(`Using latest payment date ${startDate.toISOString()} for membership ${membershipId}`); + } + + const expiryDate = calculateExpiryDate(startDate, data.subscription.frequency); const now = new Date(); - return now > expiryDate; + const isExpired = now > expiryDate; + + if (isExpired) { + logger.info(`Membership ${membershipId} expired on ${expiryDate.toISOString()}`); + } + + return isExpired; } catch (error) { - logger.error(`Error checking expiry for membership ${data.id}:`, error); + logger.error(`Error checking expiry for membership ${membershipId}:`, error); return false; } } +async function getPaymentsForMembership(membershipId: string): Promise { + try { + const docSnapshot = await app + .firestore() + .collection("membershipPayments") + .doc(membershipId) + .get(); + + if (!docSnapshot.exists) { + return []; + } + + const data = docSnapshot.data(); + const paymentsData = data?.payments || []; + + const payments: PaymentData[] = paymentsData.map((payment: any) => ({ + id: payment.id, + date: payment.date, + amount: payment.amount, + paymentMethod: payment.paymentMethod, + referenceNumber: payment.referenceNumber, + dateTimestamp: payment.dateTimestamp.toDate ? payment.dateTimestamp.toDate() : new Date(payment.dateTimestamp), + createdAt: payment.createdAt.toDate ? payment.createdAt.toDate() : new Date(payment.createdAt), + discount: payment.discount + })); + + payments.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + + return payments; + } catch (error) { + logger.error(`Error getting payments for membership ${membershipId}:`, error); + return []; + } +} + function calculateExpiryDate(startDate: Date, frequency: string): Date { const expiry = new Date(startDate); switch (frequency.toLowerCase()) { @@ -135,6 +213,30 @@ function calculateExpiryDate(startDate: Date, frequency: string): Date { return expiry; } +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 processExpiredMembership( membershipId: string, membershipData: MembershipData @@ -174,14 +276,31 @@ async function sendPlanExpiredNotification( return; } - const expiryDate = membershipData.subscription?.assignedAt?.toDate(); - const formattedDate = expiryDate - ? expiryDate.toLocaleDateString("en-US", { + let expiryDate: Date | undefined; + let formattedDate = "Unknown Date"; + + if (membershipData.subscription?.assignedAt) { + expiryDate = membershipData.subscription.assignedAt.toDate(); + formattedDate = expiryDate.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); + } else { + const payments = await getPaymentsForMembership(membershipId); + if (payments.length > 0) { + const latestPayment = payments[0]; + expiryDate = calculateRenewalDateFromPayment( + membershipData.subscription, + latestPayment.dateTimestamp + ); + formattedDate = expiryDate.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", - }) - : "Unknown Date"; + }); + } + } await app .firestore() @@ -203,9 +322,9 @@ async function sendPlanExpiredNotification( membershipId, gymName, formattedExpiryDate: formattedDate, - expiryDate: - membershipData.subscription?.assignedAt || - admin.firestore.Timestamp.fromDate(new Date()), + expiryDate: expiryDate + ? admin.firestore.Timestamp.fromDate(expiryDate) + : admin.firestore.Timestamp.fromDate(new Date()), }, }); @@ -260,4 +379,4 @@ async function getGymName(gymId: string): Promise { logger.error(`Error getting gym name for gym ${gymId}:`, error); return "Unknown Gym"; } -} +} \ No newline at end of file -- 2.43.0 From 690732d5150d34881e22aab3758e1bd078b08e21 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Mon, 4 Aug 2025 18:08:06 +0530 Subject: [PATCH 2/2] Changes Updated --- .../membershipStatusNotifications.ts | 117 ++++++++++-------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/functions/src/notifications/membershipStatusNotifications.ts b/functions/src/notifications/membershipStatusNotifications.ts index 85aa32a..ef0c238 100644 --- a/functions/src/notifications/membershipStatusNotifications.ts +++ b/functions/src/notifications/membershipStatusNotifications.ts @@ -13,7 +13,6 @@ interface MembershipData { subscription?: { name: string; frequency: string; - assignedAt?: admin.firestore.Timestamp; }; } @@ -85,7 +84,7 @@ async function findExpiredMemberships(): Promise< 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( @@ -113,7 +112,10 @@ async function findExpiredMemberships(): Promise< } } -async function checkIfMembershipExpired(membershipId: string, data: MembershipData): Promise { +async function checkIfMembershipExpired( + membershipId: string, + data: MembershipData +): Promise { try { if (!data.subscription || !data.subscription.frequency) { logger.warn( @@ -123,41 +125,49 @@ async function checkIfMembershipExpired(membershipId: string, data: MembershipDa } let startDate: Date; - - if (data.subscription.assignedAt) { - startDate = (data.subscription.assignedAt as admin.firestore.Timestamp).toDate(); - } else { - logger.info(`Using payment logic for membership ${membershipId} as assignedAt is missing`); - - const payments = await getPaymentsForMembership(membershipId); - if (payments.length === 0) { - logger.warn(`No payments found for membership ${membershipId}, cannot determine expiry`); - return false; - } - const latestPayment = payments[0]; - startDate = latestPayment.dateTimestamp; - - logger.info(`Using latest payment date ${startDate.toISOString()} for membership ${membershipId}`); + const payments = await getPaymentsForMembership(membershipId); + if (payments.length === 0) { + logger.warn( + `No payments found for membership ${membershipId}, cannot determine expiry` + ); + return false; } - const expiryDate = calculateExpiryDate(startDate, data.subscription.frequency); + const latestPayment = payments[0]; + startDate = latestPayment.dateTimestamp; + + logger.info( + `Using latest payment date ${startDate.toISOString()} for membership ${membershipId}` + ); + + const expiryDate = calculateExpiryDate( + startDate, + data.subscription.frequency + ); const now = new Date(); const isExpired = now > expiryDate; - + if (isExpired) { - logger.info(`Membership ${membershipId} expired on ${expiryDate.toISOString()}`); + logger.info( + `Membership ${membershipId} expired on ${expiryDate.toISOString()}` + ); } return isExpired; } catch (error) { - logger.error(`Error checking expiry for membership ${membershipId}:`, error); + logger.error( + `Error checking expiry for membership ${membershipId}:`, + error + ); return false; } } -async function getPaymentsForMembership(membershipId: string): Promise { +async function getPaymentsForMembership( + membershipId: string +): Promise { try { const docSnapshot = await app .firestore() @@ -178,16 +188,23 @@ async function getPaymentsForMembership(membershipId: string): Promise b.createdAt.getTime() - a.createdAt.getTime()); return payments; } catch (error) { - logger.error(`Error getting payments for membership ${membershipId}:`, error); + logger.error( + `Error getting payments for membership ${membershipId}:`, + error + ); return []; } } @@ -208,32 +225,35 @@ function calculateExpiryDate(startDate: Date, frequency: string): Date { expiry.setFullYear(expiry.getFullYear() + 1); break; default: - expiry.setMonth(expiry.getMonth() + 1); + expiry.setMonth(expiry.getMonth() + 1); } return expiry; } -function calculateRenewalDateFromPayment(subscription: any, paymentDate: Date): Date { +function calculateRenewalDateFromPayment( + subscription: any, + paymentDate: Date +): Date { const renewalDate = new Date(paymentDate); - const frequency = subscription.frequency || 'Monthly'; - + const frequency = subscription.frequency || "Monthly"; + switch (frequency.toLowerCase()) { - case 'monthly': + case "monthly": renewalDate.setMonth(renewalDate.getMonth() + 1); break; - case 'quarterly': + case "quarterly": renewalDate.setMonth(renewalDate.getMonth() + 3); break; - case 'half-yearly': + case "half-yearly": renewalDate.setMonth(renewalDate.getMonth() + 6); break; - case 'yearly': + case "yearly": renewalDate.setFullYear(renewalDate.getFullYear() + 1); break; default: renewalDate.setMonth(renewalDate.getMonth() + 1); } - + return renewalDate; } @@ -279,27 +299,18 @@ async function sendPlanExpiredNotification( let expiryDate: Date | undefined; let formattedDate = "Unknown Date"; - if (membershipData.subscription?.assignedAt) { - expiryDate = membershipData.subscription.assignedAt.toDate(); + const payments = await getPaymentsForMembership(membershipId); + if (payments.length > 0) { + const latestPayment = payments[0]; + expiryDate = calculateRenewalDateFromPayment( + membershipData.subscription, + latestPayment.dateTimestamp + ); formattedDate = expiryDate.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); - } else { - const payments = await getPaymentsForMembership(membershipId); - if (payments.length > 0) { - const latestPayment = payments[0]; - expiryDate = calculateRenewalDateFromPayment( - membershipData.subscription, - latestPayment.dateTimestamp - ); - formattedDate = expiryDate.toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }); - } } await app @@ -379,4 +390,4 @@ async function getGymName(gymId: string): Promise { logger.error(`Error getting gym name for gym ${gymId}:`, error); return "Unknown Gym"; } -} \ No newline at end of file +} -- 2.43.0