removed cron job that made rentals active. Now whether or not the rental is active is determined on the fly
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
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;
|
||||
@@ -14,8 +14,24 @@ const emailServices = require("../services/email");
|
||||
const logger = require("../utils/logger");
|
||||
const { validateS3Keys } = require("../utils/s3KeyValidator");
|
||||
const { IMAGE_LIMITS } = require("../config/imageLimits");
|
||||
const { isActive, getEffectiveStatus } = require("../utils/rentalStatus");
|
||||
const router = express.Router();
|
||||
|
||||
// Helper to add displayStatus to rental response
|
||||
const addDisplayStatus = (rental) => {
|
||||
if (!rental) return rental;
|
||||
const rentalJson = rental.toJSON ? rental.toJSON() : rental;
|
||||
return {
|
||||
...rentalJson,
|
||||
displayStatus: getEffectiveStatus(rentalJson),
|
||||
};
|
||||
};
|
||||
|
||||
// Helper to add displayStatus to array of rentals
|
||||
const addDisplayStatusToArray = (rentals) => {
|
||||
return rentals.map(addDisplayStatus);
|
||||
};
|
||||
|
||||
// Helper function to check and update review visibility
|
||||
const checkAndUpdateReviewVisibility = async (rental) => {
|
||||
const now = new Date();
|
||||
@@ -75,7 +91,7 @@ router.get("/renting", authenticateToken, async (req, res) => {
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
|
||||
res.json(rentals);
|
||||
res.json(addDisplayStatusToArray(rentals));
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error in renting route", {
|
||||
@@ -103,7 +119,7 @@ router.get("/owning", authenticateToken, async (req, res) => {
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
|
||||
res.json(rentals);
|
||||
res.json(addDisplayStatusToArray(rentals));
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error in owning route", {
|
||||
@@ -167,7 +183,7 @@ router.get("/:id", authenticateToken, async (req, res) => {
|
||||
.json({ error: "Unauthorized to view this rental" });
|
||||
}
|
||||
|
||||
res.json(rental);
|
||||
res.json(addDisplayStatus(rental));
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error fetching rental", {
|
||||
@@ -235,10 +251,11 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
||||
);
|
||||
|
||||
// Check for overlapping rentals using datetime ranges
|
||||
// Note: "active" rentals are stored as "confirmed" with startDateTime in the past
|
||||
const overlappingRental = await Rental.findOne({
|
||||
where: {
|
||||
itemId,
|
||||
status: { [Op.in]: ["confirmed", "active"] },
|
||||
status: "confirmed",
|
||||
[Op.or]: [
|
||||
{
|
||||
[Op.and]: [
|
||||
@@ -875,7 +892,7 @@ router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
|
||||
.json({ error: "Only owners can mark rentals as completed" });
|
||||
}
|
||||
|
||||
if (rental.status !== "active") {
|
||||
if (!isActive(rental)) {
|
||||
return res.status(400).json({
|
||||
error: "Can only mark active rentals as completed",
|
||||
});
|
||||
@@ -1197,7 +1214,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
.json({ error: "Only the item owner can mark return status" });
|
||||
}
|
||||
|
||||
if (rental.status !== "active") {
|
||||
if (!isActive(rental)) {
|
||||
return res.status(400).json({
|
||||
error: "Can only mark return status for active rentals",
|
||||
});
|
||||
|
||||
@@ -32,7 +32,6 @@ const uploadRoutes = require("./routes/upload");
|
||||
const healthRoutes = require("./routes/health");
|
||||
|
||||
const PayoutProcessor = require("./jobs/payoutProcessor");
|
||||
const RentalStatusJob = require("./jobs/rentalStatusJob");
|
||||
const ConditionCheckReminderJob = require("./jobs/conditionCheckReminder");
|
||||
const emailServices = require("./services/email");
|
||||
const s3Service = require("./services/s3Service");
|
||||
@@ -230,10 +229,6 @@ sequelize
|
||||
const payoutJobs = PayoutProcessor.startScheduledPayouts();
|
||||
logger.info("Payout processor started");
|
||||
|
||||
// Start the rental status job
|
||||
const rentalStatusJobs = RentalStatusJob.startScheduledStatusUpdates();
|
||||
logger.info("Rental status job started");
|
||||
|
||||
// Start the condition check reminder job
|
||||
const conditionCheckJobs =
|
||||
ConditionCheckReminderJob.startScheduledReminders();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { ConditionCheck, Rental, User } = require("../models");
|
||||
const { Op } = require("sequelize");
|
||||
const { isActive } = require("../utils/rentalStatus");
|
||||
|
||||
class ConditionCheckService {
|
||||
/**
|
||||
@@ -70,7 +71,7 @@ class ConditionCheckService {
|
||||
canSubmit =
|
||||
now >= timeWindow.start &&
|
||||
now <= timeWindow.end &&
|
||||
rental.status === "active";
|
||||
isActive(rental);
|
||||
break;
|
||||
|
||||
case "rental_end_renter":
|
||||
@@ -80,7 +81,7 @@ class ConditionCheckService {
|
||||
canSubmit =
|
||||
now >= timeWindow.start &&
|
||||
now <= timeWindow.end &&
|
||||
rental.status === "active";
|
||||
isActive(rental);
|
||||
break;
|
||||
|
||||
case "post_rental_owner":
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Rental, Item, ConditionCheck, User } = require("../models");
|
||||
const LateReturnService = require("./lateReturnService");
|
||||
const emailServices = require("./email");
|
||||
const { isActive } = require("../utils/rentalStatus");
|
||||
|
||||
class DamageAssessmentService {
|
||||
/**
|
||||
@@ -34,7 +35,7 @@ class DamageAssessmentService {
|
||||
throw new Error("Only the item owner can report damage");
|
||||
}
|
||||
|
||||
if (rental.status !== "active") {
|
||||
if (!isActive(rental)) {
|
||||
throw new Error("Can only assess damage for active rentals");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { Rental, Item, User } = require("../models");
|
||||
const emailServices = require("./email");
|
||||
const { isActive } = require("../utils/rentalStatus");
|
||||
|
||||
class LateReturnService {
|
||||
/**
|
||||
@@ -71,7 +72,7 @@ class LateReturnService {
|
||||
throw new Error("Rental not found");
|
||||
}
|
||||
|
||||
if (rental.status !== "active") {
|
||||
if (!isActive(rental)) {
|
||||
throw new Error("Can only process late returns for active rentals");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { Rental } = require("../models");
|
||||
const StripeService = require("./stripeService");
|
||||
const { isActive } = require("../utils/rentalStatus");
|
||||
|
||||
class RefundService {
|
||||
/**
|
||||
@@ -69,8 +70,8 @@ class RefundService {
|
||||
};
|
||||
}
|
||||
|
||||
// Check if rental is active
|
||||
if (rental.status === "active") {
|
||||
// Check if rental is active (computed from confirmed + start time passed)
|
||||
if (isActive(rental)) {
|
||||
return {
|
||||
canCancel: false,
|
||||
reason: "Cannot cancel active rental",
|
||||
|
||||
@@ -790,11 +790,15 @@ describe('Rentals Routes', () => {
|
||||
});
|
||||
|
||||
describe('POST /:id/mark-completed', () => {
|
||||
// Active status is computed: confirmed + startDateTime in the past
|
||||
const pastDate = new Date();
|
||||
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
||||
const mockRental = {
|
||||
id: 1,
|
||||
ownerId: 1,
|
||||
renterId: 2,
|
||||
status: 'active',
|
||||
status: 'confirmed',
|
||||
startDateTime: pastDate,
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -837,7 +841,7 @@ describe('Rentals Routes', () => {
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({
|
||||
error: 'Can only mark active or confirmed rentals as completed',
|
||||
error: 'Can only mark active rentals as completed',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ describe('ConditionCheckService', () => {
|
||||
|
||||
describe('submitConditionCheck', () => {
|
||||
// Set rental dates relative to current time for valid time window
|
||||
// Active status is computed: confirmed + startDateTime in the past
|
||||
const now = new Date();
|
||||
const mockRental = {
|
||||
id: 'rental-123',
|
||||
@@ -17,7 +18,7 @@ describe('ConditionCheckService', () => {
|
||||
renterId: 'renter-789',
|
||||
startDateTime: new Date(now.getTime() - 1000 * 60 * 60), // 1 hour ago
|
||||
endDateTime: new Date(now.getTime() + 1000 * 60 * 60 * 24), // 24 hours from now
|
||||
status: 'active'
|
||||
status: 'confirmed' // Will be computed as "active" since startDateTime is in the past
|
||||
};
|
||||
|
||||
const mockPhotos = ['/uploads/photo1.jpg', '/uploads/photo2.jpg'];
|
||||
|
||||
@@ -27,11 +27,15 @@ describe('DamageAssessmentService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mockRental for each test to avoid state pollution
|
||||
// Active status is computed: confirmed + startDateTime in the past
|
||||
const pastDate = new Date();
|
||||
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
||||
mockRental = {
|
||||
id: 'rental-123',
|
||||
ownerId: 'owner-789',
|
||||
renterId: 'renter-456',
|
||||
status: 'active',
|
||||
status: 'confirmed',
|
||||
startDateTime: pastDate,
|
||||
item: { name: 'Test Camera', dailyRate: 100 },
|
||||
update: jest.fn().mockResolvedValue({
|
||||
id: 'rental-123',
|
||||
|
||||
@@ -91,9 +91,11 @@ describe('LateReturnService', () => {
|
||||
let mockRental;
|
||||
|
||||
beforeEach(() => {
|
||||
// Active status is computed: confirmed + startDateTime in the past
|
||||
mockRental = {
|
||||
id: '123',
|
||||
status: 'active',
|
||||
status: 'confirmed',
|
||||
startDateTime: new Date('2023-05-01T08:00:00Z'), // In the past
|
||||
endDateTime: new Date('2023-06-01T10:00:00Z'),
|
||||
item: { pricePerHour: 10, name: 'Test Item' },
|
||||
renterId: 'renter-123',
|
||||
|
||||
@@ -229,8 +229,11 @@ describe('RefundService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject cancellation for active rental', () => {
|
||||
const rental = { ...baseRental, status: 'active' };
|
||||
it('should reject cancellation for active rental (computed from confirmed + past start)', () => {
|
||||
// Active status is now computed: confirmed + startDateTime in the past
|
||||
const pastDate = new Date();
|
||||
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
||||
const rental = { ...baseRental, status: 'confirmed', startDateTime: pastDate };
|
||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
|
||||
41
backend/utils/rentalStatus.js
Normal file
41
backend/utils/rentalStatus.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Utility functions for computing rental status on-the-fly.
|
||||
* The "active" status is computed based on timestamps rather than stored in the database.
|
||||
* A rental is considered "active" when it has status "confirmed" and startDateTime has passed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the effective/display status of a rental.
|
||||
* If the rental is "confirmed" and the start time has passed, returns "active".
|
||||
* Otherwise returns the stored status.
|
||||
* @param {Object} rental - The rental object with status and startDateTime
|
||||
* @returns {string} The effective status
|
||||
*/
|
||||
function getEffectiveStatus(rental) {
|
||||
const now = new Date();
|
||||
if (
|
||||
rental.status === "confirmed" &&
|
||||
new Date(rental.startDateTime) <= now
|
||||
) {
|
||||
return "active";
|
||||
}
|
||||
return rental.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a rental is currently active.
|
||||
* A rental is active when it's confirmed and the start time has passed.
|
||||
* @param {Object} rental - The rental object with status and startDateTime
|
||||
* @returns {boolean} True if the rental is currently active
|
||||
*/
|
||||
function isActive(rental) {
|
||||
const now = new Date();
|
||||
return (
|
||||
rental.status === "confirmed" && new Date(rental.startDateTime) <= now
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEffectiveStatus,
|
||||
isActive,
|
||||
};
|
||||
@@ -41,11 +41,13 @@ const RentalCancellationModal: React.FC<RentalCancellationModalProps> = ({
|
||||
setError(null);
|
||||
|
||||
// Check if rental status allows cancellation before making API call
|
||||
if (rental.status !== "pending" && rental.status !== "confirmed") {
|
||||
// Use displayStatus as it includes computed "active" status
|
||||
const effectiveStatus = rental.displayStatus || rental.status;
|
||||
if (effectiveStatus !== "pending" && effectiveStatus !== "confirmed") {
|
||||
let errorMessage = "This rental cannot be cancelled";
|
||||
if (rental.status === "active") {
|
||||
if (effectiveStatus === "active") {
|
||||
errorMessage = "Cannot cancel rental - the rental period has already started";
|
||||
} else if (rental.status === "completed") {
|
||||
} else if (effectiveStatus === "completed") {
|
||||
errorMessage = "Cannot cancel rental - the rental has already been completed";
|
||||
} else if (rental.status === "cancelled") {
|
||||
errorMessage = "This rental has already been cancelled";
|
||||
|
||||
@@ -318,13 +318,16 @@ const Owning: React.FC = () => {
|
||||
};
|
||||
|
||||
// Filter owner rentals - exclude cancelled (shown in Rental History)
|
||||
// Use displayStatus for filtering/sorting as it includes computed "active" status
|
||||
const allOwnerRentals = ownerRentals
|
||||
.filter((r) => ["pending", "confirmed", "active"].includes(r.status))
|
||||
.filter((r) => ["pending", "confirmed", "active"].includes(r.displayStatus || r.status))
|
||||
.sort((a, b) => {
|
||||
const statusOrder = { pending: 0, confirmed: 1, active: 2 };
|
||||
const aStatus = a.displayStatus || a.status;
|
||||
const bStatus = b.displayStatus || b.status;
|
||||
return (
|
||||
statusOrder[a.status as keyof typeof statusOrder] -
|
||||
statusOrder[b.status as keyof typeof statusOrder]
|
||||
statusOrder[aStatus as keyof typeof statusOrder] -
|
||||
statusOrder[bStatus as keyof typeof statusOrder]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -401,17 +404,17 @@ const Owning: React.FC = () => {
|
||||
<div className="mb-2">
|
||||
<span
|
||||
className={`badge ${
|
||||
rental.status === "active"
|
||||
(rental.displayStatus || rental.status) === "active"
|
||||
? "bg-success"
|
||||
: rental.status === "pending"
|
||||
: (rental.displayStatus || rental.status) === "pending"
|
||||
? "bg-warning"
|
||||
: rental.status === "confirmed"
|
||||
: (rental.displayStatus || rental.status) === "confirmed"
|
||||
? "bg-info"
|
||||
: "bg-danger"
|
||||
}`}
|
||||
>
|
||||
{rental.status.charAt(0).toUpperCase() +
|
||||
rental.status.slice(1)}
|
||||
{(rental.displayStatus || rental.status).charAt(0).toUpperCase() +
|
||||
(rental.displayStatus || rental.status).slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -512,7 +515,7 @@ const Owning: React.FC = () => {
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{rental.status === "confirmed" && (
|
||||
{(rental.displayStatus || rental.status) === "confirmed" && (
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger"
|
||||
onClick={() => handleCancelClick(rental)}
|
||||
@@ -520,7 +523,7 @@ const Owning: React.FC = () => {
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
{rental.status === "active" && (
|
||||
{(rental.displayStatus || rental.status) === "active" && (
|
||||
<button
|
||||
className="btn btn-sm btn-success"
|
||||
onClick={() => handleCompleteClick(rental)}
|
||||
|
||||
@@ -182,8 +182,9 @@ const Renting: React.FC = () => {
|
||||
};
|
||||
|
||||
// Filter rentals - show only active rentals (declined go to history)
|
||||
// Use displayStatus for filtering as it includes computed "active" status
|
||||
const renterActiveRentals = rentals.filter((r) =>
|
||||
["pending", "confirmed", "active"].includes(r.status)
|
||||
["pending", "confirmed", "active"].includes(r.displayStatus || r.status)
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
@@ -268,25 +269,25 @@ const Renting: React.FC = () => {
|
||||
<div className="mb-2">
|
||||
<span
|
||||
className={`badge ${
|
||||
rental.status === "active"
|
||||
(rental.displayStatus || rental.status) === "active"
|
||||
? "bg-success"
|
||||
: rental.status === "pending"
|
||||
: (rental.displayStatus || rental.status) === "pending"
|
||||
? "bg-warning"
|
||||
: rental.status === "confirmed"
|
||||
: (rental.displayStatus || rental.status) === "confirmed"
|
||||
? "bg-info"
|
||||
: rental.status === "declined"
|
||||
: (rental.displayStatus || rental.status) === "declined"
|
||||
? "bg-secondary"
|
||||
: "bg-danger"
|
||||
}`}
|
||||
>
|
||||
{rental.status === "pending"
|
||||
{(rental.displayStatus || rental.status) === "pending"
|
||||
? "Awaiting Owner Approval"
|
||||
: rental.status === "confirmed"
|
||||
: (rental.displayStatus || rental.status) === "confirmed"
|
||||
? "Confirmed & Paid"
|
||||
: rental.status === "declined"
|
||||
: (rental.displayStatus || rental.status) === "declined"
|
||||
? "Declined by Owner"
|
||||
: rental.status.charAt(0).toUpperCase() +
|
||||
rental.status.slice(1)}
|
||||
: (rental.displayStatus || rental.status).charAt(0).toUpperCase() +
|
||||
(rental.displayStatus || rental.status).slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -362,8 +363,8 @@ const Renting: React.FC = () => {
|
||||
|
||||
<div className="d-flex flex-column gap-2 mt-3">
|
||||
<div className="d-flex gap-2">
|
||||
{(rental.status === "pending" ||
|
||||
rental.status === "confirmed") && (
|
||||
{((rental.displayStatus || rental.status) === "pending" ||
|
||||
(rental.displayStatus || rental.status) === "confirmed") && (
|
||||
<button
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={() => handleCancelClick(rental)}
|
||||
@@ -371,7 +372,7 @@ const Renting: React.FC = () => {
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
{rental.status === "active" &&
|
||||
{(rental.displayStatus || rental.status) === "active" &&
|
||||
!rental.itemRating &&
|
||||
!rental.itemReviewSubmittedAt && (
|
||||
<button
|
||||
|
||||
@@ -106,6 +106,18 @@ export interface Item {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type RentalStatus =
|
||||
| "pending"
|
||||
| "confirmed"
|
||||
| "declined"
|
||||
| "active"
|
||||
| "completed"
|
||||
| "cancelled"
|
||||
| "returned_late"
|
||||
| "returned_late_and_damaged"
|
||||
| "damaged"
|
||||
| "lost";
|
||||
|
||||
export interface Rental {
|
||||
id: string;
|
||||
itemId: string;
|
||||
@@ -117,17 +129,9 @@ export interface Rental {
|
||||
// Fee tracking fields
|
||||
platformFee?: number;
|
||||
payoutAmount?: number;
|
||||
status:
|
||||
| "pending"
|
||||
| "confirmed"
|
||||
| "declined"
|
||||
| "active"
|
||||
| "completed"
|
||||
| "cancelled"
|
||||
| "returned_late"
|
||||
| "returned_late_and_damaged"
|
||||
| "damaged"
|
||||
| "lost";
|
||||
status: RentalStatus;
|
||||
// Computed status (includes "active" when confirmed + start time passed)
|
||||
displayStatus?: RentalStatus;
|
||||
paymentStatus: "pending" | "paid" | "refunded";
|
||||
// Refund tracking fields
|
||||
refundAmount?: number;
|
||||
|
||||
Reference in New Issue
Block a user