Skip to main content

ADR-002: Enable FAF on Rental Orders

Status: Proposed
Date: April 24, 2026
Reference: ADR-001-Fuel-Adjustment-Factor.md
Author: Cascade AI Assistant


Context

Currently, the Fuel Adjustment Factor (FAF) feature explicitly excludes rental orders through multiple checks in the codebase. The original ADR-001 specified that FAF should only apply to "non-rental orders", but business requirements have changed to include FAF on all order types, including rental orders (frontlift bins, wheelie bins, trash bags).

Current Exclusion Points

LocationFileLine(s)Description
1resources/js/components/AdminChangeOver.vue1871, 1876Sets faf_total_amount = 0 when is_rental = true
2app/Repositories/FuelAdjustmentRepository.php63-66Returns 0 for rental orders in applyFAFToOrderDetail()
3resources/js/components/AdminNewSkipBinOrder.vue2864Comment: "FAF only applies to non-rental orders"

Rental Order Architecture

Rental orders have a parent-child order detail structure:

Parent Order Detail (Service Pricing)
├── Created via: storeServicePricing() or AddServicePricing.vue
├── Contains: Service configuration, pricing, FAF snapshot
└── Generates Child Order Details via Runsheet

Child Order Details (Service Instances)
├── Created via: OrderRentalRepository::createDetailEmpty/EmptyReturn/ChangeOver/Delivery/Pickup
├── Generated by: RunsheetRentalRepository::generateDetail() or reRunRunsheetSession()
├── Contains: Individual service execution snapshot
└── Needs FAF from Parent or Current Partner Config

Decision

Enable FAF on rental orders by:

  1. Removing all is_rental exclusions from FAF calculation
  2. Applying FAF snapshot to parent order details during creation
  3. Applying FAF to child order details generated from runsheets
  4. Ensuring FAF line items appear on rental invoices

Changes Required

1. Frontend Vue Components

1.1 AdminChangeOver.vue

File: resources/js/components/AdminChangeOver.vue

Changes:

// Line 1871: Remove is_rental check
// BEFORE:
this.orderDetail.grandTotal = ... + (this.order.is_rental ? 0 : this.fafAmount);

// AFTER:
this.orderDetail.grandTotal = ... + this.fafAmount; // Always apply FAF

// Lines 1874-1876: Remove is_rental check
// BEFORE:
this.orderDetail.faf_applied_type = this.faf_config?.faf_type || null;
this.orderDetail.faf_applied_rate = this.faf_config?.faf_value || null;
this.orderDetail.faf_total_amount = this.order.is_rental ? 0 : this.fafAmount;

// AFTER:
const fafData = this.calculateFAF(this.orderDetail.price);
this.orderDetail.faf_applied_type = fafData.faf_applied_type;
this.orderDetail.faf_applied_rate = fafData.faf_applied_rate;
this.orderDetail.faf_total_amount = fafData.faf_total_amount; // Always apply

Action: Add calculateFAF() method if not exists (copy from AdminNewSkipBinOrder.vue)

1.2 AddServicePricing.vue (Rental Service Pricing)

File: resources/js/components/product-packages/AddServicePricing.vue

Changes Required: No changes needed. NO FAF display in service pricing forms.

Admin is already aware of FAF enable/disable/type from Partner config page. FAF will be calculated and stored on child order details when they are generated via runsheet using FuelAdjustmentRepository::calculateFAF().

1.3 EditServicePricing.vue (if exists)

File: resources/js/components/product-packages/Edit.vue or similar

Changes Required: No changes needed. NO FAF display in service pricing forms.


2. Backend Controllers

2.1 OrderController::storeServicePricing()

File: app/Http/Controllers/Admin/OrderController.php (lines 4295-4395)

Note: Parent Order Details (Service Pricing) do NOT store FAF values. No FAF fields needed in creation.

$newOrderDetail = OrderDetail::create([
// ... existing fields ...
'price' => $request['price'],
'subtotal' => $request['grandtotal'],

// NO FAF FIELDS - Parent order details don't store FAF
// FAF is calculated fresh when creating child order details

// ... rest of fields ...
]);

Child order details created via createDetailDelivery() will automatically get FAF using FuelAdjustmentRepository::calculateFAF().

2.2 OrderController::storePricingDeliveryOrder()

