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;
|
||||
101
backend/jobs/rentalStatusJob.js
Normal file
101
backend/jobs/rentalStatusJob.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const cron = require("node-cron");
|
||||
const { Rental } = require("../models");
|
||||
const { Op } = require("sequelize");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const statusUpdateSchedule = "*/15 * * * *"; // Run every 15 minutes
|
||||
|
||||
class RentalStatusJob {
|
||||
static startScheduledStatusUpdates() {
|
||||
console.log("Starting automated rental status updates...");
|
||||
|
||||
const statusJob = cron.schedule(
|
||||
statusUpdateSchedule,
|
||||
async () => {
|
||||
try {
|
||||
await this.activateStartedRentals();
|
||||
} catch (error) {
|
||||
logger.error("Error in scheduled rental status update", {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
scheduled: false,
|
||||
timezone: "America/New_York",
|
||||
}
|
||||
);
|
||||
|
||||
// Start the job
|
||||
statusJob.start();
|
||||
|
||||
console.log("Rental status job scheduled:");
|
||||
console.log("- Status updates every 15 minutes: " + statusUpdateSchedule);
|
||||
|
||||
return {
|
||||
statusJob,
|
||||
|
||||
stop() {
|
||||
statusJob.stop();
|
||||
console.log("Rental status job stopped");
|
||||
},
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
statusJobRunning: statusJob.getStatus() === "scheduled",
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static async activateStartedRentals() {
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
// Find all confirmed rentals where start time has arrived
|
||||
const rentalsToActivate = await Rental.findAll({
|
||||
where: {
|
||||
status: "confirmed",
|
||||
startDateTime: {
|
||||
[Op.lte]: now,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (rentalsToActivate.length === 0) {
|
||||
return { activated: 0 };
|
||||
}
|
||||
|
||||
// Update all matching rentals to active status
|
||||
const rentalIds = rentalsToActivate.map((r) => r.id);
|
||||
const [updateCount] = await Rental.update(
|
||||
{ status: "active" },
|
||||
{
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: rentalIds,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
logger.info("Activated started rentals", {
|
||||
count: updateCount,
|
||||
rentalIds: rentalIds,
|
||||
});
|
||||
|
||||
console.log(`Activated ${updateCount} rentals that have started`);
|
||||
|
||||
return { activated: updateCount, rentalIds };
|
||||
} catch (error) {
|
||||
logger.error("Error activating started rentals", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RentalStatusJob;
|
||||
Reference in New Issue
Block a user