diff --git a/server/scripts/size-baseline.json b/server/scripts/size-baseline.json index 68ded4957..81a14c753 100644 --- a/server/scripts/size-baseline.json +++ b/server/scripts/size-baseline.json @@ -17,7 +17,7 @@ "src/nest/places/places.service.ts": 2020, "src/nest/plugins/plugin-runtime.service.ts": 1463, "src/nest/plugins/runtime/plugin-sdk.ts": 1094, - "src/nest/reservations/reservations.service.ts": 1248, + "src/nest/reservations/reservations.service.ts": 1247, "src/nest/trips/trips.service.ts": 1013, "src/nest/vacay/vacay.service.ts": 1440 } diff --git a/server/src/nest/reservations/reservations.service.ts b/server/src/nest/reservations/reservations.service.ts index 124722509..7cd214eb8 100644 --- a/server/src/nest/reservations/reservations.service.ts +++ b/server/src/nest/reservations/reservations.service.ts @@ -1192,9 +1192,8 @@ export class ReservationsService { const updated = await this.budget.updateBudgetItem(existing.id, tripId, { name: itemName, category, total_price: entry.total_price }); this.sendCost(tripId, { event: 'budget:updated', payload: { item: updated } }, socketId, collect); } else { - const item = await this.budget.createBudgetItem(tripId, { name: itemName, category, total_price: entry.total_price }); - await this.budgetItemsRepo.setReservationId(item.id, id); - item.reservation_id = Number(id); + // The link travels on the insert, so a cost is never written without its booking. + const item = await this.budget.linkBudgetItemToReservation(tripId, Number(id), { name: itemName, category, total_price: entry.total_price! }); this.sendCost(tripId, { event: 'budget:created', payload: { item } }, socketId, collect); } } catch (err) { diff --git a/server/tests/unit/nest/reservations.service.test.ts b/server/tests/unit/nest/reservations.service.test.ts index 621f67e76..ace4a4687 100644 --- a/server/tests/unit/nest/reservations.service.test.ts +++ b/server/tests/unit/nest/reservations.service.test.ts @@ -695,16 +695,16 @@ describe('ReservationsService (DI-native, real SQL)', () => { expect(broadcast).toHaveBeenCalledWith(String(trip.id), 'budget:updated', { item: { id: item.id } }, 'sock'); }); - it('creates + links a new item when none exists, using the current title fallback', async () => { + it('creates a new item linked on its insert when none exists, using the current title fallback', async () => { const { trip } = ownerTrip(); const res = createReservation(testDb, trip.id); - const created = createBudgetItem(testDb, trip.id); // the row the mocked create "returns" - budget.createBudgetItem.mockReturnValue({ id: created.id }); + budget.linkBudgetItemToReservation.mockReturnValue({ id: 31, reservation_id: res.id }); await svc.syncBudgetOnUpdate(String(trip.id), String(res.id), '', undefined, 'Old title', 'flight', { total_price: 120 }, 'sock'); - expect(budget.createBudgetItem).toHaveBeenCalledWith(String(trip.id), { name: 'Old title', category: 'flight', total_price: 120 }); - // The service back-links the created item to the reservation itself. - expect(testDb.prepare('SELECT reservation_id FROM budget_items WHERE id = ?').get(created.id)).toEqual({ reservation_id: res.id }); - expect(broadcast).toHaveBeenCalledWith(String(trip.id), 'budget:created', { item: { id: created.id, reservation_id: res.id } }, 'sock'); + // One statement carries the link: no unlinked insert followed by a separate + // back-link that a swallowed failure inside updateWithCost could leave half done. + expect(budget.linkBudgetItemToReservation).toHaveBeenCalledWith(String(trip.id), res.id, { name: 'Old title', category: 'flight', total_price: 120 }); + expect(budget.createBudgetItem).not.toHaveBeenCalled(); + expect(broadcast).toHaveBeenCalledWith(String(trip.id), 'budget:created', { item: { id: 31, reservation_id: res.id } }, 'sock'); }); it('RESV-TX-001: updateWithCost rolls the booking edit back when its cost write fails', async () => {