File: app/Http/Controllers/Admin/OrderController.php (lines 4432-4489)

Note: Parent Order Details do NOT store FAF values. No FAF fields needed.

$newOrderDetail = OrderDetail::create([
// ... existing fields ...

// NO FAF FIELDS - Parent order details don't store FAF

// ... rest of fields ...
]);

Child order details created via OrderRentalRepository::createDetailDelivery() will automatically get FAF using FuelAdjustmentRepository::calculateFAF().

2.3 OrderController::storePickupDate() (Create Pickup Bin)

File: app/Http/Controllers/Admin/OrderController.php (lines 4243-4293)

Status: Creates child order detail directly (Pickup) - NEEDS FAF UPDATE

Current: No FAF fields in OrderDetail::create()

Required Changes:

// Calculate FAF before creating order detail
$fafBaseAmount = $request->change_over['price'] * $request->change_over['quantity'];
$faf_total_amount = FuelAdjustmentRepository::calculateFAF($fafBaseAmount);
$fafConfig = FuelAdjustmentRepository::getFAFConfig();

$baseSubtotal = $request->change_over['price'] * $request->change_over['quantity'];
$subtotalWithFAF = $baseSubtotal + $faf_total_amount;

$newOrderDetail = OrderDetail::create([
// ... existing fields ...
'subtotal' => $subtotalWithFAF, // Subtotal includes FAF

// ADD FAF FIELDS:
'faf_applied_type' => $faf_total_amount > 0 ? $fafConfig->faf_type : null,
'faf_applied_rate' => $faf_total_amount > 0 ? $fafConfig->faf_value : 0,
'faf_total_amount' => $faf_total_amount,
]);

Note: Pickup intentionally passes price=0 in runsheet flow, but manual pickup creation uses the price from the form.

2.4 OrderRentalRepository::extraOrderDetailTask() (Extra Empty Invoice)

File: app/Repositories/OrderRentalRepository.php (lines 785-889)

Status: Called by FrontBinController::postExtraInvoice() for frontlift/wheelie bin - Creates child Empty order detail - NEEDS FAF UPDATE

Current: No FAF fields in OrderDetail::query()->create()

Required Changes:

// Calculate FAF before creating order detail
$fafBaseAmount = $data['final_price'] * $data['quantity'];
$faf_total_amount = FuelAdjustmentRepository::calculateFAF($fafBaseAmount);
$fafConfig = FuelAdjustmentRepository::getFAFConfig();

$baseSubtotal = $data['final_price'] * $data['quantity'];
$subtotalWithFAF = $baseSubtotal + $faf_total_amount;

$newOrderDetail = OrderDetail::query()->create([
// ... existing fields ...
'price' => $data['final_price'],
'quantity' => $data['quantity'],
'subtotal' => $subtotalWithFAF, // Subtotal includes FAF

// ADD FAF FIELDS:
'faf_applied_type' => $faf_total_amount > 0 ? $fafConfig->faf_type : null,
'faf_applied_rate' => $faf_total_amount > 0 ? $fafConfig->faf_value : 0,
'faf_total_amount' => $faf_total_amount,

// ... rest of fields ...
]);

3. Repository Layer (Critical for Child Order Details)

3.1 OrderRentalRepository::createDetail() - BASE METHOD

File: app/Repositories/OrderRentalRepository.php (lines 1272-1342)

Changes: Add FAF snapshot support to base createDetail method using FuelAdjustmentRepository::calculateFAF()

public static function createDetail(
$detail,
$code,
$type,
$date,
$price = 0,
$qty = 1,
$additionalData = [],
$additionalPrice = 0,
$isFromRunsheetGroup = 1
) {
// ... existing code ...

// Calculate FAF using FuelAdjustmentRepository (fresh from current Partner config)
// FAF is calculated on basePrice * quantity (the amount stored to `price` attribute)
$fafBaseAmount = $price * $qty;
$faf_total_amount = FuelAdjustmentRepository::calculateFAF($fafBaseAmount);
$fafConfig = FuelAdjustmentRepository::getFAFConfig();

$faf_applied_type = null;
$faf_applied_rate = 0;

if ($faf_total_amount > 0 && $fafConfig) {
$faf_applied_type = $fafConfig->faf_type;
$faf_applied_rate = $fafConfig->faf_value;
}

// Note: Parent order detail ($detail) does NOT have FAF fields set
// FAF is calculated fresh from Partner config for child order details

// Calculate subtotal including FAF
$baseSubtotal = $price * $qty;
$subtotalWithFAF = $baseSubtotal + $faf_total_amount;

$delivery = OrderDetail::create([
// ... existing fields ...
'subtotal' => $subtotalWithFAF, // Subtotal includes FAF

// ADD THESE FAF FIELDS:
'faf_applied_type' => $faf_applied_type,
'faf_applied_rate' => $faf_applied_rate,
'faf_total_amount' => $faf_total_amount,

// ... rest of fields ...
]);

return $delivery;
}

