Purchasing
Location: dg_smart_pos_api/modules/Purchasing. Owns Local Purchase Orders (LPOs): the header/line records raised against a supplier before goods arrive. It does not do the actual receiving, that’s a GRN in Inventory (Modules\Inventory\Models\GrnLine), which links back to an LpoLine and increments its quantity_received.
Loaded as a tenant route module (Purchasing is in ModulesServiceProvider::TENANT_ROUTE_MODULES, so its routes are required directly from routes/tenant.php rather than auto-discovered by the generic module loader).
Data model
Section titled “Data model”LpoHeader- the purchase order: store, supplier (optional, see below), LPO number, order/expected dates, notes,line_count, soft-deletable, activity-logged (lpo_number,supplier_id,line_count,cancelled_at,fully_received_at).LpoLine- one ordered stock item:quantity_ordered(nullable, meaning “unrestricted”, see below),quantity_received,expected_buying_price.
Creating and updating an LPO
Section titled “Creating and updating an LPO”LpoController::store (CreateLpoRequest) requires a lines array (each needing stock_id, with optional quantity_ordered/expected_buying_price). supplier_id is nullable at the request-shape level but enforced in LpoService::create itself, which requires it unless the store’s allow_lpo_without_supplier system config is on, since that decision needs store config the request’s own validation rules can’t see. update (UpdateLpoRequest) goes through the same service.
Cancelling
Section titled “Cancelling”cancel (LpoService::cancel) records cancelled_at/cancelled_by. An LPO’s display status is derived on read, never stored directly: cancelled (if cancelled_at is set) beats fully_received (if fully_received_at is set) beats partially_received (any line with quantity_received > 0) beats open. Both LpoHeader::statusLabel() and LpoHeaderResource::computeDisplayStatus() implement this same precedence independently, statusLabel() as a 3-way collapse (no partial state) and the resource as the full 4-way one.
quantity_ordered semantics
Section titled “quantity_ordered semantics”Documented directly on LpoLine: null means quantity not yet specified. GRN receiving against that line then accepts any quantity, and the line is excluded from the header’s fully-received computation, meaning an LPO where every line stays like this can never auto-complete and must be cancelled manually. A concrete number is a hard cap: GRN receiving may not push quantity_received above it (v1: no over-receiving).
Route surface
Section titled “Route surface”All under auth:sanctum, prefix purchasing/stores/{store}, registered via modules/Purchasing/Routes/lpo.php (required once from routes/tenant.php):
| Method | Path | Controller method | Permission |
|---|---|---|---|
| GET | lpos |
LpoController::index |
lpo:view |
| POST | lpos |
LpoController::store |
lpo:create |
| GET | lpos/{lpo} |
LpoController::show |
lpo:view |
| PUT | lpos/{lpo} |
LpoController::update |
lpo:edit |
| POST | lpos/{lpo}/cancel |
LpoController::cancel |
lpo:cancel |