An order desk that keeps the ERP in charge
An order-operations platform for one branch of a publicly listed commercial-equipment company in international trade, built with AI coding agents on a locked-down desktop. It went into daily production in September 2026.
Dealers send purchase orders as PDFs and emails. Before this platform, a customer-service rep read each one and typed it into the ERP by hand, then chased pick lists, shipping papers and invoices across several tools.
I directed AI coding agents to build a desk that reads the order, keeps a person in charge of every decision, and writes orders, pick lists and shipping documents into the ERP under that person's own account.
At a glance
| My role | I wrote the specifications, made the business and risk decisions, and verified every release. AI coding agents wrote the code. |
| Users | The branch's customer-service reps, supervisors and managers |
| Built with | Node.js, a static desk app, the ERP's service API (SAP Business One), read-only SQL, PowerShell, Windows Task Scheduler, DPAPI |
| Constraints | A shared Windows host, standard user, no admin rights, no database server, no inbound ports, a forced sign-out every Saturday night |
| Timeline | First work packet in September 2026, daily production from 28 September, more than 100 packets since |
How an order moves
- Arrives. Dropped on the queue, entered by hand, or mirrored from an order typed directly into the ERP. Email intake is built and proven on a test mailbox.
- Read twice. A rules engine and a language model read it independently. When they disagree on a quantity, quantity × unit price = line total decides. Anything unreadable is held for a person.
- Reviewed. A person checks price, stock, tax and freight line by line. Every open problem is one line in a single list, and that list is the send button until it is empty.
- Sent as a draft. One click saves a draft in the ERP and runs the ERP's own approval rules on it. Clear becomes an order and is read back. Held waits for accounting. Anything that cannot be judged is held.
- Shipped. Pick lists are created in the ERP and appear on the warehouse's handhelds. Manifests and bills of lading are printed from the order, and the carrier's tracking number is written to the delivery.
- Finished. An order is finished when every line is invoiced. One merged PDF per invoice is archived: invoice, confirmation, purchase order, freight quote, pick list and signed bill of lading.
The architecture
The constraints decided the shape. The story of how it got there covers each choice.
Decisions that shaped it
The ERP decides; the desk never approves
The ERP's approval rules are queries stored in the ERP, and they do not run for documents created through its API. So before every send the desk reads the stored query text and runs it exactly as written. Nothing is copied into the desk's code, and when accounting changes a rule, the desk follows it without a release. A rule that cannot be read or judged holds the order.
Before go-live the approval logic ran in shadow for 30 days: 93.4% agreement, 97% on orders a day old, and every difference traced to its cause.
Draft first, and every write read back
Every send starts as a draft. Every write is read back and compared field by field, because "201 Created" is a claim. Once, a call answered 204 and changed nothing; the read-back caught it.
What the desk never does
It never changes stock. It never deletes an ERP document. It never uses the ERP's cancel, which posts a reversal; a cancelled order is closed with a remark. It never approves or rejects anything.
Prices: what a person typed, or nothing
A price a person typed goes to the ERP with its line. A line nobody priced is sent without a price, so the ERP's own pricing applies. A missing price is never read as zero. A 30-day back-test reproduced 99% of ERP-priced lines.
Who sees what is decided on the server
Every route that reads or writes an order checks the person's role and warehouses, and refuses with a reason. Hiding a button is decoration. The bug that taught this hid for a week behind admin-only proofs.
One action, one way
Two doors for people, one logic behind each, and no third way. How the rule was turned into tests.
Ship it like it matters
One release per packet: every test suite green, a signed bundle, every desk updated within the hour, mine included. A version number names one build. The release gates refuse a release that would drop an earlier one, a failing smoke test, or a reused version. "Not released" is an allowed answer, and it has been the right one.
Going live without a big-bang switch
- The test company first, always. Production access follows people: the production list is written from the user list, never by hand.
- Before go-live, a read-only mirror brought every open ERP order onto the desk. A new order typed in the ERP showed up as a card in about 17 seconds.
- The rollout touched nobody's machine. Each desk's updater installed the release by itself.
Results
| What | Before → after |
|---|---|
| Order list at 50 desks, p95 | 24.7 s → 0.70 s |
| Opening the queue | 735 → 137 ms |
| Opening all orders | 1,054 → 122 ms |
| 50 desks starting at once, time to first queue | 11 min → 17 s |
| Slowest mirror query, p95 | 10.2 s → 26 ms |
| Background service time on the file share | −95% and −70% |
| Forged cross-site requests on the shared host | 546 synthetic + 47 live refused, 0 written |
| Damaged input files (fuzzing) | 1,900 tried, 0 crashes |
| Order confirmation against the ERP's own print | 161 text items, 503 paths, 0 differences |
| Whole purchase orders read exactly right | 18% → 64% (held-out 17% → 62%) |
| Automated checks at release | 13,388 in 138 suites → 29,202 |
My own estimates from daily use, not instrumented: 5 to 15 minutes saved per order in customer service, and about 80% of order-entry errors avoided.
What went wrong, and what it changed
- The proofs ran only as an administrator. A supervisor's board hung for a week. Every proof now runs as each role shape. Read it
- The theory was a slow file server. A canary said otherwise, and counters found the real cause. Read it
- A copy lost fields. An allow-list became a deny-list backed by measurements, and the release waited. Read it
- A status said "working" while the reader read nothing. Status now records the last call and its result.
- Free space was read on the wrong volume. A probe now measures a machine before anything is set up on it. Read it
Where it stands
The old workstation was shut down at the end of October, and the platform's background work is moving to a dedicated machine. The plan: measure that machine with the same probe on its first day, then set it up in one go.