const cron = require("node-cron"); const { Rental, User, Item, ConditionCheck, } = require("../models"); const { Op } = require("sequelize"); const emailServices = require("../services/email"); const logger = require("../utils/logger"); const reminderSchedule = "0 * * * *"; // Run every hour class ConditionCheckReminderJob { static startScheduledReminders() { console.log("Starting automated condition check reminder job..."); const reminderJob = cron.schedule( reminderSchedule, async () => { try { await this.sendConditionCheckReminders(); } catch (error) { logger.error("Error in scheduled condition check reminders", { error: error.message, stack: error.stack, }); } }, { scheduled: false, timezone: "America/New_York", } ); // Start the job reminderJob.start(); console.log("Condition check reminder job scheduled:"); console.log("- Reminders every hour: " + reminderSchedule); return { reminderJob, stop() { reminderJob.stop(); console.log("Condition check reminder job stopped"); }, getStatus() { return { reminderJobRunning: reminderJob.getStatus() === "scheduled", }; }, }; } // Send reminders for upcoming condition check windows static async sendConditionCheckReminders() { try { const now = new Date(); const reminderWindow = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours ahead // Find rentals with upcoming condition check windows const rentals = await Rental.findAll({ where: { status: { [Op.in]: ["confirmed", "active", "completed"], }, }, include: [ { model: User, as: "owner" }, { model: User, as: "renter" }, { model: Item, as: "item" }, ], }); for (const rental of rentals) { await this.checkAndSendConditionReminders(rental, now, reminderWindow); } console.log( `Processed ${rentals.length} rentals for condition check reminders` ); } catch (error) { console.error("Error sending condition check reminders:", error); } } // Check specific rental for reminder needs static async checkAndSendConditionReminders(rental, now, reminderWindow) { const rentalStart = new Date(rental.startDateTime); const rentalEnd = new Date(rental.endDateTime); // Pre-rental owner check (24 hours before rental start) const preRentalWindow = new Date( rentalStart.getTime() - 24 * 60 * 60 * 1000 ); if (now <= preRentalWindow && preRentalWindow <= reminderWindow) { const existingCheck = await ConditionCheck.findOne({ where: { rentalId: rental.id, checkType: "pre_rental_owner", }, }); if (!existingCheck) { await this.sendPreRentalOwnerReminder(rental); } } // Rental start renter check (within 24 hours of rental start) if (now <= rentalStart && rentalStart <= reminderWindow) { const existingCheck = await ConditionCheck.findOne({ where: { rentalId: rental.id, checkType: "rental_start_renter", }, }); if (!existingCheck) { await this.sendRentalStartRenterReminder(rental); } } // Rental end renter check (within 24 hours of rental end) if (now <= rentalEnd && rentalEnd <= reminderWindow) { const existingCheck = await ConditionCheck.findOne({ where: { rentalId: rental.id, checkType: "rental_end_renter", }, }); if (!existingCheck) { await this.sendRentalEndRenterReminder(rental); } } // Post-rental owner check (24 hours after rental end) const postRentalWindow = new Date( rentalEnd.getTime() + 24 * 60 * 60 * 1000 ); if (now <= postRentalWindow && postRentalWindow <= reminderWindow) { const existingCheck = await ConditionCheck.findOne({ where: { rentalId: rental.id, checkType: "post_rental_owner", }, }); if (!existingCheck) { await this.sendPostRentalOwnerReminder(rental); } } } // Individual email senders static async sendPreRentalOwnerReminder(rental) { const notificationData = { type: "condition_check_reminder", subtype: "pre_rental_owner", title: "Condition Check Reminder", message: `Please take photos of "${rental.item.name}" before the rental begins tomorrow.`, rentalId: rental.id, userId: rental.ownerId, metadata: { checkType: "pre_rental_owner", deadline: new Date(rental.startDateTime).toISOString(), }, }; await emailServices.rentalReminder.sendConditionCheckReminder( rental.owner.email, notificationData, rental ); console.log(`Pre-rental owner reminder sent for rental ${rental.id}`); } static async sendRentalStartRenterReminder(rental) { const notificationData = { type: "condition_check_reminder", subtype: "rental_start_renter", title: "Condition Check Reminder", message: `Please take photos when you receive "${rental.item.name}" to document its condition.`, rentalId: rental.id, userId: rental.renterId, metadata: { checkType: "rental_start_renter", deadline: new Date( rental.startDateTime.getTime() + 24 * 60 * 60 * 1000 ).toISOString(), }, }; await emailServices.rentalReminder.sendConditionCheckReminder( rental.renter.email, notificationData, rental ); console.log(`Rental start renter reminder sent for rental ${rental.id}`); } static async sendRentalEndRenterReminder(rental) { const notificationData = { type: "condition_check_reminder", subtype: "rental_end_renter", title: "Condition Check Reminder", message: `Please take photos when returning "${rental.item.name}" to document its condition.`, rentalId: rental.id, userId: rental.renterId, metadata: { checkType: "rental_end_renter", deadline: new Date( rental.endDateTime.getTime() + 24 * 60 * 60 * 1000 ).toISOString(), }, }; await emailServices.rentalReminder.sendConditionCheckReminder( rental.renter.email, notificationData, rental ); console.log(`Rental end renter reminder sent for rental ${rental.id}`); } static async sendPostRentalOwnerReminder(rental) { const notificationData = { type: "condition_check_reminder", subtype: "post_rental_owner", title: "Condition Check Reminder", message: `Please take photos and mark the return status for "${rental.item.name}".`, rentalId: rental.id, userId: rental.ownerId, metadata: { checkType: "post_rental_owner", deadline: new Date( rental.endDateTime.getTime() + 48 * 60 * 60 * 1000 ).toISOString(), }, }; await emailServices.rentalReminder.sendConditionCheckReminder( rental.owner.email, notificationData, rental ); console.log(`Post-rental owner reminder sent for rental ${rental.id}`); } } module.exports = ConditionCheckReminderJob;