Decision: Child order details always calculate fresh from current Partner config using FuelAdjustmentRepository::calculateFAF($basePrice).

Important: Parent order details (Service Pricing) do NOT store FAF values - FAF is only applied when creating child order details through runsheet generation.

3.2 OrderRentalRepository::createDetailEmpty()

File: app/Repositories/OrderRentalRepository.php (lines 1072-1107)

Status: Calls createDetail() - will inherit FAF logic automatically after 3.1 update

3.3 OrderRentalRepository::createDetailEmptyReturn()

File: app/Repositories/OrderRentalRepository.php (lines 1109-1137)

Status: Calls createDetail() - will inherit FAF logic automatically after 3.1 update

3.4 OrderRentalRepository::createDetailChangeOver()

File: app/Repositories/OrderRentalRepository.php (lines 1139-1172)

Status: Calls createDetail() - will inherit FAF logic automatically after 3.1 update

3.5 OrderRentalRepository::createDetailDelivery()

File: app/Repositories/OrderRentalRepository.php (lines 1203-1238)

Status: Delegates to createDetail() - NO CHANGES NEEDED

Note: This method calls self::createDetail() which will automatically include FAF after the createDetail() update.

Call Pattern:

$delivery = self::createDetail(
$detail,
"EBNZ-" . strtoupper(Str::random(12)),
"Delivery",
$detail->delivery_date,
$detail->delivery_price, // <-- This is the $price parameter
$detail->quantity,
$additionalData,
$additionalPrice
);

FAF will be calculated as: $detail->delivery_price * $detail->quantity

3.6 OrderRentalRepository::createDetailPickup()

File: app/Repositories/OrderRentalRepository.php (lines 1240-1270)

Status: Delegates to createDetail() - NO CHANGES NEEDED

Note: This method calls self::createDetail() which will automatically include FAF after the createDetail() update.

Call Pattern:

$delivery = self::createDetail(
$detail,
"EBNZ-" . strtoupper(Str::random(12)),
"Pickup",
$detail->pickup_date,
0, // <-- $price is 0 for Pickup
$detail->quantity,
$additionalData,
$additionalPrice
);

Note: Pickup intentionally passes $price = 0, so FAF will be $0. This is correct - Pickup tasks do not have FAF.


4. Runsheet/Rerun Process

4.1 RunsheetRentalRepository::reRunRunsheetSession()

File: app/Repositories/RunsheetRentalRepository.php (lines 659-684)

Purpose: Reruns runsheet calendar to generate order details for a date range

Flow:

reRunRunsheetSession()
→ process()
→ create() or RentalBin::create()
→ generateDetail()
→ OrderRentalRepository::createDetailEmpty/EmptyReturn/ChangeOver
→ OrderRentalRepository::createDetail() [with FAF after update]

Impact: After updating createDetail() (Section 3.1), all child order details generated via rerun will automatically include FAF.

4.2 RunsheetRentalRepository::generateDetail()

File: app/Repositories/RunsheetRentalRepository.php (lines 136-225)

Purpose: Generates Empty, Empty Return, Change Over order details from runsheet

Status: Calls OrderRentalRepository methods that use createDetail() - will inherit FAF after updates


5. Invoice Integration

5.1 InvoiceManagement - FAF Line Items

File: app/Repositories/InvoiceManagement.php

Current Status: Already supports FAF via addFAFToInvoice() calls in:

  • firstInvoice() (line 95-97)
  • updateInvoice() (line 1132-1134)
  • createInvoiceFromTask() (line 1772)
  • createInvoiceFromRunsheet() (line 2057-2058)

No changes required - these methods already iterate all order details and add FAF line items if faf_total_amount > 0.


6. FuelAdjustmentRepository Cleanup

