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
| Location | File | Line(s) | Description |
|---|---|---|---|
| 1 | resources/js/components/AdminChangeOver.vue | 1871, 1876 | Sets faf_total_amount = 0 when is_rental = true |
| 2 | app/Repositories/FuelAdjustmentRepository.php | 63-66 | Returns 0 for rental orders in applyFAFToOrderDetail() |
| 3 | resources/js/components/AdminNewSkipBinOrder.vue | 2864 | Comment: "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:
- Removing all
is_rentalexclusions from FAF calculation - Applying FAF snapshot to parent order details during creation
- Applying FAF to child order details generated from runsheets
- 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 ID | Scenario | Expected Result |
|---|---|---|
| TC-001 | Create rental service pricing with FAF enabled | Parent order detail has NO FAF display; child details get FAF via runsheet |
| TC-002 | Create rental with FAF disabled | Parent order detail has NO FAF; child details will have FAF=0 |
| TC-003 | Generate runsheet tasks with FAF enabled | Child order details have FAF calculated from current Partner config |
| TC-004 | Rerun runsheet calendar | Child order details regenerated with current FAF config |
| TC-005 | Invoice generation for rental | FAF line item appears on invoice |
| TC-006 | Change rental pricing (price change) | FAF recalculated based on new price |
| TC-007 | Change-over from rental order | FAF calculated on new order from current Partner config |
| TC-008 | Delivery task created for rental | Delivery order detail includes FAF |
| TC-009 | Pickup task created for rental | Pickup order detail has NO FAF ($price=0, intentional) |
| TC-010 | Empty service from runsheet | Empty order detail includes FAF |
| TC-011 | Empty & Return service from runsheet | EmptyReturn order detail includes FAF |
| TC-012 | Change Over service from runsheet | ChangeOver order detail includes FAF |
Backward Compatibility
| Aspect | Impact | Mitigation |
|---|---|---|
| Existing rental orders | No FAF snapshot | They remain unchanged; FAF applies only to new/modified orders |
| Existing runsheets | Already generated child order details | No retroactive FAF application; new reruns will get FAF |
| Existing invoices | No FAF line items | Historical invoices remain unchanged |
| Partner config | Already supports FAF | No changes needed |
| Database schema | Already has FAF columns | No 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- Removeis_rentalFAF 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
| Category | File | Lines | Description |
|---|---|---|---|
| Vue | AdminChangeOver.vue | ~1870-1880 | Remove is_rental exclusion, add FAF display |
| Vue | AddServicePricing.vue | N/A | No changes - NO FAF display |
| Vue | Product packages edit | N/A | No changes - NO FAF display |
| Controller | OrderController.php | ~4243-4293 | Add FAF to storePickupDate() |
| Controller | OrderController.php | N/A | No changes for parent storage |
| Repository | OrderRentalRepository.php | ~785-889 | Add FAF to extraOrderDetailTask() |
| Repository | OrderRentalRepository.php | ~1272-1342 | Add FAF using calculateFAF() in createDetail() |
| Repository | OrderRentalRepository.php | ~1203-1238 | No changes - delegates to createDetail() |
| Repository | OrderRentalRepository.php | ~1240-1270 | No changes - delegates to createDetail() |
| Repository | FuelAdjustmentRepository.php | ~63-66 | Remove rental check (optional) |
Questions for Confirmation
-
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
-
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),
RerunRunsheetCalendarcommand 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
-
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