Changes Updated

This commit is contained in:
Sharon Dcruz 2025-08-13 18:44:51 +05:30
parent 57a4fab6c8
commit d7bf910baa
2 changed files with 208 additions and 4 deletions

View File

@ -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<PersonalTrainerAssign[]> {
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<string> {
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<void> {
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<void> {
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<string> {
logger.error(`Error getting gym name for gym ${gymId}:`, error);
return "Unknown Gym";
}
}
}

View File

@ -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":