Skip to content

Inventory management for a Laravel shop: warehouses, purchase orders and shipments ​

A shop that sells from a real shelf asks questions one number per product cannot answer. How many of these are promised to orders that have not shipped yet? Which of the two warehouses has them? What did the last batch cost, and when does it expire? What should we buy this week, from whom, and has it already been ordered? Which parcel did the customer's second box go in?

WooCommerce keeps a stock quantity on the product, and the rest lives in a spreadsheet, in someone's head, or in a second system, Odoo or an ERP, that has to be told about every order. A Laravel rebuild usually inherits the same single number. kitloom adds three modules on top of its shop that answer those questions where the orders are, from the admin, and let an AI agent answer them too.

Ask the agent ​

With kitloom/ai connected, an agent works on the stock with the rights of the user whose key it holds:

  • "How many Medjool dates do we have, and how many are reserved?" (inventory_levels)
  • "We found a case of almonds behind the pallets. Add twelve, reason found." (inventory_adjust)
  • "What should we reorder this week?" (purchasing_replenishment)
  • "Draft an order to Valley Nut Growers for forty pistachios and confirm it." (purchasing_order_create, purchasing_order_confirm)
  • "Ship order 1042 with UPS, tracking 1Z999AA10123456784." (shipments_create)

Every tool calls the same service the admin's button does, with the agent's user as the actor, so each move in the ledger says who made it. Nothing is deleted: a count or an adjustment corrects a number, a draft is cancelled, a parcel that came back is marked returned. Emailing a purchase order to the supplier stays with a person in the admin.

A ledger, not a number ​

kitloom/inventory keeps stock the way a warehouse does. There are locations, a warehouse and the shelves in it, a second store, a van, and three virtual ones for the suppliers, the customers and the adjustments. Every unit that changes hands is a move from one location to another, and the moves hang on transfers: a receipt from a supplier, a delivery to a customer, an internal move, an adjustment from a count. What a location holds is the sum of its moves, never a number someone typed, and the ledger can be rebuilt from them.

The shop's orders go through it without anyone noticing:

  • An order placed reserves its units: they stay on the shelf and are nobody else's. Paying changes nothing. The order shipped, or completed, moves them to the customers' location. Cancelled, it lets them go, and an order cancelled after it shipped takes them back on a receipt. A pending order that is never paid holds its units for as long as the shop's Hold stock says.
  • The product's stock in the catalogue is what is available online: on hand less reserved, over the warehouses that sell online. It is written into the product after every move, so the storefront, the feeds and an export read it as before, and on the product form the number is read-only, changed here with a reason and a person behind it.
  • Backorders stay the product's choice. A product that allows them is sold past its stock, and the next receipt fills the reservation.

A product becomes an item of the warehouse the first time the warehouse hears of it, with the quantity it had as the opening count, or all at once with inventory:import-products. Nothing is lost on the way, and a shop that does not install the module keeps its one number per product, as it always did.

Counts, cost and reorder points ​

Each item carries a unit, so a count can say twelve and a half kilos, a barcode, a reorder point with the quantity to buy, and its average cost, moved by every receipt at a price. Inventory → Counts takes a location, lists what the records say and the differences become one adjustment with a line per item. Reports show on hand by location, the stock's value at average cost, and the items at or under their reorder point, counting what is already on its way.

Lots that expire, boxes and labels ​

Food, cosmetics, medicine: what expires is bought in batches, and the batch is on the label. An item tracked by lot takes a lot number and an expiry day on every receipt line. An order takes the lot that expires first; a count goes lot by lot; a report and a daily notice list the lots that expire within the days you set.

A packaging is the box the item comes in, Case of 24, with the barcode printed on the case: scanning it on a receipt or a count stands for the whole box. Print labels makes a PDF of Code 128 labels with the SKU and the name, which any scanner reads back.

Buying: suppliers and purchase orders ​