6.1 Remove Dead Code (Optional)

File: app/Repositories/FuelAdjustmentRepository.php (lines 54-80)

The applyFAFToOrderDetail() method is dead code (never called). Can optionally remove the rental check or the entire method.

// Lines 63-66 - Remove this block:
// Check if order is rental (skip FAF for rental orders)
if ($order && $order->is_rental) {
return 0;
}

Data Flow Summary

Parent Order Detail Creation (Initial Setup)

Important: Parent Order Details (Service Pricing) do NOT store FAF values. FAF is only applied to child order details.

User fills service pricing form (AddServicePricing.vue)

Frontend displays FAF (for transparency) using Partner config

Form submission does NOT include FAF snapshot fields

POST to: OrderController::storeServicePricing()

Creates Parent OrderDetail WITHOUT FAF fields (null/zero)

Triggers: OrderRentalRepository::createDetailDelivery() [if is_create_delivery]

Creates Child OrderDetail with FAF using FuelAdjustmentRepository::calculateFAF()

Child Order Detail Generation (Runsheet/Rerun)

Runsheet schedule triggers or Admin clicks "Rerun"

RunsheetRentalRepository::reRunRunsheetSession() or process()

Calls: OrderRentalRepository::createDetailEmpty/EmptyReturn/ChangeOver()

Calls: FuelAdjustmentRepository::calculateFAF($basePrice) for fresh calculation

Calls: OrderRentalRepository::createDetail() with FAF snapshot

Creates Child OrderDetail with FAF calculated from current Partner config

FAF Config Change and Rerun Runsheet

When FAF config is updated (enable/disable or value changes):

Admin updates FAF config (Partner settings)

System triggers: RerunRunsheetCalendar command

Rerun starts from tomorrow's date (not retroactive)

For each active rental contract:

Delete existing future child order details (from tomorrow onward)

Regenerate child order details using NEW FAF config

OrderRentalRepository::createDetail() calculates fresh FAF

New child order details have updated FAF snapshot

