Skip to content

Book 5, Chapter 19 — Fuel Management

Static-analysis documentation of the Bizwiz Laravel ERP. All claims are cited to file:line from the code at /Users/melaniefayne/Desktop/retail-pay/bizwiz. Nothing here was produced by running the application.


1. Purpose (plain language)

Fuel is one of the largest recurring operating costs for a distribution business that runs a delivery fleet, so Bizwiz treats it as a full sub-ledger of its own rather than a simple expense line. The Fuel Management module answers three practical questions: what did we authorise a vehicle to burn on a route, what did the driver actually pump at the station, and what did the fuel supplier actually bill us — and then it forces those three numbers to agree before any money is paid.

The lifecycle starts with a Fuel LPO (Local Purchase Order). A dispatcher creates an LPO that ties a vehicle, a route, a driver, and a fuelling station together and estimates how much fuel that trip should need (distance estimate × consumption rate). The driver fuels up; the entry moves from pending to fueled. Later, the fuel supplier sends an electronic statement (a spreadsheet of every card swipe at their pumps). Bizwiz imports that statement and tries to match each pump transaction to an LPO/fuel entry. Matched entries get verified; if the driver over-pumped versus the estimate, a driver fuel penalty can be raised. Verified entries are approved, and finally rolled up into a Fuel Invoice that posts to Accounts Payable so the supplier can be paid.

The workflow is deliberately split across two areas of the app. The operational half (creating LPOs, confirming fuelling, watching pending/confirmed/expired entries) lives under the Fleet navigation. The finance half (uploading supplier statements, reconciling/verifying, approving, invoicing) lives under the Logistics navigation. This mirrors an operational-vs-finance separation of duties: fleet staff raise and fuel LPOs; logistics/finance staff reconcile statements and cut invoices.


2. Users & roles (permissions)

Access is gated by per-model permission strings of the form <model>___<action>, checked either against $my_permissions[...] directly in the sidebar or via the can('action','model') helper. role_id == 1 (superadmin) bypasses all checks.

Permissions observed in the two nav files:

Permission slug Grants Nav Cite
fuel-suppliers___view Fuel Suppliers list; also gates Fuelling Stations in fleet nav both fleet.blade.php:783, 791; logistics.blade.php:208
fueling-stations___view Fuelling Stations list logistics logistics.blade.php:216
fuel-lpos___view Fuel LPOs list fleet fleet.blade.php:799
pending-fuel-lpos___view (+ can('create'/'view','pending-fuel-lpos')) Pending fuel entries; create LPO both fleet.blade.php:813; logistics.blade.php:90, 99
confirmed-fuel-lpos___view Confirmed fuel entries fleet fleet.blade.php:821
expired-fuel-lpos___view Expired fuel entries fleet fleet.blade.php:836
verified-fuel-lpos___view, approved-fuel-lpos___view Verified / approved LPO lists (commented out in logistics) logistics logistics.blade.php:122, 130
fuel-verification___view Statement upload page + verification listing logistics logistics.blade.php:146, 155
fuel-statements___view Statements listing logistics logistics.blade.php:163
fuel-statements___unknown Unknown/unmatched statements listing logistics logistics.blade.php:171
fuel-approval___view Fuel approval index logistics logistics.blade.php:181
unverified-fuel-approval___view Unverified fuel approval index logistics logistics.blade.php:188
fuel-invoices___view Fuel Invoices (AP hand-off) logistics logistics.blade.php:196

Note the accidental duplication/typos in the fleet nav: the "Approved" fuel-entries item is guarded by pending-fuel-lpos___view and links to fuel-lpos.pending, and its $model string is 'Approved-fuel-lpos' (capital A), so it will never highlight (fleet.blade.php:829-831). These are code-proven navigation bugs, not intended behaviour.


3. Processes (trigger → states → routes → outcome)

3.1 The status model

