Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Crate

Self-hosted stock control where the cost of goods can be proven, line by line

Every balance is a queue of lots. Shipments consume it oldest-first, and each one remembers exactly which lots it was made of and what they cost.

Laravel PHP PostgreSQL License

Русский · Deutsch


Why

Most stock systems answer "how many" and shrug at "how much". They keep one average price per item, and the moment purchase prices drift, that average quietly stops matching reality — the margin on every order is off by a few percent and nobody can say why.

Crate keeps the layers. A receipt creates a lot with its own price; a shipment eats the queue from the oldest end and writes down which lots it took. Open any order and you see the arithmetic: four screws from the June lot at 333,42 ₽, one from late June, eight from July — and what the same line would have cost under a weighted average.

Stock as layers

Features

  • Real FIFO, not an average — lots are read with SELECT … FOR UPDATE and consumed in receipt order, so two simultaneous orders can never spend the same stock twice
  • A cost you can defend — every shipment line stores its layers: lot, date, quantity taken, purchase price. The screen also shows the weighted-average alternative and the difference between them
  • An immutable ledger — a posted document is never edited. A mistake is fixed by a reversal, which is itself a document and returns the goods to the very lots they came from
  • Reversal has rules — a receipt whose lot has already been shipped cannot be reversed: putting it back would be a lie about the cost of orders already made from it
  • Transfers keep age and price — moving stock between warehouses recreates the same layers on the other side instead of resetting the queue
  • Stock counts that mean something — the sheet snapshots the expected quantity when it opens, because the warehouse keeps working while somebody walks the aisles with a tablet. Surplus is received at the average cost of the remainder, shortage is written off through the queue: two different documents, not one with a sign
  • Integers everywhere — money in kopecks, quantity in thousandths of a unit. No floats in a system whose whole job is that the numbers add up
  • crate:verify — recomputes every balance from the ledger and compares it with the lots. A mismatch means a bug, and the nightly run finds it before an inventory does
  • Reorder alerts — an hourly job raises a signal when stock drops below the point and clears it by itself when it recovers

Cost breakdown of one order

Quick start

git clone https://github.com/dripips/crate.git
cd crate
composer install
npm install && npm run build

cp .env.example .env
php artisan key:generate
createdb crate                     # PostgreSQL 16+
php artisan migrate --seed         # four months of a hardware shop

php artisan serve

Open http://localhost:8000 and sign in as owner@example.com / crate (owner), sklad@example.com (storekeeper) or buh@example.com (viewer) — same password.

The seeder posts every document through the real ledger: 436 documents, 314 lots and about a thousand consumption rows in seven seconds. It is deterministic, so the numbers in the screenshots are the numbers you get.

Background work

php artisan queue:work     # reorder alerts
php artisan schedule:work  # hourly alerts, nightly ledger verification
php artisan crate:verify   # on demand

How the cost is computed

Question Answer
Which stock leaves first? The oldest lot by received_at, then by id
What did it cost? Σ (quantity taken from a lot × that lot's price), rounded to the kopeck once per layer, half up
What would the average have said? The weighted average of the remainder at the moment of posting, stored on the line
Where did the balance come from? Σ lots.quantity_left — and crate:verify proves it equals the ledger

Tests

php artisan test

40 tests: FIFO order and layer splitting, rounding of fractional quantities, rollback when stock is short, reversal rules in both directions, transfers preserving age and price, stock counts, reorder alerts, roles, and an end-to-end integrity check after every kind of movement.

They run on PostgreSQL rather than SQLite on purpose: row locking, bigint precision and the partial index behind the FIFO queue are exactly what would differ in production.

Dark theme

Docker

docker compose up --build
docker compose exec app php artisan migrate --seed

Stack

Laravel 13 · PHP 8.5 · PostgreSQL 18 · Blade · Tailwind 4 · Vite 8

License

MIT

About

Self-hosted stock control with real FIFO costing: layered lots, an immutable ledger and a cost breakdown per line

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages