Skip to content

Manufacturing · Internal system

Naryado

A furniture plant where order, schedule and shipment travel under one number, and five systems already in place exchange data through the loop instead of spreadsheets.

Role
Analysis, backend, integrations
Timeline
8 months
Stack
Python · PostgreSQL · Redis

What changed

20 minutes
from a confirmed order to a line in the shop-floor schedule. It used to get there the following morning.
9 → 0
points where the same data was typed into a second system by hand.
0.4%
stock discrepancy at the monthly count, against 4–6% before the cutover.
1.5 days
a week that two people spent reconciling figures for the management report. It now builds itself out of the event log.

What did not change is the production lead time. It is set by shop load and board deliveries, not by record-keeping. The loop made the date predictable: a shipping date is given on the day the order is taken, and it holds. It did not make it shorter.

Before

A salesperson confirmed the order in the CRM and then retyped it into the shop-floor spreadsheet. The warehouse checked board and edging stock in its own program, accounting cut the paperwork, a dispatcher booked the truck on the carrier’s portal. Between the customer saying yes and a line appearing in the schedule sat a full shift — and anything urgent went by phone, past every spreadsheet, and was written into them after the fact.

  • One order carried four numbers: in the CRM, in the shop spreadsheet, on the delivery note and on the run sheet. Telling a customer where their order stood took three phone calls.
  • Twelve spreadsheets across six departments, and nine points where the same data was typed in a second time.
  • Once a week two people spent a day and a half reconciling figures for the management report, and found discrepancies every time — usually in stock.

What we built

  • One order number: intake, shop-floor schedule and shipment became a single record with its own history, instead of four rows in four places.

  • Five integrations: accounting, the warehouse, the CRM, the cutting system and the carrier’s portal exchange through a queue, each in its own protocol.

  • Reporting off the same data: the weekly management report and the morning discrepancy list are both built from the event log, with no manual reconciliation.

Outcome

The five systems stayed where they were, but an order no longer moves between them by hand: it runs from intake to dispatch under one number, and the loop does the exchanging. A salesperson can see a status without calling the shop, the weekly report builds itself out of the event log, and the argument about whose numbers are right ended along with the twelve spreadsheets.

The client

A contract furniture manufacturer: cabinets and casegoods built to other people’s drawings for retail chains, developers and showrooms. Two shops, around two hundred people, up to seventy orders a day — a hundred and twenty in the run-up to the autumn season.

An order is not a single item but a kit: board cut to size, edging, drilling, hardware, packing. About forty-five shipments leave the site each day, and some of them are assembled from several orders. The catalogue runs to forty thousand line items, from chipboard décors to screws.

The brief

What the client asked for
One system instead of the spreadsheets: replace the bookkeeping in Excel and, where possible, everything else, so that the data lives in one place and salespeople stop keeping files of their own.
What the brief turned into
Replacing the five systems was off the table: accounting is run by an outside contractor, the warehouse program is off-the-shelf, cutting plans are calculated in the CAD system. And the lost time was never in the typing — typing took minutes. It was lost in waiting: the schedule lived apart from the order, and no status could be had without a phone call. So the brief was rewritten: not one system, but one number and one status across five systems that stay alive.

Why the obvious route was closed

Five systems were already running, and each of them belonged to somebody else. Every one set a condition, and every condition is visible in the architecture.

Accounting (1C)
Maintained by an outside contractor on a quarterly release train. We ordered no changes to it: we read through OData and write exactly one document back — the goods issue, once the truck has left.
The warehouse program
Off-the-shelf, no API. Its only way out is a CSV dropped into a shared folder every fifteen minutes. So a stock figure inside the loop can be a quarter of an hour behind the truth — something to show rather than hide.
The cutting system
It calculates nesting maps and standard times, and nobody was going to let us touch it. We read a replica of its database and write nothing back.
The sales CRM
It emits webhooks, but it also lets a salesperson edit a confirmed order after the fact: the kit changes after the customer has already said yes. The loop had to accept the change rather than treat a confirmed order as frozen.
The carrier’s portal
SOAP, one request per second, maintenance windows at night. Calling it synchronously was never an option: a truck is not booked at the moment the dispatcher clicks the button.
The catalogue
The same board went by three names: the supplier’s article number in purchasing, an internal code in the warehouse, a description in accounting. Until those were matched, every consolidated report was an argument about whose numbers were right.