The lifecycle status is not stored on fuel_lpos. It lives on the fuel-entry row (table new_fuel_entries, later refactored / aliased as fuel_entries) in the column entry_status, whose allowed values come from the FuelEntryStatus enum (app/Enums/FuelEntryStatus.php:5-15):

pending → fueled_incomplete → fueled → verified → approved → processed, plus the off-ramp states expired and reactivated. A transient draft string is also referenced in the controller (FuelLPOController.php:384).

3.2 Fuel LPO lifecycle (Fleet nav)

  • Create — GET fuel-lpos/create → FuelLPOController@create; store POST fuel-lpos/store (fleet.php:39-40). An LPO ties branch/route/vehicle/deliveryman and an estimate (see fuel_lpos schema §4). A companion fuel entry is created and starts pending.
  • Fuel — the driver fuels; entry becomes fueled ('entry_status' => "fueled", FuelLPOController.php:253) or fueled_incomplete.
  • Pending list — GET fuel-purchase-orders/pending → pendingEntries, detail at .../pending/details/{id} (fleet.php:56-57).
  • Confirm — GET fuel-purchase-orders/confirm/{id} → confirmLpo sets entry_status = Processed (FuelLPOController.php:639, 648); bulk confirm-all → confirmSelected (fleet.php:53-54). Confirmed list at .../confirmed → confirmedEntries (fleet.php:49).
  • Verify / Approve — fuel-purchase-orders/verified → verifiedEntries, .../approved → approvedEntries (fleet.php:58-59). approveLpo sets entry_status = Approved (FuelLPOController.php:528, 540).
  • Expire — GET fuel-lpos/expire/{id} → expireLpo sets entry_status = Expired (fleet.php:63; FuelLPOController.php:499, 503). This is automated by the scheduled command app/Console/Commands/Telematics/ExpireFuelLpos.php.
  • Reactivate — POST fuel-purchase-orders/reactivate → reactivateExpiredLpo (fleet.php:51) brings an expired LPO back (reactivated status). Only pending/expired entries are editable (FuelLPOController.php:429-430).

3.3 Reconciliation & finance lifecycle (Logistics nav)

  • Upload statement — GET fuel-statements/upload → showUploadPage; POST fuel-statements/upload → upload; POST fuel-statements/save → save (logistics.php:65-67). Import uses app/Imports/FuelReceiptImport.php and the supplier-specific app/Services/FuelStatementGalanaImporter.php.
  • Run verification — POST fuel-verification/verify → FuelVerificationRecordController@runVerification (logistics.php:73) matches statement lines to fuel entries and creates a fuel_verification_records row. Missing / unknown / unfueled / unutilised buckets are exposed at fuel-verification/{missing,unknown,unfueled,unutilized} (logistics.php:76-83).
  • Resolve exceptions — unknown payments and unfueled routes can be resolved or reset (.../unknown/resolve, .../unknown/reset, .../unfueled/resolve, .../unfueled/reset, logistics.php:78-82). A statement line with no matching entry can spawn one: POST fuel-statements/{receipt}/create-missing-lpo → createMissingLpo (logistics.php:62).
  • Penalties — POST fuel-verification/{lpo}/apply_penalty → applyPenalty (logistics.php:72) creates a driver_fuel_penalties row (over-pumping vs standard qty).
  • Approve — fuel-approval.index → FuelEntryApprovalController@showVerificationPage (logistics.php:85); pending/approved/penalty buckets at fuel-approval/{pending,approved,approved-penalties,pending-penalties} (logistics.php:89-92). A parallel unverified approval path exists via UnverifiedFuelEntryApprovalController (unverified-fuel-approval.index, logistics.php:21) for entries approved without a supplier statement.
  • Invoice — approved entries are rolled into a Fuel Invoice (fuel-invoices.index → FuelInvoiceController), which posts to AP (§5).

3.4 Reconciliation flow (Mermaid)

