Skip to content

Create Booking Form - Technical Documentation

The form uses a **2-column layout**:

PropertyValue
URL/operations/create-booking
Route Handlerportals/operations/routes.pycreate_booking() (line ~4116)
Templatetemplates/portals/operations/create_booking.html (~7,134 lines)
Blueprintoperations_portal
Base Templateportals/operations/base.html
Block{% block dashboard_content %}
Auth@login_required (Flask-Login)
Title”Create Booking - iDrv5-MyFR8”

The form uses a 2-column layout:

  • Left Column - Customer info, job type, special instructions, booking summary
  • Right Column - Tabbed booking details (load, pickup, delivery), pricing, submission

Tab navigation (#customerLoadTabs):

TabContent IDFields
Customer#customer-info-contentCustomer select, Invoice To radio, Third Party selector
Load#load-details-contentCustomer Reference Number, Document upload, Hourly rate fields (conditional)
  • Job Type - Button group: LTL, FTL, Empty Run, Towhaul
  • Activity Type - Radio buttons: Pickup, Pickup and Delivery, Delivery Only, Local Start
  • Service Level - Button group (dynamically loaded from API)
  • Additional Requirements textarea

Displays: Type, Service Type, Charge By, Items Count, Total Weight, Total Volume

Draggable, reorderable tab navigation:

TabIDDescription
Load Details#packaging-tabDefault active. Vehicle selection, unified load type entry, item tables
Pickup#pickup-tabCompany info, address (Google Autocomplete), schedule, handling options
Delivery#delivery-tabSame structure as Pickup with receiver_* prefixes
Local Start#local-start-tabConditional. Shown only for local_start activity type

Add Stop Dropdown: Allows adding extra Pickup or Delivery tabs.

  • Charge By method + units
  • Base Price, Flat Rate (auto-computed), Fuel Levy %, Tailgate Fee
  • Estimated Total (calculated)
  • Surcharges section (dynamically populated addons)
  • Price Breakdown (shown when tax addons active)
  • Action buttons: Auto Calculate, Rate Computation Modal, Clear Pricing
  • Save Booking (primary button)
  • Cancel link

FieldHTML IDTypeRequiredDefaultNotes
Customercustomer_selectSelect dropdownYes— Select Customer —Populated from /api/customers
Add Customeradd_customerButtonNoN/AOpens new customer form
Invoice Toinvoice_to_customer / invoice_to_third_partyRadioNocustomerOptions: customer, third_party
Third Party Customerthird_party_customerSelect dropdownConditionalEmptyShown when invoice_to = third_party
FieldHTML IDTypeRequiredDefaultNotes
Reference NumberN/ATextNoEmptyMax 500 chars
Documentsoperations-file-inputFile (multiple)NoN/APDF, JPG, PNG, DOC, DOCX. Max 10MB each. Drag & drop
Vehicle Typevehicle_typeSelectConditionalEmptyShown for hourly hire
Hours Requiredhours_requiredNumberConditional1For hourly hire bookings
FieldHTML IDTypeRequiredDefaultNotes
Job Typejob_typeHidden + Button groupYesftlLTL, FTL, Empty Run, Towhaul
Activity Typeactivity_*RadioYespickup_and_deliveryControls tab visibility
Service Levelselected_service_levelHidden + Button groupNoEmptyFrom /api/service-levels
FieldHTML IDTypeRequiredDefaultNotes
Vehicle Configbooking-vehicle-configSelectConditional— No vehicle selected —Required for FTL/Towhaul/Empty Run
FieldHTML IDTypeRequiredDefaultNotes
Load Typeunified-load-typeSelectConditional— Select —Controls field visibility
Selected Load Typeselected_load_typeHiddenNoEmptyfreight, pallet, container, special

First row fields:

FieldHTML IDTypeRequiredDefaultNotes
Quantityfirst-row-qtyNumberNo1min=1
Lengthfirst-row-lengthDecimalNoEmptyCM, step=0.01
Widthfirst-row-widthDecimalNoEmptyCM, step=0.01
Heightfirst-row-heightDecimalNoEmptyCM, step=0.01
Weightfirst-row-weightDecimalNoEmptyKG, step=0.01

Additional rows added dynamically via “Add Item” button (.freight-item-row class). Each row has: type select, qty, length, width, height, weight, delete button.

FieldHTML IDTypeRequiredDefaultNotes
Quantitypallet-qtyNumberNo1min=1
Lengthpallet-length-cmDecimalNo120CM, step=0.1
Widthpallet-width-cmDecimalNo120CM, step=0.1
Heightpallet-height-cmDecimalNo275CM, step=0.1
Total Weightpallet-total-weightDecimalNoEmptyKG, step=0.01
Pallet BrandpalletTypeRadioRadioNostandardstandard, chep, loscam

Hire Account Details (shown when CHEP or LOSCAM selected):

FieldHTML IDTypeRequiredDefaultNotes
Transaction Typehire-transaction-typeSelectNotransfertransfer, exchange, one-way
Docket Numberhire-docket-numberTextNoEmptyCHEP/LOSCAM docket ref
Sender Accounthire-sender-accountTextNoEmptySender account number
Receiver Accounthire-receiver-accountTextNoEmptyReceiver account number
FieldHTML IDTypeRequiredDefaultNotes
Data Sourcetowhaul-manual / towhaul-registryRadioNomanualManual Entry or From Client Registry
Saved Assettowhaul-saved-assetSelectConditionalEmptyShown when registry selected
Categorytowhaul-categorySelectYes— Select —ROAD_TRAILER, HEAVY_MACHINERY, VEHICLE, RECREATIONAL
Subcategorytowhaul-subcategoryTextNoEmptye.g. Excavator, Skel Trailer
Nicknametowhaul-nicknameTextNoEmptye.g. Yellow Cat 320
Make/Modeltowhaul-makemodelTextYesEmptye.g. Caterpillar 320D
VINtowhaul-vinTextNoEmptyVIN or Serial Number
Regotowhaul-regoTextNoEmptyRegistration plate
Couplingtowhaul-couplingSelectYes— Select —KINGPIN, PINTLE_HOOK, BALL_50MM, DRAWBAR, NONE
Brakingtowhaul-brakingSelectYes— Select —AIR_LINES, ELECTRIC, HYDRAULIC, NONE
Lengthtowhaul-lengthNumberNo0MM
Widthtowhaul-widthNumberNo0MM
Heighttowhaul-heightNumberNo0MM
Weighttowhaul-weightNumberYes0KG

Display Summaries:

  • #total-cubic-weight - Total Cubic Weight
  • #chargeable-weight - Chargeable Weight
  • #total-pallets - Total Pallets
  • #total-floor-spaces - Total Floor Spaces
  • #total-pallet-weight - Total Pallet Weight

3.5 Pickup / Delivery Tabs (Dynamic, Repeatable)

Section titled “3.5 Pickup / Delivery Tabs (Dynamic, Repeatable)”

Fields use array naming: pickup[N][field_name] and delivery[N][field_name].

FieldName PatternTypeRequiredNotes
Company Namepickup[N][company_name]TextYesSender/Receiver company
Contact Personpickup[N][contact_person]TextNoContact name
Addresspickup[N][address]Text + Google AutocompleteYesGoogle Places autocomplete
Localitypickup[N][locality]HiddenNoSuburb (from autocomplete)
Postal Codepickup[N][postal_code]HiddenNoPostcode (from autocomplete)
Statepickup[N][state]HiddenNoState (from autocomplete)
Zone IDpickup[N][zone_id]HiddenNoZone ID (from zone lookup)
Emailpickup[N][email]EmailNoContact email
Phonepickup[N][phone]TelNoContact phone
Opening Timepickup[N][opening_time]TimeNoHH:MM
Closing Timepickup[N][closing_time]TimeNoHH:MM
FieldName PatternTypeRequiredNotes
Datepickup[N][date]DateYesYYYY-MM-DD
Time Frompickup[N][time_from]TimeNoHH:MM
Time Topickup[N][time_to]TimeNoHH:MM

Handling Options (Button toggles with hidden inputs)

Section titled “Handling Options (Button toggles with hidden inputs)”
FieldName PatternTypeDefaultAddon Binding
Residentialpickup[N][residential]HiddenEmptypickup_residential
Tailgatepickup[N][tailgate]HiddenEmptypickup_tailgate
Manual Handlingpickup[N][manual_handling]HiddenEmptypickup_manual_handling
Time Slotpickup[N][time_slot]HiddenEmptypickup_time_slot
FieldName PatternTypeRequiredNotes
Instructionspickup[N][special_instructions]TextareaNoMax 500 chars

Delivery tabs follow the same structure with delivery[N][...] naming.

Shown only when activity_type = local_start. Prefix: local_start_*.

FieldHTML IDTypeRequiredNotes
Company Namelocal_start_company_nameTextNoSite/company name
Street Numberlocal_start_street_numberTextNo
Streetlocal_start_streetTextNo
Suburblocal_start_suburbTextNo
Statelocal_start_stateTextNo
Postcodelocal_start_postcodeTextNo
Zone IDlocal_start_zone_idHiddenNo
Opening Timelocal_start_opening_timeTimeNo
Closing Timelocal_start_closing_timeTimeNo
Contact Namelocal_start_contact_nameTextNo
Contact Numberlocal_start_contact_numberTextNo
Contact Emaillocal_start_contact_emailEmailNo
Noteslocal_start_notesTextareaNo
FieldHTML IDTypeRequiredDefaultNotes
Charge By Methodcharge_by_methodSelectYesLoading…From rate calculation methods API
Charge By Unitscharge_by_unitsNumberNo0min=0, step=0.01
Base Pricebase_priceCurrencyYes0.00
Flat Rateflat-rate-inputCurrency (read-only)No0.00Auto-computed from rate engine
Fuel Levyfuel-levy-inputPercentageNo20Fuel surcharge %
Tailgate Feeservice-fee-inputCurrencyNo0
Total Costtotal-costDisplay (span)No0.00Calculated

Hidden fields for rate calculation: sender_address, receiver_address, pickup_locality, delivery_locality, pickup_postal_code, delivery_postal_code, pickup_administrative_area_level_1, delivery_administrative_area_level_1, pickup_zone_id, delivery_zone_id, selected_service_level, chargeable-weight.


window._addonCache = {} // Keyed by ui_binding
window._addonCacheAll = [] // All applicable addons
window._activeAddonItems = {} // Keyed by addon_id -> line item data
window._computedEngineTotal = null // Last computed rate from API
FunctionPurpose
toggleHandlingOption(button)Toggle handling option buttons (residential, tailgate, etc.). Toggles active state, sets hidden input, calls addAddonByBinding() or removeAddonByBinding(), updates total cost
addAddonByBinding(binding)Add addon by UI binding string. Looks up in cache, creates line item, renders it
removeAddonByBinding(binding)Remove addon by UI binding. Deletes from _activeAddonItems, removes DOM element
renderAddonLineItem(item)Render addon as styled line item in surcharges list. Shows trigger mode badge, label, amount, taxable status
removeManualAddon(addonId)Remove a manually added addon
FunctionPurpose
handleLoadTypeChange(value)Handle main load type dropdown change. Hides all conditional fields, shows fields for selected type, updates selected_load_type hidden input
addFreightRow()Add additional freight item row with all input fields
clearLoadTypeForm()Clear all load type fields and reset selection
handleBrandChange(value)Handle pallet brand selection (Standard/CHEP/LOSCAM). Shows/hides hire account details
toggleHireDetails(event)Toggle hire account details section
FunctionPurpose
calculateRatesFromAPI(showModalLog)Primary rate calculation. Validates fields, collects data, POSTs to /api/rate-entries/compute-rate, falls back to /api/rate-entries/check-entry. When showModalLog=true shows detailed modal; when false auto-fills silently
applyComputedRate()Apply lastComputedRate to form. Sets base_price and flat_rate fields, stores in _computedEngineTotal, calls updateTotalCost()
updateTotalCost()Calculate and display total. Processes fuel levy, tailgate, all active addons (fixed/percentage), handles per-unit addons, applies taxes, updates #total-cost display
clearPricing()Reset all pricing fields to defaults
FunctionPurpose
fillInAddress(place, type)Populate address fields from Google Places result. Extracts components, populates form, calls zone resolution API
fillInLocalStartAddress(place)Populate local start address from Google Places. Also extracts business name
FunctionPurpose
collectPackageData()Collect all items/packages. Checks load type, collects freight/pallet/container data, falls back to legacy tables. Returns array of package objects
collectAddressData(type)Collect address fields into structured object. Parameter: ‘pickup’ or ‘delivery’
FunctionPurpose
submitBooking()Collect all form data, build JSON payload, POST to /api/connotes/, handle success/error responses
FunctionPurpose
_getBookingQuantityForUnit(unitType)Calculate total quantity for a unit type. Handles: pallet/item (sum qty), kg (sum weight), km (distance), cubic_meter (total CBM)
logToRatesModal(html, scrollToBottom)Append HTML to rates computation log modal

Primary rate calculation endpoint.

Request:

{
"rate_card_id": 123,
"customer_id": 456,
"origin_zone_id": 1,
"destination_zone_id": 2,
"service_level_id": 3,
"items": [
{
"packaging_type": "Carton",
"quantity": 2,
"length_cm": 60,
"width_cm": 40,
"height_cm": 30,
"weight_kg": 25
}
]
}

Response:

{
"success": true,
"data": {
"base_rate": 150.00,
"flat_rate": 150.00,
"final_total": 195.00,
"rate_calculation_method": "Per Pallet",
"minimum_charge": 50.00
}
}

Fallback endpoint when compute-rate returns no results. Similar request/response structure.

Resolves address to zone ID.

Request:

{
"suburb": "Parramatta",
"postcode": "2150",
"state": "NSW"
}

Response:

{
"found": true,
"zone_id": 42
}
EndpointMethodUsed For
/api/service-levelsGETService type button group
/api/rate-entries/rate-calculation-methodsGETCharge By dropdown
/api/transport-equipment/configurationsGETVehicle type dropdown
/api/pallet-typesGETPallet dimensions & pricing
/api/container-typesGETContainer selection
/api/packaging-typesGETFreight type selection
/api/customersGETCustomer dropdown (loaded server-side)

GET /api/addons/for-context?form_target=Booking&customer_id=X&rate_card_id=Y

Section titled “GET /api/addons/for-context?form_target=Booking&customer_id=X&rate_card_id=Y”

Response:

{
"success": true,
"data": [
{
"id": 1,
"name": "Residential Surcharge",
"alias": "residential",
"addon_type": "Surcharge",
"value_type": "Fixed amount",
"default_value": 25.00,
"customer_override_value": null,
"ui_binding": "pickup_residential",
"trigger_mode": "automatic",
"calculation_order": 1,
"applies_on": "subtotal",
"application_scope": "per_booking",
"is_taxable": true,
"is_tax_addon": false,
"tax_code": null,
"tax_inclusive": false,
"client_visible": true
}
]
}

Request:

{
"customer_id": 456,
"invoice_to": "customer",
"third_party_customer_id": null,
"job_type_id": 2,
"activity_type_id": 1,
"service_level_id": 3,
"special_instructions": "Handle with care",
"pickup_address": {
"address_line1": "123 Main St",
"suburb": "Parramatta",
"state": "NSW",
"postcode": "2150",
"contact_name": "John Smith",
"contact_phone": "0412345678",
"contact_email": "john@example.com",
"company_name": "Acme Corp",
"country": "Australia"
},
"delivery_address": { },
"local_start_address": null,
"load_type": "freight",
"packages": [
{
"packaging_type": "Carton",
"quantity": 2,
"length_cm": 60,
"width_cm": 40,
"height_cm": 30,
"weight_kg": 25
}
],
"base_rate": 150.00,
"flat_rate": 150.00,
"computed_total": 195.00,
"fuel_levy_percentage": 20,
"tailgate_fee": 0,
"is_tailgate_pickup": false,
"is_tailgate_delivery": false,
"gst_rate": 10,
"addon_line_items": [
{
"addon_id": 1,
"alias": "residential",
"name": "Residential Surcharge",
"value_type": "Fixed amount",
"raw_value": "25",
"amount": 25.00,
"applies_on": "subtotal",
"client_visible": true
}
]
}

Response:

{
"success": true,
"message": "Booking created successfully",
"booking_id": 789,
"connote_number": "CON-2026-000789"
}

1. User fills in required fields:
- Customer (required)
- Pickup address (required) → auto-resolves zone via /api/rate-entries/check-zone
- Delivery address (required) → auto-resolves zone
- Load type and items (recommended)
- Service level (optional)
2. Trigger: "Auto Calculate" button click or auto-trigger
3. calculateRatesFromAPI()
├─ Validate required fields present
├─ Collect booking data (customer, addresses, items)
├─ POST /api/rate-entries/compute-rate
│ ├─ Success → store in lastComputedRate, show Apply button
│ └─ No results → fallback to POST /api/rate-entries/check-entry
└─ Show computation modal with detailed log
4. User clicks "Apply Rate"
5. applyComputedRate()
├─ Set base_price field
├─ Set flat_rate field
├─ Store in _computedEngineTotal
└─ Call updateTotalCost()
totalWeight = SUM(quantity * weight for each item)
cubicWeight = SUM(L * W * H / 5000 for each item) // Australian standard
chargeableWeight = MAX(totalWeight, cubicWeight)

Updated in real-time as items change. Displayed in #total-cubic-weight and #chargeable-weight.

subtotal = base_price (from rate engine or manual)
For each addon in _activeAddonItems (ordered by calculation_order):
if value_type == "percentage":
amount = subtotal * (raw_value / 100)
else:
amount = raw_value (fixed)
if application_scope == "per_unit":
amount *= unitQuantity
runningTotal += amount
For each tax addon (is_tax_addon == true):
taxableSubtotal = sum of non-tax, taxable addon amounts
if tax_inclusive:
display as "(X% incl.)"
else:
tax = taxableSubtotal * (tax_rate / 100)
runningTotal += tax
final = runningTotal + fuel_levy + tailgate_fee

1. User clicks "Save Booking"
2. Button disabled, loading state shown
3. submitBooking()
├─ Collect all form data:
│ ├─ Customer info
│ ├─ Pickup/Delivery addresses & times
│ ├─ All items/packages (via collectPackageData())
│ ├─ Pricing fields
│ └─ Active addons
├─ Build JSON payload
└─ Validate required fields
4. POST /api/connotes/
5. Backend creates:
├─ Connote record
├─ Address records
├─ Item/package records
├─ Pricing records
└─ Addon line items
6. Response handling:
├─ Success → show success alert with connote number,
│ offer "View Details" / "Create Another"
└─ Error → display validation errors, re-enable Save button

Client-side: Customer (required), Pickup Address (required), Delivery Address (required), Base Price (required when not auto-computed), Job Type (required).

Server-side (in /api/connotes/): Customer fields, address structure, package data, pricing fields, addon data structure.


Job TypeEffect
FTLVehicle required, Load details shown
LTLItem entry required, Packaging shown
Empty RunVehicle required
TowhaulSpecial towhaul form (coupling, braking, dimensions)
HourlyHours + Vehicle type fields shown
Activity TypeVisible Tabs
PickupLoad Details, Pickup
Pickup and DeliveryLoad Details, Pickup, Delivery
Delivery OnlyLoad Details, Delivery
Local StartLoad Details, Local Start
Load TypeFields ShownHidden Value
FreightQty, L, W, H, Weight (multiple rows)selected_load_type=freight
PalletQty, L, W, H, Weight, Brand radioselected_load_type=pallet
ContainerContainer type dropdownselected_load_type=container
Special/TowhaulAsset classification, identity, technical specsselected_load_type=special
BrandHire Details Shown
StandardNo
CHEPYes - Transaction type, Docket #, Sender/Receiver accounts
LOSCAMYes - Transaction type, Docket #, Sender/Receiver accounts

8.5 Handling Option Buttons Trigger Addons

Section titled “8.5 Handling Option Buttons Trigger Addons”
ButtonUI BindingAuto-added Addon
Residential Addresspickup_residential / delivery_residentialResidential surcharge
Tailgate Requiredpickup_tailgate / delivery_tailgateTailgate surcharge
Manual Handlingpickup_manual_handling / delivery_manual_handlingManual handling surcharge
Time Slot Requiredpickup_time_slot / delivery_time_slotTime slot surcharge

LibraryVersionSourcePurpose
jQuery3.6.0CDNDOM manipulation, AJAX
Bootstrap5.3+CDN (via base template)Modals, buttons, layout, grid
FlatpickrLatestCDNDate/time picker inputs
SortableJS1.15.0CDNDrag-and-drop tab reordering
Google Maps APILatestVia google_maps_api_keyAddress autocomplete (AU only)
Bootstrap IconsLatestCDNIcons
FilePurpose
static/js/create_booking_skeleton.jsLoading skeleton screens
static/js/booking-tabs-manager.jsDynamic tab creation and management for multiple pickups/deliveries

The majority of form logic is in <script> blocks within the template (~3,500+ lines):

  • Addon management (~200 lines)
  • Load type handling (~500 lines)
  • Rate calculation (~1,000 lines)
  • Form submission (~1,000 lines)
  • Address handling (~400 lines)
  • Google Maps init (~100 lines)
  • Addon system initialization (~300 lines)

ComponentPath
Templatetemplates/portals/operations/create_booking.html
Route Handlerportals/operations/routes.py (line ~4116)
Booking Tabs Managerstatic/js/booking-tabs-manager.js
Skeleton Loaderstatic/js/create_booking_skeleton.js
Rate Entries APIapi/rate_entries_api.py
Connotes APIapi/connotes_api.py
Addons APIapi/addons_api.py
Service Levels APIapi/service_levels_api.py
Customers APIapi/customers_api.py
Transport Equipment APIapi/transport_equipment_api.py
Operations Base Templatetemplates/portals/operations/base.html

  • All form data sanitized on backend
  • CSRF protection via Flask-WTF
  • Google Maps API key validated server-side
  • Rate calculation validated on backend (client cannot set arbitrary rates without server confirmation)
  • File upload restricted to safe types: PDF, JPG, PNG, DOC, DOCX (max 10MB each)
  • @login_required on the route handler