Let's talk
data-modelling

Every booking scheme lost its last day

A booking window ran to 5 September. On that day a dealer placing a first order was told it had closed. Every scheme the company had run had lost its last day.

·

You run a seasonal booking scheme for one region: 325 dealers, 141 products, open from 26 August to 5 September. A dealer who has been meaning to book all week finally sits down on the fifth, opens the app, and is told the scheme has closed. A dealer down the road who booked last week opens his existing order, changes a quantity, and it goes through.

The window was still open. Both dealers were inside it. Only one of them could act.

Every booking scheme this company had ever run had lost its last day in the same way, and nobody had reported it, because a dealer told the scheme is closed assumes he has missed it. On this scheme it would have cost a real trading day.

What was actually going on

The scheme’s closing date is stored as a date, with no time on it. When the app asked “is it still open”, the code compared that date against the current moment, written out with its time. Compared as text, “5 September” is shorter than “5 September, nine in the morning”, and a shorter string that matches the start of a longer one counts as less than it. So on the closing day, the end date read as already past.

Only the path for a new order made that comparison. Editing an existing order took a different path, which is why one dealer sailed through and the other was refused.

It was found during the production release, not by a complaint. The verification suite for the release exercised the closing day, because the closing day is the case that matters on a scheme.

What nearly went wrong as well

Two other things surfaced in the same release, and they belong in the story because they are the kind of thing an owner never sees.

The seed data for the scheme, the products, rates and terms, was applied by a script with a list of steps. One step was missing from the list. A run that followed the runbook to the letter would have put the scheme live with placeholder labels and a closing date 25 days after the client’s, the lifting window rather than the booking window. It was caught because the test system still carried a wrong link to the terms, and that traced to an insert that quietly does nothing once the row already exists.

And two mistakes of our own. The runbook said a permission grant had to land in the same window as the code, and the code was deployed first anyway; for about twenty minutes, mobile reps could not modify orders until the grant was run. The runbook now says grant before deploy, because the grant is harmless on the old code and the gap is not. Then the release check counted dealers in scope by the staff-to-depot mapping and reported one dealer instead of 325; dealers attach to a depot by a different column, the one the app actually reads. A check has to ask the same question the code asks, or it raises a false alarm on a correct release.

What we changed

The date is now compared as a date. The release order was rewritten: schema before code, because the code declares the new columns and reads them all, so code first would turn every booking read into an error; permissions before deploy; the seed applied dark, then enabled.

The scheme went live and was verified on production with real dealers: dealers in the two target depots see it, a dealer elsewhere does not, the catalogue returns 141 products, dealers outside the region still see their own orders on the older scheme, and the 105 existing orders on that older scheme are intact with all 4,182 line items. Twenty-two of twenty-two automated checks passed.

The scheme itself was built as a subset of the real multi-scheme design rather than a one-off: every switch is a column in the database, so the whole thing can be turned off without another app release.

What it did not fix

The last days already lost on earlier schemes are gone; there is no record of the dealers who were turned away. Two products on the client’s list were dropped by their decision, so the scheme runs on 141 rather than 143. And the release had to be verified twice because of our own two mistakes, which is the honest cost of doing it by runbook rather than by memory.

The mechanism

A date-only column comes back from the database as text. Compared against a full timestamp as text, the date is a prefix of the timestamp and so sorts before it. The fix is one line; the finding is that the closing day of a window is the day to test.

Where this ends up

Sazinga Field runs booking schemes with a dated window, and the closing day is now the first case in the release checks rather than the last one anybody thinks of.

This came out of building Sazinga Field

Orders, stock, dispatch and the people on the road, in one place. The problem above is one we met while building it, and what we did about it is in the product.

If you run something like this, tell us how it works today and we will tell you what it would take to move.