SDLC Corp Procure-to-stock, traceability and replenishment in one system

Every item, lot and serial on one ledger.

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

stockyard.app / inventory / current-stock

Current stock

Central Warehouse · period 2026-08 OPEN · balances from ledger

Search item or code
Items on hand
1,284
across 6 locations
Below reorder point
17
replenishment suggested
Awaiting receipt
9
POs partially received
Lots near expiry
4
within 30 days
Balances by item and location available = on hand − reserved − blocked
ItemLocationOn handReservedAvailable
Handheld barcode scanner
ITM-000412 SERIAL
Central Warehouse641252
Nitrile gloves, box of 100
ITM-000178 LOT
Central Warehouse3400328
Reflective safety jacket
ITM-000093 NONE
Regional DC51240472
GPS tracker unit
ITM-000265 SERIAL
Central Warehouse28622
Ledger buckets ITM-000412
ON_HAND64
RESERVED12
BLOCKED0
IN_TRANSIT8

available = 52
derived, never stored

Interface shown with demonstration data.

One system in place of a paper trail

Bin cards Manual issue slips Gate register Stock spreadsheets Detached invoice files Stockyard
Two halves, one record

Buying material and controlling stock are the same story.

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.

Half one

Material acquisition

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.

  • Requirement, requisition and funds check with a budget lock
  • RFQ, invited suppliers and side-by-side quotation comparison
  • Split award across suppliers, partial receipt, short close
  • Server-generated document numbers, never client-supplied
PO-2026-00184 · 3 linesPARTIALLY_RECEIVED
Ordered 240 Accepted 180 Pending 60
Half two

Inventory control

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.

  • Sixteen transaction types over four ledger buckets
  • Lot, serial and expiry control enforced at posting time
  • Weighted average cost updated as goods are received
  • Balances rebuildable from the ledger, and checked against it
TXN-2026-01937 · GOODS_RECEIPTPOSTED
Validate Lock balances Commit
Requirement to stock

Eight documents, one unbroken chain.

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.

01

Requirement

A department states what it needs and why. One requirement can be met over several requisitions.

02

Requisition

Approved against a budget line. Approval places a commitment on the funds and holds it.

03

RFQ

Floated to invited suppliers. An unanswered RFQ can be re-floated against the same requisition line.

04

Quotations

Every invited supplier quotes the same line, and the responses compare cleanly side by side.

05

Purchase order

Awarded from a quotation line, whole or split across suppliers, and approved before it is sent.

06

Gate entry

The delivery is logged at the gate before anything is counted as received.

07

Goods receipt

Inspected quantity splits into accepted and rejected. Rejected quantity never enters stock.

08

Stock posting

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.

The stock ledger

A stock figure you can prove.

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.

Four buckets, one identity

Every ledger row moves quantity in exactly one of ON_HAND, RESERVED, BLOCKED or IN_TRANSIT, keyed by item, location and lot.

Posting twice is safe

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.

Nothing is edited after posting

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.

Reconciliation is a command

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.

InventoryPostingService single writer
01 validate document, user, permission
02 validate accounting period is OPEN
03 validate tracking: NONE / LOT / SERIAL
04 validate lot status and expiry
05 validate available quantity
06 check idempotencyKey
07 BEGIN · lock balance rows FOR UPDATE
08 write transaction header and lines
09 write allocation rows
10 write bucketed ledger entries
11 update balances and serial history
12 update weighted average cost
13 write audit log · mark document POSTED
14 COMMIT
No controller, screen or job writes a balance directly.

For every item, location, lot and bucket: SUM(ledger.qtyDelta) = balance.bucket. All four buckets are rebuildable from the ledger alone.

SN-SCN-0004127 Handheld barcode scanner · ITM-000412
ISSUED
Received and made available GR-2026-00311 · Central Warehouse · 12 Mar 2026
Reserved for the night-shift picking line RSV-2026-00042 · 19 Mar 2026
Dispatched in transit to Regional DC TRF-2026-00087 · counted in IN_TRANSIT, not on hand · 24 Mar 2026
Received at Regional DC TRF-2026-00087 · 26 Mar 2026
Service recorded, returned to available MNT-2026-00019 · battery module replaced · 02 Jun 2026
Issued to Inbound Dock 3 ISS-2026-00524 · 11 Jul 2026
Lots and serials

Ask where one unit has been, and get an answer.

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.

One serial, one state

Available, reserved, issued, in transit, under maintenance, damaged or scrapped. A serial never carries a quantity greater than one.

In transit is not on hand

Dispatched units sit at a transit location and count only towards IN_TRANSIT, so a transfer can never be counted twice on the way.

Expiry has real consequences

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.

Service history follows the unit

Maintenance alerts and service records attach to the serial itself, so a unit's repair history survives every transfer between sites.

Modules

The whole store, in inventory language.

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.

Masters

