Sales platform for a travel agency

mid-sized travel agency · Tourism and excursions · 2026

The problem

The agency sells excursions: a chartered coach, a planned route, a fixed departure. Every trip has a fixed number of seats, and every seat sold carries a price that depends on who sits in it.

All of it lived in a shared spreadsheet, edited by three people at once. That works until the day it stops working, and it always stops the same way: two people selling the last seat on the coach, a payment nobody marked as received, a price typed wrong on a row nobody reviewed.

The cost does not show up as a loss on the books. It shows up at the front desk: someone calls to undo a sale, someone travels standing, someone finds out at boarding that they paid a different price from the person next to them. Each of those episodes eats hours from people who should be selling the next trip, and wears down a relationship with customers who tend to come back every year.

They had already tried disciplining the spreadsheet with rules agreed between the three of them. Agreed rules do not survive high season.

The constraints

The decisions

Age computed at the trip date

Excursion pricing varies by age bracket, and a child can move brackets between purchase and boarding. Computing at purchase time is simpler to implement and produces wrong charges months later, always in the agency's favour, always discovered by the customer on the bus.

discarded alternative

Freeze the price at purchase. Discarded: it moves the error to customer service on the day of the trip.

The seat is held before payment, and the hold expires on its own

A seat leaves the pool the moment someone starts buying it, and returns if payment does not arrive within the window. A short in-memory hold handles it, without depending on anyone remembering to release it.

It is the only way two staff members can sell at the same time without selling the same seat, which was exactly the failure the spreadsheet could not prevent.

discarded alternative

Confirm the seat only once payment clears. Discarded: it opens a window where a seat is both sold and available, and it is precisely in high season, with two simultaneous sales, that the window matters.

The spreadsheet stays the source of truth until the last stage

The migration was designed in stages: trip setup and sales first, then payments, then boarding. While a stage has not migrated, the spreadsheet remains the source for that stage, with two-way import.

It is more work than switching everything at once, and it is the reason the proposal fits inside high season. A system that requires stopping sales in order to start never starts.

discarded alternative

A single cutover, with a date on which the whole operation moves. Discarded: it concentrates all the risk on one day, and if that day goes wrong the agency loses sales that do not come back, because the excursion leaves with an empty coach.

The result

The work stopped at discovery and architecture, and that is what was delivered: a map of the operation as it actually runs, the decisions above with the rejected options recorded beside them, and the technical design that supports replacing the spreadsheet in stages.

The concrete change, before a single line of code, was clarity. The three people running the spreadsheet now have it written down: what each of them does, where one steps on another, and in what order to replace each piece without stopping sales.

There is no number here because no system reached production to measure. When one does, the indicators that matter are already defined: how many duplicate sales happen per season, and how much service time goes into correcting what the spreadsheet let through.

tech sheet

stack
Next.js · NestJS · PostgreSQL · Redis
role
Discovery and solution architecture
← all cases