flowchart TD
    A[Create Fuel LPO<br/>vehicle+route+driver+station<br/>estimate = distance x rate] --> B[Fuel entry: pending]
    B --> C{Driver fuels?}
    C -->|yes| D[fueled / fueled_incomplete]
    C -->|no, timed out| Z[expired<br/>ExpireFuelLpos cmd]
    Z -.reactivate.-> B
    D --> E[Confirm entry<br/>confirmLpo]

    S[Supplier statement<br/>Galana / CSV upload] --> T[fuel_statements rows<br/>receipt lines]
    T --> U[runVerification: match<br/>statement line to fuel entry]
    E --> U
    U -->|matched| V[fuel_verification_records<br/>entry -> verified]
    U -->|no match| W[Unknown / Unmatched<br/>resolve or create-missing-lpo]
    V --> P{over-pumped vs<br/>estimate?}
    P -->|yes| PEN[driver_fuel_penalties]
    P -->|no| X
    PEN --> X[Approve<br/>entry -> approved]
    W -.resolved.-> V
    X --> I[Fuel Invoice<br/>group approved entries by supplier/branch/period]
    I --> AP[Accounts Payable<br/>wa_supp_trans + wa_gl_trans]
    AP --> PV[PaymentVoucherItem<br/>payable_type = 'fuel']

4. Tables touched & key data

Verbatim column names (including misspellings/inconsistencies) from the migrations.

fuel_lpos (2023_10_25_133318_create_fuel_lpos_table.php:17-27): id, lpo_number, branch_id, route_id, vehicle_id, deliveryman_id, pre_mileage, pre_fuel, distance_estimate, fuel_estimate, timestamps. Later added: fueled status (2023_12_20), shift_id (2024_06_24_155609), last-shift details (2024_06_25_123722), backdated flag (2025_06_18_125429). Note the LPO row carries no lifecycle status column of its own — status lives on the fuel entry.

new_fuel_entries / refactored fuel_entries (2023_10_25_140958:16-30; refactor 2024_07_07_122247): id, fuel_lpo_id, distance_estimate, distance_covered, fuel_estimate, fuel_consumed, consumption_rate, fuel_price, fuel_type, pre_mileage, current_mileage, image. Grown over time with: multiple (2024_01_02_075253), invoice details (2024_02_04_101733), receipt details + receipt_number (2024_06_24_183148, 2024_08_19), fueling_station (2024_06_25_092800), status/entry_status (2024_06_25_133452), photos (2024_08_08), resolution_comments (2024_08_29_085107), approval (2024_08_29_155257), created_by (2025_03_26), branch + backfill (2025_04_30), odometer (2025_07_21), fueling_branch_id + FK + index (2025_08_14 / 2026_07_28). Note the misspelled table token fueling throughout.

fuel_stations (2023_10_25_121430:17-25): id, branch_id (FK → restaurants), name, slug, location_name, lat, lng. Added: fuel_price (2024_01_02_051944), supplier (2024_06_24_112725), verify_fuel_entries flag (2025_04_11_142421), card_number (2025_05_07_140459).

fuel_suppliers (2024_06_20_143034; columns in 2024_06_24_091344): supplier profile linked to a wa_suppliers account.

fuel_statements (2024_08_25_165805:15-25): id, timestamp, receipt_number, branch_id, matched_fuel_entry_id (nullable — null = unmatched/unknown), verification_record_id (nullable), quantity, terminal_price, discount, narrative. resolution_comments added 2024_08_29_065027; Galana upload tracking added 2026_05_06. A second fuel_statements migration exists (2026_02_17_152917_add_fuel_statements_table.php).

fuel_verification_records (2024_08_26_071732:14-17): id, branch_id, verification_date, fueling_date. This is the reconciliation header linking a fuelling day/branch to matched statement lines and entries.

fuel_invoices (2025_03_15_180900:16-33): id, invoice_number, wa_supplier_id, cu_invoice_number, supplier_invoice_number, invoice_date, restaurant_id, from, to, memo, vat_amount, withholding_amount, amount, created_by, status enum ['pending','paid'], payment_voucher_id (nullable), file_name. Plus fuel_expense_gl_account (nullable-false), invoice_type (2025_04_13, e.g. 'lpo'), VAT breakdown columns (2026_06_19: vat_rate, vatable_amount, non_vatable_amount), withholding backfill (2025_08_16).