How the work ran

  1. 01

    Discovery

    1 month

    Twenty-three interviews, from salesperson to storeman, and the path of an order drawn out on a wall. That is where the twelve spreadsheets, the nine double-entry points and the four numbers per order were counted. The output was not a spec — it was the decision not to replace the five systems.

  2. 02

    The core

    6 weeks

    The order, schedule and shipment model, one number, a mapping table of external identifiers, an event log. PostgreSQL only, no integrations yet: the loop first learned to run on its own, on data typed in by hand.

  3. 03

    Integrations

    2 months

    Five connections, plus the catalogue matching. 86% of the line items matched automatically on article number; the remaining 5,600 took two of the client’s people three weeks, working to a rule and a list of exceptions.

  4. 04

    Schedule and shipping

    6 weeks

    Queues per work centre, order completeness, a shipment assembled from several orders, truck booking. This is where the statuses a salesperson can see appeared — the thing the whole project was for.

  5. 05

    Parallel run and cutover

    2 months

    For six weeks the loop and the spreadsheets recorded the same work side by side, and the differences were worked through every morning. The spreadsheets were switched off one at a time; the shop and logistics ones went last.

Technical decisions

Three things in the stack, each with one job here: Python does the talking to other people’s systems, PostgreSQL holds the data and answers for its consistency, Redis keeps the exchange asynchronous.

  • One number instead of four

    Order, schedule and shipment are rows in one database, tied by foreign keys and changed in one transaction: nothing ships against an order that never made it into the schedule. The five systems’ own identifiers sit alongside in a mapping table — that is what lets anyone say which 1C document a shop-floor line corresponds to.

  • A log, not a current state

    Every change is an event in an append-only PostgreSQL table: who, when, from which system, and what the value was before. Partitioned by month. It buys two answers that did not exist before — why a number is what it is, and at which point it diverged.

  • Five integrations, five different ways in

    OData for accounting, webhooks from the CRM, CSV off a shared folder for the warehouse, a database replica for the cutting system, SOAP for the carrier. Python earns its place for the usual reason: parsing someone else’s format and speaking someone else’s protocol is library work, and only the rules had to be written by us.

  • A queue instead of direct calls

    The exchange runs through Redis: every system gets its own worker and its own pace, retries back off, and an event key lives for forty-eight hours, so a redelivery cannot create a second shipment. The rate limits for the carrier’s portal and for 1C live there too — both fall over if you call them faster than they asked. So does the cache of the forty-thousand-item catalogue, which the warehouse hands over as a file.

  • Discrepancies are shown, not swallowed

    A stock figure carries the timestamp of the last warehouse export — up to fifteen minutes old, and visibly so. Anything that fails to reconcile during an exchange is not quietly corrected: it lands on a list the planner works through each morning. In the first six months that list was never empty, and for a loop stitched together from five systems that is the normal state, not a fault.

What stayed outside the scope

Some of the work was deliberately left out of the loop, and some of it waits for data that does not exist yet.

  • The interfaces. The client’s own frontend developer built them on top of our API; we answered for the data model, the service and the five integrations.

  • Machine loading. The loop shows a queue per work centre but does not sequence it: optimising that needs a year of actual operation times, and those have only been accumulating since launch.

  • Reporting operations at the machine. A work centre still signs off from the supervisor’s tablet; terminals at the machines are the next step.

  • Purchasing and supply. They stayed in accounting and in email: nobody wanted to pull them in before the loop had settled on orders.

In progress: lead times calculated from what actually happened rather than from standard times, using the first year in the log, and moving the weekly report to self-service so nobody has to come to us for it.

Contact

Send a description of the task

A reply with the scope, the timeline and a budget estimate comes within 24 hours.

The first call is 30 minutes, with no commitment on your side.