Stockyard carries material from the first stated requirement through purchase, inspection, receipt, issue and return — and keeps a single immutable stock ledger that every balance in the system can be rebuilt from.
One PostgreSQL source of truth Posted documents are immutable Permission-checked on the server
Central Warehouse · period 2026-08 OPEN · balances from ledger
| Item | Location | On hand | Reserved | Available |
|---|---|---|---|---|
| Handheld barcode scanner ITM-000412 SERIAL | Central Warehouse | 64 | 12 | 52 |
| Nitrile gloves, box of 100 ITM-000178 LOT | Central Warehouse | 340 | 0 | 328 |
| Reflective safety jacket ITM-000093 NONE | Regional DC | 512 | 40 | 472 |
| GPS tracker unit ITM-000265 SERIAL | Central Warehouse | 28 | 6 | 22 |
available = 52
derived, never stored
Interface shown with demonstration data.
One system in place of a paper trail
Most operations run procurement in one system and stock in another, then reconcile by hand. In Stockyard every downstream line holds a real foreign key to the line above it, so a quantity standing on a shelf can be traced back through its receipt, its purchase order, its quotation and the requisition that started it.
Capture a requirement, check funds before committing them, float an RFQ to invited suppliers, compare what comes back, and award a purchase order that stays linked to the quotation line it came from.
One posting service owns every change to stock. Receipts, issues, returns, transfers, reservations, blocks and adjustments all pass through it, and each one writes ledger rows inside a single database transaction.
Each step below is a real document with its own status, approval history and line items. The link between a step and the one before it is a foreign key in PostgreSQL, not a note in a remarks field — which is what makes a question like "where did this unit come from?" answerable by a join.
A department states what it needs and why. One requirement can be met over several requisitions.
Approved against a budget line. Approval places a commitment on the funds and holds it.
Floated to invited suppliers. An unanswered RFQ can be re-floated against the same requisition line.
Every invited supplier quotes the same line, and the responses compare cleanly side by side.
Awarded from a quotation line, whole or split across suppliers, and approved before it is sent.
The delivery is logged at the gate before anything is counted as received.
Inspected quantity splits into accepted and rejected. Rejected quantity never enters stock.
Accepted quantity is posted to lots and locations, and the item's average cost is recalculated.
A goods receipt on its own never consumes budget. The commitment raised at requisition approval
stays HELD through receipt and is only CONSUMED when the supplier
invoice is posted or the payment request is approved — so committed funds and spent funds are
never the same number by accident.
Cached balances are fast, but they are not the truth. In Stockyard the truth is an append-only ledger of bucket movements. Balances are a projection of it, and a command replays the ledger to prove the two still agree.
Every ledger row moves quantity in exactly one of ON_HAND, RESERVED, BLOCKED or IN_TRANSIT, keyed by item, location and lot.
Each posting carries a deterministic idempotency key. A double-click or a retried request returns the transaction that already exists instead of creating a second one.
A posted transaction is never changed or deleted. Corrections create a reversal with equal and opposite effects, and a transaction can be reversed at most once.
Rebuilding balances from the ledger is a routine operation, not a rescue. It diffs what it replays against what is cached and fails loudly on any mismatch.
For every item, location, lot and bucket: SUM(ledger.qtyDelta) = balance.bucket. All four buckets are rebuildable from
the ledger alone.
Consumables are tracked by lot and expiry. Equipment is tracked unit by unit, and every movement a unit makes is written to its own history as the stock posting happens — not reconstructed afterwards from timestamps.
Available, reserved, issued, in transit, under maintenance, damaged or scrapped. A serial never carries a quantity greater than one.
Dispatched units sit at a transit location and count only towards IN_TRANSIT, so a transfer can never be counted twice on the way.
Lots move from active to near-expiry to expired on calendar dates, and expired stock is blocked by a posting rather than quietly hidden from a report.
Maintenance alerts and service records attach to the serial itself, so a unit's repair history survives every transfer between sites.
Navigation says Items, Suppliers, Requisitions, Goods Receipts and Serial Numbers — the words a storekeeper already uses. Destinations that are not enabled for a role are visibly disabled rather than links that lead nowhere.
Items, groups, catalogues, suppliers, departments and sections, warehouses and locations, units of measure, budgets.
Requirements, requisitions, RFQs, supplier quotations, quotation comparison and purchase orders.
Gate entries, inspection, goods receipts with lot allocation and receipt serials, supplier invoices.
Current stock, opening stock, the stock ledger, issues, returns, reservations and adjustments.
Lot registry with expiry state, serial generation, serial status and full movement history.
Direct transfers posted in one step, and in-transit transfers dispatched and received separately.
Safety stock, reorder points, replenishment counts, forecast rules and demand history from posted movements.
Maintenance alerts and service records linked to the serialised unit they were carried out on.
Funds availability, budget commitments, payment requests and approvals, with payment advice output.
Stock reports, pending transactions, on-hand with expiry, material transactions, movement statistics, inbound summary.
One shared approval engine used by requisitions, purchase orders, receipts, transfers, adjustments and payments.
Alerts, accounting periods, audit trail, job monitor, ledger reconciliation, users and roles.
The pattern is always the same: material arrives against an order, sits somewhere under a name, moves when someone asks for it, and has to be accounted for at the end of the month. Stockyard is built for that pattern rather than one industry's version of it.
Raw material and spare parts issued against work orders, with lot traceability back to the supplier batch.
Multi-warehouse balances, in-transit transfers between sites, and reorder points that reflect real demand history.
Serialised equipment issued to technicians and vehicles, with service history that follows the unit.
Expiry-controlled consumables where near-expiry and expired stock have to be blocked rather than quietly listed.
Departmental requisitions, central stores, and issues to sections that need to be attributable afterwards.
RFQ, comparison and approval trails where the buying decision has to be defensible line by line.
A storekeeper should not need to understand a budget commitment, and an approver should not be able to post stock. Permissions are checked on the server for every write, so hiding a button is a convenience rather than the control.
Receives deliveries, issues material against demand, records returns and counts what is on the shelf.
Turns a stated requirement into a purchase order and follows it through to full receipt.
Sees one queue of everything waiting, with the document, its lines and its history in view.
Runs the masters, opens and closes accounting periods, and answers to auditors.
A stock system is only as good as what it refuses to let you do. These controls are enforced in the database and the service layer, where a determined user with a REST client cannot get around them.
Every private route requires authentication, and every write requires the matching permission. Object and organisation relationships are validated, so changing an ID in a request does not open another department's data.
Requisitions, purchase orders, goods receipts, transfers, adjustments, manual cost changes and payment requests all route through the same engine. No module keeps a private approval path.
Master changes, status transitions, approvals, stock postings, reversals, cost updates and period changes are recorded with old and new values. Application users cannot edit or delete audit records.
Postings are validated against an open period on calendar dates, so a closed month cannot be reopened by back-dating a receipt.
Non-negative buckets, inspected equals accepted plus rejected, received never above dispatched, unique document numbers and item codes, composite keys so a lot cannot attach to the wrong item.
Not "something went wrong", but which item, which location, how much was asked for, how much is available and what is blocking it.
One deployable application, four workspaces, and a shared contract package that owns every enum and API type so the browser and the server can never disagree about what a status means. No message brokers, no service mesh, nothing that makes a warehouse harder to run at 2am.
A Node process, a PostgreSQL database and static frontend assets. The same build deploys as managed cloud, into your own private tenancy, or fully on-premise.
Quantities, conversion factors and money use PostgreSQL numeric and cross the API through one shared decimal codec, so arithmetic on a value is never string concatenation.
Medium information density, searchable selects for item, supplier and location, dependent fields, and a visible reason whenever an action is unavailable.
| Layer | Technology |
|---|---|
| Frontend | React · Vite · TypeScript (strict) · Ant Design · TanStack Query · React Hook Form · Zod · Recharts |
| Backend | Node.js · Express · TypeScript (strict) · Zod · JWT |
| Database | PostgreSQL with Prisma — the only datastore, no secondary cache of stock |
| Contracts | One shared package — enums, schemas, API envelope and the decimal codec, defined once |
| Documents | Validated attachments and server-generated document numbers from a number sequence |
| Operations | Ledger rebuild and demand-history rebuild commands, job monitor, audit export |
Secrets stay out of the repository, passwords are hashed, tokens expire, uploads are size and type checked with sanitised filenames, and responses never carry a stack trace.
The number shown is a cached balance, but it is never the authority. Every movement writes an append-only ledger entry against one of four buckets, and a rebuild command replays those entries, compares the result with the cached balances and exits non-zero on any difference. If the two ever drift, the command says so rather than the store finding out at a physical count.
Nothing is posted twice. Each posting derives a deterministic idempotency key from its source document and operation, and that key is unique in the database. A repeated request — a double-click, a retry after a timeout — hits the constraint and returns the transaction that already exists.
Not by editing it. A posted document is terminal and its business fields cannot be mutated by any service. A correction creates a reversal transaction with equal and opposite bucket effects, linked back to the original, and a transaction can be reversed at most once. The original stays visible, which is exactly what an audit needs.
An in-transit transfer posts twice: a dispatch that moves quantity out of the source and into a transit location, and a receipt that moves it into the destination. Units in transit count only towards IN_TRANSIT and never towards on hand, so the same unit is not visible in two sites at once. Four separate mechanisms — a status guard, a unique key, a line constraint and a balance constraint — prevent the receipt being taken twice.
No. Approving a requisition places a commitment on the budget and holds it. That commitment stays held through the purchase order and the goods receipt, and is only consumed when the supplier invoice is posted or the payment request is approved. Cancellations, short closes and unused balances release the relevant amount back.
Weighted average cost is recalculated inside goods-receipt posting, in one central costing service, using the existing quantity and cost against the received quantity and cost. A manual correction is possible but governed: it needs an explicit permission, a mandatory reason, approval through the shared engine, and it records the old and new cost in the audit trail.
The same build runs as managed cloud, inside your own cloud tenancy, or fully on-premise including restricted environments. It needs a Node runtime, a PostgreSQL instance and a web server for the static frontend. There is no external service the application depends on to calculate a stock figure.
Pricing is scoped to your deployment shape, the modules you take, the number of sites and user volume, and it can be structured for a phased rollout. Our team prepares a written commercial proposal after a scoping call.