The deadline that costs money isn't the expiry date.
A grocery retailer was writing off fresh food not because it expired, but because nobody told the supplier in time. We built the system that turns an expiry date into a report-by deadline — and chases it.
- IndustryGrocery retail
- Project typeBuild · Product Development
- Team6 people
- Duration~4 months
A regional grocery chain with fresh food on every shelf.
Several stores in the region. A big part of every shelf is fresh — dairy, produce, meat, anything with a short shelf life. On a good day, most of it moves. What doesn't is where the problem starts.
- Multiple stores, hundreds to low-thousands of live batches at any moment
- Several producers, each with its own return terms
- Near-expiry stock tracked in spreadsheets
- Return deadlines chased in Viber/WhatsApp groups

The deadline that costs money is a derived date.
Food that won't sell before it expires can be returned to the supplier — but only inside a contractual window. Miss it, and the store eats the loss.
That window is a derived date: expiry minus the supplier's lead time, different for every producer, on every batch. On a spreadsheet, it's invisible until it has already passed — and by then the goods are a write-off, not a return.
- The ERP (Datalab PANTHEON) stays the system of record — the tool integrates with it read-only, with write-back deliberately deferred.
- Return terms differ per producer and change as contracts are renegotiated — they can't be baked into code.
- Access is scoped per store: staff see only their own store's batches; management sees the whole chain.
- Daily users are store and procurement managers on tablets and phones — the workflow has to read like a traffic light, not a report.
How the supplier returns deadline engine works.
Three mechanisms doing the work: one canonical rule that turns expiry into a report-by date, an engine that pushes people at the right time, and a read-only boundary that keeps risk out of the ERP.

Two producers, the same carton expiry — different report-by dates. That derived, per-producer date is the whole game.
One canonical rule
report-by = expiry − lead time, computed per batch and driven through a single state machine.
An engine that pushes
Scheduled jobs run the ERP sync and send a 07:00 digest plus in-app alerts — idempotent and per-batch deduplicated.
Read-only boundary
Stock is pulled read-only from the ERP. Producer terms live as data — a new contract is a config edit, not a deploy.
The compute is tested where it hurts
The one rule that costs money — report-by = expiry − producer lead time — is covered by unit and property tests across the awkward edges: batches with the window already passed, missing lead times, month and time-zone boundaries. Idempotency and per-batch/day dedup are verified against fixed fixtures, so a replay provably can't double-send or skip a deadline.
The run is observable — built to fail loudly, not silently
Health checks and Application Insights watch every scheduled run. A failed or missed job, a stalled ERP sync, or a dependency outage raises an alert to us before the 07:00 window closes — so the silent failure a low-code stack can't see is exactly the one we get paged on. Because the recompute is idempotent, recovery is usually a safe re-run rather than a scramble.
Change without redeploys, plus a stabilisation tail
Producer terms live as data, so the most common business change — a renegotiated lead time or settlement type — is a config edit, not a release. After v1.0 we ran a stabilisation phase and stayed on for monitoring and support, tightening alerts and edge-case handling as real volume moved through.



Scope decisions we made on purpose, not by omission.
- Write-back into the ERP
The tool records and issues the reporting, but posting the final credit note or return back into PANTHEON stays with the ERP's own process for now. A conscious choice not to hold write credentials to the system of record until it's clearly warranted — and to stop the tool from becoming a second source of truth for stock. Automated write-back is the planned next phase.
- A native mobile app
The workflow runs as a responsive web app on the tablets and phones already in the store, rather than a separate native app to build and maintain. When the store already has the device, the browser is the shortest path to production.
- Direct supplier-side integration
Producers are notified by e-mail because that's what they act on today. A direct API/EDI handshake with suppliers is a later option, not a v1 requirement — the deadline engine is the same either way; the channel is the incremental change.
Six people, one lead, a stack you already know.
The same team that scoped the model in Discovery shipped it into production — and still supports it. The stack is right-sized for a production line-of-business system: mainstream tools, no lock-in, no surprises.
Team
- Engineering leadFull-timeArchitecture, delivery, code review
- Backend engineerFull-time.NET APIs, PANTHEON sync, background jobs
- Frontend engineerFull-timeReact app, dashboards, forms
- QA engineerFull-timeTest plans, regression, release sign-off
- DesignPart-timeProduct surfaces, prototype-to-production fidelity
- Product & DevOpsPart-timeDiscovery, releases, Azure infrastructure
Stack
What changed on the floor.
The deadline stopped being invisible — everything below follows from that.
The deadline that mattered became visible — each store sees what must be reported today, instead of reconstructing it from a spreadsheet after the loss.
Supplier reporting moved off Viber/WhatsApp into one workflow with a timestamped, append-only audit trail — down to the e-mail provider's delivery confirmation, not just that a notice was sent.
Management got a cross-store view of where risk and losses concentrate, by store and by product.
What the client says the team used to miss routinely became the default — reporting to producers on time went from the exception to the norm.
It all used to hang in Viber groups and Excel, and something always slipped through. Now in the morning I just see what has to be reported today — I don't have to keep it in my head anymore.

This is you if…
- You've got a deadline that's computed, not scheduled.
It's derived per counterparty — an SLA window, a contractual notice, a warranty clock — and it moves whenever the underlying contract does. Different for every supplier, on every batch, on every ticket.
- The deadline lives outside your system of record.
In a spreadsheet with formulas two people fully understand, or in a chat group where somebody @-mentions when something's due. Every slip starts the same way — nobody looked in time.
- When one slips, both the loss and the audit trail hurt.
A written-off shipment, a missed credit note, an SLA penalty. And when the counterparty pushes back on a disputed one, you're reconstructing from an inbox instead of producing a timestamped record.
If any of that maps to your operation — that's the shape of deadline this system was built to catch. And the audit trail it was built to keep.
Have a deadline like this hiding in a spreadsheet?
A derived, per-counterparty date that quietly costs money whenever it slips — fresh food nearing its expiry, warranty windows, SLA clocks, contractual notice periods. It's exactly the kind of problem we take end to end: a Discovery prototype to lock the model, then a production system that owns the outcome.