kitloom/purchasing keeps the suppliers you buy from, the people there, and what each sells you at what price, in what smallest quantity, with what lead time. A purchase order is drafted with its lines, emailed to the supplier as a PDF, confirmed, which readies a receipt in the warehouse so the goods count as incoming, and received when they come, all at once or a box at a time, at the order's price. A delivery that came short leaves the rest expected on a new receipt.

Replenishment lists every item at or under its reorder point, with what is on its way subtracted, how many to order, and the supplier it is bought from at their price. Tick the lines and one draft per supplier is made. Every morning the same list reaches you as one notice, with kitloom/notifications.

Shipping: parcels with a tracking number ​

kitloom/shipments ships an order in one parcel or several. From the order's page, Ship lists the lines still to ship, takes all of them or some, the carrier and the tracking number; the carrier's tracking page fills in from the number. Shipping a draft moves the units out of the warehouse, writes a note on the order, emails the customer the tracking link, texts it when the site texts (kitloom/text-messages), and completes the order when its last unit has left. The customer sees every parcel on the order in their account, in every shop theme, with the tracking number linked to the carrier's page.

Ten common carriers come with it, Canada Post, UPS, FedEx, DHL, USPS, Royal Mail, DPD, GLS, Nova Poshta and Ukrposhta; your own is a name and an address with {number} in it. The module works on a shop without the warehouse too: the units then leave the shop's own stock number.

For a Laravel developer ​

The three modules are Laravel packages with tables of their own, Eloquent models you can swap in the ModelRegistry, migrations, events (PurchaseOrderConfirmed, ShipmentShipped, …), queued notifications and both admins, Filament and Nova. They meet the shop through two small contracts, so nothing in the shop names them:

  • Kitloom\Shop\Contracts\StockLedger — reserve, release, fulfil, moveLine. The shop binds its own ledger over the product's stock number; the warehouse binds its own when installed.
  • Kitloom\Shop\Contracts\OrderExtra — a block the customer sees on their order, which the themes draw without knowing what it is. Shipments is one.
php
$transfers = app(\Kitloom\Inventory\Transfers::class);
$receipt = $transfers->receive($warehouse, [
    ['item' => $item, 'quantity' => 24, 'unit_cost' => 3.10, 'lot_number' => 'L-2411', 'expires_at' => '2027-03-01'],
], ['note' => 'Delivery of 2 November'], $user);

$shipment = app(\Kitloom\Shipments\Shipments::class)->create($order, $lines, ['carrier_id' => $ups->id, 'tracking_number' => '1Z999AA10123456784'], $user);
app(\Kitloom\Shipments\Shipments::class)->ship($shipment, $user);

Quantities are decimals with three places, dates are UTC, every write takes the acting user, and the ledger is the only writer of the levels: inventory:rebuild --check compares the two, and kitloom/health runs the same check. With kitloom/spreadsheets the items, levels, moves, suppliers and price lists go out as CSV or Excel and come back in, and a count sheet comes in as adjustments. kitloom/webhooks tells other systems about inventory.level.changed, purchase_order.received and shipment.shipped.

Watch it ​

Items, locations and an adjustment
Receipts and deliveries: how stock moves
Counting a shelf, and the reports
Lots that expire, boxes and labels
Suppliers and their prices
A purchase order, from draft to received
What to reorder, in one click
Ship an order with a tracking number
Carriers, tracking links and the settings

Install it ​

The modules come with kitloom, for either admin, and each is optional: the warehouse alone, the warehouse with purchasing, shipments with or without the warehouse. On a Filament site with the shop:

bash
composer require kitloom/inventory kitloom/inventory-filament kitloom/purchasing kitloom/purchasing-filament kitloom/shipments kitloom/shipments-filament
php artisan migrate
php artisan inventory:import-products

On a Nova site the admin packages end in -nova. The last command makes an item of every product that manages its stock, with its quantity as the opening count; a shop that sold nothing yet can skip it. Every detail is in the guides: Inventory, Purchasing, Shipments.

Last updated:

kitloom — modules and themes for Laravel.
Contact · Pricing · Services · License · Refunds · Privacy · Community · YouTube · [email protected]