mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
fix(reservations): a booking read on its own carries its day slots, so an edit no longer drops them
The create and update answers, the reservation:updated broadcast and the MCP reservation tools all hand back the single booking read, and that read never loaded reservation_day_positions. The day list puts a booking moved on a day by that slot before its time, and the client replaces the booking with the answer, so a booking moved on a day jumped back to its time after every edit until the next reload. The read now attaches day_positions in the shape the trip list already sends. Refs #2631
This commit is contained in:
@@ -32,6 +32,20 @@ export class ReservationDayPositionsRepository extends TrekRepository<Reservatio
|
||||
.execute<{ reservation_id: number; day_id: number; position: number }[]>('all', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* `SELECT day_id, position FROM reservation_day_positions WHERE reservation_id = ?`:
|
||||
* one booking's slots, for the single-booking read the create, update and
|
||||
* broadcast payloads come from. Without them a booking moved on a day lost
|
||||
* that slot in the list after every edit until the next reload (#2631).
|
||||
*/
|
||||
async listForReservation(reservation_id: number): Promise<{ day_id: number; position: number }[]> {
|
||||
return this.kysely<ReservationDayPositionsKyselyDB>()
|
||||
.selectFrom('reservation_day_positions')
|
||||
.select(['day_id', 'position'])
|
||||
.where('reservation_id', '=', reservation_id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* RS31 (`ReservationsService.updatePositions`'s day branch) — `INSERT OR
|
||||
* REPLACE INTO reservation_day_positions (reservation_id, day_id,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ReservationDayPositions } from '../../db/entities/ReservationDayPositions.entity';
|
||||
import { ReservationEndpoints } from '../../db/entities/ReservationEndpoints.entity';
|
||||
import { ReservationTravelers } from '../../db/entities/ReservationTravelers.entity';
|
||||
import { Reservations } from '../../db/entities/Reservations.entity';
|
||||
@@ -11,14 +12,17 @@ import { Module } from '@nestjs/common';
|
||||
* in — which would re-close the cycle that airtrail.bridge used to dodge.
|
||||
*
|
||||
* `MikroOrmModule.forFeature([Reservations, ReservationEndpoints,
|
||||
* ReservationTravelers])` registers `ReservationsRepository`/
|
||||
* `ReservationEndpointsRepository`/`ReservationTravelersRepository` for
|
||||
* ReservationTravelers, ReservationDayPositions])` registers `ReservationsRepository`/
|
||||
* `ReservationEndpointsRepository`/`ReservationTravelersRepository`/
|
||||
* `ReservationDayPositionsRepository` for
|
||||
* `ReservationsReadService`'s `@InjectRepository` constructor params (Plan
|
||||
* 3d Task 2, R11) — the entity classes only, not `ReservationsModule`
|
||||
* itself, so this module stays the same leaf it always was.
|
||||
*/
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Reservations, ReservationEndpoints, ReservationTravelers])],
|
||||
imports: [
|
||||
MikroOrmModule.forFeature([Reservations, ReservationEndpoints, ReservationTravelers, ReservationDayPositions]),
|
||||
],
|
||||
providers: [ReservationsReadService],
|
||||
exports: [ReservationsReadService],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReservationDayPositions } from '../../db/entities/ReservationDayPositions.entity';
|
||||
import { ReservationEndpoints } from '../../db/entities/ReservationEndpoints.entity';
|
||||
import { ReservationTravelers } from '../../db/entities/ReservationTravelers.entity';
|
||||
import { Reservations } from '../../db/entities/Reservations.entity';
|
||||
import type { ReservationDayPositionsRepository } from '../../db/repositories/ReservationDayPositions.repository';
|
||||
import type { ReservationEndpointsRepository } from '../../db/repositories/ReservationEndpoints.repository';
|
||||
import type { ReservationTravelersRepository } from '../../db/repositories/ReservationTravelers.repository';
|
||||
import type { ReservationsRepository } from '../../db/repositories/Reservations.repository';
|
||||
@@ -53,6 +55,7 @@ export class ReservationsReadService {
|
||||
@InjectRepository(Reservations) private readonly reservationsRepo: ReservationsRepository,
|
||||
@InjectRepository(ReservationEndpoints) private readonly endpointsRepo: ReservationEndpointsRepository,
|
||||
@InjectRepository(ReservationTravelers) private readonly travelersRepo: ReservationTravelersRepository,
|
||||
@InjectRepository(ReservationDayPositions) private readonly dayPositionsRepo: ReservationDayPositionsRepository,
|
||||
) {}
|
||||
|
||||
async getReservationWithJoins(id: string | number): Promise<ReservationRow | undefined> {
|
||||
@@ -62,6 +65,7 @@ export class ReservationsReadService {
|
||||
const result: ReservationRow = { ...row };
|
||||
result.endpoints = await this.loadEndpoints(row.id);
|
||||
result.travelers = await this.loadTravelers(row.id);
|
||||
result.day_positions = await this.loadDayPositions(row.id);
|
||||
// accommodation_id is a TEXT column; the integer FK reads back as a numeric
|
||||
// string (e.g. "14.0"). Normalize to an int so clients can parse it.
|
||||
result.accommodation_id = row.accommodation_id == null ? null : Math.trunc(Number(row.accommodation_id));
|
||||
@@ -74,6 +78,17 @@ export class ReservationsReadService {
|
||||
return (await this.endpointsRepo.listForReservation(reservationId)) as ReservationEndpoint[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The booking's slot per day, in the shape `ReservationsService.list` sends:
|
||||
* `{ [day_id]: position }`, or null without any. The list reads it before the
|
||||
* booking's time, so a payload without it put a moved booking back by the clock.
|
||||
*/
|
||||
async loadDayPositions(reservationId: number): Promise<Record<number, number> | null> {
|
||||
const rows = await this.dayPositionsRepo.listForReservation(reservationId);
|
||||
if (rows.length === 0) return null;
|
||||
return Object.fromEntries(rows.map((dp) => [dp.day_id, dp.position]));
|
||||
}
|
||||
|
||||
async loadTravelers(reservationId: number | string): Promise<ReservationTraveler[]> {
|
||||
// RR3 — no ORDER BY (unlike RS6), matching the legacy statement's own row order.
|
||||
const rows = await this.travelersRepo.listForReservation(Number(reservationId));
|
||||
|
||||
Reference in New Issue
Block a user