Items, groups, catalogues, suppliers, departments and sections, warehouses and locations, units of measure, budgets.

/masters/items · /masters/suppliers

Material acquisition

Requirements, requisitions, RFQs, supplier quotations, quotation comparison and purchase orders.

/acquisition/purchase-orders

Receiving

Gate entries, inspection, goods receipts with lot allocation and receipt serials, supplier invoices.

/receiving/goods-receipts

Inventory operations

Current stock, opening stock, the stock ledger, issues, returns, reservations and adjustments.

/inventory/current-stock · /inventory/stock-ledger

Lots and serials

Lot registry with expiry state, serial generation, serial status and full movement history.

/traceability/lots · /traceability/serials

Transfers

Direct transfers posted in one step, and in-transit transfers dispatched and received separately.

/transfers

Planning

Safety stock, reorder points, replenishment counts, forecast rules and demand history from posted movements.

/planning/replenishment

Maintenance

Maintenance alerts and service records linked to the serialised unit they were carried out on.

/maintenance

Payment support

Funds availability, budget commitments, payment requests and approvals, with payment advice output.

/payments

Reports

Stock reports, pending transactions, on-hand with expiry, material transactions, movement statistics, inbound summary.

/reports/movement-statistics

Approvals

One shared approval engine used by requisitions, purchase orders, receipts, transfers, adjustments and payments.

/approvals

Administration

Alerts, accounting periods, audit trail, job monitor, ledger reconciliation, users and roles.

/administration/audit-trail
Use cases

Wherever stock gets counted twice.

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.

Manufacturing & spares

Raw material and spare parts issued against work orders, with lot traceability back to the supplier batch.

Distribution & 3PL

Multi-warehouse balances, in-transit transfers between sites, and reorder points that reflect real demand history.

Field service & assets

Serialised equipment issued to technicians and vehicles, with service history that follows the unit.

Healthcare & lab supplies

Expiry-controlled consumables where near-expiry and expired stock have to be blocked rather than quietly listed.

Campus & facilities

Departmental requisitions, central stores, and issues to sections that need to be attributable afterwards.

Regulated procurement

RFQ, comparison and approval trails where the buying decision has to be defensible line by line.

Who uses it

Four people, four screens, one record.

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.

Storekeeper

inventory.post

Receives deliveries, issues material against demand, records returns and counts what is on the shelf.

  • Gate entry and goods receipt
  • Issues, returns, reservations
  • Blocked reasons shown in plain words

Procurement

acquisition.manage

Turns a stated requirement into a purchase order and follows it through to full receipt.

  • RFQ and quotation comparison
  • Split award and short close
  • Pending quantity per PO line

Approver

approval.act

Sees one queue of everything waiting, with the document, its lines and its history in view.

  • Approve or reject with remarks
  • Previous and resulting status recorded
  • Every step kept for audit

Administrator

admin.manage

Runs the masters, opens and closes accounting periods, and answers to auditors.

  • Users, roles and permissions
  • Period close blocks back-dated postings
  • Ledger reconciliation and job monitor
Controls

The parts an auditor asks about first.

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.

Role-based access

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.

One approval engine

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.

Audit trail

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.

Accounting periods

Postings are validated against an open period on calendar dates, so a closed month cannot be reopened by back-dating a receipt.

Constraints in the database

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.

Errors that say why

Not "something went wrong", but which item, which location, how much was asked for, how much is available and what is blocking it.

Scope

A complete inventory platform, not a stock table with a form on it.

94
database entities
314
REST endpoints
65
application screens
20
backend modules
16
stock transaction types
4
reconciled ledger buckets
How it is built

A modular monolith, deliberately.

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.

Runs where your policy says it has to

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.

Exact numbers, never floats

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.

Built for a store, not a browser demo

Medium information density, searchable selects for item, supplier and location, dependent fields, and a visible reason whenever an action is unavailable.

LayerTechnology
FrontendReact · Vite · TypeScript (strict) · Ant Design · TanStack Query · React Hook Form · Zod · Recharts
BackendNode.js · Express · TypeScript (strict) · Zod · JWT
DatabasePostgreSQL with Prisma — the only datastore, no secondary cache of stock
ContractsOne shared package — enums, schemas, API envelope and the decimal codec, defined once
DocumentsValidated attachments and server-generated document numbers from a number sequence
OperationsLedger 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.

Questions

What buyers ask first.

What makes the stock figure on screen trustworthy?

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.

What happens if a receipt is submitted twice?

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.

Can a posted document be corrected?

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.

How are serialised assets counted while they are being transferred?

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.

Does receiving goods spend the budget?

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.

How is average cost calculated, and can it be overridden?

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.

Where can it be deployed?

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.

How is it licensed and priced?

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.

Let’s Talk About Your Product

Get expert guidance on scope, architecture, timelines, and delivery approach so you can move forward with confidence.

What happens next?