fuel_invoice_items (2025_03_15_180900:37-42): id, fuel_invoice_id, fuel_entry_id, quantity, unit_price, amount — one line per approved fuel entry rolled into the invoice.

driver_fuel_penalties (2025_06_30_085918:14-20): id, driver_id, created_by, fuel_lpo_id, std_fuel_qty, actual_fuel_qty, penalty_per_liter. status added 2025_07_01_124116.

fuel_entry_images (2025_07_22_142110) with backfill from existing photos.

AP tables (written to, not owned): wa_supp_trans, wa_gl_trans (via WaSuppTran/WaGlTran), financial_notes (fuel_invoice_id FK added 2026_04_16), and voucher_items / PaymentVoucherItem.


5. Interactions with other modules

5.1 Accounts Payable seam (Book 3) — the money hand-off

This is the load-bearing integration. When a Fuel Invoice is posted (FuelInvoiceController@store, FuelInvoiceController.php:113), the controller writes directly into the AP/GL ledgers:

  1. Supplier transaction — a WaSuppTran (wa_supp_trans) row is created with document_no = invoice_number, supplier_no = supplier->supplier_code, VAT breakdown, and total_amount_inc_vat (FuelInvoiceController.php:210-222). The GRN/series type comes from WaNumerSeriesCode where module = 'FUEL_INVOICES' (line 208).
  2. GL postings — three WaGlTran (wa_gl_trans) rows: a credit to creditors (amount * -1, account from SupplierChartOfAccountService::getCreditorsAccountCodeForPosting, lines 224-240), a debit to the fuel expense GL (amount - vat_amount, companyPreference->fuelExpenseGlAccount, lines 242-258), and a VAT input line when vat_amount > 0 (taxVat->getOutputGlAccount, lines 260-278). All three carry transaction_no = invoice_number and wa_supp_tran_id.
  3. Payment linkage — FuelInvoice::payment()/payments() is a PaymentVoucherItem with payable_type = 'fuel' (FuelInvoice.php:47-66); the AP payment-voucher engine settles the supplier through this polymorphic payable. The listing query filters voucher_items.payable_type = 'fuel' (FuelInvoiceController.php:56). This confirms the guide's earlier note that fuel is a distinct payable type.
  4. Credit/debit notes — FinancialNote rows link back via fuel_invoice_id (FuelInvoice.php:68-76; migration 2026_04_16).
  5. Reversal — deleting an invoice removes its WaSuppTran (where document_no = invoice_number) and WaGlTran (where transaction_no = invoice_number) rows (FuelInvoiceController.php:568-570).

The model relations confirm the shape: supplier() → WaSupplier on wa_supplier_id, suppTran() → WaSuppTran on document_no↔invoice_number, glTransactions() → WaGlTran on transaction_no↔invoice_number (FuelInvoice.php:27-55).

5.2 Vehicle register (Book 5, Ch18)

Fuel LPOs reference vehicle_id, route_id, deliveryman_id (fuel_lpos schema). Consumption estimation uses a per-vehicle rate: migration 2026_01_30_165949_add_fuel_consumption_rate_to_vehicles_table.php adds fuel_consumption_rate to vehicles, and app/Services/FuelEstimationService.php computes fuel_estimate from distance × rate. FuelEntryVehicleFinancialsService ties fuel spend back to vehicle financials. Cross-reference Ch18 for the vehicle master; this chapter only consumes it.

5.3 Branches / stations

Every LPO, statement, and invoice is scoped to a branch_id / restaurant_id (→ restaurants). Stations belong to a branch and to a supplier, and can carry a verify_fuel_entries flag (2025_04_11) that governs whether that station's pumps go through verification.

5.4 Telematics / scheduling

