Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions migrations/2026-04-23-cancelled-by-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { sql } from 'kysely';

export async function up(db) {
await sql`
CREATE OR REPLACE PROCEDURE cancel_ride_share_request_by_provider(
p_request_id INTEGER,
p_customer_id INTEGER,
p_provider_id INTEGER,
p_now BIGINT
) AS $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM request r
INNER JOIN ride_share_tour as rst on r.ride_share_tour=rst.id
INNER JOIN ride_share_vehicle as v on rst.vehicle=v.id
WHERE r.customer = p_customer_id
AND r.id = p_request_id
AND v.owner=p_provider_id
) THEN
RETURN;
END IF;

UPDATE request r
SET cancelled = true,
cancelled_by_customer = false,
cancelled_at = p_now
WHERE r.id = p_request_id;

UPDATE event e
SET cancelled = TRUE
WHERE e.request = p_request_id;
END;
$$ LANGUAGE plpgsql;
`.execute(db);
}

export async function down() { }
1 change: 1 addition & 0 deletions src/lib/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ const translations: Translations = {
requestBy: 'Anfrage von',
offerBy: 'Angebot von',
acceptRequest: 'Mitfahrt bestätigen',
declineRequest: 'Mitfahrt ablehnen',
requestAccepted: 'Mitfahrt bestätigt',
requestCancelled: 'Mitfahrt abgesagt',
showMap: 'Karte anzeigen',
Expand Down
1 change: 1 addition & 0 deletions src/lib/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ const translations: Translations = {
requestBy: 'Request from',
offerBy: 'Offered by',
acceptRequest: 'Confirm ride',
declineRequest: 'Decline ride',
requestAccepted: 'Ride confirmed',
requestCancelled: 'Ride cancelled',
showMap: 'Show map',
Expand Down
1 change: 1 addition & 0 deletions src/lib/i18n/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ export type Translations = {
requestBy: string;
offerBy: string;
acceptRequest: string;
declineRequest: string;
requestAccepted: string;
requestCancelled: string;
showMap: string;
Expand Down
285 changes: 176 additions & 109 deletions src/lib/server/booking/rideShare/cancelRideShareRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import { oneToManyCarRouting } from '$lib/server/util/oneToManyCarRouting';
import { MAX_RIDE_SHARE_TOUR_TIME, PASSENGER_CHANGE_DURATION } from '$lib/constants';
import { sortEventsByTime } from '$lib/testHelpers';
import { retry } from '$lib/server/db/retryQuery';
import CancelNotificationCustomer from '$lib/server/email/CancelNotificationCustomer.svelte';

export const cancelRideShareRequest = async (requestId: number, userId: number) => {
export type CancelledBy = 'provider' | 'passenger';

export const cancelRideShareRequest = async (
requestId: number,
userId: number,
cancelledBy: CancelledBy
) => {
console.log(
'Cancel Request PARAMS START: ',
JSON.stringify({ requestId, userId }, null, '\t'),
Expand All @@ -23,38 +30,7 @@ export const cancelRideShareRequest = async (requestId: number, userId: number)
.selectFrom('request')
.where('request.id', '=', requestId)
.innerJoin('rideShareTour as relevant_tour', 'relevant_tour.id', 'request.rideShareTour')
.select((eb) => [
'relevant_tour.id as tourId',
jsonArrayFrom(
eb
.selectFrom('request as cancelled_request')
.where('cancelled_request.id', '=', requestId)
.innerJoin(
'rideShareTour as relevant_tour',
'cancelled_request.rideShareTour',
'relevant_tour.id'
)
.innerJoin(
'request as relevant_request',
'relevant_request.tour',
'relevant_tour.id'
)
.select((eb) => [
jsonArrayFrom(
eb
.selectFrom('event')
.innerJoin('eventGroup', 'eventGroup.id', 'event.eventGroupId')
.whereRef('event.request', '=', 'relevant_request.id')
.select([
'eventGroup.scheduledTimeStart',
'eventGroup.scheduledTimeEnd',
'event.isPickup',
'event.request as requestId'
])
).as('events')
])
).as('requests')
])
.select('request.customer')
.executeTakeFirst();
if (tour === undefined) {
console.log(
Expand All @@ -63,83 +39,10 @@ export const cancelRideShareRequest = async (requestId: number, userId: number)
);
return;
}
const queryResult =
await sql`CALL cancel_ride_share_request(${requestId}, ${userId}, ${Date.now()})`.execute(
trx
);
const tourInfo = await trx
.selectFrom('request as cancelled_request')
.where('cancelled_request.id', '=', requestId)
.innerJoin('rideShareTour', 'rideShareTour.id', 'cancelled_request.rideShareTour')
.innerJoin('rideShareVehicle', 'rideShareTour.vehicle', 'rideShareVehicle.id')
.innerJoin('user', 'user.id', 'rideShareVehicle.owner')
.select((eb) => [
'cancelled_request.cancelled as wasRequestCancelled',
'rideShareTour.vehicle',
'rideShareTour.id',
'user.name',
'user.firstName',
'user.email',
'cancelled_request.pending',
jsonArrayFrom(
eb
.selectFrom('request as cancelled_request')
.innerJoin(
'rideShareTour as cancelled_tour',
'cancelled_tour.id',
'cancelled_request.rideShareTour'
)
.innerJoin('request', 'request.rideShareTour', 'cancelled_tour.id')
.innerJoin('event', 'event.request', 'request.id')
.innerJoin('eventGroup', 'eventGroup.id', 'event.eventGroupId')
.where('cancelled_request.id', '=', requestId)
.select([
'eventGroup.address',
'eventGroup.scheduledTimeStart',
'eventGroup.scheduledTimeEnd',
'event.cancelled',
'eventGroup.prevLegDuration',
'eventGroup.nextLegDuration',
'eventGroup.lat',
'eventGroup.lng',
'request.id as requestid',
'cancelled_tour.id as tourid',
'event.id as eventid',
'event.eventGroupId',
'request.pending'
])
).as('events')
])
.executeTakeFirst();
if (tourInfo === undefined) {
console.log(
'Tour was undefined unexpectedly in cancelRequest cannot send notification Emails, requestId: ',
requestId
);
return;
}
if (!tourInfo.wasRequestCancelled) {
console.log('The request could not be cancelled due to missing authorization.');
return;
if (cancelledBy === 'provider') {
return await cancelledByProvider(requestId, tour.customer, userId, trx);
}
console.assert(queryResult.rows.length === 1);
if (!tourInfo.pending) {
await updateLegDurations(tourInfo.events, requestId, trx);
}
try {
await sendMail(CancelNotificationCompany, 'Stornierte Buchung', tourInfo.email, {
events: tourInfo.events,
name: tourInfo.firstName + ' ' + tourInfo.name
});
} catch {
console.log(
'Failed to send cancellation email to company with email: ',
tourInfo.email,
' tourId: ',
tourInfo.id
);
}
console.log('Cancel Ride Share Request - success', { requestId, userId });
return await cancelledByCustomer(requestId, userId, trx);
})
);
};
Expand Down Expand Up @@ -236,3 +139,167 @@ async function updateLegDurations(
await update(cancelledIdx2 - 1, cancelledIdx2 + 1, uncancelledEvents, trx);
}
}
async function cancelledByProvider(
requestId: number,
customerId: number,
providerId: number,
trx: Transaction<Database>
) {
if (customerId === providerId) {
console.log('Customer id matches provider id -> use cancel ride share tour instead.');
return;
}
const queryResult =
await sql`CALL cancel_ride_share_request_by_provider(${requestId}, ${customerId}, ${providerId}, ${Date.now()})`.execute(
trx
);
const tourInfo = await trx
.selectFrom('request as cancelled_request')
.where('cancelled_request.id', '=', requestId)
.innerJoin('rideShareTour', 'rideShareTour.id', 'cancelled_request.rideShareTour')
.innerJoin('user', 'user.id', 'cancelled_request.customer')
.select((eb) => [
'cancelled_request.cancelled as wasRequestCancelled',
'rideShareTour.id',
'user.name',
'user.firstName',
'user.email',
'cancelled_request.pending',
jsonArrayFrom(
eb
.selectFrom('request as cancelled_request')
.innerJoin(
'rideShareTour as cancelled_tour',
'cancelled_tour.id',
'cancelled_request.rideShareTour'
)
.innerJoin('request', 'request.rideShareTour', 'cancelled_tour.id')
.innerJoin('event', 'event.request', 'request.id')
.innerJoin('eventGroup', 'eventGroup.id', 'event.eventGroupId')
.where('cancelled_request.id', '=', requestId)
.select([
'eventGroup.address',
'eventGroup.scheduledTimeStart',
'eventGroup.scheduledTimeEnd',
'event.cancelled',
'eventGroup.prevLegDuration',
'eventGroup.nextLegDuration',
'eventGroup.lat',
'eventGroup.lng',
'request.id as requestid',
'cancelled_tour.id as tourid',
'event.id as eventid',
'event.eventGroupId',
'request.pending'
])
).as('events')
])
.executeTakeFirst();
if (tourInfo === undefined) {
console.log(
'Tour was undefined unexpectedly in cancelRequest cannot send notification Emails, requestId: ',
requestId
);
return;
}
if (!tourInfo.wasRequestCancelled) {
console.log('The request could not be cancelled due to missing authorization.');
return;
}
console.assert(queryResult.rows.length === 1);
if (!tourInfo.pending) {
await updateLegDurations(tourInfo.events, requestId, trx);
}
try {
await sendMail(CancelNotificationCustomer, 'Stornierte Buchung', tourInfo.email, {
events: tourInfo.events,
name: tourInfo.firstName + ' ' + tourInfo.name
});
} catch {
console.log(
'Failed to send cancellation email to customer with email: ',
tourInfo.email,
' tourId: ',
tourInfo.id
);
}
console.log('Cancel Ride Share Request - success', { requestId, providerId });
}

async function cancelledByCustomer(requestId: number, userId: number, trx: Transaction<Database>) {
const queryResult =
await sql`CALL cancel_ride_share_request(${requestId}, ${userId}, ${Date.now()})`.execute(trx);
const tourInfo = await trx
.selectFrom('request as cancelled_request')
.where('cancelled_request.id', '=', requestId)
.innerJoin('rideShareTour', 'rideShareTour.id', 'cancelled_request.rideShareTour')
.innerJoin('rideShareVehicle', 'rideShareTour.vehicle', 'rideShareVehicle.id')
.innerJoin('user', 'user.id', 'rideShareVehicle.owner')
.select((eb) => [
'cancelled_request.cancelled as wasRequestCancelled',
'rideShareTour.vehicle',
'rideShareTour.id',
'user.name',
'user.firstName',
'user.email',
'cancelled_request.pending',
jsonArrayFrom(
eb
.selectFrom('request as cancelled_request')
.innerJoin(
'rideShareTour as cancelled_tour',
'cancelled_tour.id',
'cancelled_request.rideShareTour'
)
.innerJoin('request', 'request.rideShareTour', 'cancelled_tour.id')
.innerJoin('event', 'event.request', 'request.id')
.innerJoin('eventGroup', 'eventGroup.id', 'event.eventGroupId')
.where('cancelled_request.id', '=', requestId)
.select([
'eventGroup.address',
'eventGroup.scheduledTimeStart',
'eventGroup.scheduledTimeEnd',
'event.cancelled',
'eventGroup.prevLegDuration',
'eventGroup.nextLegDuration',
'eventGroup.lat',
'eventGroup.lng',
'request.id as requestid',
'cancelled_tour.id as tourid',
'event.id as eventid',
'event.eventGroupId',
'request.pending'
])
).as('events')
])
.executeTakeFirst();
if (tourInfo === undefined) {
console.log(
'Tour was undefined unexpectedly in cancelRequest cannot send notification Emails, requestId: ',
requestId
);
return;
}
if (!tourInfo.wasRequestCancelled) {
console.log('The request could not be cancelled due to missing authorization.');
return;
}
console.assert(queryResult.rows.length === 1);
if (!tourInfo.pending) {
await updateLegDurations(tourInfo.events, requestId, trx);
}
try {
await sendMail(CancelNotificationCompany, 'Stornierte Buchung', tourInfo.email, {
events: tourInfo.events,
name: tourInfo.firstName + ' ' + tourInfo.name
});
} catch {
console.log(
'Failed to send cancellation email to company with email: ',
tourInfo.email,
' tourId: ',
tourInfo.id
);
}
console.log('Cancel Ride Share Request - success', { requestId, userId });
}
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ describe('add ride share request', () => {
expect(whiteResponse2.direct[0].length).toBe(0);

// cancel request -> a new request should bookable
await cancelRideShareRequest(requestId, mockUserId);
await cancelRideShareRequest(requestId, mockUserId, 'passenger');
const whiteResponse3 = await whiteRideShare(body2).then((r) => r.json());
expect(whiteResponse3.direct.length).toBe(1);
expect(whiteResponse3.direct[0].length).not.toBe(0);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/server/simulation/actions/cancelRequestRsLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function cancelRequestRsLocal(customerId: number): Promise<ActionRe
};
}
const r = randomInt(0, requests.length - 1);
await cancelRideShareRequest(requests[r].requestId, customerId);
await cancelRideShareRequest(requests[r].requestId, customerId, 'passenger');
return {
lastActionSpecifics: {
vehicleId: requests[r].vehicleId,
Expand Down
Loading
Loading