Skip to content

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).

  • 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.

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.

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.

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).

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
  • Inventory - GRN receiving lines back against LpoLine
  • Pdfs - LPO document export