ADR-001: Fuel Adjustment Factor (FAF) Implementation
Status
Accepted
Context
The Access Waste Management System needed a mechanism to apply a variable fuel surcharge (Fuel Adjustment Factor) to skip bin services. This surcharge accounts for fluctuating diesel/fuel costs in vehicle operations. The feature needed to:
- Be configurable per supplier (Partner)
- Support both percentage-based and fixed-amount calculations
- Apply only to non-rental orders (one-time/ad-hoc services)
- Display transparently on invoices as a separate line item
- Sync with Xero accounting system
- Maintain historical accuracy (immutable snapshots on orders)
Commits: 2f4ca69b through d0e482bb
ADR-001-A: FAF as General Partner Setting
Decision
FAF is configured at the Partner level as a general setting that applies to all non-rental orders for a supplier, rather than being configured per waste type or pricing tier.
Context
Initially considered configuring FAF per waste type (via the pricings table). However, business requirements clarified that FAF should apply uniformly across all skip bin services for a supplier.
Consequences
Positive:
- Simplified admin interface - single configuration point
- Consistent FAF application across all waste types
- Reduced configuration overhead for administrators
Negative:
- Less granular control - cannot disable FAF for specific waste types
- All non-rental orders for a supplier share the same FAF rate
Implementation
Database migration adds FAF columns to partners table:
has_faf(boolean) - enable/disable FAFfaf_type(string: 'percentage' or 'fixed')faf_value(decimal) - the rate or fixed amount
Admin UI is a separate card on the Bin Hire page (/admin/skip-bin/{type_id}/{supplier_id}/{area_id}) with its own AJAX form that saves to the Partner model.
ADR-001-B: Immutable FAF Snapshot Pattern
Decision
FAF values are stored as static snapshots on order_details at order creation time, preserving the exact rate and amount applied for historical accuracy and audit purposes.
Context
FAF rates may change over time as fuel costs fluctuate. For invoice integrity and audit trails, the system must record what FAF was actually applied at the time of order creation, not reference the current configuration.
Consequences
Positive:
- Historical invoices remain accurate even if FAF rates change
- Complete audit trail for regulatory compliance
- Immutable record prevents disputes about what was charged
Negative:
- Additional storage overhead (3 columns per order detail)
- Cannot retroactively apply new FAF rates to existing orders
Implementation
Database migration adds snapshot columns to order_details:
faf_applied_type(string) - 'percentage' or 'fixed' at time of orderfaf_applied_rate(decimal) - the rate value usedfaf_total_amount(decimal) - calculated FAF amount applied
The FuelAdjustmentRepository::applyFAFToOrderDetail() method captures the snapshot during order creation.
ADR-001-C: View-Only Admin Access
Decision
Administrators cannot modify FAF on individual orders. FAF is calculated automatically from Partner configuration and displayed as read-only.
Context
CEO requirement for transparency and consistency. FAF should be a business rule applied uniformly, not subject to manual adjustment per order which could lead to inconsistent application or disputes.
Consequences
Positive:
- Ensures consistent FAF application across all orders
- Reduces admin decision fatigue
- Prevents accidental or intentional under/over-charging
- Simplified UI - no override controls needed
Negative:
- Edge cases require updating Partner config rather than individual orders
- Less flexibility for exceptional circumstances
Implementation
- Admin order creation: FAF is calculated and displayed but not editable
- Admin order edit: FAF snapshot is displayed read-only
- Change-over orders: FAF recalculates from current Partner config (not inherited from original order)
- No UI controls for modifying FAF on existing orders
ADR-001-D: Dual Calculation Methods
Decision
FAF supports two calculation types:
- Percentage-based:
FAF = Base Price × (FAF Rate / 100) - Fixed amount:
FAF = Fixed FAF Value
Context
Business needs flexibility in how fuel costs are recovered. Percentage scales with service price (appropriate when fuel costs correlate with service value), while fixed amount provides predictable per-service recovery.
Consequences
Positive:
- Flexibility to match business pricing strategy
- Can switch methods as business needs change
- UI dynamically shows $ or % prefix based on selection
Negative:
- Slightly more complex calculation logic
- Admin must understand when to use each method
Implementation
FuelAdjustmentRepository::calculateFAF() handles both methods:
$fafAmount = $partner->faf_type === 'percentage'
? $basePrice * ($partner->faf_value / 100)
: $partner->faf_value;
ADR-001-E: Invoice Integration via OrderInvoiceExtra
Decision
FAF appears on invoices as a separate line item using the existing OrderInvoiceExtra model, rather than modifying order totals directly or using a dedicated FAF invoice model.
Context
The system already has infrastructure for extra/optional line items on invoices (OrderInvoiceExtra). Reusing this pattern maintains consistency and minimizes new code.
Consequences
Positive:
- Reuses existing invoice line item infrastructure
- Natural fit for "extra charge" concept
- Invoice templates already render OrderInvoiceExtra items
- Tax calculations handled consistently
Negative:
- FAF line items mixed with other extra services in the same table
- Querying FAF totals requires filtering by name pattern
Implementation
FuelAdjustmentRepository::addFAFToInvoice() creates an OrderInvoiceExtra record:
name: "Fuel Adjustment Factor (X%)" or "Fuel Adjustment Factor"unit_cost: FAF amount from order detail snapshottotal_cost: same as unit_cost (qty = 1)qty: 1
ADR-001-F: Idempotent FAF Creation
Decision
FAF line items on invoices use an idempotent creation pattern to prevent duplicate entries when invoices are regenerated or updated.
Context
Invoices can be regenerated multiple times (e.g., corrections, updates). Without idempotency, each regeneration would create duplicate FAF line items.
Consequences
Positive:
- Safe to call multiple times without side effects
- Simplifies invoice regeneration logic
- Prevents data corruption from duplicate charges
Negative:
- Requires name-based lookup (fragile if display text changes)
- Slightly more complex than simple insert
Implementation
// Check for existing FAF line item
$extra = OrderInvoiceExtra::query()->where([
'order_invoice_id' => $invoice->id,
'order_detail_id' => $orderDetail->id,
])->where('name', 'like', 'Fuel Adjustment Factor%')->first();
// Create new or update existing
if (!$extra) {
$extra = new OrderInvoiceExtra();
}
$extra->fill([...])->save();
ADR-001-G: Xero Integration Using Shared AccountCode
Decision
FAF line items in Xero use the same AccountCode as other line items ($itemXero), not a separate dedicated account code.
Context
CEO rejected the idea of a separate xero_faf_account_code field. Simpler accounting approach treats FAF as part of service revenue rather than tracking separately.
Consequences
Positive:
- Simpler configuration - no additional Xero account code to manage
- Consistent with treating FAF as part of service pricing
- Reduced accounting complexity
Negative:
- Cannot isolate FAF revenue in Xero reports by account code
- Fuel surcharge revenue mixed with base service revenue in accounting
Implementation
In InvoiceManagement::xeroItemNonRental(), FAF line item uses $itemXero:
$lineItems[] = [
'Description' => $fafDescription,
'Quantity' => 1,
'UnitAmount' => round($orderDetail->faf_total_amount, 2),
'AccountCode' => $itemXero, // Same as main service
];
Date
April 10, 2026
Author
Cascade AI Assistant (based on commits by Aryan Jaya)