schema updates to rental statuses
This commit is contained in:
@@ -64,15 +64,15 @@ const Rental = sequelize.define("Rental", {
|
|||||||
"damaged",
|
"damaged",
|
||||||
"lost"
|
"lost"
|
||||||
),
|
),
|
||||||
defaultValue: "pending",
|
allowNull: false,
|
||||||
},
|
},
|
||||||
paymentStatus: {
|
paymentStatus: {
|
||||||
type: DataTypes.ENUM("pending", "paid", "refunded", "not_required"),
|
type: DataTypes.ENUM("pending", "paid", "refunded", "not_required"),
|
||||||
defaultValue: "pending",
|
allowNull: false,
|
||||||
},
|
},
|
||||||
payoutStatus: {
|
payoutStatus: {
|
||||||
type: DataTypes.ENUM("pending", "processing", "completed", "failed"),
|
type: DataTypes.ENUM("pending", "completed", "failed"),
|
||||||
defaultValue: "pending",
|
allowNull: true,
|
||||||
},
|
},
|
||||||
payoutProcessedAt: {
|
payoutProcessedAt: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
|
|||||||
@@ -823,7 +823,7 @@ router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await rental.update({ status: "completed" });
|
await rental.update({ status: "completed", payoutStatus: "pending" });
|
||||||
|
|
||||||
const updatedRental = await Rental.findByPk(rental.id, {
|
const updatedRental = await Rental.findByPk(rental.id, {
|
||||||
include: [
|
include: [
|
||||||
@@ -1131,6 +1131,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => {
|
|||||||
// Item returned on time
|
// Item returned on time
|
||||||
updatedRental = await rental.update({
|
updatedRental = await rental.update({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
|
payoutStatus: "pending",
|
||||||
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
});
|
});
|
||||||
@@ -1174,6 +1175,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => {
|
|||||||
// Item returned damaged
|
// Item returned damaged
|
||||||
const damageUpdates = {
|
const damageUpdates = {
|
||||||
status: "damaged",
|
status: "damaged",
|
||||||
|
payoutStatus: "pending",
|
||||||
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
};
|
};
|
||||||
@@ -1217,6 +1219,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => {
|
|||||||
// Item reported as lost
|
// Item reported as lost
|
||||||
updatedRental = await rental.update({
|
updatedRental = await rental.update({
|
||||||
status: "lost",
|
status: "lost",
|
||||||
|
payoutStatus: "pending",
|
||||||
itemLostReportedAt: new Date(),
|
itemLostReportedAt: new Date(),
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class LateReturnService {
|
|||||||
const updates = {
|
const updates = {
|
||||||
actualReturnDateTime: new Date(actualReturnDateTime),
|
actualReturnDateTime: new Date(actualReturnDateTime),
|
||||||
status: lateCalculation.isLate ? "returned_late" : "completed",
|
status: lateCalculation.isLate ? "returned_late" : "completed",
|
||||||
|
payoutStatus: "pending",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (notes) {
|
if (notes) {
|
||||||
|
|||||||
@@ -50,11 +50,6 @@ class PayoutService {
|
|||||||
throw new Error("Invalid payout amount");
|
throw new Error("Invalid payout amount");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status to processing
|
|
||||||
await rental.update({
|
|
||||||
payoutStatus: "processing",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Stripe transfer
|
// Create Stripe transfer
|
||||||
const transfer = await StripeService.createTransfer({
|
const transfer = await StripeService.createTransfer({
|
||||||
amount: rental.payoutAmount,
|
amount: rental.payoutAmount,
|
||||||
|
|||||||
@@ -186,11 +186,6 @@ describe('PayoutService', () => {
|
|||||||
it('should successfully process a rental payout', async () => {
|
it('should successfully process a rental payout', async () => {
|
||||||
const result = await PayoutService.processRentalPayout(mockRental);
|
const result = await PayoutService.processRentalPayout(mockRental);
|
||||||
|
|
||||||
// Verify status update to processing
|
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(1, {
|
|
||||||
payoutStatus: 'processing'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify Stripe transfer creation
|
// Verify Stripe transfer creation
|
||||||
expect(mockCreateTransfer).toHaveBeenCalledWith({
|
expect(mockCreateTransfer).toHaveBeenCalledWith({
|
||||||
amount: 9500,
|
amount: 9500,
|
||||||
@@ -206,7 +201,7 @@ describe('PayoutService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Verify status update to completed
|
// Verify status update to completed
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(2, {
|
expect(mockRental.update).toHaveBeenCalledWith({
|
||||||
payoutStatus: 'completed',
|
payoutStatus: 'completed',
|
||||||
payoutProcessedAt: expect.any(Date),
|
payoutProcessedAt: expect.any(Date),
|
||||||
stripeTransferId: 'tr_123456789'
|
stripeTransferId: 'tr_123456789'
|
||||||
@@ -260,13 +255,8 @@ describe('PayoutService', () => {
|
|||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Stripe transfer failed');
|
.rejects.toThrow('Stripe transfer failed');
|
||||||
|
|
||||||
// Verify processing status was set
|
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(1, {
|
|
||||||
payoutStatus: 'processing'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify failure status was set
|
// Verify failure status was set
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(2, {
|
expect(mockRental.update).toHaveBeenCalledWith({
|
||||||
payoutStatus: 'failed'
|
payoutStatus: 'failed'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,9 +286,7 @@ describe('PayoutService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dbError = new Error('Database completion update failed');
|
const dbError = new Error('Database completion update failed');
|
||||||
mockRental.update
|
mockRental.update.mockRejectedValueOnce(dbError);
|
||||||
.mockResolvedValueOnce(true) // processing update succeeds
|
|
||||||
.mockRejectedValueOnce(dbError); // completion update fails
|
|
||||||
|
|
||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Database completion update failed');
|
.rejects.toThrow('Database completion update failed');
|
||||||
@@ -315,16 +303,14 @@ describe('PayoutService', () => {
|
|||||||
const updateError = new Error('Update failed status failed');
|
const updateError = new Error('Update failed status failed');
|
||||||
|
|
||||||
mockCreateTransfer.mockRejectedValue(stripeError);
|
mockCreateTransfer.mockRejectedValue(stripeError);
|
||||||
mockRental.update
|
mockRental.update.mockRejectedValueOnce(updateError);
|
||||||
.mockResolvedValueOnce(true) // processing update succeeds
|
|
||||||
.mockRejectedValueOnce(updateError); // failed status update fails
|
|
||||||
|
|
||||||
// The service will throw the update error since it happens in the catch block
|
// The service will throw the update error since it happens in the catch block
|
||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Update failed status failed');
|
.rejects.toThrow('Update failed status failed');
|
||||||
|
|
||||||
// Should still attempt to update to failed status
|
// Should still attempt to update to failed status
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(2, {
|
expect(mockRental.update).toHaveBeenCalledWith({
|
||||||
payoutStatus: 'failed'
|
payoutStatus: 'failed'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -211,15 +211,15 @@ const EarningsDashboard: React.FC = () => {
|
|||||||
className={`badge ${
|
className={`badge ${
|
||||||
rental.payoutStatus === "completed"
|
rental.payoutStatus === "completed"
|
||||||
? "bg-success"
|
? "bg-success"
|
||||||
: rental.payoutStatus === "processing"
|
: rental.payoutStatus === "failed"
|
||||||
? "bg-warning"
|
? "bg-danger"
|
||||||
: "bg-secondary"
|
: "bg-secondary"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{rental.payoutStatus === "completed"
|
{rental.payoutStatus === "completed"
|
||||||
? "Paid"
|
? "Paid"
|
||||||
: rental.payoutStatus === "processing"
|
: rental.payoutStatus === "failed"
|
||||||
? "Processing"
|
? "Failed"
|
||||||
: "Pending"}
|
: "Pending"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export interface Rental {
|
|||||||
stripePaymentIntentId?: string;
|
stripePaymentIntentId?: string;
|
||||||
stripePaymentMethodId?: string;
|
stripePaymentMethodId?: string;
|
||||||
// Payout status tracking
|
// Payout status tracking
|
||||||
payoutStatus?: "pending" | "processing" | "completed" | "failed";
|
payoutStatus?: "pending" | "completed" | "failed" | null;
|
||||||
payoutProcessedAt?: string;
|
payoutProcessedAt?: string;
|
||||||
stripeTransferId?: string;
|
stripeTransferId?: string;
|
||||||
deliveryMethod: "pickup" | "delivery";
|
deliveryMethod: "pickup" | "delivery";
|
||||||
|
|||||||
Reference in New Issue
Block a user