email plus return item statuses
This commit is contained in:
258
backend/jobs/conditionCheckReminder.js
Normal file
258
backend/jobs/conditionCheckReminder.js
Normal file
@@ -0,0 +1,258 @@
|
||||
const cron = require("node-cron");
|
||||
const {
|
||||
Rental,
|
||||
User,
|
||||
Item,
|
||||
ConditionCheck,
|
||||
} = require("../models");
|
||||
const { Op } = require("sequelize");
|
||||
const emailService = require("../services/emailService");
|
||||
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 emailService.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 emailService.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 emailService.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 emailService.sendConditionCheckReminder(
|
||||
rental.owner.email,
|
||||
notificationData,
|
||||
rental
|
||||
);
|
||||
|
||||
console.log(`Post-rental owner reminder sent for rental ${rental.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConditionCheckReminderJob;
|
||||
Reference in New Issue
Block a user