@fleetbase/fleetops-engine 0.6.38 → 0.6.40
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 +43 -38
- package/addon/components/layout/fleet-ops-sidebar.js +103 -24
- 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 +1088 -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 +52 -0
- package/addon/components/vehicle/form.hbs +51 -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.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 +19 -0
- 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/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 +31 -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 +79 -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.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/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/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 +4 -2
- 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 +53 -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 +38 -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/migrations/2026_04_14_000001_drop_redundant_capacity_columns_from_payloads_table.php +42 -0
- package/server/migrations/2026_04_14_000002_rename_capacity_columns_on_vehicles_table.php +48 -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/Api/v1/OrderController.php +15 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +18 -2
- 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/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 +18 -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 +380 -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,380 @@
|
|
|
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
|
+
* Vehicle capacity is read from the existing first-class columns on the
|
|
20
|
+
* vehicles table (payload_capacity, payload_capacity_volume, etc.).
|
|
21
|
+
*
|
|
22
|
+
* Order/payload capacity demand is computed dynamically by aggregating the
|
|
23
|
+
* weight and dimensions of the payload's entities — no denormalised cache
|
|
24
|
+
* columns are needed or used on the payloads table.
|
|
25
|
+
*/
|
|
26
|
+
class OrchestrationPayloadBuilder
|
|
27
|
+
{
|
|
28
|
+
/**
|
|
29
|
+
* Safely read a meta key from a model, returning $default on any error.
|
|
30
|
+
*
|
|
31
|
+
* Vehicle::getAllMeta() has a strict array return type but some rows store
|
|
32
|
+
* the meta column as a raw JSON string rather than a decoded array, causing
|
|
33
|
+
* a TypeError at runtime. This wrapper catches that and any other meta
|
|
34
|
+
* read failure so a single bad vehicle row does not abort the whole run.
|
|
35
|
+
*
|
|
36
|
+
* @param \Illuminate\Database\Eloquent\Model $model
|
|
37
|
+
*/
|
|
38
|
+
protected static function safeMeta($model, string $key, $default = null)
|
|
39
|
+
{
|
|
40
|
+
try {
|
|
41
|
+
return $model->getMeta($key) ?? $default;
|
|
42
|
+
} catch (\Throwable $e) {
|
|
43
|
+
return $default;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Compute the multi-dimensional capacity demand for a payload by aggregating
|
|
49
|
+
* its entities' weight and volume values.
|
|
50
|
+
*
|
|
51
|
+
* Returns a 4-element integer array matching the vehicle capacity array:
|
|
52
|
+
* [weight_kg, volume_litres, pallets, parcels]
|
|
53
|
+
*
|
|
54
|
+
* Weight is normalised to kg from entity.weight_unit.
|
|
55
|
+
* Volume is derived from entity length × width × height (dimensions_unit normalised to metres)
|
|
56
|
+
* and expressed in litres (×1000) so it can be stored as an integer for VROOM.
|
|
57
|
+
*
|
|
58
|
+
* Falls back to order meta keys (weight_kg, volume_m3, pallets, parcels) for
|
|
59
|
+
* backwards compatibility with orders that were created before entity-level
|
|
60
|
+
* dimension data was captured.
|
|
61
|
+
*/
|
|
62
|
+
protected static function computePayloadDemand(Order $order): array
|
|
63
|
+
{
|
|
64
|
+
$payload = $order->payload;
|
|
65
|
+
|
|
66
|
+
if ($payload && $payload->entities && $payload->entities->isNotEmpty()) {
|
|
67
|
+
$totalWeightKg = 0.0;
|
|
68
|
+
$totalVolumeLit = 0.0;
|
|
69
|
+
$totalParcels = 0;
|
|
70
|
+
|
|
71
|
+
foreach ($payload->entities as $entity) {
|
|
72
|
+
// --- Weight ---
|
|
73
|
+
$rawWeight = (float) ($entity->weight ?? 0);
|
|
74
|
+
$weightUnit = strtolower($entity->weight_unit ?? 'kg');
|
|
75
|
+
$weightKg = match ($weightUnit) {
|
|
76
|
+
'g', 'gram', 'grams' => $rawWeight / 1000,
|
|
77
|
+
'lb', 'lbs', 'pound', 'pounds' => $rawWeight * 0.453592,
|
|
78
|
+
'oz', 'ounce', 'ounces' => $rawWeight * 0.0283495,
|
|
79
|
+
't', 'ton', 'tonne', 'tonnes' => $rawWeight * 1000,
|
|
80
|
+
default => $rawWeight, // kg assumed
|
|
81
|
+
};
|
|
82
|
+
$totalWeightKg += $weightKg;
|
|
83
|
+
|
|
84
|
+
// --- Volume (L × W × H → m³ → litres) ---
|
|
85
|
+
$l = (float) ($entity->length ?? 0);
|
|
86
|
+
$w = (float) ($entity->width ?? 0);
|
|
87
|
+
$h = (float) ($entity->height ?? 0);
|
|
88
|
+
$unit = strtolower($entity->dimensions_unit ?? 'm');
|
|
89
|
+
|
|
90
|
+
if ($l > 0 && $w > 0 && $h > 0) {
|
|
91
|
+
// Normalise to metres
|
|
92
|
+
$factor = match ($unit) {
|
|
93
|
+
'cm', 'centimeter', 'centimetre' => 0.01,
|
|
94
|
+
'mm', 'millimeter', 'millimetre' => 0.001,
|
|
95
|
+
'in', 'inch', 'inches' => 0.0254,
|
|
96
|
+
'ft', 'foot', 'feet' => 0.3048,
|
|
97
|
+
default => 1.0, // metres assumed
|
|
98
|
+
};
|
|
99
|
+
$volumeM3 = ($l * $factor) * ($w * $factor) * ($h * $factor);
|
|
100
|
+
$totalVolumeLit += $volumeM3 * 1000; // m³ → litres
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$totalParcels++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [
|
|
107
|
+
(int) round($totalWeightKg),
|
|
108
|
+
(int) round($totalVolumeLit),
|
|
109
|
+
0, // pallet count not tracked at entity level
|
|
110
|
+
$totalParcels,
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fallback: order meta keys for backwards compatibility
|
|
115
|
+
return [
|
|
116
|
+
(int) round((float) ($order->getMeta('weight_kg') ?? 0)),
|
|
117
|
+
(int) round((float) ($order->getMeta('volume_m3') ?? 0) * 1000),
|
|
118
|
+
(int) ($order->getMeta('pallets') ?? 0),
|
|
119
|
+
(int) ($order->getMeta('parcels') ?? 1),
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build the vehicle capacity array from the vehicle's first-class columns.
|
|
125
|
+
*
|
|
126
|
+
* Returns a 4-element integer array:
|
|
127
|
+
* [weight_kg, volume_litres, pallets, parcels]
|
|
128
|
+
*
|
|
129
|
+
* payload_capacity — existing column, weight in kg
|
|
130
|
+
* payload_capacity_volume — new column, volume in m³ (stored as litres for VROOM)
|
|
131
|
+
* payload_capacity_pallets / payload_capacity_parcels — new columns
|
|
132
|
+
*/
|
|
133
|
+
protected static function buildVehicleCapacity(Vehicle $vehicle): array
|
|
134
|
+
{
|
|
135
|
+
return [
|
|
136
|
+
(int) round((float) ($vehicle->payload_capacity ?? static::safeMeta($vehicle, 'max_weight_kg', 0))),
|
|
137
|
+
(int) round((float) ($vehicle->payload_capacity_volume ?? static::safeMeta($vehicle, 'max_volume_m3', 0)) * 1000),
|
|
138
|
+
(int) ($vehicle->payload_capacity_pallets ?? static::safeMeta($vehicle, 'max_pallets', 0)),
|
|
139
|
+
(int) ($vehicle->payload_capacity_parcels ?? static::safeMeta($vehicle, 'max_parcels', 100)),
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build the normalized job list from a collection of Orders.
|
|
145
|
+
*
|
|
146
|
+
* Each job contains:
|
|
147
|
+
* - id: order public_id
|
|
148
|
+
* - location: [longitude, latitude] of the delivery destination
|
|
149
|
+
* - pickup: [longitude, latitude] of the pickup (for PDPTW shipments)
|
|
150
|
+
* - service: service time in seconds (waypoint.service_time → order meta → default 300)
|
|
151
|
+
* - time_windows: [[earliest_unix, latest_unix]] from order.time_window_start/end or scheduled_at
|
|
152
|
+
* - skills: integer skill codes from order.required_skills or custom fields
|
|
153
|
+
* - amount: multi-dimensional capacity demand [weight_kg, volume_litres, pallets, parcels]
|
|
154
|
+
* - priority: orchestrator_priority (0–100, higher = more important)
|
|
155
|
+
* - description: human-readable label for debugging
|
|
156
|
+
*/
|
|
157
|
+
public static function buildJobs(Collection $orders): array
|
|
158
|
+
{
|
|
159
|
+
return $orders->map(function (Order $order) {
|
|
160
|
+
$payload = $order->payload;
|
|
161
|
+
$destination = $payload?->dropoff ?? $payload?->waypoints?->last();
|
|
162
|
+
|
|
163
|
+
if (!$destination || !$destination->location) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// --- Location ---
|
|
168
|
+
$job = [
|
|
169
|
+
'id' => $order->public_id,
|
|
170
|
+
'location' => [$destination->location->getLng(), $destination->location->getLat()],
|
|
171
|
+
'description' => $order->public_id,
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
// Pickup location (for pickup-and-delivery problems)
|
|
175
|
+
$pickup = $payload?->pickup;
|
|
176
|
+
if ($pickup && $pickup->location) {
|
|
177
|
+
$job['pickup'] = [$pickup->location->getLng(), $pickup->location->getLat()];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- Service time ---
|
|
181
|
+
// Prefer waypoint-level service_time, then order meta, then default 300s
|
|
182
|
+
$waypointMarker = $payload?->waypointMarkers?->last();
|
|
183
|
+
$job['service'] = (int) (
|
|
184
|
+
$waypointMarker?->service_time
|
|
185
|
+
?? $order->getMeta('service_time_seconds')
|
|
186
|
+
?? 300
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// --- Capacity demand ---
|
|
190
|
+
// Aggregated dynamically from payload entities; falls back to order meta.
|
|
191
|
+
$job['amount'] = static::computePayloadDemand($order);
|
|
192
|
+
|
|
193
|
+
// --- Time windows ---
|
|
194
|
+
// Prefer explicit orchestrator time_window columns, fall back to scheduled_at
|
|
195
|
+
if ($order->time_window_start && $order->time_window_end) {
|
|
196
|
+
$job['time_windows'] = [[
|
|
197
|
+
$order->time_window_start->timestamp,
|
|
198
|
+
$order->time_window_end->timestamp,
|
|
199
|
+
]];
|
|
200
|
+
} elseif ($order->scheduled_at) {
|
|
201
|
+
$start = $order->scheduled_at->timestamp;
|
|
202
|
+
$end = $order->scheduled_at->copy()->addHours(4)->timestamp;
|
|
203
|
+
$job['time_windows'] = [[$start, $end]];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Skills ---
|
|
207
|
+
// Prefer first-class required_skills JSON column, fall back to custom fields
|
|
208
|
+
$skills = static::resolveSkills(
|
|
209
|
+
$order->required_skills ?? [],
|
|
210
|
+
$order->custom_fields ?? []
|
|
211
|
+
);
|
|
212
|
+
if (!empty($skills)) {
|
|
213
|
+
$job['skills'] = $skills;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// --- Priority ---
|
|
217
|
+
if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
|
|
218
|
+
$job['priority'] = (int) $order->orchestrator_priority;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return $job;
|
|
222
|
+
})->filter()->values()->toArray();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Build vehicles for vehicle-only allocation mode — no driver required.
|
|
227
|
+
*
|
|
228
|
+
* Uses vehicle.location as the start position. Skills and capacity are
|
|
229
|
+
* read from the vehicle only. Used by the 'assign_vehicles' mode.
|
|
230
|
+
*/
|
|
231
|
+
public static function buildVehiclesOnly(Collection $vehicles): array
|
|
232
|
+
{
|
|
233
|
+
return $vehicles->map(function (Vehicle $vehicle) {
|
|
234
|
+
if (!$vehicle->location) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
$entry = [
|
|
238
|
+
'id' => $vehicle->public_id,
|
|
239
|
+
'driver_id' => null,
|
|
240
|
+
'start' => [$vehicle->location->getLng(), $vehicle->location->getLat()],
|
|
241
|
+
];
|
|
242
|
+
if ($vehicle->return_to_depot && $vehicle->location) {
|
|
243
|
+
$entry['end'] = [$vehicle->location->getLng(), $vehicle->location->getLat()];
|
|
244
|
+
}
|
|
245
|
+
$entry['capacity'] = static::buildVehicleCapacity($vehicle);
|
|
246
|
+
if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
|
|
247
|
+
$entry['max_tasks'] = (int) $vehicle->max_tasks;
|
|
248
|
+
}
|
|
249
|
+
if ($vehicle->time_window_start && $vehicle->time_window_end) {
|
|
250
|
+
$entry['time_window'] = [
|
|
251
|
+
$vehicle->time_window_start->timestamp,
|
|
252
|
+
$vehicle->time_window_end->timestamp,
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
$skills = static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []);
|
|
256
|
+
if (!empty($skills)) {
|
|
257
|
+
$entry['skills'] = $skills;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return $entry;
|
|
261
|
+
})->filter()->values()->toArray();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Build vehicles for driver+vehicle allocation mode.
|
|
266
|
+
*
|
|
267
|
+
* Uses driver.location as the start position, falling back to vehicle.location.
|
|
268
|
+
* Capacity is read from the vehicle. Time windows prefer the driver's explicit
|
|
269
|
+
* window, then the driver's active shift, then the vehicle's window.
|
|
270
|
+
*/
|
|
271
|
+
public static function buildVehicles(Collection $vehicles): array
|
|
272
|
+
{
|
|
273
|
+
return $vehicles->map(function (Vehicle $vehicle) {
|
|
274
|
+
$driver = $vehicle->driver;
|
|
275
|
+
// For vehicle-only mode, fall back to vehicle.location when no driver
|
|
276
|
+
$startLocation = $driver?->location ?? $vehicle->location;
|
|
277
|
+
if (!$startLocation) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
// --- Start position ---
|
|
281
|
+
$entry = [
|
|
282
|
+
'id' => $vehicle->public_id,
|
|
283
|
+
'driver_id' => $driver?->public_id,
|
|
284
|
+
'start' => [$startLocation->getLng(), $startLocation->getLat()],
|
|
285
|
+
];
|
|
286
|
+
// --- Return to depot ---
|
|
287
|
+
if ($vehicle->return_to_depot && $startLocation) {
|
|
288
|
+
$entry['end'] = [$startLocation->getLng(), $startLocation->getLat()];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// --- Multi-dimensional capacity ---
|
|
292
|
+
$entry['capacity'] = static::buildVehicleCapacity($vehicle);
|
|
293
|
+
|
|
294
|
+
// --- Max tasks ---
|
|
295
|
+
if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
|
|
296
|
+
$entry['max_tasks'] = (int) $vehicle->max_tasks;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// --- Max travel time (seconds) ---
|
|
300
|
+
$maxTravel = $driver?->max_travel_time ?? static::safeMeta($vehicle, 'max_travel_time_seconds');
|
|
301
|
+
if ($maxTravel) {
|
|
302
|
+
$entry['max_travel_time'] = (int) $maxTravel;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// --- Time window ---
|
|
306
|
+
// Priority: driver.time_window_start/end columns → active shift → vehicle.time_window columns
|
|
307
|
+
if ($driver?->time_window_start && $driver?->time_window_end) {
|
|
308
|
+
$entry['time_window'] = [
|
|
309
|
+
$driver->time_window_start->timestamp,
|
|
310
|
+
$driver->time_window_end->timestamp,
|
|
311
|
+
];
|
|
312
|
+
} else {
|
|
313
|
+
$activeShift = $driver?->activeShiftFor(now());
|
|
314
|
+
if ($activeShift) {
|
|
315
|
+
$entry['time_window'] = [
|
|
316
|
+
$activeShift->start_at->timestamp,
|
|
317
|
+
$activeShift->end_at->timestamp,
|
|
318
|
+
];
|
|
319
|
+
} elseif ($vehicle->time_window_start && $vehicle->time_window_end) {
|
|
320
|
+
$entry['time_window'] = [
|
|
321
|
+
$vehicle->time_window_start->timestamp,
|
|
322
|
+
$vehicle->time_window_end->timestamp,
|
|
323
|
+
];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- Skills ---
|
|
328
|
+
$skills = array_values(array_unique(array_merge(
|
|
329
|
+
static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []),
|
|
330
|
+
static::resolveSkills($driver?->skills ?? [], $driver?->custom_fields ?? [])
|
|
331
|
+
)));
|
|
332
|
+
if (!empty($skills)) {
|
|
333
|
+
$entry['skills'] = $skills;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return $entry;
|
|
337
|
+
})->filter()->values()->toArray();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Resolve integer skill codes from a first-class skills array and/or custom fields.
|
|
342
|
+
*
|
|
343
|
+
* The skills array (from the new JSON column) contains string identifiers like
|
|
344
|
+
* "cold_chain", "hazmat", "fragile". These are hashed to stable positive integers.
|
|
345
|
+
* Custom fields that are boolean-true are also included for backwards compatibility.
|
|
346
|
+
*
|
|
347
|
+
* @param array $skills String skill identifiers from the skills JSON column
|
|
348
|
+
* @param array $customFields Custom fields array from HasCustomFields trait
|
|
349
|
+
*
|
|
350
|
+
* @return array<int>
|
|
351
|
+
*/
|
|
352
|
+
public static function resolveSkills(array $skills, array $customFields = []): array
|
|
353
|
+
{
|
|
354
|
+
$codes = [];
|
|
355
|
+
|
|
356
|
+
// First-class skills column: hash each string identifier to a stable integer
|
|
357
|
+
foreach ($skills as $skill) {
|
|
358
|
+
if (is_string($skill) && !empty($skill)) {
|
|
359
|
+
$codes[] = abs(crc32($skill)) % 2147483647 + 1;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Backwards-compatible custom fields: boolean-true fields treated as skills
|
|
364
|
+
foreach ($customFields as $key => $value) {
|
|
365
|
+
if ($value === true || $value === 'true' || $value === '1' || $value === 1) {
|
|
366
|
+
$codes[] = abs(crc32($key)) % 2147483647 + 1;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return array_values(array_unique($codes));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @deprecated use resolveSkills() instead
|
|
375
|
+
*/
|
|
376
|
+
protected static function extractSkillsFromCustomFields(array $customFields): array
|
|
377
|
+
{
|
|
378
|
+
return static::resolveSkills([], $customFields);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -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
|
}
|