Scheduled commands drive automation: ExpireFuelLpos (auto-expiry), FuelVerificationCommand (batch verification), CreateMissingFuelVerifications, and SetFuelInvoiceVatRate — all under app/Console/Commands/.


6. Alternatives & variants (flags / tenant)

  • Two navs, one lifecycle — the operational surface (fleet.blade.php:777-840) and the finance surface (logistics.blade.php:77-224) are alternate entry points into the same underlying entities. Much of the fleet-nav "Fuel Entries" tree and the logistics-nav "Fuel LPOs" tree are Blade-commented-out (fleet.blade.php:777; logistics.blade.php:108-138), indicating an in-progress migration of the LPO views from fleet into logistics.
  • Invoice type — fuel_invoices.invoice_type ('lpo' per isLpo(), FuelInvoice.php:57-59) distinguishes LPO-backed invoices from other fuel invoice types (2025_04_13 migration).
  • Verified vs unverified approval — two approval controllers exist: FuelEntryApprovalController (statement-verified path) and UnverifiedFuelEntryApprovalController (approve without a matching supplier statement). This is a deliberate fallback for suppliers that don't send statements.
  • Supplier-specific import — FuelStatementGalanaImporter is a named importer for the Galana supplier format, alongside the generic FuelReceiptImport. Galana upload tracking columns (2026_05_06) suggest additional supplier importers may follow.
  • Station verification flag — verify_fuel_entries per station (2025_04_11) toggles whether a station's entries require verification.
  • Withholding tax — withholding_amount on invoices with a backfill migration (2025_08_16) implies WHT handling that may be tenant/config dependent.

7. Open questions — CODE-PROVEN vs INFERENCE

CODE-PROVEN

  • Lifecycle status is stored on the fuel-entry row (entry_status), not on fuel_lpos; enum values pending/fueled_incomplete/fueled/verified/approved/processed/expired/reactivated (FuelEntryStatus.php:5-15; FuelLPOController.php:253, 503, 540, 648).
  • confirmLpo sets status to Processed, not Confirmed — there is no Confirmed enum case (FuelLPOController.php:648; enum lacks it).
  • Fuel invoices post to AP through WaSuppTran + three WaGlTran legs and settle via PaymentVoucherItem with payable_type = 'fuel' (FuelInvoiceController.php:210-278; FuelInvoice.php:47-66).
  • Statement lines with matched_fuel_entry_id = NULL are the "unknown/unmatched" bucket (fuel_statements.php:19; route fuel-statements.list.unknown, logistics.php:63).
  • Fleet nav has a mislabeled "Approved" item guarded by pending-fuel-lpos___view, linking to fuel-lpos.pending, model string 'Approved-fuel-lpos' (fleet.blade.php:829-838) — a UI bug.
  • Two fuel_statements create migrations exist (2024_08_25 and 2026_02_17_152917).

INFERENCE (needs runtime/data confirmation)

  • The exact matching algorithm in runVerification (by receipt_number? by station+date+quantity tolerance?) was not read line-by-line; I infer it matches statement lines to fuel entries on branch/date/receipt and quantity, based on the fuel_statements columns and the unknown/unfueled/unutilised buckets. Not verified against controller body.
  • I infer that a driver penalty is raised when actual_fuel_qty > std_fuel_qty beyond a threshold, from driver_fuel_penalties columns (std_fuel_qty, actual_fuel_qty, penalty_per_liter); the threshold/trigger logic in applyPenalty was not read.
  • I infer the fleet→logistics split reflects operational-vs-finance separation of duties; the commented-out Blade blocks support an in-progress consolidation, but no code comment states the rationale explicitly.
  • The precise grouping key that turns approved entries into a single fuel_invoice (supplier + branch + from/to period) is inferred from the fuel_invoices columns (wa_supplier_id, restaurant_id, from, to); the aggregation code in FuelLpoInvoiceController/FuelInvoiceController@store upstream of line 200 was not fully traced.
  • Whether withholding_amount is always computed or tenant-gated is inferred from the backfill migration; not confirmed in config.

