@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,192 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Orchestration\Engines;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
|
|
6
|
+
use Fleetbase\FleetOps\Orchestration\Support\OrchestrationPayloadBuilder;
|
|
7
|
+
use Fleetbase\Models\Setting;
|
|
8
|
+
use Illuminate\Support\Collection;
|
|
9
|
+
use Illuminate\Support\Facades\Http;
|
|
10
|
+
use Illuminate\Support\Facades\Log;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* VroomOrchestrationEngine.
|
|
14
|
+
*
|
|
15
|
+
* Self-contained VROOM integration for the FleetOps Orchestrator Workbench.
|
|
16
|
+
* This engine does NOT depend on the optional `fleetbase/vroom` extension —
|
|
17
|
+
* it communicates directly with the VROOM HTTP API using the same base URI
|
|
18
|
+
* and API-key convention that the extension uses, so the two are compatible
|
|
19
|
+
* when both are installed.
|
|
20
|
+
*
|
|
21
|
+
* Resolution order for configuration:
|
|
22
|
+
*
|
|
23
|
+
* 1. Company-level setting — `Setting::lookupCompany('vroom', 'api_key')`
|
|
24
|
+
* (written by the fleetbase/vroom extension settings page, if installed)
|
|
25
|
+
* 2. Environment variable — `VROOM_API_KEY`
|
|
26
|
+
* 3. No key — requests are sent without an api_key parameter
|
|
27
|
+
* (works for self-hosted VROOM instances that do not require auth)
|
|
28
|
+
*
|
|
29
|
+
* Base URI: `config('vroom.base_uri')` → `VROOM_HOST` env → default production URL
|
|
30
|
+
* Timeout: `VROOM_TIMEOUT` env (default 30 s)
|
|
31
|
+
*
|
|
32
|
+
* The VROOM HTTP API endpoint used is `POST {base_uri}/solve`, matching the
|
|
33
|
+
* verso-optim.com hosted service and the fleetbase/vroom extension.
|
|
34
|
+
*/
|
|
35
|
+
class VroomOrchestrationEngine implements OrchestrationEngineInterface
|
|
36
|
+
{
|
|
37
|
+
public function getName(): string
|
|
38
|
+
{
|
|
39
|
+
return 'VROOM';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public function getIdentifier(): string
|
|
43
|
+
{
|
|
44
|
+
return 'vroom';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Run the VROOM VRP solver.
|
|
49
|
+
*
|
|
50
|
+
* Constructs a VROOM-format payload from the normalized jobs/vehicles
|
|
51
|
+
* produced by OrchestrationPayloadBuilder, calls the VROOM HTTP API, and
|
|
52
|
+
* maps the response back to the standard OrchestrationEngineInterface result
|
|
53
|
+
* shape.
|
|
54
|
+
*
|
|
55
|
+
* @throws \RuntimeException if the VROOM API is unreachable or returns an error
|
|
56
|
+
*/
|
|
57
|
+
public function allocate(Collection $orders, Collection $vehicles, array $options = []): array
|
|
58
|
+
{
|
|
59
|
+
$jobs = OrchestrationPayloadBuilder::buildJobs($orders);
|
|
60
|
+
$vroomVehicles = OrchestrationPayloadBuilder::buildVehicles($vehicles);
|
|
61
|
+
|
|
62
|
+
if (empty($jobs)) {
|
|
63
|
+
return ['assignments' => [], 'unassigned' => [], 'summary' => []];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Map normalized vehicle entries to VROOM vehicle objects.
|
|
67
|
+
// The 'driver_id' key is not part of the VROOM spec — we carry it in
|
|
68
|
+
// a description field and use it when mapping results back.
|
|
69
|
+
$vroomPayload = [
|
|
70
|
+
'jobs' => $jobs,
|
|
71
|
+
'vehicles' => array_map(function (array $v) {
|
|
72
|
+
$vehicle = [
|
|
73
|
+
'id' => crc32($v['id']), // VROOM requires integer IDs
|
|
74
|
+
'description' => json_encode(['vehicle_id' => $v['id'], 'driver_id' => $v['driver_id']]),
|
|
75
|
+
'start' => $v['start'],
|
|
76
|
+
'capacity' => $v['capacity'],
|
|
77
|
+
];
|
|
78
|
+
if (isset($v['time_window'])) {
|
|
79
|
+
$vehicle['time_window'] = $v['time_window'];
|
|
80
|
+
}
|
|
81
|
+
if (isset($v['skills'])) {
|
|
82
|
+
$vehicle['skills'] = $v['skills'];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return $vehicle;
|
|
86
|
+
}, $vroomVehicles),
|
|
87
|
+
'options' => [
|
|
88
|
+
'g' => $options['geometry'] ?? false,
|
|
89
|
+
],
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// Map job IDs to integer IDs for VROOM, keeping a reverse lookup
|
|
93
|
+
$jobIdMap = [];
|
|
94
|
+
$jobIdReverse = [];
|
|
95
|
+
foreach ($vroomPayload['jobs'] as &$job) {
|
|
96
|
+
$intId = crc32($job['id']);
|
|
97
|
+
$jobIdReverse[$intId] = $job['id'];
|
|
98
|
+
$job['id'] = $intId;
|
|
99
|
+
$jobIdMap[$job['id']] = true;
|
|
100
|
+
}
|
|
101
|
+
unset($job);
|
|
102
|
+
|
|
103
|
+
// ── Resolve connection config ─────────────────────────────────────────
|
|
104
|
+
// Base URI: prefer the vroom extension config, then VROOM_HOST env, then
|
|
105
|
+
// the production verso-optim.com hosted service.
|
|
106
|
+
$baseUri = config('vroom.base_uri', env('VROOM_HOST', 'https://api.verso-optim.com/vrp/v1'));
|
|
107
|
+
$timeout = (int) env('VROOM_TIMEOUT', 30);
|
|
108
|
+
|
|
109
|
+
// ── Resolve API key ───────────────────────────────────────────────────
|
|
110
|
+
// 1. Company-level setting (written by fleetbase/vroom extension UI)
|
|
111
|
+
// 2. VROOM_API_KEY env var
|
|
112
|
+
// 3. null — no key appended (for self-hosted instances)
|
|
113
|
+
$apiKey = null;
|
|
114
|
+
try {
|
|
115
|
+
$apiKey = Setting::lookupCompany('vroom', ['api_key' => null])['api_key'] ?? null;
|
|
116
|
+
} catch (\Throwable) {
|
|
117
|
+
// Setting table may not exist in minimal installs — ignore
|
|
118
|
+
}
|
|
119
|
+
if (!$apiKey) {
|
|
120
|
+
$apiKey = env('VROOM_API_KEY');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Build the solve URL — append api_key as query param when present
|
|
124
|
+
$solveUrl = rtrim($baseUri, '/') . '/solve';
|
|
125
|
+
if ($apiKey) {
|
|
126
|
+
$solveUrl .= '?api_key=' . urlencode($apiKey);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Call VROOM ────────────────────────────────────────────────────────
|
|
130
|
+
try {
|
|
131
|
+
$response = Http::timeout($timeout)->post($solveUrl, $vroomPayload);
|
|
132
|
+
} catch (\Exception $e) {
|
|
133
|
+
Log::error('[VroomOrchestrationEngine] HTTP request failed: ' . $e->getMessage());
|
|
134
|
+
throw new \RuntimeException('VROOM allocation engine is unavailable: ' . $e->getMessage() . ' — ensure VROOM_HOST is reachable or switch to the built-in greedy engine.', 0, $e);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!$response->successful()) {
|
|
138
|
+
throw new \RuntimeException('VROOM returned an error: HTTP ' . $response->status() . ' — ' . $response->body());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$result = $response->json();
|
|
142
|
+
|
|
143
|
+
return $this->mapVroomResponse($result, $jobIdReverse);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Map a VROOM API response to the standard OrchestrationEngineInterface shape.
|
|
148
|
+
*
|
|
149
|
+
* @param array $jobIdReverse Map of VROOM integer job ID → order public_id
|
|
150
|
+
*/
|
|
151
|
+
protected function mapVroomResponse(array $vroomResult, array $jobIdReverse): array
|
|
152
|
+
{
|
|
153
|
+
$assignments = [];
|
|
154
|
+
|
|
155
|
+
foreach ($vroomResult['routes'] ?? [] as $route) {
|
|
156
|
+
$vehicleDesc = json_decode($route['description'] ?? '{}', true);
|
|
157
|
+
$vehicleId = $vehicleDesc['vehicle_id'] ?? null;
|
|
158
|
+
$driverId = $vehicleDesc['driver_id'] ?? null;
|
|
159
|
+
$sequence = 0;
|
|
160
|
+
|
|
161
|
+
foreach ($route['steps'] ?? [] as $step) {
|
|
162
|
+
if ($step['type'] !== 'job') {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
$orderId = $jobIdReverse[$step['id']] ?? null;
|
|
166
|
+
if (!$orderId) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
$assignments[] = [
|
|
170
|
+
'order_id' => $orderId,
|
|
171
|
+
'vehicle_id' => $vehicleId,
|
|
172
|
+
'driver_id' => $driverId,
|
|
173
|
+
'sequence' => ++$sequence,
|
|
174
|
+
'arrival' => $step['arrival'] ?? null,
|
|
175
|
+
'duration' => $step['duration'] ?? null,
|
|
176
|
+
'distance' => $step['distance'] ?? null,
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
$unassigned = array_map(
|
|
182
|
+
fn ($u) => $jobIdReverse[$u['id']] ?? $u['id'],
|
|
183
|
+
$vroomResult['unassigned'] ?? []
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return [
|
|
187
|
+
'assignments' => $assignments,
|
|
188
|
+
'unassigned' => array_values($unassigned),
|
|
189
|
+
'summary' => $vroomResult['summary'] ?? [],
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Orchestration;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* OrchestrationEngineRegistry.
|
|
9
|
+
*
|
|
10
|
+
* A simple service-locator for orchestration engines. Engines register
|
|
11
|
+
* themselves (typically from a service provider) and the
|
|
12
|
+
* OrchestrationController resolves the active engine at runtime from the
|
|
13
|
+
* FleetOps setting key 'fleetops.orchestrator_engine'.
|
|
14
|
+
*
|
|
15
|
+
* Third-party extensions can register their own engines by resolving this
|
|
16
|
+
* registry in their service provider:
|
|
17
|
+
*
|
|
18
|
+
* $this->app->resolving(OrchestrationEngineRegistry::class, function ($registry) {
|
|
19
|
+
* $registry->register(new MyCustomEngine());
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
class OrchestrationEngineRegistry
|
|
23
|
+
{
|
|
24
|
+
/**
|
|
25
|
+
* Registered engines keyed by their identifier.
|
|
26
|
+
*
|
|
27
|
+
* @var array<string, OrchestrationEngineInterface>
|
|
28
|
+
*/
|
|
29
|
+
protected array $engines = [];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register an orchestration engine.
|
|
33
|
+
*
|
|
34
|
+
* @throws \InvalidArgumentException if an engine with the same identifier is already registered
|
|
35
|
+
*/
|
|
36
|
+
public function register(OrchestrationEngineInterface $engine): void
|
|
37
|
+
{
|
|
38
|
+
$id = $engine->getIdentifier();
|
|
39
|
+
if (isset($this->engines[$id])) {
|
|
40
|
+
throw new \InvalidArgumentException("An orchestration engine with identifier '{$id}' is already registered.");
|
|
41
|
+
}
|
|
42
|
+
$this->engines[$id] = $engine;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve an engine by identifier.
|
|
47
|
+
*
|
|
48
|
+
* @throws \RuntimeException if no engine with the given identifier is registered
|
|
49
|
+
*/
|
|
50
|
+
public function resolve(string $identifier): OrchestrationEngineInterface
|
|
51
|
+
{
|
|
52
|
+
if (!isset($this->engines[$identifier])) {
|
|
53
|
+
throw new \RuntimeException("No orchestration engine registered with identifier '{$identifier}'. " . 'Available engines: ' . implode(', ', array_keys($this->engines)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return $this->engines[$identifier];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return all registered engines as an array of {id, name} pairs.
|
|
61
|
+
* Used by the settings API to populate the engine selector dropdown.
|
|
62
|
+
*
|
|
63
|
+
* @return array<array{id: string, name: string}>
|
|
64
|
+
*/
|
|
65
|
+
public function available(): array
|
|
66
|
+
{
|
|
67
|
+
return array_values(array_map(
|
|
68
|
+
fn (OrchestrationEngineInterface $engine) => [
|
|
69
|
+
'id' => $engine->getIdentifier(),
|
|
70
|
+
'name' => $engine->getName(),
|
|
71
|
+
],
|
|
72
|
+
$this->engines
|
|
73
|
+
));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check whether an engine with the given identifier is registered.
|
|
78
|
+
*/
|
|
79
|
+
public function has(string $identifier): bool
|
|
80
|
+
{
|
|
81
|
+
return isset($this->engines[$identifier]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Orchestration\Support;
|
|
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
|
+
* OrchestrationPayloadBuilder.
|
|
12
|
+
*
|
|
13
|
+
* Transforms FleetOps Order and Vehicle/Driver records into a normalized
|
|
14
|
+
* intermediate representation that engine adapters can consume. This class
|
|
15
|
+
* is engine-agnostic — it does not produce VROOM JSON directly. Each engine
|
|
16
|
+
* adapter (e.g. VroomOrchestrationEngine) calls the builder and then maps the
|
|
17
|
+
* normalized output to its own wire format.
|
|
18
|
+
*
|
|
19
|
+
* The builder now reads first-class orchestrator columns (skills, capacity_*,
|
|
20
|
+
* time_window_*, service_time, orchestrator_priority) added by the 2026-04-08
|
|
21
|
+
* migrations, falling back to custom fields and meta for backwards compatibility.
|
|
22
|
+
*/
|
|
23
|
+
class OrchestrationPayloadBuilder
|
|
24
|
+
{
|
|
25
|
+
/**
|
|
26
|
+
* Safely read a meta key from a model, returning $default on any error.
|
|
27
|
+
*
|
|
28
|
+
* Vehicle::getAllMeta() has a strict array return type but some rows store
|
|
29
|
+
* the meta column as a raw JSON string rather than a decoded array, causing
|
|
30
|
+
* a TypeError at runtime. This wrapper catches that and any other meta
|
|
31
|
+
* read failure so a single bad vehicle row does not abort the whole run.
|
|
32
|
+
*
|
|
33
|
+
* @param \Illuminate\Database\Eloquent\Model $model
|
|
34
|
+
*/
|
|
35
|
+
protected static function safeMeta($model, string $key, $default = null)
|
|
36
|
+
{
|
|
37
|
+
try {
|
|
38
|
+
return $model->getMeta($key) ?? $default;
|
|
39
|
+
} catch (\Throwable $e) {
|
|
40
|
+
return $default;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the normalized job list from a collection of Orders.
|
|
46
|
+
*
|
|
47
|
+
* Each job contains:
|
|
48
|
+
* - id: order public_id
|
|
49
|
+
* - location: [longitude, latitude] of the delivery destination
|
|
50
|
+
* - pickup: [longitude, latitude] of the pickup (for PDPTW shipments)
|
|
51
|
+
* - service: service time in seconds (waypoint.service_time → order meta → default 300)
|
|
52
|
+
* - time_windows: [[earliest_unix, latest_unix]] from order.time_window_start/end or scheduled_at
|
|
53
|
+
* - skills: integer skill codes from order.required_skills or custom fields
|
|
54
|
+
* - amount: multi-dimensional capacity demand [weight_kg, volume_m3, pallets, parcels]
|
|
55
|
+
* - priority: orchestrator_priority (0–100, higher = more important)
|
|
56
|
+
* - description: human-readable label for debugging
|
|
57
|
+
*/
|
|
58
|
+
public static function buildJobs(Collection $orders): array
|
|
59
|
+
{
|
|
60
|
+
return $orders->map(function (Order $order) {
|
|
61
|
+
$payload = $order->payload;
|
|
62
|
+
$destination = $payload?->dropoff ?? $payload?->waypoints?->last();
|
|
63
|
+
|
|
64
|
+
if (!$destination || !$destination->location) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Location ---
|
|
69
|
+
$job = [
|
|
70
|
+
'id' => $order->public_id,
|
|
71
|
+
'location' => [$destination->location->getLng(), $destination->location->getLat()],
|
|
72
|
+
'description' => $order->public_id,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// Pickup location (for pickup-and-delivery problems)
|
|
76
|
+
$pickup = $payload?->pickup;
|
|
77
|
+
if ($pickup && $pickup->location) {
|
|
78
|
+
$job['pickup'] = [$pickup->location->getLng(), $pickup->location->getLat()];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Service time ---
|
|
82
|
+
// Prefer waypoint-level service_time, then order meta, then default 300s
|
|
83
|
+
$waypointMarker = $payload?->waypointMarkers?->last();
|
|
84
|
+
$job['service'] = (int) (
|
|
85
|
+
$waypointMarker?->service_time
|
|
86
|
+
?? $order->getMeta('service_time_seconds')
|
|
87
|
+
?? 300
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// --- Capacity demand (multi-dimensional) ---
|
|
91
|
+
// [weight_kg, volume_m3, pallets, parcels] — must match vehicle capacity array
|
|
92
|
+
$job['amount'] = [
|
|
93
|
+
(int) round((float) ($payload?->capacity_weight_kg ?? $order->getMeta('weight_kg') ?? 0)),
|
|
94
|
+
(int) round((float) ($payload?->capacity_volume_m3 ?? $order->getMeta('volume_m3') ?? 0) * 1000), // store as litres for integer
|
|
95
|
+
(int) ($payload?->capacity_pallets ?? $order->getMeta('pallets') ?? 0),
|
|
96
|
+
(int) ($payload?->capacity_parcels ?? $order->getMeta('parcels') ?? 1),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// --- Time windows ---
|
|
100
|
+
// Prefer explicit orchestrator time_window columns, fall back to scheduled_at
|
|
101
|
+
if ($order->time_window_start && $order->time_window_end) {
|
|
102
|
+
$job['time_windows'] = [[
|
|
103
|
+
$order->time_window_start->timestamp,
|
|
104
|
+
$order->time_window_end->timestamp,
|
|
105
|
+
]];
|
|
106
|
+
} elseif ($order->scheduled_at) {
|
|
107
|
+
$start = $order->scheduled_at->timestamp;
|
|
108
|
+
$end = $order->scheduled_at->copy()->addHours(4)->timestamp;
|
|
109
|
+
$job['time_windows'] = [[$start, $end]];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Skills ---
|
|
113
|
+
// Prefer first-class required_skills JSON column, fall back to custom fields
|
|
114
|
+
$skills = static::resolveSkills(
|
|
115
|
+
$order->required_skills ?? [],
|
|
116
|
+
$order->custom_fields ?? []
|
|
117
|
+
);
|
|
118
|
+
if (!empty($skills)) {
|
|
119
|
+
$job['skills'] = $skills;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- Priority ---
|
|
123
|
+
if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
|
|
124
|
+
$job['priority'] = (int) $order->orchestrator_priority;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return $job;
|
|
128
|
+
})->filter()->values()->toArray();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build vehicles for vehicle-only allocation mode — no driver required.
|
|
133
|
+
*
|
|
134
|
+
* Uses vehicle.location as the start position. Skills and capacity are
|
|
135
|
+
* read from the vehicle only. Used by the 'assign_vehicles' mode.
|
|
136
|
+
*/
|
|
137
|
+
public static function buildVehiclesOnly(Collection $vehicles): array
|
|
138
|
+
{
|
|
139
|
+
return $vehicles->map(function (Vehicle $vehicle) {
|
|
140
|
+
if (!$vehicle->location) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
$entry = [
|
|
144
|
+
'id' => $vehicle->public_id,
|
|
145
|
+
'driver_id' => null,
|
|
146
|
+
'start' => [$vehicle->location->getLng(), $vehicle->location->getLat()],
|
|
147
|
+
];
|
|
148
|
+
if ($vehicle->return_to_depot && $vehicle->location) {
|
|
149
|
+
$entry['end'] = [$vehicle->location->getLng(), $vehicle->location->getLat()];
|
|
150
|
+
}
|
|
151
|
+
$entry['capacity'] = [
|
|
152
|
+
(int) round((float) ($vehicle->capacity_weight_kg ?? static::safeMeta($vehicle, 'max_weight_kg', 0))),
|
|
153
|
+
(int) round((float) ($vehicle->capacity_volume_m3 ?? static::safeMeta($vehicle, 'max_volume_m3', 0)) * 1000),
|
|
154
|
+
(int) ($vehicle->capacity_pallets ?? static::safeMeta($vehicle, 'max_pallets', 0)),
|
|
155
|
+
(int) ($vehicle->capacity_parcels ?? static::safeMeta($vehicle, 'max_parcels', 100)),
|
|
156
|
+
];
|
|
157
|
+
if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
|
|
158
|
+
$entry['max_tasks'] = (int) $vehicle->max_tasks;
|
|
159
|
+
}
|
|
160
|
+
if ($vehicle->time_window_start && $vehicle->time_window_end) {
|
|
161
|
+
$entry['time_window'] = [
|
|
162
|
+
$vehicle->time_window_start->timestamp,
|
|
163
|
+
$vehicle->time_window_end->timestamp,
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
$skills = static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []);
|
|
167
|
+
if (!empty($skills)) {
|
|
168
|
+
$entry['skills'] = $skills;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return $entry;
|
|
172
|
+
})->filter()->values()->toArray();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public static function buildVehicles(Collection $vehicles): array
|
|
176
|
+
{
|
|
177
|
+
return $vehicles->map(function (Vehicle $vehicle) {
|
|
178
|
+
$driver = $vehicle->driver;
|
|
179
|
+
// For vehicle-only mode, fall back to vehicle.location when no driver
|
|
180
|
+
$startLocation = $driver?->location ?? $vehicle->location;
|
|
181
|
+
if (!$startLocation) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
// --- Start position ---
|
|
185
|
+
$entry = [
|
|
186
|
+
'id' => $vehicle->public_id,
|
|
187
|
+
'driver_id' => $driver?->public_id,
|
|
188
|
+
'start' => [$startLocation->getLng(), $startLocation->getLat()],
|
|
189
|
+
];
|
|
190
|
+
// --- Return to depot ---
|
|
191
|
+
if ($vehicle->return_to_depot && $startLocation) {
|
|
192
|
+
$entry['end'] = [$startLocation->getLng(), $startLocation->getLat()];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// --- Multi-dimensional capacity ---
|
|
196
|
+
// [weight_kg, volume_l (×1000 from m3), pallets, parcels]
|
|
197
|
+
$entry['capacity'] = [
|
|
198
|
+
(int) round((float) ($vehicle->capacity_weight_kg ?? static::safeMeta($vehicle, 'max_weight_kg', 0))),
|
|
199
|
+
(int) round((float) ($vehicle->capacity_volume_m3 ?? static::safeMeta($vehicle, 'max_volume_m3', 0)) * 1000),
|
|
200
|
+
(int) ($vehicle->capacity_pallets ?? static::safeMeta($vehicle, 'max_pallets', 0)),
|
|
201
|
+
(int) ($vehicle->capacity_parcels ?? static::safeMeta($vehicle, 'max_parcels', 100)),
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
// --- Max tasks ---
|
|
205
|
+
if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
|
|
206
|
+
$entry['max_tasks'] = (int) $vehicle->max_tasks;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Max travel time (seconds) ---
|
|
210
|
+
$maxTravel = $driver?->max_travel_time ?? static::safeMeta($vehicle, 'max_travel_time_seconds');
|
|
211
|
+
if ($maxTravel) {
|
|
212
|
+
$entry['max_travel_time'] = (int) $maxTravel;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --- Time window ---
|
|
216
|
+
// Priority: driver.time_window_start/end columns → active shift → vehicle.time_window columns
|
|
217
|
+
if ($driver?->time_window_start && $driver?->time_window_end) {
|
|
218
|
+
$entry['time_window'] = [
|
|
219
|
+
$driver->time_window_start->timestamp,
|
|
220
|
+
$driver->time_window_end->timestamp,
|
|
221
|
+
];
|
|
222
|
+
} else {
|
|
223
|
+
$activeShift = $driver?->activeShiftFor(now());
|
|
224
|
+
if ($activeShift) {
|
|
225
|
+
$entry['time_window'] = [
|
|
226
|
+
$activeShift->start_at->timestamp,
|
|
227
|
+
$activeShift->end_at->timestamp,
|
|
228
|
+
];
|
|
229
|
+
} elseif ($vehicle->time_window_start && $vehicle->time_window_end) {
|
|
230
|
+
$entry['time_window'] = [
|
|
231
|
+
$vehicle->time_window_start->timestamp,
|
|
232
|
+
$vehicle->time_window_end->timestamp,
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// --- Skills ---
|
|
238
|
+
$skills = array_values(array_unique(array_merge(
|
|
239
|
+
static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []),
|
|
240
|
+
static::resolveSkills($driver?->skills ?? [], $driver?->custom_fields ?? [])
|
|
241
|
+
)));
|
|
242
|
+
if (!empty($skills)) {
|
|
243
|
+
$entry['skills'] = $skills;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return $entry;
|
|
247
|
+
})->filter()->values()->toArray();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Resolve integer skill codes from a first-class skills array and/or custom fields.
|
|
252
|
+
*
|
|
253
|
+
* The skills array (from the new JSON column) contains string identifiers like
|
|
254
|
+
* "cold_chain", "hazmat", "fragile". These are hashed to stable positive integers.
|
|
255
|
+
* Custom fields that are boolean-true are also included for backwards compatibility.
|
|
256
|
+
*
|
|
257
|
+
* @param array $skills String skill identifiers from the skills JSON column
|
|
258
|
+
* @param array $customFields Custom fields array from HasCustomFields trait
|
|
259
|
+
*
|
|
260
|
+
* @return array<int>
|
|
261
|
+
*/
|
|
262
|
+
public static function resolveSkills(array $skills, array $customFields = []): array
|
|
263
|
+
{
|
|
264
|
+
$codes = [];
|
|
265
|
+
|
|
266
|
+
// First-class skills column: hash each string identifier to a stable integer
|
|
267
|
+
foreach ($skills as $skill) {
|
|
268
|
+
if (is_string($skill) && !empty($skill)) {
|
|
269
|
+
$codes[] = abs(crc32($skill)) % 2147483647 + 1;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Backwards-compatible custom fields: boolean-true fields treated as skills
|
|
274
|
+
foreach ($customFields as $key => $value) {
|
|
275
|
+
if ($value === true || $value === 'true' || $value === '1' || $value === 1) {
|
|
276
|
+
$codes[] = abs(crc32($key)) % 2147483647 + 1;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return array_values(array_unique($codes));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @deprecated use resolveSkills() instead
|
|
285
|
+
*/
|
|
286
|
+
protected static function extractSkillsFromCustomFields(array $customFields): array
|
|
287
|
+
{
|
|
288
|
+
return static::resolveSkills([], $customFields);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -19,7 +19,7 @@ class EventServiceProvider extends ServiceProvider
|
|
|
19
19
|
\Fleetbase\FleetOps\Events\OrderDispatched::class => [\Fleetbase\FleetOps\Listeners\HandleOrderDispatched::class, \Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
|
|
20
20
|
\Fleetbase\FleetOps\Events\OrderDispatchFailed::class => [\Fleetbase\FleetOps\Listeners\HandleOrderDispatchFailed::class, \Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
|
|
21
21
|
\Fleetbase\FleetOps\Events\OrderDriverAssigned::class => [\Fleetbase\FleetOps\Listeners\HandleOrderDriverAssigned::class, \Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
|
|
22
|
-
\Fleetbase\FleetOps\Events\OrderCompleted::class => [\Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
|
|
22
|
+
\Fleetbase\FleetOps\Events\OrderCompleted::class => [\Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class, \Fleetbase\FleetOps\Listeners\HandleDeliveryCompletion::class],
|
|
23
23
|
\Fleetbase\FleetOps\Events\OrderFailed::class => [\Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
|
|
24
24
|
\Fleetbase\FleetOps\Events\OrderReady::class => [\Fleetbase\FleetOps\Listeners\HandleOrderReady::class],
|
|
25
25
|
|
|
@@ -27,5 +27,11 @@ class EventServiceProvider extends ServiceProvider
|
|
|
27
27
|
* Core Events
|
|
28
28
|
*/
|
|
29
29
|
\Fleetbase\Events\UserRemovedFromCompany::class => [\Fleetbase\FleetOps\Listeners\HandleUserRemovedFromCompany::class],
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Scheduling Events
|
|
33
|
+
*/
|
|
34
|
+
\Fleetbase\Events\ScheduleItemCreated::class => [\Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange::class],
|
|
35
|
+
\Fleetbase\Events\ScheduleItemUpdated::class => [\Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange::class],
|
|
30
36
|
];
|
|
31
37
|
}
|
|
@@ -23,21 +23,22 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
23
23
|
* @var array
|
|
24
24
|
*/
|
|
25
25
|
public $observers = [
|
|
26
|
-
\Fleetbase\FleetOps\Models\Order::class
|
|
27
|
-
\Fleetbase\FleetOps\Models\Payload::class
|
|
28
|
-
\Fleetbase\FleetOps\Models\Place::class
|
|
29
|
-
\Fleetbase\FleetOps\Models\ServiceRate::class
|
|
30
|
-
\Fleetbase\FleetOps\Models\PurchaseRate::class
|
|
31
|
-
\Fleetbase\FleetOps\Models\ServiceArea::class
|
|
32
|
-
\Fleetbase\FleetOps\Models\TrackingNumber::class
|
|
33
|
-
\Fleetbase\FleetOps\Models\Driver::class
|
|
34
|
-
\Fleetbase\FleetOps\Models\Vehicle::class
|
|
35
|
-
\Fleetbase\FleetOps\Models\Fleet::class
|
|
36
|
-
\Fleetbase\FleetOps\Models\Contact::class
|
|
37
|
-
\Fleetbase\Models\User::class
|
|
38
|
-
\Fleetbase\Models\Company::class
|
|
39
|
-
\Fleetbase\Models\CompanyUser::class
|
|
40
|
-
\Fleetbase\Models\Category::class
|
|
26
|
+
\Fleetbase\FleetOps\Models\Order::class => \Fleetbase\FleetOps\Observers\OrderObserver::class,
|
|
27
|
+
\Fleetbase\FleetOps\Models\Payload::class => \Fleetbase\FleetOps\Observers\PayloadObserver::class,
|
|
28
|
+
\Fleetbase\FleetOps\Models\Place::class => \Fleetbase\FleetOps\Observers\PlaceObserver::class,
|
|
29
|
+
\Fleetbase\FleetOps\Models\ServiceRate::class => \Fleetbase\FleetOps\Observers\ServiceRateObserver::class,
|
|
30
|
+
\Fleetbase\FleetOps\Models\PurchaseRate::class => \Fleetbase\FleetOps\Observers\PurchaseRateObserver::class,
|
|
31
|
+
\Fleetbase\FleetOps\Models\ServiceArea::class => \Fleetbase\FleetOps\Observers\ServiceAreaObserver::class,
|
|
32
|
+
\Fleetbase\FleetOps\Models\TrackingNumber::class => \Fleetbase\FleetOps\Observers\TrackingNumberObserver::class,
|
|
33
|
+
\Fleetbase\FleetOps\Models\Driver::class => \Fleetbase\FleetOps\Observers\DriverObserver::class,
|
|
34
|
+
\Fleetbase\FleetOps\Models\Vehicle::class => \Fleetbase\FleetOps\Observers\VehicleObserver::class,
|
|
35
|
+
\Fleetbase\FleetOps\Models\Fleet::class => \Fleetbase\FleetOps\Observers\FleetObserver::class,
|
|
36
|
+
\Fleetbase\FleetOps\Models\Contact::class => \Fleetbase\FleetOps\Observers\ContactObserver::class,
|
|
37
|
+
\Fleetbase\Models\User::class => \Fleetbase\FleetOps\Observers\UserObserver::class,
|
|
38
|
+
\Fleetbase\Models\Company::class => \Fleetbase\FleetOps\Observers\CompanyObserver::class,
|
|
39
|
+
\Fleetbase\Models\CompanyUser::class => \Fleetbase\FleetOps\Observers\CompanyUserObserver::class,
|
|
40
|
+
\Fleetbase\Models\Category::class => \Fleetbase\FleetOps\Observers\CategoryObserver::class,
|
|
41
|
+
\Fleetbase\FleetOps\Models\WorkOrder::class => \Fleetbase\FleetOps\Observers\WorkOrderObserver::class,
|
|
41
42
|
];
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -61,6 +62,8 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
61
62
|
\Fleetbase\FleetOps\Console\Commands\SendDriverNotification::class,
|
|
62
63
|
\Fleetbase\FleetOps\Console\Commands\ReplayVehicleLocations::class,
|
|
63
64
|
\Fleetbase\FleetOps\Console\Commands\TestEmail::class,
|
|
65
|
+
\Fleetbase\FleetOps\Console\Commands\ProcessMaintenanceTriggers::class,
|
|
66
|
+
\Fleetbase\FleetOps\Console\Commands\SendMaintenanceReminders::class,
|
|
64
67
|
];
|
|
65
68
|
|
|
66
69
|
/**
|
|
@@ -80,6 +83,13 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
80
83
|
{
|
|
81
84
|
$this->app->register(CoreServiceProvider::class);
|
|
82
85
|
$this->app->register(ReportSchemaServiceProvider::class);
|
|
86
|
+
|
|
87
|
+
// Register the OrchestrationEngineRegistry as a singleton so that engines
|
|
88
|
+
// registered from any service provider share the same instance.
|
|
89
|
+
$this->app->singleton(
|
|
90
|
+
\Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry::class,
|
|
91
|
+
fn () => new \Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry()
|
|
92
|
+
);
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
/**
|
|
@@ -98,9 +108,26 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
98
108
|
$schedule->command('fleetops:dispatch-adhoc')->everyMinute()->withoutOverlapping()->storeOutputInDb();
|
|
99
109
|
$schedule->command('fleetops:update-estimations')->everyTenMinutes()->withoutOverlapping();
|
|
100
110
|
$schedule->command('fleetops:purge-service-quotes')->daily()->withoutOverlapping();
|
|
111
|
+
$schedule->command('fleetops:process-maintenance-triggers')->daily()->withoutOverlapping()->storeOutputInDb();
|
|
112
|
+
$schedule->command('fleetops:send-maintenance-reminders')->daily()->withoutOverlapping()->storeOutputInDb();
|
|
101
113
|
});
|
|
102
114
|
$this->registerNotifications();
|
|
103
115
|
$this->registerExpansionsFrom(__DIR__ . '/../Expansions');
|
|
116
|
+
|
|
117
|
+
// Register built-in orchestration engines.
|
|
118
|
+
// Third-party engines can register themselves by resolving the
|
|
119
|
+
// OrchestrationEngineRegistry singleton from their own service providers.
|
|
120
|
+
$this->app->resolving(
|
|
121
|
+
\Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry::class,
|
|
122
|
+
function (\Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry $registry) {
|
|
123
|
+
if (!$registry->has('vroom')) {
|
|
124
|
+
$registry->register(new \Fleetbase\FleetOps\Orchestration\Engines\VroomOrchestrationEngine());
|
|
125
|
+
}
|
|
126
|
+
if (!$registry->has('greedy')) {
|
|
127
|
+
$registry->register(new \Fleetbase\FleetOps\Orchestration\Engines\GreedyOrchestrationEngine());
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
104
131
|
$this->loadRoutesFrom(__DIR__ . '/../routes.php');
|
|
105
132
|
$this->loadMigrationsFrom(__DIR__ . '/../../migrations');
|
|
106
133
|
$this->loadViewsFrom(__DIR__ . '/../../resources/views', 'fleetops');
|