From d7bf910baa60a84c7da8bc65bcbb95fb32ed3250 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Wed, 13 Aug 2025 18:44:51 +0530 Subject: [PATCH] Changes Updated --- .../membershipStatusNotifications.ts | 192 +++++++++++++++++- .../src/notifications/processNotification.ts | 20 +- 2 files changed, 208 insertions(+), 4 deletions(-) diff --git a/functions/src/notifications/membershipStatusNotifications.ts b/functions/src/notifications/membershipStatusNotifications.ts index 1c01fff..7dd0541 100644 --- a/functions/src/notifications/membershipStatusNotifications.ts +++ b/functions/src/notifications/membershipStatusNotifications.ts @@ -33,6 +33,15 @@ interface PaymentData { discount?: number; } +interface PersonalTrainerAssign { + id: string; + ownerId: string; + trainerId?: string; + clientId: string; + membershipId: string; + gymId: string; +} + export const checkExpiredMemberships = onSchedule( { schedule: "*/5 * * * *", @@ -367,6 +376,66 @@ function calculateRenewalDateFromPayment( 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(), + })) as PersonalTrainerAssign[]; + } catch (error) { + logger.error( + `Error getting trainer assignments for membership ${membershipId}:`, + error + ); + return []; + } +} + +async function getTrainerName(trainerId: string): Promise { + try { + const doc = await app + .firestore() + .collection("trainer_profiles") + .doc(trainerId) + .get(); + + if (!doc.exists) { + const userDoc = await app + .firestore() + .collection("users") + .doc(trainerId) + .get(); + + if (userDoc.exists) { + const userData = userDoc.data(); + return userData?.name || userData?.displayName || "Unknown Trainer"; + } + return "Unknown Trainer"; + } + + const data = doc.data(); + const fields = data?.fields; + if (fields) { + const firstName = fields["first-name"] || ""; + const lastName = fields["last-name"] || ""; + return `${firstName} ${lastName}`.trim() || "Unknown Trainer"; + } + + return data?.name || data?.displayName || "Unknown Trainer"; + } catch (error) { + logger.error(`Error getting trainer name for ${trainerId}:`, error); + return "Unknown Trainer"; + } +} + async function processExpiredMembership( membershipId: string, membershipData: MembershipData @@ -378,7 +447,10 @@ async function processExpiredMembership( }); logger.info(`Marked membership ${membershipId} as EXPIRED`); + await sendPlanExpiredNotification(membershipId, membershipData); + + await sendTrainerNotifications(membershipId, membershipData, "expired"); } catch (error) { logger.error(`Error processing membership ${membershipId}:`, error); } @@ -390,7 +462,10 @@ async function processExpiringMembership( ): Promise { try { logger.info(`Processing expiring membership ${membershipId}`); + await sendPlanExpiringNotification(membershipId, membershipData); + + await sendTrainerNotifications(membershipId, membershipData, "expiring"); } catch (error) { logger.error( `Error processing expiring membership ${membershipId}:`, @@ -399,6 +474,121 @@ async function processExpiringMembership( } } +async function sendTrainerNotifications( + membershipId: string, + membershipData: MembershipData, + notificationType: "expired" | "expiring" +): Promise { + try { + const trainerAssignments = await getTrainerAssignmentsForMembership(membershipId); + + if (trainerAssignments.length === 0) { + logger.info(`No trainer assignments found for membership ${membershipId}`); + return; + } + + const clientName = await getClientName(membershipId, membershipData.userId); + const gymName = await getGymName(membershipData.gymId); + + let expiryDate: Date | undefined; + let formattedDate = "Unknown Date"; + let daysUntilExpiry = 0; + + 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", + }); + + if (notificationType === "expiring") { + const now = new Date(); + const timeDiff = expiryDate.getTime() - now.getTime(); + daysUntilExpiry = Math.ceil(timeDiff / (1000 * 3600 * 24)); + } + } + + for (const assignment of trainerAssignments) { + if (!assignment.trainerId) continue; + + try { + const trainerName = await getTrainerName(assignment.trainerId); + + const notifType = notificationType === "expired" ? "trainer_client_plan_expired" : "trainer_client_plan_expiring"; + const existing = await app + .firestore() + .collection("notifications") + .where("type", "==", notifType) + .where("recipientId", "==", assignment.trainerId) + .where("data.membershipId", "==", membershipId) + .where( + "data.expiryDate", + "==", + expiryDate + ? admin.firestore.Timestamp.fromDate(expiryDate) + : admin.firestore.Timestamp.fromDate(new Date()) + ) + .limit(1) + .get(); + + if (!existing.empty) { + logger.info( + `${notificationType} notification already sent to trainer ${assignment.trainerId} for membership ${membershipId}, skipping...` + ); + continue; + } + + const notificationData: any = { + senderId: "system", + recipientId: assignment.trainerId, + type: notifType, + notificationSent: false, + timestamp: admin.firestore.FieldValue.serverTimestamp(), + read: false, + readBy: [], + data: { + planName: membershipData.subscription?.name || "Unknown Plan", + clientName, + membershipId, + gymName, + assignmentId: assignment.id, + formattedExpiryDate: formattedDate, + expiryDate: expiryDate + ? admin.firestore.Timestamp.fromDate(expiryDate) + : admin.firestore.Timestamp.fromDate(new Date()), + }, + }; + + if (notificationType === "expiring") { + notificationData.data.daysUntilExpiry = daysUntilExpiry; + } + + await app.firestore().collection("notifications").add(notificationData); + + logger.info( + `${notificationType} notification sent to trainer ${assignment.trainerId} (${trainerName}) for client ${clientName}'s membership ${membershipId}` + ); + } catch (trainerError) { + logger.error( + `Error sending notification to trainer ${assignment.trainerId} for membership ${membershipId}:`, + trainerError + ); + } + } + } catch (error) { + logger.error( + `Error sending trainer notifications for membership ${membershipId}:`, + error + ); + } +} + async function sendPlanExpiredNotification( membershipId: string, membershipData: MembershipData @@ -609,4 +799,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 diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index 2efed4c..d8edce7 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -180,7 +180,7 @@ function prepareNotificationMessage( case "trainer_response": title = notification.data?.title || - (notification.data?.status === "Accepted" + (notification.data?.status === "accepted" ? "Trainer Request Accepted" : "Trainer Request Update"); body = @@ -240,14 +240,28 @@ function prepareNotificationMessage( title = notification.data?.title || "Plan Expired"; body = notification.data?.message || - `${notification.data?.clientName}'s membership subscription for ${notification.data?.planName} has expired.`; + `${notification.data?.clientName}'s membership for ${notification.data?.planName} has expired.`; break; case "plan_expiring_soon": title = notification.data?.title || "Plan Expiring Soon"; body = notification.data?.message || - `${notification.data?.clientName}'s membership subscription for ${notification.data?.planName} will expire on ${notification.data?.formattedExpiryDate}.`; + `${notification.data?.clientName}'s membership for ${notification.data?.planName} will expire on ${notification.data?.formattedExpiryDate}.`; + break; + + case "trainer_client_plan_expired": + title = notification.data?.title || "Client Plan Expired"; + body = + notification.data?.message || + `${notification.data?.clientName}'s membership for ${notification.data?.planName} has expired.`; + break; + + case "trainer_client_plan_expiring": + title = notification.data?.title || "Client Plan Expiring Soon"; + body = + notification.data?.message || + `${notification.data?.clientName}'s membership for ${notification.data?.planName} will expire on ${notification.data?.formattedExpiryDate}.`; break; case "schedule_update":