8. Source references (file:line)

Navigation - resources/views/admin/includes/sidebar_includes/fleet.blade.php:777-840 — Fuel Management + Fuel Entries tree, permission guards, the 'Approved-fuel-lpos' bug (829-838). - resources/views/admin/includes/sidebar_includes/logistics.blade.php:77-224 — Fuel Management, Fuel Statements, approval, invoices, configuration; can()-guarded create/view (90, 99); commented-out LPO subtree (108-138).

Routes - routes/modules/fleet.php:31-63 — stations, entries, suppliers, LPO lifecycle (create/store/confirm/verify/approve/expire/reactivate, reconcile-excel). - routes/modules/logistics.php:50-93 — reports, statement upload/save/show/unknown, verification (verify, records, unknown/unfueled/unutilized resolve/reset, penalty), approval, invoices.

Controllers - app/Http/Controllers/Admin/FuelLPOController.php:253, 384, 429-430, 499-540, 639-666 — status transitions. - app/Http/Controllers/FuelInvoiceController.php:56, 113, 200-278, 568-570 — AP/GL posting and reversal. - app/Http/Controllers/FuelStatementController.php, FuelVerificationRecordController.php, FuelEntryApprovalController.php, Admin/UnverifiedFuelEntryApprovalController.php, Admin/FuelLpoInvoiceController.php — statement, verification, approval, invoicing surfaces.

Models / enums / services - app/Models/FuelInvoice.php:27-76 — AP relations (WaSupplier, WaSuppTran, WaGlTran, PaymentVoucherItem payable_type='fuel', FinancialNote). - app/Models/FuelStatement.php:15-22; app/Models/FuelVerificationRecord.php; app/Models/FuelInvoiceItem.php; app/Models/DriverFuelPenalty.php; app/FuelLpo.php; app/Model/Fuelentry.php; app/NewFuelEntry.php. - app/Enums/FuelEntryStatus.php:5-15; app/Enums/FuelEntryParentTypes.php. - app/Services/FuelEstimationService.php, FuelEntryVehicleFinancialsService.php, FuelStatementGalanaImporter.php; app/Imports/FuelReceiptImport.php. - app/Console/Commands/Telematics/ExpireFuelLpos.php, FuelVerificationCommand.php, CreateMissingFuelVerifications.php; app/Console/Commands/SetFuelInvoiceVatRate.php.

Migrations (database/migrations/) - 2023_10_25_133318_create_fuel_lpos_table.php:17-27 - 2023_10_25_140958_create_new_fuel_entries_table.php:16-30 (+ many alters through 2026_07_28) - 2023_10_25_121430_create_fuel_stations_table.php:17-25 (+ 2024_01_02_051944, 2024_06_24_112725, 2025_04_11_142421, 2025_05_07_140459) - 2024_06_20_143034_create_fuel_suppliers_table.php - 2024_08_25_165805_create_fuel_statements_table.php:15-25; 2026_02_17_152917_add_fuel_statements_table.php; 2026_05_06_120000_add_galana_upload_tracking_to_fuel_statements.php - 2024_08_26_071732_create_fuel_verification_records_table.php:14-17 - 2025_03_15_180900_create_fuel_invoices_table.php:16-42; 2025_04_13_180753_add_invoice_type; 2026_06_19_140000_add_vat_breakdown; 2025_08_16_103306_backfill_withholding; 2026_04_16_100000_add_fuel_invoice_id_to_financial_notes_table.php - 2025_06_30_085918_create_driver_fuel_penalties_table.php:14-20 (+ 2025_07_01_124116 status) - 2026_01_30_165949_add_fuel_consumption_rate_to_vehicles_table.php

Cross-references: Ch18 (Vehicle register — vehicle_id, fuel_consumption_rate); Book 3 Accounts Payable (wa_supp_trans, wa_gl_trans, payment vouchers). Not covered here: Tyres (Ch20), Service (Ch21), Inbound logistics (Ch22).