@fleetbase/fleetops-engine 0.6.37 → 0.6.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/components/activity/form.hbs +2 -12
- package/addon/components/activity/logic-builder.hbs +1 -1
- package/addon/components/custom-entity/form.hbs +4 -20
- package/addon/components/device/form.hbs +3 -15
- package/addon/components/device/panel-header.hbs +13 -2
- package/addon/components/driver/details.hbs +33 -2
- package/addon/components/driver/form.hbs +42 -0
- package/addon/components/driver/panel-header.hbs +8 -1
- package/addon/components/driver/schedule.hbs +115 -76
- package/addon/components/driver/schedule.js +349 -157
- package/addon/components/driver-onboard-settings.hbs +2 -8
- package/addon/components/entity-field-editing-settings.hbs +2 -5
- package/addon/components/equipment/card.hbs +49 -0
- package/addon/components/equipment/card.js +6 -0
- package/addon/components/equipment/details.hbs +83 -44
- package/addon/components/equipment/form.hbs +111 -41
- package/addon/components/equipment/form.js +78 -10
- package/addon/components/equipment/panel-header.hbs +36 -0
- package/addon/components/equipment/panel-header.js +2 -0
- package/addon/components/fleet/driver-listing.hbs +3 -1
- package/addon/components/fleet/vehicle-listing.hbs +4 -7
- package/addon/components/fleet-panel/vehicle-listing.hbs +1 -6
- package/addon/components/fuel-report/form.hbs +1 -5
- package/addon/components/layout/fleet-ops-sidebar.hbs +40 -36
- package/addon/components/layout/fleet-ops-sidebar.js +61 -10
- package/addon/components/maintenance/cost-panel.hbs +176 -0
- package/addon/components/maintenance/cost-panel.js +241 -0
- package/addon/components/maintenance/details.hbs +123 -60
- package/addon/components/maintenance/form.hbs +138 -78
- package/addon/components/maintenance/form.js +131 -6
- package/addon/components/maintenance/panel-header.hbs +31 -0
- package/addon/components/maintenance/panel-header.js +2 -0
- package/addon/components/maintenance-schedule/details.hbs +260 -0
- package/addon/components/maintenance-schedule/details.js +158 -0
- package/addon/components/maintenance-schedule/form.hbs +287 -0
- package/addon/components/maintenance-schedule/form.js +199 -0
- package/addon/components/map/container.hbs +1 -1
- package/addon/components/map/drawer/device-event-listing.hbs +1 -1
- package/addon/components/map/drawer/driver-listing.hbs +1 -6
- package/addon/components/map/drawer/place-listing.hbs +1 -6
- package/addon/components/map/drawer/vehicle-listing.hbs +1 -6
- package/addon/components/map/drawer.hbs +8 -1
- package/addon/components/map/leaflet-live-map.hbs +2 -1
- package/addon/components/map/toolbar/visibility-control-panel.hbs +1 -1
- package/addon/components/map/toolbar.hbs +4 -29
- package/addon/components/modals/add-driver-shift.hbs +155 -0
- package/addon/components/modals/add-driver-shift.js +210 -0
- package/addon/components/modals/bulk-assign-orders.hbs +67 -0
- package/addon/components/modals/driver-shift.hbs +43 -0
- package/addon/components/modals/driver-shift.js +56 -0
- package/addon/components/modals/entity-form.hbs +5 -28
- package/addon/components/modals/orchestrator-import.hbs +351 -0
- package/addon/components/modals/orchestrator-import.js +807 -0
- package/addon/components/modals/order-config-new-status.hbs +1 -5
- package/addon/components/modals/scheduling-conflict.hbs +47 -0
- package/addon/components/modals/scheduling-conflict.js +53 -0
- package/addon/components/modals/send-work-order.hbs +91 -0
- package/addon/components/modals/send-work-order.js +3 -0
- package/addon/components/modals/service-area-form.hbs +1 -6
- package/addon/components/modals/set-driver-availability.hbs +50 -0
- package/addon/components/modals/set-driver-availability.js +57 -0
- package/addon/components/modals/update-order-activity.hbs +13 -9
- package/addon/components/modals/user-form.hbs +1 -5
- package/addon/components/modals/vehicle-form.hbs +17 -102
- package/addon/components/modals/vendor-form.hbs +15 -82
- package/addon/components/orchestrator/card-fields-settings.hbs +76 -0
- package/addon/components/orchestrator/card-fields-settings.js +134 -0
- package/addon/components/orchestrator/order-pool.hbs +264 -0
- package/addon/components/orchestrator/order-pool.js +394 -0
- package/addon/components/orchestrator/phase-builder.hbs +162 -0
- package/addon/components/orchestrator/phase-builder.js +162 -0
- package/addon/components/orchestrator/plan-viewer.hbs +278 -0
- package/addon/components/orchestrator/plan-viewer.js +342 -0
- package/addon/components/orchestrator/resource-panel.hbs +301 -0
- package/addon/components/orchestrator/resource-panel.js +106 -0
- package/addon/components/orchestrator-workbench.hbs +318 -0
- package/addon/components/orchestrator-workbench.js +1087 -0
- package/addon/components/order/details/custom-fields.hbs +10 -1
- package/addon/components/order/details/detail.hbs +37 -2
- package/addon/components/order/details/detail.js +0 -1
- package/addon/components/order/details/integrated-vendor-details.hbs +4 -4
- package/addon/components/order/details/notes.hbs +1 -7
- package/addon/components/order/details/route.hbs +1 -5
- package/addon/components/order/form/details.hbs +44 -10
- package/addon/components/order/form/details.js +57 -0
- package/addon/components/order/form/notes.hbs +1 -7
- package/addon/components/order/form/payload.hbs +1 -7
- package/addon/components/order/form/route.hbs +3 -15
- package/addon/components/order/header.hbs +1 -7
- package/addon/components/order/route-editor.hbs +4 -25
- package/addon/components/order/schedule-card.hbs +102 -95
- package/addon/components/order/schedule-card.js +8 -3
- package/addon/components/order-config-manager/details.hbs +2 -10
- package/addon/components/order-config-manager/entities.hbs +1 -7
- package/addon/components/order-config-manager.hbs +1 -6
- package/addon/components/part/card.hbs +49 -0
- package/addon/components/part/card.js +6 -0
- package/addon/components/part/details.hbs +102 -56
- package/addon/components/part/form.hbs +131 -56
- package/addon/components/part/form.js +78 -11
- package/addon/components/part/panel-header.hbs +36 -0
- package/addon/components/part/panel-header.js +2 -0
- package/addon/components/place/form.hbs +1 -7
- package/addon/components/sensor/details.hbs +1 -1
- package/addon/components/sensor/form.hbs +5 -3
- package/addon/components/sensor/panel-header.hbs +8 -1
- package/addon/components/service-area/form.hbs +1 -6
- package/addon/components/service-rate/form.hbs +12 -60
- package/addon/components/telematic/form.hbs +6 -2
- package/addon/components/vehicle/details/maintenance-history.hbs +42 -0
- package/addon/components/vehicle/details/maintenance-history.js +32 -0
- package/addon/components/vehicle/details/schedules.hbs +40 -0
- package/addon/components/vehicle/details/schedules.js +32 -0
- package/addon/components/vehicle/details/work-orders.hbs +42 -0
- package/addon/components/vehicle/details/work-orders.js +32 -0
- package/addon/components/vehicle/details.hbs +30 -0
- package/addon/components/vehicle/form.hbs +39 -0
- package/addon/components/vehicle/panel-header.hbs +19 -49
- package/addon/components/warranty/details.hbs +3 -2
- package/addon/components/warranty/form.hbs +3 -17
- package/addon/components/work-order/details.hbs +135 -40
- package/addon/components/work-order/form.hbs +178 -45
- package/addon/components/work-order/form.js +197 -4
- package/addon/components/work-order/panel-header.hbs +31 -0
- package/addon/components/work-order/panel-header.js +2 -0
- package/addon/controllers/connectivity/devices/index/details.js +1 -1
- package/addon/controllers/maintenance/equipment/index/details/index.js +0 -1
- package/addon/controllers/maintenance/equipment/index/details.js +36 -1
- package/addon/controllers/maintenance/equipment/index/edit.js +56 -1
- package/addon/controllers/maintenance/equipment/index/new.js +32 -1
- package/addon/controllers/maintenance/equipment/index.js +127 -113
- package/addon/controllers/maintenance/maintenances/index/details/index.js +3 -0
- package/addon/controllers/maintenance/maintenances/index/details.js +54 -0
- package/addon/controllers/maintenance/maintenances/index/edit.js +68 -0
- package/addon/controllers/maintenance/maintenances/index/new.js +34 -0
- package/addon/controllers/maintenance/maintenances/index.js +191 -0
- package/addon/controllers/maintenance/parts/index/details/index.js +0 -1
- package/addon/controllers/maintenance/parts/index/details.js +36 -1
- package/addon/controllers/maintenance/parts/index/edit.js +56 -1
- package/addon/controllers/maintenance/parts/index/new.js +32 -1
- package/addon/controllers/maintenance/parts/index.js +135 -113
- package/addon/controllers/maintenance/schedules/index/details.js +115 -0
- package/addon/controllers/maintenance/schedules/index/edit.js +41 -0
- package/addon/controllers/maintenance/schedules/index/new.js +33 -0
- package/addon/controllers/maintenance/schedules/index.js +280 -0
- package/addon/controllers/maintenance/work-orders/index/details.js +41 -1
- package/addon/controllers/maintenance/work-orders/index/edit.js +67 -1
- package/addon/controllers/maintenance/work-orders/index/new.js +43 -1
- package/addon/controllers/maintenance/work-orders/index.js +105 -113
- package/addon/controllers/management/drivers/index/details.js +6 -1
- package/addon/controllers/management/vehicles/index/details.js +65 -0
- package/addon/controllers/management/vehicles/index.js +18 -0
- package/addon/controllers/operations/orchestrator.js +10 -0
- package/addon/controllers/operations/orders/index/details.js +5 -0
- package/addon/controllers/operations/orders/index.js +6 -0
- package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
- package/addon/controllers/operations/scheduler/index.js +799 -275
- package/addon/controllers/operations/scheduler.js +21 -0
- package/addon/controllers/settings/orchestrator.js +70 -0
- package/addon/controllers/settings/scheduling.js +155 -0
- package/addon/extension.js +74 -1
- package/addon/instance-initializers/register-vroom-allocation.js +27 -0
- package/addon/models/maintenance-schedule.js +61 -0
- package/addon/routes/maintenance/equipment/index/details.js +27 -1
- package/addon/routes/maintenance/equipment/index/edit.js +27 -1
- package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
- package/addon/routes/maintenance/maintenances/index/details.js +29 -0
- package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
- package/addon/routes/maintenance/maintenances/index/new.js +3 -0
- package/addon/routes/maintenance/maintenances/index.js +23 -0
- package/addon/routes/maintenance/maintenances.js +3 -0
- package/addon/routes/maintenance/parts/index/details.js +27 -1
- package/addon/routes/maintenance/parts/index/edit.js +27 -1
- package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
- package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
- package/addon/routes/maintenance/schedules/index/details.js +25 -0
- package/addon/routes/maintenance/schedules/index/edit.js +25 -0
- package/addon/routes/maintenance/schedules/index/new.js +2 -0
- package/addon/routes/maintenance/schedules/index.js +21 -0
- package/addon/routes/maintenance/schedules.js +2 -0
- package/addon/routes/maintenance/work-orders/index/details.js +27 -1
- package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
- package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
- package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
- package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
- package/addon/routes/operations/orchestrator.js +23 -0
- package/addon/routes/operations/orders/index/details/virtual.js +23 -0
- package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
- package/addon/routes/operations/scheduler/index.js +48 -26
- package/addon/routes/operations/scheduler.js +14 -1
- package/addon/routes/settings/orchestrator.js +27 -0
- package/addon/routes/settings/scheduling.js +3 -0
- package/addon/routes.js +33 -1
- package/addon/services/driver-actions.js +40 -7
- package/addon/services/driver-scheduling.js +4 -1
- package/addon/services/equipment-actions.js +15 -5
- package/addon/services/leaflet-map-manager.js +14 -6
- package/addon/services/maintenance-actions.js +17 -14
- package/addon/services/maintenance-schedule-actions.js +118 -0
- package/addon/services/orchestration-engine-interface.js +49 -0
- package/addon/services/orchestration-engine.js +74 -0
- package/addon/services/order-actions.js +15 -0
- package/addon/services/order-allocation.js +116 -0
- package/addon/services/part-actions.js +12 -2
- package/addon/services/scheduling.js +316 -0
- package/addon/services/vehicle-actions.js +70 -7
- package/addon/services/vroom-allocation-engine.js +45 -0
- package/addon/services/work-order-actions.js +80 -0
- package/addon/styles/fleetops-engine.css +1658 -0
- package/addon/templates/analytics/reports/index/edit.hbs +1 -1
- package/addon/templates/analytics/reports/index/new.hbs +1 -1
- package/addon/templates/application.hbs +6 -1
- package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
- package/addon/templates/connectivity/devices.hbs +0 -1
- package/addon/templates/connectivity/events/index/details.hbs +0 -1
- package/addon/templates/connectivity/events.hbs +0 -1
- package/addon/templates/connectivity/sensors.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
- package/addon/templates/connectivity/telematics.hbs +0 -1
- package/addon/templates/connectivity/tracking.hbs +0 -1
- package/addon/templates/connectivity.hbs +0 -1
- package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
- package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
- package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
- package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
- package/addon/templates/maintenance/equipment/index.hbs +48 -13
- package/addon/templates/maintenance/equipment.hbs +0 -1
- package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
- package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
- package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
- package/addon/templates/maintenance/maintenances/index.hbs +14 -0
- package/addon/templates/maintenance/maintenances.hbs +1 -0
- package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
- package/addon/templates/maintenance/parts/index/details.hbs +15 -2
- package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
- package/addon/templates/maintenance/parts/index/new.hbs +1 -2
- package/addon/templates/maintenance/parts/index.hbs +48 -13
- package/addon/templates/maintenance/parts.hbs +0 -1
- package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
- package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
- package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
- package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
- package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
- package/addon/templates/maintenance/schedules/index.hbs +40 -0
- package/addon/templates/maintenance/schedules.hbs +1 -0
- package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
- package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
- package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
- package/addon/templates/maintenance/work-orders.hbs +0 -1
- package/addon/templates/management/contacts/customers/edit.hbs +1 -2
- package/addon/templates/management/contacts/customers/new.hbs +1 -2
- package/addon/templates/management/contacts/customers.hbs +1 -1
- package/addon/templates/management/contacts/index/edit.hbs +1 -2
- package/addon/templates/management/contacts/index/new.hbs +1 -2
- package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
- package/addon/templates/management/drivers/index/edit.hbs +1 -2
- package/addon/templates/management/drivers/index/new.hbs +1 -2
- package/addon/templates/management/fleets/index/edit.hbs +1 -2
- package/addon/templates/management/fleets/index/new.hbs +1 -2
- package/addon/templates/management/fleets/index.hbs +1 -2
- package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
- package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
- package/addon/templates/management/fuel-reports/index.hbs +1 -2
- package/addon/templates/management/issues/index/edit.hbs +1 -2
- package/addon/templates/management/issues/index/new.hbs +1 -2
- package/addon/templates/management/issues/index.hbs +1 -2
- package/addon/templates/management/places/index/details/activity.hbs +0 -1
- package/addon/templates/management/places/index/details/comments.hbs +0 -1
- package/addon/templates/management/places/index/details/documents.hbs +0 -1
- package/addon/templates/management/places/index/details/map.hbs +0 -1
- package/addon/templates/management/places/index/details/operations.hbs +0 -1
- package/addon/templates/management/places/index/details/performance.hbs +0 -1
- package/addon/templates/management/places/index/details/rules.hbs +0 -1
- package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
- package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
- package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
- package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -1
- package/addon/templates/management/vehicles/index/edit.hbs +1 -2
- package/addon/templates/management/vehicles/index/new.hbs +1 -2
- package/addon/templates/management/vendors/index/edit.hbs +1 -2
- package/addon/templates/management/vendors/index/new.hbs +1 -2
- package/addon/templates/management/vendors/index.hbs +1 -2
- package/addon/templates/management/vendors/integrated.hbs +1 -2
- package/addon/templates/operations/orchestrator.hbs +1 -0
- package/addon/templates/operations/orders/index/details/virtual.hbs +1 -0
- package/addon/templates/operations/orders/index.hbs +6 -1
- package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
- package/addon/templates/operations/scheduler/index.hbs +147 -88
- package/addon/templates/operations/scheduler.hbs +7 -1
- package/addon/templates/settings/avatars.hbs +1 -1
- package/addon/templates/settings/orchestrator.hbs +65 -0
- package/addon/templates/settings/payments/index.hbs +1 -5
- package/addon/templates/settings/scheduling.hbs +82 -0
- package/addon/utils/create-full-calendar-event-from-order.js +52 -14
- package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
- package/addon/utils/fleet-ops-options.js +254 -0
- package/addon/utils/route-colors.js +99 -0
- package/addon/utils/to-calendar-date.js +70 -0
- package/app/components/driver/schedule.js +1 -0
- package/app/components/maintenance/cost-panel.js +1 -0
- package/app/components/maintenance/panel-header.js +1 -0
- package/app/components/maintenance-schedule/details.js +1 -0
- package/app/components/maintenance-schedule/form.js +1 -0
- package/app/components/modals/add-driver-shift.js +1 -0
- package/app/components/modals/bulk-assign-orders.js +1 -0
- package/app/components/modals/driver-shift.js +1 -0
- package/app/components/modals/orchestrator-import.js +1 -0
- package/app/components/modals/scheduling-conflict.js +1 -0
- package/app/components/modals/send-work-order.js +1 -0
- package/app/components/modals/set-driver-availability.js +1 -0
- package/app/components/orchestrator/card-fields-settings.js +1 -0
- package/app/components/orchestrator/order-pool.js +1 -0
- package/app/components/orchestrator/phase-builder.js +1 -0
- package/app/components/orchestrator/plan-viewer.js +1 -0
- package/app/components/orchestrator/resource-panel.js +1 -0
- package/app/components/orchestrator-workbench.js +1 -0
- package/app/components/vehicle/details/maintenance-history.js +1 -0
- package/app/components/vehicle/details/schedules.js +1 -0
- package/app/components/vehicle/details/work-orders.js +1 -0
- package/app/controllers/operations/orchestrator.js +1 -0
- package/app/controllers/settings/orchestrator.js +1 -0
- package/app/controllers/settings/scheduling.js +1 -0
- package/app/routes/operations/orchestrator.js +1 -0
- package/app/routes/operations/orders/index/details/virtual.js +1 -0
- package/app/routes/settings/orchestrator.js +1 -0
- package/app/routes/settings/scheduling.js +1 -0
- package/app/services/maintenance-schedule-actions.js +1 -0
- package/app/services/orchestration-engine-interface.js +1 -0
- package/app/services/orchestration-engine.js +1 -0
- package/app/services/order-allocation.js +1 -0
- package/app/services/scheduling.js +1 -0
- package/app/services/vroom-allocation-engine.js +1 -0
- package/app/templates/operations/orders/index/details/virtual.js +1 -0
- package/app/templates/settings/scheduling.js +1 -0
- package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
- package/app/utils/route-colors.js +1 -0
- package/composer.json +5 -3
- package/extension.json +1 -1
- package/package.json +6 -5
- package/server/config/fleetops.php +20 -1
- package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
- package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
- package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
- package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
- package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
- package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
- package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
- package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
- package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +58 -0
- package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
- package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
- package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +41 -0
- package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
- package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
- package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
- package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
- package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
- package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
- package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
- package/server/src/Auth/Schemas/FleetOps.php +44 -0
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
- package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
- package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
- package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
- package/server/src/Http/Resources/v1/Driver.php +1 -0
- package/server/src/Http/Resources/v1/Maintenance.php +138 -0
- package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
- package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
- package/server/src/Http/Resources/v1/Order.php +7 -4
- package/server/src/Http/Resources/v1/Waypoint.php +7 -0
- package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
- package/server/src/Imports/EquipmentImport.php +32 -0
- package/server/src/Imports/MaintenanceImport.php +32 -0
- package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
- package/server/src/Imports/PartImport.php +32 -0
- package/server/src/Imports/WorkOrderImport.php +32 -0
- package/server/src/Jobs/ProcessAllocationJob.php +119 -0
- package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
- package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
- package/server/src/Mail/WorkOrderDispatched.php +58 -0
- package/server/src/Models/Asset.php +2 -2
- package/server/src/Models/Device.php +1 -1
- package/server/src/Models/Driver.php +82 -4
- package/server/src/Models/Equipment.php +62 -2
- package/server/src/Models/Maintenance.php +127 -9
- package/server/src/Models/MaintenanceSchedule.php +353 -0
- package/server/src/Models/Manifest.php +214 -0
- package/server/src/Models/ManifestStop.php +162 -0
- package/server/src/Models/Order.php +70 -0
- package/server/src/Models/OrderConfig.php +5 -2
- package/server/src/Models/Part.php +69 -3
- package/server/src/Models/Payload.php +7 -2
- package/server/src/Models/Place.php +1 -1
- package/server/src/Models/Sensor.php +1 -1
- package/server/src/Models/ServiceQuote.php +1 -1
- package/server/src/Models/Vehicle.php +20 -1
- package/server/src/Models/Warranty.php +1 -1
- package/server/src/Models/Waypoint.php +7 -1
- package/server/src/Models/WorkOrder.php +122 -12
- package/server/src/Notifications/DriverShiftChanged.php +110 -0
- package/server/src/Observers/WorkOrderObserver.php +107 -0
- package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
- package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
- package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
- package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
- package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +290 -0
- package/server/src/Providers/EventServiceProvider.php +7 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
- package/server/src/routes.php +65 -4
- package/translations/ar-ae.yml +44 -12
- package/translations/bg-bg.yaml +51 -10
- package/translations/en-us.yaml +444 -1
- package/translations/fr-fr.yaml +51 -10
- package/translations/mn-mn.yaml +51 -10
- package/translations/pt-br.yaml +51 -10
- package/translations/ru-ru.yaml +51 -10
- package/translations/vi-vn.yaml +48 -12
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Orchestration\Engines;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
8
|
+
use Illuminate\Support\Collection;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DriverAssignmentEngine.
|
|
12
|
+
*
|
|
13
|
+
* Greedy shift-aware engine for matching available drivers to vehicles that
|
|
14
|
+
* have planned orders (vehicle_assigned_uuid set, driver_assigned_uuid null).
|
|
15
|
+
*
|
|
16
|
+
* Matching priority:
|
|
17
|
+
* 1. Driver is online and on an active shift
|
|
18
|
+
* 2. Driver skills satisfy the required skills of the vehicle's planned orders
|
|
19
|
+
* 3. Driver is geographically closest to the vehicle's current location
|
|
20
|
+
*
|
|
21
|
+
* This engine does not use VROOM — it is a deterministic greedy pass.
|
|
22
|
+
* For V2, a VROOM-based multi-depot driver assignment can replace this.
|
|
23
|
+
*/
|
|
24
|
+
class DriverAssignmentEngine
|
|
25
|
+
{
|
|
26
|
+
/**
|
|
27
|
+
* Assign available drivers to vehicles with planned orders.
|
|
28
|
+
*
|
|
29
|
+
* @param Collection $orders Orders with vehicle_assigned_uuid set but no driver
|
|
30
|
+
* @param Collection $vehicles Vehicles to assign drivers to
|
|
31
|
+
* @param array $options Options: require_active_shift (bool, default true)
|
|
32
|
+
*
|
|
33
|
+
* @return array{assignments: array, unassigned: array, summary: array}
|
|
34
|
+
*/
|
|
35
|
+
public function assign(Collection $orders, Collection $vehicles, array $options = []): array
|
|
36
|
+
{
|
|
37
|
+
$requireActiveShift = $options['require_active_shift'] ?? false;
|
|
38
|
+
$respectSkills = $options['respect_skills'] ?? true;
|
|
39
|
+
|
|
40
|
+
// Collect all company drivers — online status and vehicle linkage are
|
|
41
|
+
// treated as soft preferences (scored), not hard filters.
|
|
42
|
+
$companyUuid = $orders->first()?->company_uuid;
|
|
43
|
+
$availableDrivers = Driver::where('company_uuid', $companyUuid)
|
|
44
|
+
->with(['scheduleItems'])
|
|
45
|
+
->get();
|
|
46
|
+
|
|
47
|
+
// Shift-awareness: if require_active_shift is explicitly set, filter
|
|
48
|
+
// to drivers that have an active shift right now. Drivers with NO
|
|
49
|
+
// schedule items at all are treated as always available — only drivers
|
|
50
|
+
// with a schedule but no active shift are excluded.
|
|
51
|
+
if ($requireActiveShift) {
|
|
52
|
+
$availableDrivers = $availableDrivers->filter(function (Driver $driver) {
|
|
53
|
+
$hasSchedule = $driver->scheduleItems && $driver->scheduleItems->isNotEmpty();
|
|
54
|
+
// No schedule → always available
|
|
55
|
+
if (!$hasSchedule) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Has a schedule → must have an active shift right now
|
|
60
|
+
return $driver->activeShiftFor(now()) !== null;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ($availableDrivers->isEmpty()) {
|
|
65
|
+
return [
|
|
66
|
+
'assignments' => [],
|
|
67
|
+
'unassigned' => $vehicles->pluck('public_id')->toArray(),
|
|
68
|
+
'summary' => ['message' => 'No available drivers found.'],
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$assignments = [];
|
|
73
|
+
$assignedDrivers = [];
|
|
74
|
+
$assignedVehicles = [];
|
|
75
|
+
$unassignedOrders = [];
|
|
76
|
+
|
|
77
|
+
// Determine if this is a standalone run (no prior vehicle assignment)
|
|
78
|
+
// or a post-vehicle-assignment run.
|
|
79
|
+
$hasVehicleAssignments = $orders->filter(fn ($o) => !empty($o->vehicle_assigned_uuid))->isNotEmpty();
|
|
80
|
+
|
|
81
|
+
if ($hasVehicleAssignments) {
|
|
82
|
+
// ── Post-vehicle-assignment mode ──────────────────────────────────
|
|
83
|
+
// Orders already have a vehicle. Group by vehicle and find the
|
|
84
|
+
// best driver for each vehicle group.
|
|
85
|
+
$ordersByVehicle = $orders->groupBy('vehicle_assigned_uuid');
|
|
86
|
+
|
|
87
|
+
foreach ($vehicles as $vehicle) {
|
|
88
|
+
$vehicleOrders = $ordersByVehicle->get($vehicle->uuid, collect());
|
|
89
|
+
$requiredSkills = $this->aggregateRequiredSkills($vehicleOrders);
|
|
90
|
+
|
|
91
|
+
$bestDriver = $this->findBestDriver(
|
|
92
|
+
$vehicle,
|
|
93
|
+
$availableDrivers->reject(fn ($d) => in_array($d->uuid, $assignedDrivers)),
|
|
94
|
+
$requiredSkills,
|
|
95
|
+
$respectSkills
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!$bestDriver) {
|
|
99
|
+
foreach ($vehicleOrders as $order) {
|
|
100
|
+
$unassignedOrders[] = $order->public_id;
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
$assignedDrivers[] = $bestDriver->uuid;
|
|
106
|
+
|
|
107
|
+
foreach ($vehicleOrders as $order) {
|
|
108
|
+
$assignments[] = [
|
|
109
|
+
'order_id' => $order->public_id,
|
|
110
|
+
'vehicle_id' => $vehicle->public_id,
|
|
111
|
+
'driver_id' => $bestDriver->public_id,
|
|
112
|
+
'sequence' => null,
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// ── Standalone mode ───────────────────────────────────────────────
|
|
118
|
+
// No prior vehicle assignment. Assign each order to the nearest
|
|
119
|
+
// available vehicle + best matching driver pair.
|
|
120
|
+
$availableVehicles = $vehicles->values();
|
|
121
|
+
|
|
122
|
+
foreach ($orders as $order) {
|
|
123
|
+
// Find the nearest unassigned vehicle
|
|
124
|
+
$pickupLat = $order->payload?->pickup?->location?->getLat();
|
|
125
|
+
$pickupLng = $order->payload?->pickup?->location?->getLng();
|
|
126
|
+
|
|
127
|
+
$bestVehicle = $availableVehicles
|
|
128
|
+
->reject(fn ($v) => in_array($v->uuid, $assignedVehicles))
|
|
129
|
+
->sortBy(function ($vehicle) use ($pickupLat, $pickupLng) {
|
|
130
|
+
if (!$pickupLat || !$pickupLng || !$vehicle->location) {
|
|
131
|
+
return PHP_INT_MAX;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return $this->haversineDistance(
|
|
135
|
+
$pickupLat, $pickupLng,
|
|
136
|
+
$vehicle->location->getLat(), $vehicle->location->getLng()
|
|
137
|
+
);
|
|
138
|
+
})
|
|
139
|
+
->first();
|
|
140
|
+
|
|
141
|
+
if (!$bestVehicle) {
|
|
142
|
+
$unassignedOrders[] = $order->public_id;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
$requiredSkills = $order->required_skills ?? [];
|
|
147
|
+
$bestDriver = $this->findBestDriver(
|
|
148
|
+
$bestVehicle,
|
|
149
|
+
$availableDrivers->reject(fn ($d) => in_array($d->uuid, $assignedDrivers)),
|
|
150
|
+
$requiredSkills,
|
|
151
|
+
$respectSkills
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (!$bestDriver) {
|
|
155
|
+
$unassignedOrders[] = $order->public_id;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$assignedVehicles[] = $bestVehicle->uuid;
|
|
160
|
+
$assignedDrivers[] = $bestDriver->uuid;
|
|
161
|
+
|
|
162
|
+
$assignments[] = [
|
|
163
|
+
'order_id' => $order->public_id,
|
|
164
|
+
'vehicle_id' => $bestVehicle->public_id,
|
|
165
|
+
'driver_id' => $bestDriver->public_id,
|
|
166
|
+
'sequence' => null,
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [
|
|
172
|
+
'assignments' => $assignments,
|
|
173
|
+
'unassigned' => $unassignedOrders,
|
|
174
|
+
'summary' => [
|
|
175
|
+
'drivers_assigned' => count($assignedDrivers),
|
|
176
|
+
'vehicles_assigned' => count($assignedVehicles),
|
|
177
|
+
],
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Find the best available driver for a given vehicle.
|
|
183
|
+
*
|
|
184
|
+
* Scoring:
|
|
185
|
+
* - Skills match: +100 per matching skill
|
|
186
|
+
* - Active shift: +50
|
|
187
|
+
* - Proximity: inversely proportional to distance (max +50)
|
|
188
|
+
*/
|
|
189
|
+
protected function findBestDriver(
|
|
190
|
+
Vehicle $vehicle,
|
|
191
|
+
Collection $availableDrivers,
|
|
192
|
+
array $requiredSkills,
|
|
193
|
+
bool $respectSkills,
|
|
194
|
+
): ?Driver {
|
|
195
|
+
if ($availableDrivers->isEmpty()) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
$vehicleLat = $vehicle->location?->getLat();
|
|
200
|
+
$vehicleLng = $vehicle->location?->getLng();
|
|
201
|
+
|
|
202
|
+
$scored = $availableDrivers->map(function (Driver $driver) use (
|
|
203
|
+
$requiredSkills, $respectSkills, $vehicleLat, $vehicleLng
|
|
204
|
+
) {
|
|
205
|
+
$score = 0;
|
|
206
|
+
|
|
207
|
+
// Skills check
|
|
208
|
+
if ($respectSkills && !empty($requiredSkills)) {
|
|
209
|
+
$driverSkills = $driver->skills ?? [];
|
|
210
|
+
$matchCount = count(array_intersect($requiredSkills, $driverSkills));
|
|
211
|
+
if ($matchCount < count($requiredSkills)) {
|
|
212
|
+
// Driver does not meet all required skills — disqualify
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
$score += $matchCount * 100;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Online bonus
|
|
219
|
+
if ($driver->online) {
|
|
220
|
+
$score += 30;
|
|
221
|
+
}
|
|
222
|
+
// Active shift bonus — only meaningful if the driver has a schedule
|
|
223
|
+
$hasSchedule = $driver->scheduleItems && $driver->scheduleItems->isNotEmpty();
|
|
224
|
+
if ($hasSchedule && $driver->activeShiftFor(now()) !== null) {
|
|
225
|
+
$score += 50;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Proximity bonus (only if vehicle has a known location)
|
|
229
|
+
if ($vehicleLat && $vehicleLng && $driver->location) {
|
|
230
|
+
$distance = $this->haversineDistance(
|
|
231
|
+
$vehicleLat, $vehicleLng,
|
|
232
|
+
$driver->location->getLat(), $driver->location->getLng()
|
|
233
|
+
);
|
|
234
|
+
// Closer = higher score; cap at 50 points for distance ≤ 1 km
|
|
235
|
+
$proximityScore = max(0, 50 - ($distance / 1000));
|
|
236
|
+
$score += $proximityScore;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return ['driver' => $driver, 'score' => $score];
|
|
240
|
+
})->filter()->sortByDesc('score');
|
|
241
|
+
|
|
242
|
+
return $scored->first()['driver'] ?? null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Aggregate the unique required skills from a collection of orders.
|
|
247
|
+
*/
|
|
248
|
+
protected function aggregateRequiredSkills(Collection $orders): array
|
|
249
|
+
{
|
|
250
|
+
return $orders->flatMap(fn ($o) => $o->required_skills ?? [])->unique()->values()->toArray();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Calculate the Haversine distance in metres between two lat/lng points.
|
|
255
|
+
*/
|
|
256
|
+
protected function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float
|
|
257
|
+
{
|
|
258
|
+
$earthRadius = 6371000; // metres
|
|
259
|
+
$dLat = deg2rad($lat2 - $lat1);
|
|
260
|
+
$dLng = deg2rad($lng2 - $lng1);
|
|
261
|
+
$a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
|
|
262
|
+
|
|
263
|
+
return $earthRadius * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Orchestration\Engines;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
|
|
6
|
+
use Illuminate\Support\Collection;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GreedyOrchestrationEngine.
|
|
10
|
+
*
|
|
11
|
+
* A simple, dependency-free allocation engine that assigns orders to vehicles
|
|
12
|
+
* without requiring an external routing service such as VROOM.
|
|
13
|
+
*
|
|
14
|
+
* Algorithm:
|
|
15
|
+
* 1. Sort orders by priority (descending) then scheduled_at (ascending).
|
|
16
|
+
* 2. For each order, find the nearest available vehicle (by Haversine distance
|
|
17
|
+
* from the vehicle/driver location to the order pickup point).
|
|
18
|
+
* 3. Assign the order to that vehicle and mark the vehicle as "occupied" for
|
|
19
|
+
* the purpose of this run (one order per vehicle per run by default; pass
|
|
20
|
+
* `allow_multi_order = true` in $options to allow multiple orders per vehicle).
|
|
21
|
+
*
|
|
22
|
+
* This engine is registered as 'greedy' in the OrchestrationEngineRegistry and is
|
|
23
|
+
* used automatically when VROOM is unavailable or when the user explicitly
|
|
24
|
+
* selects it in the orchestrator settings.
|
|
25
|
+
*/
|
|
26
|
+
class GreedyOrchestrationEngine implements OrchestrationEngineInterface
|
|
27
|
+
{
|
|
28
|
+
public function getName(): string
|
|
29
|
+
{
|
|
30
|
+
return 'Greedy (built-in)';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public function getIdentifier(): string
|
|
34
|
+
{
|
|
35
|
+
return 'greedy';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Run the greedy nearest-vehicle assignment.
|
|
40
|
+
*/
|
|
41
|
+
public function allocate(Collection $orders, Collection $vehicles, array $options = []): array
|
|
42
|
+
{
|
|
43
|
+
$allowMulti = (bool) ($options['allow_multi_order'] ?? false);
|
|
44
|
+
|
|
45
|
+
// Sort orders: highest priority first, then earliest scheduled_at
|
|
46
|
+
$sorted = $orders->sortBy([
|
|
47
|
+
fn ($a, $b) => ($b->orchestrator_priority ?? 0) <=> ($a->orchestrator_priority ?? 0),
|
|
48
|
+
fn ($a, $b) => ($a->scheduled_at ?? now()) <=> ($b->scheduled_at ?? now()),
|
|
49
|
+
])->values();
|
|
50
|
+
|
|
51
|
+
// Build a mutable list of available vehicles with their current location
|
|
52
|
+
$vehiclePool = $vehicles->map(function ($vehicle) {
|
|
53
|
+
$driver = $vehicle->driver;
|
|
54
|
+
$location = $driver?->location ?? $vehicle->location;
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
'model' => $vehicle,
|
|
58
|
+
'id' => $vehicle->public_id,
|
|
59
|
+
'driver_id' => $driver?->public_id,
|
|
60
|
+
'lat' => $location?->getLat(),
|
|
61
|
+
'lng' => $location?->getLng(),
|
|
62
|
+
'assigned' => 0,
|
|
63
|
+
];
|
|
64
|
+
})->values()->toArray();
|
|
65
|
+
|
|
66
|
+
$assignments = [];
|
|
67
|
+
$unassigned = [];
|
|
68
|
+
|
|
69
|
+
foreach ($sorted as $order) {
|
|
70
|
+
$pickupLat = $order->payload?->pickup?->lat ?? null;
|
|
71
|
+
$pickupLng = $order->payload?->pickup?->lng ?? null;
|
|
72
|
+
|
|
73
|
+
// Find the best vehicle: nearest with capacity remaining
|
|
74
|
+
$bestIdx = null;
|
|
75
|
+
$bestDist = PHP_INT_MAX;
|
|
76
|
+
|
|
77
|
+
foreach ($vehiclePool as $idx => $v) {
|
|
78
|
+
// In single-order mode, skip vehicles that already have an assignment
|
|
79
|
+
if (!$allowMulti && $v['assigned'] > 0) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ($v['lat'] === null || $v['lng'] === null) {
|
|
84
|
+
// Vehicle has no known location — eligible but deprioritised
|
|
85
|
+
if ($bestIdx === null) {
|
|
86
|
+
$bestIdx = $idx;
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ($pickupLat !== null && $pickupLng !== null) {
|
|
92
|
+
$dist = $this->haversineDistance($v['lat'], $v['lng'], (float) $pickupLat, (float) $pickupLng);
|
|
93
|
+
} else {
|
|
94
|
+
$dist = 0; // No pickup location — treat as zero distance
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ($dist < $bestDist) {
|
|
98
|
+
$bestDist = $dist;
|
|
99
|
+
$bestIdx = $idx;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ($bestIdx === null) {
|
|
104
|
+
$unassigned[] = $order->public_id;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
$v = $vehiclePool[$bestIdx];
|
|
109
|
+
|
|
110
|
+
$assignments[] = [
|
|
111
|
+
'order_id' => $order->public_id,
|
|
112
|
+
'vehicle_id' => $v['id'],
|
|
113
|
+
'driver_id' => $v['driver_id'],
|
|
114
|
+
'sequence' => ++$vehiclePool[$bestIdx]['assigned'],
|
|
115
|
+
'arrival' => null,
|
|
116
|
+
'duration' => null,
|
|
117
|
+
'distance' => $bestDist < PHP_INT_MAX ? (int) round($bestDist) : null,
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// Update vehicle's "current position" to the order dropoff so the
|
|
121
|
+
// next order assigned to this vehicle is measured from the dropoff.
|
|
122
|
+
if ($allowMulti) {
|
|
123
|
+
$dropoffLat = $order->payload?->dropoff?->lat;
|
|
124
|
+
$dropoffLng = $order->payload?->dropoff?->lng;
|
|
125
|
+
if ($dropoffLat !== null && $dropoffLng !== null) {
|
|
126
|
+
$vehiclePool[$bestIdx]['lat'] = (float) $dropoffLat;
|
|
127
|
+
$vehiclePool[$bestIdx]['lng'] = (float) $dropoffLng;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [
|
|
133
|
+
'assignments' => $assignments,
|
|
134
|
+
'unassigned' => $unassigned,
|
|
135
|
+
'summary' => [
|
|
136
|
+
'engine' => 'greedy',
|
|
137
|
+
'assigned' => count($assignments),
|
|
138
|
+
'unassigned' => count($unassigned),
|
|
139
|
+
],
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Haversine distance in metres between two lat/lng points.
|
|
145
|
+
*/
|
|
146
|
+
protected function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float
|
|
147
|
+
{
|
|
148
|
+
$R = 6371000;
|
|
149
|
+
$dLat = deg2rad($lat2 - $lat1);
|
|
150
|
+
$dLng = deg2rad($lng2 - $lng1);
|
|
151
|
+
$a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
|
|
152
|
+
|
|
153
|
+
return $R * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Orchestration\Engines;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Support\Collection;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* RouteSequencingEngine.
|
|
9
|
+
*
|
|
10
|
+
* Used for the `optimize_routes` orchestration mode.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the allocation engines (Greedy, VROOM), this engine does NOT
|
|
13
|
+
* re-assign orders to vehicles. Instead it:
|
|
14
|
+
*
|
|
15
|
+
* 1. Groups the provided orders by their already-assigned vehicle
|
|
16
|
+
* (vehicle_assigned_uuid).
|
|
17
|
+
* 2. For each vehicle group, sequences the stops in an optimal order
|
|
18
|
+
* using a nearest-neighbour TSP heuristic.
|
|
19
|
+
* 3. Returns one assignment entry per order, preserving the existing
|
|
20
|
+
* vehicle_id / driver_id and updating only the sequence number.
|
|
21
|
+
*
|
|
22
|
+
* This ensures that running "Assign Vehicles" followed by "Optimize Routes"
|
|
23
|
+
* produces one properly-sequenced route per vehicle rather than re-running
|
|
24
|
+
* the allocation algorithm.
|
|
25
|
+
*/
|
|
26
|
+
class RouteSequencingEngine
|
|
27
|
+
{
|
|
28
|
+
/**
|
|
29
|
+
* Sequence stops for each vehicle group.
|
|
30
|
+
*
|
|
31
|
+
* @param Collection $orders Orders with payload.pickup, payload.dropoff, payload.waypoints loaded
|
|
32
|
+
* @param array $options Engine options (currently unused, reserved for future use)
|
|
33
|
+
*
|
|
34
|
+
* @return array Standard orchestration result: { assignments, unassigned, summary }
|
|
35
|
+
*/
|
|
36
|
+
public function sequence(Collection $orders, array $options = []): array
|
|
37
|
+
{
|
|
38
|
+
$assignments = [];
|
|
39
|
+
$unassigned = [];
|
|
40
|
+
|
|
41
|
+
// Group orders by their currently assigned vehicle UUID
|
|
42
|
+
$byVehicle = [];
|
|
43
|
+
foreach ($orders as $order) {
|
|
44
|
+
$vehicleUuid = $order->vehicle_assigned_uuid ?? null;
|
|
45
|
+
if (!$vehicleUuid) {
|
|
46
|
+
// Order has no vehicle assignment — cannot sequence it
|
|
47
|
+
$unassigned[] = $order->public_id;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
$byVehicle[$vehicleUuid][] = $order;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
foreach ($byVehicle as $vehicleUuid => $vehicleOrders) {
|
|
54
|
+
$vehicle = $vehicleOrders[0]->vehicle ?? null;
|
|
55
|
+
|
|
56
|
+
// Determine driver public_id from the vehicle relationship
|
|
57
|
+
$driverPublicId = null;
|
|
58
|
+
if ($vehicle && $vehicle->driver) {
|
|
59
|
+
$driverPublicId = $vehicle->driver->public_id ?? null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Get vehicle's current location as the starting point for sequencing
|
|
63
|
+
$startLat = null;
|
|
64
|
+
$startLng = null;
|
|
65
|
+
if ($vehicle) {
|
|
66
|
+
$driver = $vehicle->driver;
|
|
67
|
+
$location = $driver?->location ?? $vehicle->location;
|
|
68
|
+
$startLat = $location?->getLat();
|
|
69
|
+
$startLng = $location?->getLng();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Build a flat list of stops: each order contributes pickup + dropoff
|
|
73
|
+
// (or waypoints for multi-drop orders). We keep pickup before its own
|
|
74
|
+
// dropoff as a hard constraint.
|
|
75
|
+
$sequenced = $this->_sequenceOrdersForVehicle($vehicleOrders, $startLat, $startLng);
|
|
76
|
+
|
|
77
|
+
// Build assignment entries — one per order, with the sequence number
|
|
78
|
+
// being the position of the order's FIRST stop in the sequenced list.
|
|
79
|
+
$orderSequences = [];
|
|
80
|
+
foreach ($sequenced as $seq => $stop) {
|
|
81
|
+
$orderId = $stop['order_public_id'];
|
|
82
|
+
// Only record the first occurrence (pickup position) per order
|
|
83
|
+
if (!isset($orderSequences[$orderId])) {
|
|
84
|
+
$orderSequences[$orderId] = $seq + 1; // 1-based
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
foreach ($vehicleOrders as $order) {
|
|
89
|
+
$assignments[] = [
|
|
90
|
+
'order_id' => $order->public_id,
|
|
91
|
+
'vehicle_id' => $vehicle?->public_id ?? $vehicleUuid,
|
|
92
|
+
'driver_id' => $driverPublicId,
|
|
93
|
+
'sequence' => $orderSequences[$order->public_id] ?? 1,
|
|
94
|
+
'waypoint_sequence' => null,
|
|
95
|
+
'arrival' => null,
|
|
96
|
+
'duration' => null,
|
|
97
|
+
'distance' => null,
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return [
|
|
103
|
+
'assignments' => $assignments,
|
|
104
|
+
'unassigned' => $unassigned,
|
|
105
|
+
'summary' => [
|
|
106
|
+
'engine' => 'route_sequencing',
|
|
107
|
+
'assigned' => count($assignments),
|
|
108
|
+
'unassigned' => count($unassigned),
|
|
109
|
+
],
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Sequence stops for a single vehicle's orders using nearest-neighbour TSP.
|
|
115
|
+
*
|
|
116
|
+
* Constraints:
|
|
117
|
+
* - Each order's pickup must appear before its dropoff (precedence constraint).
|
|
118
|
+
* - Starts from the vehicle/driver's current location if known.
|
|
119
|
+
*
|
|
120
|
+
* @param array $orders Array of Order models
|
|
121
|
+
* @param float|null $startLat Vehicle starting latitude
|
|
122
|
+
* @param float|null $startLng Vehicle starting longitude
|
|
123
|
+
*
|
|
124
|
+
* @return array Ordered array of stop records: { order_public_id, lat, lng, type }
|
|
125
|
+
*/
|
|
126
|
+
protected function _sequenceOrdersForVehicle(array $orders, ?float $startLat, ?float $startLng): array
|
|
127
|
+
{
|
|
128
|
+
// Build a flat pool of stops with precedence constraints
|
|
129
|
+
$pool = [];
|
|
130
|
+
foreach ($orders as $order) {
|
|
131
|
+
$payload = $order->payload;
|
|
132
|
+
if (!$payload) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
$waypoints = $payload->waypoints;
|
|
137
|
+
$hasWaypoints = $waypoints && $waypoints->count() > 0;
|
|
138
|
+
$isMultiDrop = $hasWaypoints && !$payload->pickup_uuid && !$payload->dropoff_uuid;
|
|
139
|
+
|
|
140
|
+
if ($isMultiDrop) {
|
|
141
|
+
// Multi-drop: add each waypoint as a stop
|
|
142
|
+
$sorted = $waypoints->sortBy('order')->values();
|
|
143
|
+
foreach ($sorted as $idx => $wp) {
|
|
144
|
+
$place = $wp->place;
|
|
145
|
+
if (!$place) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
$pool[] = [
|
|
149
|
+
'order_public_id' => $order->public_id,
|
|
150
|
+
'lat' => (float) ($place->lat ?? 0),
|
|
151
|
+
'lng' => (float) ($place->lng ?? 0),
|
|
152
|
+
'type' => 'waypoint',
|
|
153
|
+
'precedence_after' => $idx > 0 ? ($pool[count($pool) - 1]['id'] ?? null) : null,
|
|
154
|
+
'id' => $order->public_id . '_wp_' . $idx,
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
// Standard pickup → dropoff
|
|
159
|
+
$pickup = $payload->pickup;
|
|
160
|
+
$dropoff = $payload->dropoff;
|
|
161
|
+
|
|
162
|
+
$pickupId = $order->public_id . '_pickup';
|
|
163
|
+
$dropoffId = $order->public_id . '_dropoff';
|
|
164
|
+
|
|
165
|
+
if ($pickup && $pickup->lat && $pickup->lng) {
|
|
166
|
+
$pool[] = [
|
|
167
|
+
'order_public_id' => $order->public_id,
|
|
168
|
+
'lat' => (float) $pickup->lat,
|
|
169
|
+
'lng' => (float) $pickup->lng,
|
|
170
|
+
'type' => 'pickup',
|
|
171
|
+
'precedence_after' => null, // pickup has no prerequisite
|
|
172
|
+
'id' => $pickupId,
|
|
173
|
+
'blocks' => $dropoffId, // dropoff must come after this
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if ($dropoff && $dropoff->lat && $dropoff->lng) {
|
|
178
|
+
$pool[] = [
|
|
179
|
+
'order_public_id' => $order->public_id,
|
|
180
|
+
'lat' => (float) $dropoff->lat,
|
|
181
|
+
'lng' => (float) $dropoff->lng,
|
|
182
|
+
'type' => 'dropoff',
|
|
183
|
+
'precedence_after' => $pickupId, // must come after pickup
|
|
184
|
+
'id' => $dropoffId,
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (empty($pool)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Nearest-neighbour TSP with precedence constraints
|
|
195
|
+
$visited = [];
|
|
196
|
+
$sequence = [];
|
|
197
|
+
$curLat = $startLat;
|
|
198
|
+
$curLng = $startLng;
|
|
199
|
+
|
|
200
|
+
while (count($visited) < count($pool)) {
|
|
201
|
+
$bestIdx = null;
|
|
202
|
+
$bestDist = PHP_INT_MAX;
|
|
203
|
+
|
|
204
|
+
foreach ($pool as $idx => $stop) {
|
|
205
|
+
if (in_array($idx, $visited)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check precedence: if this stop requires another stop to come first
|
|
210
|
+
if (!empty($stop['precedence_after'])) {
|
|
211
|
+
$prerequisiteId = $stop['precedence_after'];
|
|
212
|
+
$prerequisiteDone = false;
|
|
213
|
+
foreach ($sequence as $done) {
|
|
214
|
+
if ($done['id'] === $prerequisiteId) {
|
|
215
|
+
$prerequisiteDone = true;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!$prerequisiteDone) {
|
|
220
|
+
continue;
|
|
221
|
+
} // not yet eligible
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if ($curLat === null || $curLng === null) {
|
|
225
|
+
// No current position — just pick the first eligible stop
|
|
226
|
+
$bestIdx = $idx;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
$dist = $this->_haversine($curLat, $curLng, $stop['lat'], $stop['lng']);
|
|
231
|
+
if ($dist < $bestDist) {
|
|
232
|
+
$bestDist = $dist;
|
|
233
|
+
$bestIdx = $idx;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if ($bestIdx === null) {
|
|
238
|
+
// No eligible stop found — add all remaining stops in original order
|
|
239
|
+
// (safety fallback to avoid infinite loop)
|
|
240
|
+
foreach ($pool as $idx => $stop) {
|
|
241
|
+
if (!in_array($idx, $visited)) {
|
|
242
|
+
$visited[] = $idx;
|
|
243
|
+
$sequence[] = $stop;
|
|
244
|
+
$curLat = $stop['lat'];
|
|
245
|
+
$curLng = $stop['lng'];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$visited[] = $bestIdx;
|
|
252
|
+
$sequence[] = $pool[$bestIdx];
|
|
253
|
+
$curLat = $pool[$bestIdx]['lat'];
|
|
254
|
+
$curLng = $pool[$bestIdx]['lng'];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return $sequence;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Haversine distance in metres.
|
|
262
|
+
*/
|
|
263
|
+
protected function _haversine(float $lat1, float $lng1, float $lat2, float $lng2): float
|
|
264
|
+
{
|
|
265
|
+
$R = 6371000;
|
|
266
|
+
$dLat = deg2rad($lat2 - $lat1);
|
|
267
|
+
$dLng = deg2rad($lng2 - $lng1);
|
|
268
|
+
$a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
|
|
269
|
+
|
|
270
|
+
return $R * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
271
|
+
}
|
|
272
|
+
}
|