Adding status to reservations and stopping deletion on booking
This commit is contained in:
parent
25fb2c9bdc
commit
671084dc7b
6 changed files with 72 additions and 18 deletions
31
database/migrations/1739366336570-ReservationStatuses.ts
Normal file
31
database/migrations/1739366336570-ReservationStatuses.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class ReservationStatuses1739366336570 implements MigrationInterface {
|
||||||
|
name = 'ReservationStatuses1739366336570'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "temporary_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "status" varchar(32) NOT NULL DEFAULT ('pending'), "waitingListId" integer, "ownerId" varchar(32) NOT NULL, "opponents" json NOT NULL)`,
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`INSERT INTO "temporary_reservations"("id", "dateRangeStart", "dateRangeEnd", "status", "waitingListId", "ownerId", "opponents") SELECT "id", "dateRangeStart", "dateRangeEnd", CASE "waitListed" WHEN true THEN "on_waiting_list" ELSE "pending" END, "waitingListId", "ownerId", "opponents" FROM "reservations"`,
|
||||||
|
)
|
||||||
|
await queryRunner.query(`DROP TABLE "reservations"`)
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "temporary_reservations" RENAME TO "reservations"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "reservations" RENAME TO "temporary_reservations"`,
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "waitListed" boolean NOT NULL DEFAULT (0), "waitingListId" integer, "ownerId" varchar(32) NOT NULL, "opponents" json NOT NULL)`,
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`INSERT INTO "reservations"("id", "dateRangeStart", "dateRangeEnd", "waitListed", "waitingListId", "ownerId", "opponents") SELECT "id", "dateRangeStart", "dateRangeEnd", CASE "status" WHEN "on_waiting_list" THEN true ELSE false, "waitingListId", "ownerId", "opponents" FROM "temporary_reservations"`,
|
||||||
|
)
|
||||||
|
await queryRunner.query(`DROP TABLE "temporary_reservations"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -121,14 +121,14 @@ export class NtfyProvider implements OnApplicationBootstrap {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendReservationWaitlistedNotification(
|
async sendReservationOnWaitingListNotification(
|
||||||
reservationId: string,
|
reservationId: string,
|
||||||
startTime: Dayjs,
|
startTime: Dayjs,
|
||||||
endTime: Dayjs,
|
endTime: Dayjs,
|
||||||
) {
|
) {
|
||||||
await this.publishQueue.add(
|
await this.publishQueue.add(
|
||||||
...NtfyProvider.defaultJob({
|
...NtfyProvider.defaultJob({
|
||||||
title: 'Reservation waitlisted',
|
title: 'Reservation added to waiting list',
|
||||||
message: `${reservationId} - ${startTime.format()} to ${endTime.format()}`,
|
message: `${reservationId} - ${startTime.format()} to ${endTime.format()}`,
|
||||||
tags: [MessageTags.badminton, MessageTags.hourglass],
|
tags: [MessageTags.badminton, MessageTags.hourglass],
|
||||||
}),
|
}),
|
||||||
|
|
@ -138,7 +138,7 @@ export class NtfyProvider implements OnApplicationBootstrap {
|
||||||
async sendWaitListEmailReceivedNotification(subject: string) {
|
async sendWaitListEmailReceivedNotification(subject: string) {
|
||||||
await this.publishQueue.add(
|
await this.publishQueue.add(
|
||||||
...NtfyProvider.defaultJob({
|
...NtfyProvider.defaultJob({
|
||||||
title: 'Wait listed reservation available',
|
title: 'Reservation on waiting list has become available',
|
||||||
message: `${subject}`,
|
message: `${subject}`,
|
||||||
tags: [MessageTags.badminton, MessageTags.hourglass],
|
tags: [MessageTags.badminton, MessageTags.hourglass],
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Exclude, Transform, Type } from 'class-transformer'
|
import { Exclude, Transform, Type } from 'class-transformer'
|
||||||
|
import { IsEnum } from 'class-validator'
|
||||||
import { Dayjs } from 'dayjs'
|
import { Dayjs } from 'dayjs'
|
||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
|
||||||
|
|
@ -12,6 +13,12 @@ export interface Opponent {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ReservationStatus {
|
||||||
|
Pending = 'pending',
|
||||||
|
OnWaitingList = 'on_waiting_list',
|
||||||
|
Booked = 'booked',
|
||||||
|
}
|
||||||
|
|
||||||
@Entity({ name: 'reservations' })
|
@Entity({ name: 'reservations' })
|
||||||
export class Reservation {
|
export class Reservation {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
|
@ -39,8 +46,13 @@ export class Reservation {
|
||||||
@Column('json', { nullable: false })
|
@Column('json', { nullable: false })
|
||||||
opponents: Opponent[]
|
opponents: Opponent[]
|
||||||
|
|
||||||
@Column('boolean', { default: false })
|
@Column('varchar', {
|
||||||
waitListed: boolean
|
length: 32,
|
||||||
|
nullable: false,
|
||||||
|
default: ReservationStatus.Pending,
|
||||||
|
})
|
||||||
|
@IsEnum(ReservationStatus)
|
||||||
|
status: ReservationStatus
|
||||||
|
|
||||||
@Column('int', { nullable: true })
|
@Column('int', { nullable: true })
|
||||||
waitingListId: number
|
waitingListId: number
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Repository } from 'typeorm'
|
||||||
import dayjs from '../common/dayjs'
|
import dayjs from '../common/dayjs'
|
||||||
import { LoggerService } from '../logger/service.logger'
|
import { LoggerService } from '../logger/service.logger'
|
||||||
import { BaanReserverenService } from '../runner/baanreserveren/service'
|
import { BaanReserverenService } from '../runner/baanreserveren/service'
|
||||||
import { Reservation } from './entity'
|
import { Reservation, ReservationStatus } from './entity'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReservationsService {
|
export class ReservationsService {
|
||||||
|
|
@ -50,7 +50,9 @@ export class ReservationsService {
|
||||||
endDate: dayjs().add(7, 'days').toISOString(),
|
endDate: dayjs().add(7, 'days').toISOString(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.andWhere(`waitListed = false`)
|
.andWhere('status <> :status', {
|
||||||
|
status: ReservationStatus.OnWaitingList,
|
||||||
|
})
|
||||||
.orderBy('dateRangeStart', 'ASC')
|
.orderBy('dateRangeStart', 'ASC')
|
||||||
|
|
||||||
return await query.getMany()
|
return await query.getMany()
|
||||||
|
|
@ -65,7 +67,7 @@ export class ReservationsService {
|
||||||
.andWhere(`DATE(dateRangeEnd) >= DATE(:date)`, {
|
.andWhere(`DATE(dateRangeEnd) >= DATE(:date)`, {
|
||||||
date: date.toISOString(),
|
date: date.toISOString(),
|
||||||
})
|
})
|
||||||
.andWhere('waitListed = true')
|
.andWhere('status = :status', { status: ReservationStatus.OnWaitingList })
|
||||||
.getMany()
|
.getMany()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { Process, Processor } from '@nestjs/bull'
|
import { Process, Processor } from '@nestjs/bull'
|
||||||
import { Inject } from '@nestjs/common'
|
import { Inject } from '@nestjs/common'
|
||||||
import { Job } from 'bull'
|
|
||||||
import { instanceToPlain, plainToInstance } from 'class-transformer'
|
import { instanceToPlain, plainToInstance } from 'class-transformer'
|
||||||
|
|
||||||
import { LoggerService } from '../logger/service.logger'
|
import { LoggerService } from '../logger/service.logger'
|
||||||
|
|
@ -11,7 +10,7 @@ import {
|
||||||
} from '../runner/baanreserveren/service'
|
} from '../runner/baanreserveren/service'
|
||||||
import { RESERVATIONS_QUEUE_NAME, ReservationsJob } from './config'
|
import { RESERVATIONS_QUEUE_NAME, ReservationsJob } from './config'
|
||||||
import { DAILY_RESERVATIONS_ATTEMPTS } from './cron'
|
import { DAILY_RESERVATIONS_ATTEMPTS } from './cron'
|
||||||
import { Reservation } from './entity'
|
import { Reservation, ReservationStatus } from './entity'
|
||||||
import { ReservationsService } from './service'
|
import { ReservationsService } from './service'
|
||||||
|
|
||||||
@Processor(RESERVATIONS_QUEUE_NAME)
|
@Processor(RESERVATIONS_QUEUE_NAME)
|
||||||
|
|
@ -63,10 +62,10 @@ export class ReservationsWorker {
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(shouldWaitlist || attemptsMade === DAILY_RESERVATIONS_ATTEMPTS) &&
|
(shouldWaitlist || attemptsMade === DAILY_RESERVATIONS_ATTEMPTS) &&
|
||||||
!reservation.waitListed
|
reservation.status !== ReservationStatus.OnWaitingList
|
||||||
) {
|
) {
|
||||||
this.loggerService.log('Adding reservation to waiting list')
|
this.loggerService.log('Adding reservation to waiting list')
|
||||||
await this.ntfyProvider.sendReservationWaitlistedNotification(
|
await this.ntfyProvider.sendReservationOnWaitingListNotification(
|
||||||
reservation.id,
|
reservation.id,
|
||||||
reservation.dateRangeStart,
|
reservation.dateRangeStart,
|
||||||
reservation.dateRangeEnd,
|
reservation.dateRangeEnd,
|
||||||
|
|
@ -89,7 +88,9 @@ export class ReservationsWorker {
|
||||||
} else {
|
} else {
|
||||||
await this.brService.performReservation(reservation, timeSensitive)
|
await this.brService.performReservation(reservation, timeSensitive)
|
||||||
}
|
}
|
||||||
await this.reservationsService.deleteById(reservation.id)
|
await this.reservationsService.update(reservation.id, {
|
||||||
|
status: ReservationStatus.Booked,
|
||||||
|
})
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await this.handleReservationErrors(
|
await this.handleReservationErrors(
|
||||||
error as Error,
|
error as Error,
|
||||||
|
|
@ -105,12 +106,12 @@ export class ReservationsWorker {
|
||||||
timeSensitive = true,
|
timeSensitive = true,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const waitingListId = await this.brService.addReservationToWaitList(
|
const waitingListId = await this.brService.addReservationToWaitingList(
|
||||||
reservation,
|
reservation,
|
||||||
timeSensitive,
|
timeSensitive,
|
||||||
)
|
)
|
||||||
await this.reservationsService.update(reservation.id, {
|
await this.reservationsService.update(reservation.id, {
|
||||||
waitListed: true,
|
status: ReservationStatus.OnWaitingList,
|
||||||
waitingListId,
|
waitingListId,
|
||||||
})
|
})
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,11 @@ import dayjs from '../../common/dayjs'
|
||||||
import { LoggerService } from '../../logger/service.logger'
|
import { LoggerService } from '../../logger/service.logger'
|
||||||
import { MONITORING_QUEUE_NAME, MonitoringQueue } from '../../monitoring/config'
|
import { MONITORING_QUEUE_NAME, MonitoringQueue } from '../../monitoring/config'
|
||||||
import { MonitorType } from '../../monitoring/entity'
|
import { MonitorType } from '../../monitoring/entity'
|
||||||
import { Opponent, Reservation } from '../../reservations/entity'
|
import {
|
||||||
|
Opponent,
|
||||||
|
Reservation,
|
||||||
|
ReservationStatus,
|
||||||
|
} from '../../reservations/entity'
|
||||||
import { EmptyPage } from '../pages/empty'
|
import { EmptyPage } from '../pages/empty'
|
||||||
|
|
||||||
export const BAAN_RESERVEREN_ROOT_URL = 'https://squashcity.baanreserveren.nl'
|
export const BAAN_RESERVEREN_ROOT_URL = 'https://squashcity.baanreserveren.nl'
|
||||||
|
|
@ -741,7 +745,7 @@ export class BaanReserverenService {
|
||||||
throw new NoCourtAvailableError('Could not reserve court')
|
throw new NoCourtAvailableError('Could not reserve court')
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addReservationToWaitList(
|
public async addReservationToWaitingList(
|
||||||
reservation: Reservation,
|
reservation: Reservation,
|
||||||
timeSensitive = true,
|
timeSensitive = true,
|
||||||
) {
|
) {
|
||||||
|
|
@ -774,7 +778,11 @@ export class BaanReserverenService {
|
||||||
|
|
||||||
public async removeReservationFromWaitList(reservation: Reservation) {
|
public async removeReservationFromWaitList(reservation: Reservation) {
|
||||||
try {
|
try {
|
||||||
if (!reservation.waitListed || !reservation.waitingListId) return
|
if (
|
||||||
|
reservation.status !== ReservationStatus.OnWaitingList ||
|
||||||
|
!reservation.waitingListId
|
||||||
|
)
|
||||||
|
return
|
||||||
await this.init()
|
await this.init()
|
||||||
await this.navigateToWaitingList()
|
await this.navigateToWaitingList()
|
||||||
await this.deleteWaitingListEntryRowById(reservation.waitingListId)
|
await this.deleteWaitingListEntryRowById(reservation.waitingListId)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue