Travel & Tourism// diagnostic

The client approved version four, and something moved

In short

If a client quotes a detail your current itinerary does not contain, the system almost certainly edited the trip in place and kept no snapshot of what was approved. Reconstruct what you can from the sent proposal, then start recording every change against a frozen revision — who, when, which field, old value, new value — so the next argument is answerable instead of arguable.

Key takeaways

  • The symptom is a client quoting something real. Start by believing them and prove where your record went thin.
  • An itinerary storing current state only cannot say what was approved, at any level of goodwill from your team.
  • In 2 of the 4 causes nobody edited the trip at all — the component or the template underneath it moved instead.
  • The minimum audit row is 5 fields: actor, timestamp, field path, old value, new value, plus the revision it belonged to.
  • Freeze a render at every revision. Re-generating a PDF today reproduces the fault you are trying to diagnose.
  • A supplier content sync can rewrite a proposal agreed 6 weeks earlier, with no internal user to blame.

A client says the itinerary they approved included a private airport transfer, and the trip in front of you shows a shared shuttle. Nobody remembers changing it and nobody can prove it was ever there. That is not a memory problem. It is a storage problem: the itinerary holds current state and nothing else, so the version they approved no longer exists anywhere you can query.

Begin by assuming the client is right. They usually are, because they are reading from something — a PDF, an email, a link they opened in March — and you are reading from a record that has been written over since. The useful work is finding exactly where your reconstruction of the approved state runs out. That boundary names which of 4 causes you are looking at, and 2 of them involve nobody touching the trip at all.

Reconstruct the approved state, and mark where it fails

Do this before touching any code. It takes under 60 minutes on one trip and it produces the finding, which is rarely the one anybody expected.

  1. Find the artefact the client is quoting — the sent email, the attachment, the proposal link. Note its date and which revision label it carries, if any.
  2. Put it beside the trip as it stands now, day by day. Record every difference, not only the disputed one. The other 3 or 4 are the evidence.
  3. For each difference, ask whether the itinerary row changed or the thing it points at changed. A day still referencing the same transfer component, whose description now reads differently, is a different fault from a day whose transfer row was replaced.
  4. Try to render the trip as at the approval date. If the answer is 'we can show you the current version', you have confirmed there are no snapshots and can stop hunting for a hidden log.
  5. List the derived values that moved without an edit: service dates, transfer legs, per-day costing lines. These change on their own when anything upstream moves, which is the recalculation set in what has to recalculate when a day moves.
  6. Write down the last point at which the 2 records agree. That timestamp, and the fields that diverge after it, are the diagnosis.

Four ways an approved detail leaves without a trace

Ranked by how often each turns out to be the one. The first is the most common and the least interesting. Rows 3 and 4 are what catch experienced teams.

CauseWhat you observeConfirming testWhose change it was
Edited in place, no snapshot takenCurrent trip differs from the sent PDF; no stored revision matches that PDFTry to render the trip as at the approval date, and failSomeone on your team, unrecorded
Component record mutated underneath the tripThe day still points at the same component, but its title, inclusions or duration read differentlyCompare the component's own updated timestamp with the approval dateWhoever maintains the product library
Template edited and pushed into live tripsSeveral unrelated trips changed the same way on the same daySearch for the identical difference across other trips built from that templateWhoever edited the template
Supplier sync overwrote the descriptionWording changed with no internal user involved; often a whole category moves at onceCheck the last import run against the date the wording changedThe supplier, through your integration
Causes of an unexplained itinerary change, and the evidence that confirms each

The day did not change; the thing it points at did

This is the cause most teams never suspect, because the itinerary genuinely was not edited. A trip day holds a reference to a component — 1 purchasable service with its own supplier, duration and cancellation term, as the itinerary component defines it. If the day stores a pointer and the component is mutable, editing that component changes every trip that ever referenced it, including trips that ran 2 years ago.

  • Descriptions and inclusions. A product manager tidies the wording of a city tour, and 40 open proposals now describe something slightly different from what was agreed.
  • Durations and timings. A 4-hour half-day becomes a 8-hour full-day in the library, and the affected days quietly stop being physically possible.
  • Cancellation terms. The term the client accepted is replaced by the one the supplier offers today, which is what gets quoted at them if they cancel.
  • Supplier identity. A component reassigned to a different operator changes who actually delivers the service, with no visible change on the itinerary line.
  • Long trips multiply the exposure: a 14-day, 3-country trip carries dozens of component references across several suppliers and currencies, as modelling a fourteen-day, three-country trip sets out row by row.

The fix is a modelling decision, not a process one. Copy the fields that appear in the proposal — title, inclusions, duration, cancellation term, supplier name — onto the revision at the moment it is sent, and keep the pointer alongside for operations. The proposal then says what it said, and the live product stays free to move.

One template edit, thirty live trips

The same failure at a larger blast radius. A saved composition is edited to correct an error — a wrong hotel, a missing meal — and the edit propagates into trips already quoted, already approved, sometimes operating. The tell is 30 unrelated trips changing identically on 1 day, which no in-place edit by a single consultant produces.

The supplier rewrote the product and your proposal followed

If any inventory is imported — bedbank content, activity descriptions, ticketing product names — the supplier can rewrite text you have already sent. There is no internal user to attribute it to, and the sync run that overwrote the text usually keeps no record of what it replaced. That makes it the hardest of the 4 to detect after the fact.

  • Stage imported content and promote it into proposal-facing fields on an explicit action, rather than writing it straight through.
  • Keep the previous value on every import for fields that appear in client documents. 2 generations answers nearly every question of this kind.
  • Stamp each imported record with the run identifier that last wrote it, so a wording change traces to a dated job rather than to a person who does not exist.

The minimum audit record, field by field

Full event sourcing is not required and rarely earns its place in an itinerary builder. What is required is 1 row per change, written by the same code path that performs the change, carrying 5 fields plus the revision it belonged to.

FieldWhat it holdsWhy it is not optional
actorThe user, or the named integration or scheduled job, that made the change'The system' is not an answer. An import run needs an identity exactly as a person does.
changed_atServer timestamp with offset, in UTC, not a date2 edits on the same day are ordinary, and the order between them is usually the whole question
targetA stable path: trip, revision, day offset, component reference, field nameA free-text note about what changed cannot be diffed, filtered or replayed later
old_value and new_valueBoth sides, serialised, for the single field that movedStoring only the new value gives you a list of edits you cannot run backwards
revision_idThe client-visible revision current at the moment of the changeIt is what turns a list of edits into 'this happened after they approved'
The smallest audit row that makes the question answerable

A version history that stores only the current state is not a history. It is one row that has been overwritten a number of times nobody counted.

Two additions make it genuinely useful. Freeze a render at every client-visible revision — a stored file, not a re-generation — because a re-generation reads today's data and reproduces the fault you are diagnosing, the subject of the exported proposal and the live itinerary disagreeing. And keep version, variant and revision distinct in the schema; collapsing the 3 is why so many builders can show a change list but cannot say which changes a client ever saw, which version, variant, revision takes apart.

What the reconstruction will not recover, and what to tell the client

Be plain about the limit. Without stored revisions you can recover what was sent, from the sent document, and roughly when the divergence began, from updated timestamps. You cannot recover who changed it, why, or the intermediate states. If 3 people edited the trip across 2 weeks, the database holds the result of all 3 and evidence of none.

Operationally: honour the sent document as agreed scope for this trip, then fix the model. It matters beyond the itinerary too — a client disputing a charge compares the document they hold against a line on a card statement, and the 2 have to be reconcilable, the point of whose name appears on the traveller's statement. Where they read the detail matters as well, since identical data reads differently by layout, as how day-by-day itineraries were laid out in our travel concepts shows.

Retrofitting revisions and an audit table into a live itinerary builder is contained work — a single write path, a snapshot table, a frozen render and a consultant-facing diff view — and it is the shape of narrow internal build we scope under internal tools and ops. The rest of this silo sits under itinerary building and the trip data model, inside our travel and tourism work.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

How do you track itinerary changes after a client has approved a proposal?

Freeze a revision at the moment of sending and write 1 audit row per change afterwards. The revision stores client-visible fields as copies plus a rendered document; the audit row records actor, timestamp, field path, old value, new value and the current revision. Copying rather than pointing is what makes the snapshot survive later edits to the product library.

The itinerary changed but nobody edited it. How is that possible?

Because the trip is almost certainly storing pointers to components and templates that other people can edit. A product manager rewriting a tour description, a template correction pushed into live trips, or an overnight inventory import from a supplier will each change what a proposal says without anyone opening the trip. Check the component's updated timestamp before assuming a person edited the itinerary.

Is a full version history worth building, or is an audit log enough?

An audit log plus a frozen snapshot at each client-visible revision answers almost every real question and costs far less than event sourcing. Event sourcing earns its place when you need to replay a trip to any arbitrary point in time. Answering 'what did they approve, and what moved since' does not require that.

Should the client be able to see the change history themselves?

Show differences between client-visible revisions, never the internal edit log. A client benefits from a clear list of what moved between revision 3 and revision 4. They do not benefit from watching a consultant try 4 hotels before settling on one. Keep the internal log complete and derive the client-facing view from revision boundaries only.

  • itinerary builder
  • audit trail
  • versioning
  • trip data model
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Read next

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation