Files
rentall-app/backend/services/conditionCheckService.js

353 lines
9.9 KiB
JavaScript

const { ConditionCheck, Rental, User } = require("../models");
const { Op } = require("sequelize");
const { isActive } = require("../utils/rentalStatus");
class ConditionCheckService {
/**
* Validate if a condition check can be submitted
* @param {string} rentalId - Rental ID
* @param {string} checkType - Type of check (pre_rental_owner, rental_start_renter, etc.)
* @param {string} userId - User attempting to submit
* @returns {Object} - { canSubmit, reason, timeWindow }
*/
static async validateConditionCheck(rentalId, checkType, userId) {
const rental = await Rental.findByPk(rentalId);
if (!rental) {
return { canSubmit: false, reason: "Rental not found" };
}
// Check user permissions
const isOwner = rental.ownerId === userId;
const isRenter = rental.renterId === userId;
if (checkType.includes("owner") && !isOwner) {
return {
canSubmit: false,
reason: "Only the item owner can submit owner condition checks",
};
}
if (checkType.includes("renter") && !isRenter) {
return {
canSubmit: false,
reason: "Only the renter can submit renter condition checks",
};
}
// Check if already submitted
const existingCheck = await ConditionCheck.findOne({
where: { rentalId, checkType },
});
if (existingCheck) {
return {
canSubmit: false,
reason: "Condition check already submitted for this type",
};
}
// Check time windows (24 hour windows)
const now = new Date();
const startDate = new Date(rental.startDateTime);
const endDate = new Date(rental.endDateTime);
const twentyFourHours = 24 * 60 * 60 * 1000;
let timeWindow = {};
let canSubmit = false;
switch (checkType) {
case "pre_rental_owner":
// 24 hours before rental starts
timeWindow.start = new Date(startDate.getTime() - twentyFourHours);
timeWindow.end = startDate;
canSubmit = now >= timeWindow.start && now <= timeWindow.end;
break;
case "rental_start_renter":
// 24 hours after rental starts
timeWindow.start = startDate;
timeWindow.end = new Date(startDate.getTime() + twentyFourHours);
canSubmit =
now >= timeWindow.start &&
now <= timeWindow.end &&
isActive(rental);
break;
case "rental_end_renter":
// 24 hours before rental ends
timeWindow.start = new Date(endDate.getTime() - twentyFourHours);
timeWindow.end = endDate;
canSubmit =
now >= timeWindow.start &&
now <= timeWindow.end &&
isActive(rental);
break;
case "post_rental_owner":
// Can be submitted anytime (integrated into return flow)
timeWindow.start = endDate;
timeWindow.end = null; // No time limit
canSubmit = true; // Always allowed when owner marks return
break;
default:
return { canSubmit: false, reason: "Invalid check type" };
}
if (!canSubmit) {
const isBeforeWindow = now < timeWindow.start;
const isAfterWindow = now > timeWindow.end;
let reason = "Outside of allowed time window";
if (isBeforeWindow) {
reason = `Too early. Check can be submitted starting ${timeWindow.start.toLocaleString()}`;
} else if (isAfterWindow) {
reason = `Pre-Rental Condition can only be submitted before start of rental period`;
}
return { canSubmit: false, reason, timeWindow };
}
return { canSubmit: true, timeWindow };
}
/**
* Submit a condition check with photos
* @param {string} rentalId - Rental ID
* @param {string} checkType - Type of check
* @param {string} userId - User submitting the check
* @param {Array} imageFilenames - Array of image filenames
* @param {string} notes - Optional notes
* @returns {Object} - Created condition check
*/
static async submitConditionCheck(
rentalId,
checkType,
userId,
imageFilenames = [],
notes = null
) {
// Validate the check
const validation = await this.validateConditionCheck(
rentalId,
checkType,
userId
);
if (!validation.canSubmit) {
throw new Error(validation.reason);
}
// Validate photos (basic validation)
if (imageFilenames.length > 20) {
throw new Error("Maximum 20 photos allowed per condition check");
}
const conditionCheck = await ConditionCheck.create({
rentalId,
checkType,
submittedBy: userId,
imageFilenames,
notes,
});
return conditionCheck;
}
/**
* Get all condition checks for a rental
* @param {string} rentalId - Rental ID
* @returns {Array} - Array of condition checks with user info
*/
static async getConditionChecks(rentalId) {
const checks = await ConditionCheck.findAll({
where: { rentalId },
include: [
{
model: User,
as: "submittedByUser",
attributes: ["id", "firstName", "lastName"],
},
],
order: [["submittedAt", "ASC"]],
});
return checks;
}
/**
* Get condition check timeline for a rental
* @param {string} rentalId - Rental ID
* @returns {Object} - Timeline showing what checks are available/completed
*/
static async getConditionCheckTimeline(rentalId) {
const rental = await Rental.findByPk(rentalId);
if (!rental) {
throw new Error("Rental not found");
}
const existingChecks = await ConditionCheck.findAll({
where: { rentalId },
include: [
{
model: User,
as: "submittedByUser",
attributes: ["id", "firstName", "lastName"],
},
],
});
const checkTypes = [
"pre_rental_owner",
"rental_start_renter",
"rental_end_renter",
"post_rental_owner",
];
const timeline = {};
for (const checkType of checkTypes) {
const existingCheck = existingChecks.find(
(check) => check.checkType === checkType
);
if (existingCheck) {
timeline[checkType] = {
status: "completed",
submittedAt: existingCheck.submittedAt,
submittedBy: existingCheck.submittedBy,
photoCount: existingCheck.imageFilenames.length,
hasNotes: !!existingCheck.notes,
};
} else {
// Calculate if this check type is available
const now = new Date();
const startDate = new Date(rental.startDateTime);
const endDate = new Date(rental.endDateTime);
const twentyFourHours = 24 * 60 * 60 * 1000;
let timeWindow = {};
let status = "not_available";
switch (checkType) {
case "pre_rental_owner":
timeWindow.start = new Date(startDate.getTime() - twentyFourHours);
timeWindow.end = startDate;
break;
case "rental_start_renter":
timeWindow.start = startDate;
timeWindow.end = new Date(startDate.getTime() + twentyFourHours);
break;
case "rental_end_renter":
timeWindow.start = new Date(endDate.getTime() - twentyFourHours);
timeWindow.end = endDate;
break;
case "post_rental_owner":
timeWindow.start = endDate;
timeWindow.end = new Date(endDate.getTime() + twentyFourHours);
break;
}
if (now >= timeWindow.start && now <= timeWindow.end) {
status = "available";
} else if (now < timeWindow.start) {
status = "pending";
} else {
status = "expired";
}
timeline[checkType] = {
status,
timeWindow,
availableFrom: timeWindow.start,
availableUntil: timeWindow.end,
};
}
}
return {
rental: {
id: rental.id,
startDateTime: rental.startDateTime,
endDateTime: rental.endDateTime,
status: rental.status,
},
timeline,
};
}
/**
* Get available condition checks for a user
* @param {string} userId - User ID
* @returns {Array} - Array of available condition checks
*/
static async getAvailableChecks(userId) {
const now = new Date();
const twentyFourHours = 24 * 60 * 60 * 1000;
// Find rentals where user is owner or renter
const rentals = await Rental.findAll({
where: {
[Op.or]: [{ ownerId: userId }, { renterId: userId }],
status: {
[Op.in]: ["confirmed", "active", "completed"],
},
},
});
const availableChecks = [];
for (const rental of rentals) {
const isOwner = rental.ownerId === userId;
const isRenter = rental.renterId === userId;
const startDate = new Date(rental.startDateTime);
const endDate = new Date(rental.endDateTime);
// Check each type of condition check
const checkTypes = [];
if (isOwner) {
// Only include pre_rental_owner; post_rental is now part of return flow
checkTypes.push("pre_rental_owner");
}
if (isRenter) {
checkTypes.push("rental_start_renter", "rental_end_renter");
}
for (const checkType of checkTypes) {
// Check if already submitted
const existing = await ConditionCheck.findOne({
where: { rentalId: rental.id, checkType },
});
if (!existing) {
const validation = await this.validateConditionCheck(
rental.id,
checkType,
userId
);
if (validation.canSubmit) {
availableChecks.push({
rentalId: rental.id,
checkType,
rental: {
id: rental.id,
itemId: rental.itemId,
startDateTime: rental.startDateTime,
endDateTime: rental.endDateTime,
},
timeWindow: validation.timeWindow,
});
}
}
}
}
return availableChecks;
}
}
module.exports = ConditionCheckService;