Key Points:

  • Rerun only affects future runsheets (from tomorrow's date)
  • Existing order details (today and past) remain unchanged
  • Existing invoices remain unchanged
  • New FAF config applies to all future child order detail generations

Invoice Generation

Invoice created via InvoiceManagement::firstInvoice/updateInvoice/etc

Iterates all order details

For each detail with faf_total_amount > 0:

FuelAdjustmentRepository::addFAFToInvoice()

Creates OrderInvoiceExtra line item with is_faf = true

Testing Scenarios

Test IDScenarioExpected Result
TC-001Create rental service pricing with FAF enabledParent order detail has NO FAF display; child details get FAF via runsheet
TC-002Create rental with FAF disabledParent order detail has NO FAF; child details will have FAF=0
TC-003Generate runsheet tasks with FAF enabledChild order details have FAF calculated from current Partner config
TC-004Rerun runsheet calendarChild order details regenerated with current FAF config
TC-005Invoice generation for rentalFAF line item appears on invoice
TC-006Change rental pricing (price change)FAF recalculated based on new price
TC-007Change-over from rental orderFAF calculated on new order from current Partner config
TC-008Delivery task created for rentalDelivery order detail includes FAF
TC-009Pickup task created for rentalPickup order detail has NO FAF ($price=0, intentional)
TC-010Empty service from runsheetEmpty order detail includes FAF
TC-011Empty & Return service from runsheetEmptyReturn order detail includes FAF
TC-012Change Over service from runsheetChangeOver order detail includes FAF

Backward Compatibility

AspectImpactMitigation
Existing rental ordersNo FAF snapshotThey remain unchanged; FAF applies only to new/modified orders
Existing runsheetsAlready generated child order detailsNo retroactive FAF application; new reruns will get FAF
Existing invoicesNo FAF line itemsHistorical invoices remain unchanged
Partner configAlready supports FAFNo changes needed
Database schemaAlready has FAF columnsNo migrations required

UI/UX Considerations

Rental Order Creation Flow

FAF should be displayed for transparency in child order creation forms.

FAF Display Locations

Backend Admin Forms (for transparency):

  • Add Service Pricing - NO FAF display (admin aware from Partner config)
  • Edit Service Pricing - NO FAF display (admin aware from Partner config)
  • Create Pickup Bin - Show FAF line item
  • Extra Empty Invoice - Show FAF line item
  • Empty & Return - Show FAF line item (child order detail view)
  • Change Over - Show FAF line item

Invoices (customer-facing):

  • Invoice preview
  • PDF invoice (already supported)
  • Email invoice (already supported)

Order Detail Views:

  • Show FAF amount in child order detail read-only view

Implementation Checklist

Phase 1: Frontend (Vue Components)

  • Update AdminChangeOver.vue - Remove is_rental FAF exclusion, add FAF calculation and display
  • No changes needed for AddServicePricing.vue - NO FAF display in service pricing
  • No changes needed for Edit service pricing component - NO FAF display in service pricing

Phase 2: Backend Controllers (Manual Child Detail Creation)

  • Update OrderController::storePickupDate() - Add FAF fields (Create Pickup Bin)
  • Verify OrderController::storeServicePricing() - No FAF fields needed (parent doesn't store FAF)
  • Verify OrderController::storePricingDeliveryOrder() - No FAF fields needed (parent doesn't store FAF)

Phase 3: Repository Layer

  • Update OrderRentalRepository::createDetail() - Add FAF calculation and snapshot (runsheet flow)
  • Update OrderRentalRepository::extraOrderDetailTask() - Add FAF fields (Extra Empty Invoice)
  • OrderRentalRepository::createDetailDelivery() - No changes needed (delegates to createDetail)
  • OrderRentalRepository::createDetailPickup() - No changes needed - Intentionally has no FAF in runsheet ($price=0)
  • (Optional) Remove dead code from FuelAdjustmentRepository::applyFAFToOrderDetail()

Phase 4: Testing

  • Test rental order creation with FAF enabled
  • Test rental order creation with FAF disabled
  • Test runsheet task generation includes FAF
  • Test rerun runsheet after FAF config change applies new FAF from tomorrow's date only
  • Test invoice generation includes FAF line item
  • Test change-over from rental includes FAF
  • Test delivery task creation includes FAF
  • Test pickup task creation includes FAF
  • Test frontlift bin, wheelie bin, trash bag categories

Files to Modify Summary

CategoryFileLinesDescription
VueAdminChangeOver.vue~1870-1880Remove is_rental exclusion, add FAF display
VueAddServicePricing.vueN/ANo changes - NO FAF display
VueProduct packages editN/ANo changes - NO FAF display
ControllerOrderController.php~4243-4293Add FAF to storePickupDate()
ControllerOrderController.phpN/ANo changes for parent storage
RepositoryOrderRentalRepository.php~785-889Add FAF to extraOrderDetailTask()
RepositoryOrderRentalRepository.php~1272-1342Add FAF using calculateFAF() in createDetail()
RepositoryOrderRentalRepository.php~1203-1238No changes - delegates to createDetail()
RepositoryOrderRentalRepository.php~1240-1270No changes - delegates to createDetail()
RepositoryFuelAdjustmentRepository.php~63-66Remove rental check (optional)

Questions for Confirmation

  1. FAF Calculation Method for Child Order Details:

    • Option A: Calculate fresh from current Partner config (recommended)
    • Option B: Inherit from parent's FAF snapshot
    • Answer: Option A: Calculate fresh from current Partner config
  2. FAF Config Change and Rerun Runsheet:

    • Should existing active rental contracts get FAF applied to future runsheet generations when FAF config changes?
    • Answer: Yes. After FAF config is updated (enable/disable or value changes), RerunRunsheetCalendar command will be executed starting from tomorrow's date (not retroactive). This will:
      • Delete existing future child order details (from tomorrow onward)
      • Regenerate them using the new FAF config via OrderRentalRepository::createDetail()
      • Existing order details (today and past) remain unchanged
      • Existing invoices remain unchanged
  3. FAF Display in UI:

    • Should FAF be displayed as a separate line item in rental order summaries?
    • Answer:
      • Service Pricing forms: NO FAF display (admin aware from Partner config)
      • Child order creation (Change Over, Pickup Bin, etc.): YES - FAF displayed for transparency
      • Invoices: YES - FAF line item appears
      • Child order detail views: YES - FAF amount displayed

References

  • ADR-001-Fuel-Adjustment-Factor.md (Original FAF implementation)
  • TC-001-Fuel-Adjustment-Factor.md (Test cases)

End of Document