diff --git a/backend/migrations/20241124000005-create-rentals.js b/backend/migrations/20241124000005-create-rentals.js new file mode 100644 index 0000000..f8aae32 --- /dev/null +++ b/backend/migrations/20241124000005-create-rentals.js @@ -0,0 +1,210 @@ +"use strict"; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable("Rentals", { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + primaryKey: true, + }, + itemId: { + type: Sequelize.UUID, + allowNull: false, + references: { + model: "Items", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + renterId: { + type: Sequelize.UUID, + allowNull: false, + references: { + model: "Users", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + ownerId: { + type: Sequelize.UUID, + allowNull: false, + references: { + model: "Users", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + startDateTime: { + type: Sequelize.DATE, + allowNull: false, + }, + endDateTime: { + type: Sequelize.DATE, + allowNull: false, + }, + totalAmount: { + type: Sequelize.DECIMAL(10, 2), + allowNull: false, + }, + platformFee: { + type: Sequelize.DECIMAL(10, 2), + allowNull: false, + }, + payoutAmount: { + type: Sequelize.DECIMAL(10, 2), + allowNull: false, + }, + status: { + type: Sequelize.ENUM( + "pending", + "confirmed", + "declined", + "active", + "completed", + "cancelled", + "returned_late", + "returned_late_and_damaged", + "damaged", + "lost" + ), + allowNull: false, + }, + paymentStatus: { + type: Sequelize.ENUM("pending", "paid", "refunded", "not_required"), + allowNull: false, + }, + payoutStatus: { + type: Sequelize.ENUM("pending", "completed", "failed"), + allowNull: true, + }, + payoutProcessedAt: { + type: Sequelize.DATE, + }, + stripeTransferId: { + type: Sequelize.STRING, + }, + refundAmount: { + type: Sequelize.DECIMAL(10, 2), + }, + refundProcessedAt: { + type: Sequelize.DATE, + }, + refundReason: { + type: Sequelize.TEXT, + }, + stripeRefundId: { + type: Sequelize.STRING, + }, + cancelledBy: { + type: Sequelize.ENUM("renter", "owner"), + }, + cancelledAt: { + type: Sequelize.DATE, + }, + declineReason: { + type: Sequelize.TEXT, + }, + stripePaymentMethodId: { + type: Sequelize.STRING, + }, + stripePaymentIntentId: { + type: Sequelize.STRING, + }, + paymentMethodBrand: { + type: Sequelize.STRING, + }, + paymentMethodLast4: { + type: Sequelize.STRING, + }, + chargedAt: { + type: Sequelize.DATE, + }, + deliveryMethod: { + type: Sequelize.ENUM("pickup", "delivery"), + defaultValue: "pickup", + }, + deliveryAddress: { + type: Sequelize.TEXT, + }, + intendedUse: { + type: Sequelize.TEXT, + }, + itemRating: { + type: Sequelize.INTEGER, + }, + itemReview: { + type: Sequelize.TEXT, + }, + itemReviewSubmittedAt: { + type: Sequelize.DATE, + }, + itemReviewVisible: { + type: Sequelize.BOOLEAN, + defaultValue: false, + }, + renterRating: { + type: Sequelize.INTEGER, + }, + renterReview: { + type: Sequelize.TEXT, + }, + renterReviewSubmittedAt: { + type: Sequelize.DATE, + }, + renterReviewVisible: { + type: Sequelize.BOOLEAN, + defaultValue: false, + }, + itemPrivateMessage: { + type: Sequelize.TEXT, + }, + renterPrivateMessage: { + type: Sequelize.TEXT, + }, + actualReturnDateTime: { + type: Sequelize.DATE, + }, + lateFees: { + type: Sequelize.DECIMAL(10, 2), + defaultValue: 0.0, + }, + damageFees: { + type: Sequelize.DECIMAL(10, 2), + defaultValue: 0.0, + }, + replacementFees: { + type: Sequelize.DECIMAL(10, 2), + defaultValue: 0.0, + }, + itemLostReportedAt: { + type: Sequelize.DATE, + }, + damageAssessment: { + type: Sequelize.JSONB, + defaultValue: {}, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: false, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + }, + }); + + // Add indexes + await queryInterface.addIndex("Rentals", ["itemId"]); + await queryInterface.addIndex("Rentals", ["renterId"]); + await queryInterface.addIndex("Rentals", ["ownerId"]); + await queryInterface.addIndex("Rentals", ["status"]); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable("Rentals"); + }, +}; diff --git a/backend/models/Rental.js b/backend/models/Rental.js index fc4bb9b..26f9aa3 100644 --- a/backend/models/Rental.js +++ b/backend/models/Rental.js @@ -124,7 +124,7 @@ const Rental = sequelize.define("Rental", { deliveryAddress: { type: DataTypes.TEXT, }, - notes: { + intendedUse: { type: DataTypes.TEXT, }, // Renter's review of the item (existing fields renamed for clarity) diff --git a/backend/routes/rentals.js b/backend/routes/rentals.js index a25d3f2..ace90c6 100644 --- a/backend/routes/rentals.js +++ b/backend/routes/rentals.js @@ -183,7 +183,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => { endDateTime, deliveryMethod, deliveryAddress, - notes, + intendedUse, stripePaymentMethodId, } = req.body; @@ -274,7 +274,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => { status: "pending", deliveryMethod, deliveryAddress, - notes, + intendedUse, }; // Only add stripePaymentMethodId if it's provided (for paid rentals) @@ -1099,7 +1099,7 @@ router.post("/:id/cancel", authenticateToken, async (req, res) => { // Mark item return status (owner only) router.post("/:id/mark-return", authenticateToken, async (req, res) => { try { - const { status, actualReturnDateTime, notes, statusOptions } = req.body; + const { status, actualReturnDateTime, statusOptions } = req.body; const rentalId = req.params.id; const userId = req.user.id; @@ -1133,7 +1133,6 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { status: "completed", payoutStatus: "pending", actualReturnDateTime: actualReturnDateTime || rental.endDateTime, - notes: notes || null, }); // Fetch full rental details with associations for email @@ -1177,15 +1176,13 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { status: "damaged", payoutStatus: "pending", actualReturnDateTime: actualReturnDateTime || rental.endDateTime, - notes: notes || null, }; // Check if ALSO returned late if (statusOptions?.returned_late && actualReturnDateTime) { const lateReturnDamaged = await LateReturnService.processLateReturn( rentalId, - actualReturnDateTime, - notes + actualReturnDateTime ); damageUpdates.status = "returned_late_and_damaged"; damageUpdates.lateFees = lateReturnDamaged.lateCalculation.lateFee; @@ -1207,8 +1204,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { const lateReturn = await LateReturnService.processLateReturn( rentalId, - actualReturnDateTime, - notes + actualReturnDateTime ); updatedRental = lateReturn.rental; @@ -1221,7 +1217,6 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { status: "lost", payoutStatus: "pending", itemLostReportedAt: new Date(), - notes: notes || null, }); // Send notification to customer service diff --git a/backend/services/email/core/TemplateManager.js b/backend/services/email/core/TemplateManager.js index bd852c2..66b49bc 100644 --- a/backend/services/email/core/TemplateManager.js +++ b/backend/services/email/core/TemplateManager.js @@ -303,7 +303,7 @@ class TemplateManager {
Total Amount: ${{totalAmount}}
Your Earnings: ${{payoutAmount}}
Delivery Method: {{deliveryMethod}}
-Renter Notes: {{rentalNotes}}
+Intended Use: {{intendedUse}}
Please respond to this request within 24 hours.
` diff --git a/backend/services/email/domain/RentalFlowEmailService.js b/backend/services/email/domain/RentalFlowEmailService.js index c04c056..7ef053f 100644 --- a/backend/services/email/domain/RentalFlowEmailService.js +++ b/backend/services/email/domain/RentalFlowEmailService.js @@ -53,7 +53,6 @@ class RentalFlowEmailService { * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.payoutAmount - Owner's payout amount * @param {string} rental.deliveryMethod - Delivery method - * @param {string} rental.notes - Rental notes from renter * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendRentalRequestEmail(owner, renter, rental) { @@ -88,7 +87,7 @@ class RentalFlowEmailService { ? parseFloat(rental.payoutAmount).toFixed(2) : "0.00", deliveryMethod: rental.deliveryMethod || "Not specified", - rentalNotes: rental.notes || "No additional notes provided", + intendedUse: rental.intendedUse || "Not specified", approveUrl: approveUrl, }; diff --git a/backend/services/lateReturnService.js b/backend/services/lateReturnService.js index 078305a..bb315fe 100644 --- a/backend/services/lateReturnService.js +++ b/backend/services/lateReturnService.js @@ -60,10 +60,9 @@ class LateReturnService { * Process late return and update rental with fees * @param {string} rentalId - Rental ID * @param {Date} actualReturnDateTime - When item was returned - * @param {string} notes - Optional notes about the return * @returns {Object} - Updated rental with late fee information */ - static async processLateReturn(rentalId, actualReturnDateTime, notes = null) { + static async processLateReturn(rentalId, actualReturnDateTime) { const rental = await Rental.findByPk(rentalId, { include: [{ model: Item, as: "item" }], }); @@ -84,10 +83,6 @@ class LateReturnService { payoutStatus: "pending", }; - if (notes) { - updates.notes = notes; - } - const updatedRental = await rental.update(updates); // Send notification to customer service if late return detected diff --git a/backend/templates/emails/rentalRequestToOwner.html b/backend/templates/emails/rentalRequestToOwner.html index 6c371cf..4607a6e 100644 --- a/backend/templates/emails/rentalRequestToOwner.html +++ b/backend/templates/emails/rentalRequestToOwner.html @@ -295,8 +295,8 @@Renter's Notes:
-{{rentalNotes}}
+Intended Use:
+{{intendedUse}}