@fleetbase/fleetops-engine 0.6.70 → 0.6.72
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/RELEASE.md +18 -47
- package/addon/components/admin/tracking-page-settings.hbs +56 -0
- package/addon/components/admin/tracking-page-settings.js +67 -0
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/driver/details.hbs +1 -1
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/equipment/form.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +11 -3
- package/addon/components/maintenance/form.hbs +3 -3
- package/addon/components/map/google-live-map.js +2 -2
- package/addon/components/map/leaflet-live-map.js +45 -32
- package/addon/components/orchestrator/phase-builder.js +2 -1
- package/addon/components/orchestrator-workbench.hbs +7 -1
- package/addon/components/orchestrator-workbench.js +16 -2
- package/addon/components/order/details/activity.hbs +8 -6
- package/addon/components/order/details/activity.js +9 -0
- package/addon/components/order/details.hbs +29 -0
- package/addon/components/order/details.js +26 -0
- package/addon/components/order/form/details.hbs +30 -4
- package/addon/components/order/form/details.js +46 -0
- package/addon/components/order/form/route.hbs +5 -3
- package/addon/components/order/form/route.js +24 -1
- package/addon/components/order/form.hbs +12 -0
- package/addon/components/order/form.js +21 -1
- package/addon/components/order/kanban.js +72 -0
- package/addon/components/order-config-manager/activity-flow.js +45 -10
- package/addon/components/order-config-manager/lifecycle.hbs +250 -0
- package/addon/components/order-config-manager/lifecycle.js +358 -0
- package/addon/components/order-config-manager.js +5 -1
- package/addon/components/service-rate/details.hbs +1 -1
- package/addon/components/service-rate/form.hbs +1 -1
- package/addon/components/tracking-page/full-view.hbs +276 -0
- package/addon/components/tracking-page/full-view.js +474 -0
- package/addon/components/tracking-page/locale-selector.js +30 -0
- package/addon/components/tracking-page/map.hbs +44 -0
- package/addon/components/tracking-page/map.js +328 -0
- package/addon/components/tracking-page/order-list.hbs +35 -0
- package/addon/components/tracking-page/public-view.hbs +60 -0
- package/addon/components/tracking-page/settings-preview.hbs +21 -0
- package/addon/components/tracking-page/settings-preview.js +23 -0
- package/addon/components/tracking-page/verify-panel.hbs +109 -0
- package/addon/components/tracking-page.hbs +142 -0
- package/addon/components/tracking-page.js +564 -0
- package/addon/components/trailer/form.hbs +2 -2
- package/addon/components/vehicle/form.hbs +3 -3
- package/addon/components/warranty/form.hbs +2 -2
- package/addon/components/widget/live-fleet.js +54 -6
- package/addon/components/widget/proof-of-delivery.hbs +49 -0
- package/addon/components/widget/proof-of-delivery.js +62 -0
- package/addon/components/work-order/form.hbs +3 -3
- package/addon/controllers/analytics/reports/index.js +10 -1
- package/addon/controllers/connectivity/devices/index/details.js +1 -0
- package/addon/controllers/connectivity/devices/index.js +8 -2
- package/addon/controllers/connectivity/events/index.js +31 -1
- package/addon/controllers/connectivity/sensors/index/details.js +1 -1
- package/addon/controllers/connectivity/sensors/index.js +37 -1
- package/addon/controllers/maintenance/equipment/index/details.js +2 -2
- package/addon/controllers/maintenance/equipment/index.js +28 -9
- package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
- package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
- package/addon/controllers/maintenance/inspection-submissions/index.js +49 -9
- package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
- package/addon/controllers/maintenance/maintenances/index.js +20 -1
- package/addon/controllers/maintenance/parts/index/details.js +2 -2
- package/addon/controllers/maintenance/parts/index.js +28 -8
- package/addon/controllers/maintenance/schedules/index.js +33 -4
- package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
- package/addon/controllers/maintenance/work-orders/index.js +39 -8
- package/addon/controllers/management/contacts/customers.js +13 -0
- package/addon/controllers/management/contacts/index.js +43 -1
- package/addon/controllers/management/drivers/index/details.js +1 -0
- package/addon/controllers/management/drivers/index.js +32 -2
- package/addon/controllers/management/fleets/index/details.js +1 -0
- package/addon/controllers/management/fleets/index.js +39 -1
- package/addon/controllers/management/fuel-reports/index.js +22 -2
- package/addon/controllers/management/fuel-transactions/index.js +43 -7
- package/addon/controllers/management/issues/index/details.js +1 -0
- package/addon/controllers/management/issues/index.js +22 -2
- package/addon/controllers/management/places/index.js +36 -1
- package/addon/controllers/management/trailers/index/details.js +1 -0
- package/addon/controllers/management/trailers/index.js +50 -2
- package/addon/controllers/management/vehicles/index/details.js +1 -0
- package/addon/controllers/management/vehicles/index.js +33 -2
- package/addon/controllers/management/vendors/index.js +26 -2
- package/addon/controllers/operations/orders/index/details.js +50 -7
- package/addon/controllers/operations/orders/index.js +59 -4
- package/addon/controllers/operations/scheduler/index.js +45 -14
- package/addon/controllers/operations/service-rates/index.js +15 -1
- package/addon/controllers/settings/index.js +6 -0
- package/addon/controllers/settings/map.js +75 -0
- package/addon/controllers/settings/orchestrator.js +8 -6
- package/addon/controllers/settings/tracking-page.js +212 -0
- package/addon/extension.js +97 -1
- package/addon/helpers/parcel-box-image.js +16 -0
- package/addon/routes/operations/orders/index/details.js +1 -0
- package/addon/routes/operations/orders/index/new.js +5 -4
- package/addon/routes/operations/scheduler/index.js +1 -1
- package/addon/routes/settings/tracking-page.js +31 -0
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +1 -0
- package/addon/services/customer-actions.js +2 -1
- package/addon/services/device-actions.js +1 -0
- package/addon/services/device-event-actions.js +1 -0
- package/addon/services/driver-actions.js +4 -13
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/fleet-actions.js +1 -0
- package/addon/services/fuel-report-actions.js +1 -0
- package/addon/services/inspection-form-actions.js +1 -0
- package/addon/services/inspection-submission-actions.js +1 -0
- package/addon/services/integrated-vendor-actions.js +1 -0
- package/addon/services/issue-actions.js +1 -0
- package/addon/services/location.js +69 -21
- package/addon/services/maintenance-actions.js +1 -0
- package/addon/services/maintenance-schedule-actions.js +2 -1
- package/addon/services/map-settings.js +28 -0
- package/addon/services/order-actions.js +7 -0
- package/addon/services/order-allocation.js +1 -1
- package/addon/services/order-presentation.js +129 -0
- package/addon/services/part-actions.js +1 -0
- package/addon/services/place-actions.js +1 -0
- package/addon/services/sensor-actions.js +1 -0
- package/addon/services/service-area-actions.js +1 -0
- package/addon/services/service-rate-actions.js +1 -0
- package/addon/services/telematic-actions.js +1 -0
- package/addon/services/trailer-actions.js +4 -14
- package/addon/services/vehicle-actions.js +4 -13
- package/addon/services/vendor-actions.js +1 -0
- package/addon/services/work-order-actions.js +1 -0
- package/addon/services/zone-actions.js +1 -0
- package/addon/styles/fleetops-engine.css +1391 -0
- package/addon/templates/analytics/reports/index/details.hbs +1 -0
- package/addon/templates/analytics/reports/index.hbs +1 -0
- package/addon/templates/connectivity/devices/index/details.hbs +1 -0
- package/addon/templates/connectivity/devices/index.hbs +1 -0
- package/addon/templates/connectivity/events/index.hbs +1 -0
- package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
- package/addon/templates/connectivity/sensors/index.hbs +1 -0
- package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
- package/addon/templates/maintenance/equipment/index.hbs +2 -0
- package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index.hbs +1 -0
- package/addon/templates/maintenance/parts/index/details.hbs +1 -0
- package/addon/templates/maintenance/parts/index.hbs +2 -0
- package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
- package/addon/templates/maintenance/schedules/index.hbs +2 -0
- package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
- package/addon/templates/maintenance/work-orders/index.hbs +1 -0
- package/addon/templates/management/contacts/customers/details.hbs +1 -0
- package/addon/templates/management/contacts/customers.hbs +1 -0
- package/addon/templates/management/contacts/index/details.hbs +1 -0
- package/addon/templates/management/contacts/index.hbs +1 -0
- package/addon/templates/management/contacts.hbs +1 -0
- package/addon/templates/management/drivers/index/details.hbs +1 -0
- package/addon/templates/management/drivers/index.hbs +2 -0
- package/addon/templates/management/fleets/index/details.hbs +1 -0
- package/addon/templates/management/fleets/index.hbs +1 -0
- package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
- package/addon/templates/management/fuel-reports/index.hbs +1 -0
- package/addon/templates/management/fuel-transactions/index.hbs +1 -0
- package/addon/templates/management/issues/index/details.hbs +1 -0
- package/addon/templates/management/issues/index.hbs +1 -0
- package/addon/templates/management/places/index/details.hbs +1 -0
- package/addon/templates/management/places/index.hbs +1 -0
- package/addon/templates/management/trailers/index/details.hbs +1 -0
- package/addon/templates/management/trailers/index.hbs +2 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -0
- package/addon/templates/management/vehicles/index.hbs +2 -0
- package/addon/templates/management/vendors/index/details.hbs +1 -0
- package/addon/templates/management/vendors/index.hbs +2 -0
- package/addon/templates/management/vendors/integrated.hbs +1 -0
- package/addon/templates/operations/orders/index/details.hbs +1 -0
- package/addon/templates/operations/orders/index.hbs +1 -0
- package/addon/templates/operations/service-rates/index/details.hbs +1 -0
- package/addon/templates/operations/service-rates/index.hbs +1 -0
- package/addon/templates/settings/map.hbs +46 -0
- package/addon/templates/settings/tracking-page.hbs +318 -0
- package/addon/utils/identity-cell-resource.js +2 -56
- package/addon/utils/map-default-view.js +132 -0
- package/addon/utils/normalize-order-config-flow.js +6 -1
- package/addon/utils/placeholder-images.js +4 -69
- package/addon/utils/resource-descriptors/assets.js +2 -57
- package/addon/utils/resource-descriptors/helpers.js +23 -122
- package/addon/utils/resource-descriptors/operations.js +2 -61
- package/addon/utils/resource-descriptors/people.js +3 -142
- package/addon/utils/resource-descriptors/shared.js +18 -0
- package/addon/utils/tracking-live-source.js +123 -0
- package/addon/utils/tracking-page-theme.js +123 -0
- package/app/components/admin/tracking-page-settings.js +1 -0
- package/app/components/tracking-page/full-view.js +1 -0
- package/app/components/tracking-page/locale-selector.js +1 -0
- package/app/components/tracking-page/map.js +1 -0
- package/app/components/tracking-page/order-list.js +1 -0
- package/app/components/tracking-page/public-view.js +1 -0
- package/app/components/tracking-page/settings-preview.js +1 -0
- package/app/components/tracking-page/verify-panel.js +1 -0
- package/app/components/tracking-page.js +1 -0
- package/app/components/widget/proof-of-delivery.js +1 -0
- package/app/controllers/settings/tracking-page.js +1 -0
- package/app/helpers/parcel-box-image.js +1 -0
- package/app/routes/settings/tracking-page.js +1 -0
- package/app/services/order-presentation.js +1 -0
- package/app/templates/settings/tracking-page.js +1 -0
- package/app/utils/map-default-view.js +1 -0
- package/app/utils/tracking-live-source.js +1 -0
- package/app/utils/tracking-page-theme.js +1 -0
- package/composer.json +2 -2
- package/extension.json +1 -1
- package/package.json +4 -4
- package/scripts/pest-bootstrap.php +4 -0
- package/server/config/fleetops.php +4 -0
- package/server/migrations/2026_10_08_000001_create_tracking_sessions_tables.php +51 -0
- package/server/migrations/2026_10_10_000001_add_trashed_index_to_device_events.php +40 -0
- package/server/migrations/2026_10_10_000002_add_trashed_index_to_positions.php +40 -0
- package/server/migrations/2026_10_12_000001_add_deleted_at_to_tracking_sessions_tables.php +32 -0
- package/server/resources/views/labels/default.php +2 -2
- package/server/resources/views/labels/entity-label.php +2 -2
- package/server/resources/views/labels/waypoint-label.php +2 -2
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +1 -0
- package/server/src/Console/Commands/PruneTelematicsData.php +101 -26
- package/server/src/Events/OrderChanged.php +257 -0
- package/server/src/Events/OrderDriverUnassigned.php +68 -0
- package/server/src/Exceptions/OrderEditLockedException.php +32 -0
- package/server/src/Expansions/CompanyExpansion.php +3 -2
- package/server/src/Expansions/UserExpansion.php +12 -10
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +108 -18
- package/server/src/Http/Controllers/Api/v1/DriverController.php +95 -10
- package/server/src/Http/Controllers/Api/v1/EntityController.php +66 -0
- package/server/src/Http/Controllers/Api/v1/FleetController.php +10 -4
- package/server/src/Http/Controllers/Api/v1/OrderController.php +139 -36
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +76 -8
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +3 -3
- package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +6 -32
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +5 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +3 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +10 -0
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +16 -2
- package/server/src/Http/Controllers/Internal/v1/InspectionSubmissionController.php +13 -8
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +4 -2
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +93 -11
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +118 -28
- package/server/src/Http/Controllers/Internal/v1/PositionController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/ProofController.php +55 -4
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +303 -10
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +1 -0
- package/server/src/Http/Controllers/Public/TrackingController.php +518 -0
- package/server/src/Http/Filter/ContactFilter.php +1 -1
- package/server/src/Http/Filter/InspectionFormFilter.php +23 -0
- package/server/src/Http/Filter/InspectionSubmissionFilter.php +23 -0
- package/server/src/Http/Filter/MaintenanceFilter.php +23 -0
- package/server/src/Http/Filter/OrderFilter.php +39 -4
- package/server/src/Http/Filter/ProofFilter.php +18 -0
- package/server/src/Http/Filter/RouteFilter.php +18 -0
- package/server/src/Http/Filter/TelematicFilter.php +23 -0
- package/server/src/Http/Filter/WarrantyFilter.php +23 -0
- package/server/src/Http/Requests/CancelOrderRequest.php +2 -1
- package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +14 -1
- package/server/src/Http/Resources/v1/Index/Customer.php +18 -0
- package/server/src/Http/Resources/v1/Index/Order.php +11 -2
- package/server/src/Http/Resources/v1/PublicOrderTracking.php +153 -0
- package/server/src/Http/Resources/v1/TrackingNumber.php +8 -34
- package/server/src/Http/Resources/v1/Zone.php +31 -23
- package/server/src/Jobs/FinalizeApiOrderCreation.php +3 -1
- package/server/src/Jobs/ProcessAllocationJob.php +16 -6
- package/server/src/Jobs/PublishTrackingUpdate.php +50 -0
- package/server/src/Listeners/HandleGeofenceEntered.php +31 -15
- package/server/src/Listeners/HandleOrderCanceled.php +9 -1
- package/server/src/Listeners/HandleUserRemovedFromCompany.php +2 -1
- package/server/src/Listeners/PublishTrackingUpdates.php +27 -0
- package/server/src/Mail/CustomerCredentialsMail.php +4 -2
- package/server/src/Models/AssetConnection.php +3 -0
- package/server/src/Models/Contact.php +3 -0
- package/server/src/Models/Device.php +28 -0
- package/server/src/Models/DeviceEvent.php +7 -0
- package/server/src/Models/Driver.php +20 -7
- package/server/src/Models/Entity.php +12 -4
- package/server/src/Models/Fleet.php +11 -32
- package/server/src/Models/FleetDriver.php +3 -0
- package/server/src/Models/FleetVehicle.php +3 -0
- package/server/src/Models/FuelReport.php +5 -13
- package/server/src/Models/Issue.php +4 -16
- package/server/src/Models/Order.php +234 -11
- package/server/src/Models/OrderConfig.php +129 -0
- package/server/src/Models/Payload.php +227 -35
- package/server/src/Models/Place.php +7 -1
- package/server/src/Models/ServiceArea.php +7 -0
- package/server/src/Models/ServiceRate.php +7 -17
- package/server/src/Models/TrackingNumber.php +109 -6
- package/server/src/Models/TrackingSession.php +33 -0
- package/server/src/Models/TrackingSessionGrant.php +22 -0
- package/server/src/Models/TrackingStatus.php +6 -0
- package/server/src/Models/Vehicle.php +21 -2
- package/server/src/Models/Vendor.php +7 -0
- package/server/src/Models/Waypoint.php +13 -4
- package/server/src/Notifications/OrderAssigned.php +2 -1
- package/server/src/Notifications/OrderCanceled.php +7 -1
- package/server/src/Notifications/OrderChanged.php +87 -0
- package/server/src/Notifications/OrderCompleted.php +2 -1
- package/server/src/Notifications/OrderDispatchFailed.php +2 -1
- package/server/src/Notifications/OrderDispatched.php +2 -1
- package/server/src/Notifications/OrderFailed.php +2 -1
- package/server/src/Notifications/OrderUnassigned.php +66 -0
- package/server/src/Notifications/WaypointCompleted.php +2 -1
- package/server/src/Observers/CompanyUserObserver.php +8 -7
- package/server/src/Observers/DriverObserver.php +6 -1
- package/server/src/Observers/OrderObserver.php +141 -1
- package/server/src/Observers/TrackingNumberObserver.php +18 -6
- package/server/src/Observers/UserObserver.php +2 -1
- package/server/src/Observers/VehicleObserver.php +8 -3
- package/server/src/Orchestration/Engines/RouteSequencingEngine.php +21 -3
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +38 -8
- package/server/src/Orchestration/OrchestrationEngineRegistry.php +61 -1
- package/server/src/Orchestration/Support/OrchestratorSettings.php +61 -0
- package/server/src/Orchestration/Support/RouteMetrics.php +165 -0
- package/server/src/Providers/EventServiceProvider.php +11 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +13 -0
- package/server/src/Support/ApiCacheThrottle.php +76 -0
- package/server/src/Support/LiveOrderQuery.php +3 -1
- package/server/src/Support/OrderEdit.php +365 -0
- package/server/src/Support/OrderEditPolicy.php +292 -0
- package/server/src/Support/ProfileAccountManager.php +54 -1
- package/server/src/Support/ResolvesOrderServiceStops.php +5 -0
- package/server/src/Support/SocketChannels.php +346 -0
- package/server/src/Support/Telematics/TelematicService.php +7 -2
- package/server/src/Support/Telematics/Telemetry/Inbox.php +8 -1
- package/server/src/Support/TrackingChannel.php +55 -0
- package/server/src/Support/TrackingCode.php +242 -0
- package/server/src/Support/TrackingPage/TrackingAccess.php +356 -0
- package/server/src/Support/TrackingPage/TrackingPageConfig.php +264 -0
- package/server/src/Support/TrackingPage/TrackingRecipient.php +111 -0
- package/server/src/Support/TrackingPage/TrackingResolver.php +201 -0
- package/server/src/Support/TrackingPage/TrackingTarget.php +40 -0
- package/server/src/Support/TrackingPage/TrackingView.php +543 -0
- package/server/src/Support/TrackingPublisher.php +261 -0
- package/server/src/Support/TrackingScope.php +182 -0
- package/server/src/Support/TrackingUpdate.php +402 -0
- package/server/src/Support/Utils.php +15 -3
- package/server/src/Traits/HasTrackingNumber.php +2 -0
- package/server/src/routes.php +39 -7
- package/server/tests/ApiCustomerControllerContractsTest.php +54 -7
- package/server/tests/ApiDriverPasswordContractsTest.php +25 -21
- package/server/tests/ApiEntityControllerContractsTest.php +84 -0
- package/server/tests/ApiOrderControllerContractsTest.php +245 -0
- package/server/tests/ApiPayloadControllerContractsTest.php +121 -0
- package/server/tests/ApiPlaceControllerContractsTest.php +1 -1
- package/server/tests/ApiTrackingNumberControllerContractsTest.php +6 -9
- package/server/tests/CompactResourceSerializationTest.php +23 -59
- package/server/tests/ControllerFilterContractsTest.php +1 -1
- package/server/tests/CustomerContactIdentitySafetyTest.php +1 -1
- package/server/tests/EventContractsTest.php +26 -60
- package/server/tests/ExpansionContractsTest.php +15 -2
- package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +29 -3
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +23 -0
- package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +16 -16
- package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +41 -14
- package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +57 -22
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +23 -9
- package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +171 -1
- package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +110 -14
- package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
- package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +7 -5
- package/server/tests/Feature/Http/Internal/PositionControllerReplayChannelTest.php +135 -0
- package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +3 -3
- package/server/tests/Feature/Http/Internal/ProofControllerRecentTest.php +170 -0
- package/server/tests/Feature/Http/Public/TrackingControllerTest.php +495 -0
- package/server/tests/IndexCustomerPhotoTest.php +151 -0
- package/server/tests/ModelAccessorContractsTest.php +4 -3
- package/server/tests/NotificationAndMailContractsTest.php +5 -8
- package/server/tests/ObserverContractsTest.php +72 -10
- package/server/tests/OrderActivityFlowRegressionTest.php +3 -2
- package/server/tests/OrderConfigLifecycleTest.php +120 -0
- package/server/tests/OrderFilterExecutionTest.php +167 -2
- package/server/tests/ProcessAllocationJobContractsTest.php +20 -0
- package/server/tests/PublicOrderTrackingResourceTest.php +221 -0
- package/server/tests/QueueJobContractsTest.php +2 -1
- package/server/tests/RequestContractsTest.php +1 -1
- package/server/tests/RouteRegistrationExecutionTest.php +26 -0
- package/server/tests/RouteSequencingEngineTest.php +17 -0
- package/server/tests/SettingControllerContractsTest.php +110 -0
- package/server/tests/Support/TrackingPageDatabase.php +200 -0
- package/server/tests/TenantScopingTest.php +206 -0
- package/server/tests/TrackingPageSettingsTest.php +343 -0
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +51 -0
- package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
- package/server/tests/Unit/Http/Filter/ContactFilterAddressTest.php +61 -0
- package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +30 -0
- package/server/tests/Unit/Jobs/FinalizeApiOrderCreationDispatchGuardTest.php +266 -0
- package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +182 -22
- package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +5 -0
- package/server/tests/Unit/Models/DriverAssignVehicleTest.php +81 -0
- package/server/tests/Unit/Models/FleetTest.php +4 -3
- package/server/tests/Unit/Models/OrderCancelReasonTest.php +143 -0
- package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +60 -0
- package/server/tests/Unit/Models/OrderPayloadRouteTest.php +4 -2
- package/server/tests/Unit/Models/PayloadTest.php +18 -4
- package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +2 -2
- package/server/tests/Unit/Models/TrackingNumberTest.php +67 -6
- package/server/tests/Unit/Models/VehicleTest.php +16 -2
- package/server/tests/Unit/Models/WaypointTest.php +4 -3
- package/server/tests/Unit/Notifications/OrderChangeNotificationsTest.php +59 -0
- package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +3 -3
- package/server/tests/Unit/Observers/DriverDeletionCleanupTest.php +158 -0
- package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +20 -0
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +3 -3
- package/server/tests/Unit/Observers/OrderAssignmentObserverTest.php +100 -0
- package/server/tests/Unit/Observers/OrderDeleteCascadeTest.php +213 -0
- package/server/tests/Unit/Observers/VehicleObserverDeleteTest.php +67 -0
- package/server/tests/Unit/Orchestration/EngineSelectionAndRouteMetricsTest.php +336 -0
- package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +8 -1
- package/server/tests/Unit/Support/ApiCacheDependenciesTest.php +347 -0
- package/server/tests/Unit/Support/OrderEditPolicyTest.php +156 -0
- package/server/tests/Unit/Support/OrderOperationsTest.php +514 -0
- package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +4 -3
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +141 -0
- package/server/tests/Unit/Support/QuerySeamsTest.php +5 -1
- package/server/tests/Unit/Support/SocketChannelsTest.php +396 -0
- package/server/tests/Unit/Support/TrackingChannelTest.php +232 -0
- package/server/tests/Unit/Support/TrackingCodeTest.php +186 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingAccessTest.php +200 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingRecipientTest.php +51 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingResolverTest.php +181 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingViewTest.php +479 -0
- package/server/tests/Unit/Support/TrackingPublisherTest.php +425 -0
- package/server/tests/Unit/Support/TrackingUpdateTest.php +363 -0
- package/server/tests/Unit/Support/UtilsAdditionalTest.php +9 -0
- package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +25 -0
- package/server/tests/VehicleResourceContractsTest.php +46 -0
- package/server/tests/VroomOrchestrationEngineTest.php +34 -1
- package/server/tests/ZoneGeofenceSettingsResourceTest.php +121 -0
- package/translations/ar-ae.yml +274 -0
- package/translations/bg-bg.yaml +274 -0
- package/translations/en-us.yaml +347 -0
- package/translations/es-pa.yaml +274 -0
- package/translations/fr-fr.yaml +274 -0
- package/translations/mn-mn.yaml +274 -0
- package/translations/pt-br.yaml +274 -0
- package/translations/ru-ru.yaml +276 -2
- package/translations/uk-ua.yaml +275 -1
- package/translations/vi-vn.yaml +274 -0
- package/addon/components/cell/contact-identity.hbs +0 -1
- package/addon/components/cell/customer-identity.hbs +0 -1
- package/addon/components/cell/driver-identity.hbs +0 -1
- package/addon/components/cell/fleet-identity.hbs +0 -1
- package/addon/components/cell/order-identity.hbs +0 -1
- package/addon/components/cell/place-identity.hbs +0 -1
- package/addon/components/cell/vehicle-identity.hbs +0 -1
- package/addon/components/cell/vendor-identity.hbs +0 -1
- package/addon/components/contact/pill.hbs +0 -24
- package/addon/components/contact/summary.hbs +0 -1
- package/addon/components/customer/pill.hbs +0 -24
- package/addon/components/customer/summary.hbs +0 -1
- package/addon/components/driver/pill.hbs +0 -24
- package/addon/components/driver/summary.hbs +0 -1
- package/addon/components/fleet/pill.hbs +0 -24
- package/addon/components/fleet/summary.hbs +0 -1
- package/addon/components/order/pill.hbs +0 -24
- package/addon/components/order/summary.hbs +0 -1
- package/addon/components/place/pill.hbs +0 -24
- package/addon/components/place/summary.hbs +0 -1
- package/addon/components/select-option/contact.hbs +0 -12
- package/addon/components/select-option/customer.hbs +0 -12
- package/addon/components/select-option/driver.hbs +0 -12
- package/addon/components/select-option/fleet.hbs +0 -12
- package/addon/components/select-option/order.hbs +0 -12
- package/addon/components/select-option/place.hbs +0 -12
- package/addon/components/select-option/vehicle.hbs +0 -12
- package/addon/components/select-option/vendor.hbs +0 -12
- package/addon/components/vehicle/pill.hbs +0 -24
- package/addon/components/vehicle/summary.hbs +0 -1
- package/addon/components/vendor/pill.hbs +0 -24
- package/addon/components/vendor/summary.hbs +0 -1
- package/app/components/cell/contact-identity.js +0 -1
- package/app/components/cell/customer-identity.js +0 -1
- package/app/components/cell/driver-identity.js +0 -1
- package/app/components/cell/fleet-identity.js +0 -1
- package/app/components/cell/order-identity.js +0 -1
- package/app/components/cell/place-identity.js +0 -1
- package/app/components/cell/vehicle-identity.js +0 -1
- package/app/components/cell/vendor-identity.js +0 -1
- package/app/components/contact/pill.js +0 -1
- package/app/components/contact/summary.js +0 -1
- package/app/components/customer/pill.js +0 -1
- package/app/components/customer/summary.js +0 -1
- package/app/components/driver/pill.js +0 -1
- package/app/components/driver/summary.js +0 -1
- package/app/components/fleet/pill.js +0 -1
- package/app/components/fleet/summary.js +0 -1
- package/app/components/order/pill.js +0 -1
- package/app/components/order/summary.js +0 -1
- package/app/components/place/pill.js +0 -1
- package/app/components/place/summary.js +0 -1
- package/app/components/select-option/contact.js +0 -1
- package/app/components/select-option/customer.js +0 -1
- package/app/components/select-option/driver.js +0 -1
- package/app/components/select-option/fleet.js +0 -1
- package/app/components/select-option/order.js +0 -1
- package/app/components/select-option/place.js +0 -1
- package/app/components/select-option/vehicle.js +0 -1
- package/app/components/select-option/vendor.js +0 -1
- package/app/components/vehicle/pill.js +0 -1
- package/app/components/vehicle/summary.js +0 -1
- package/app/components/vendor/pill.js +0 -1
- package/app/components/vendor/summary.js +0 -1
|
@@ -13,12 +13,15 @@ use Fleetbase\FleetOps\Models\Payload;
|
|
|
13
13
|
use Fleetbase\FleetOps\Models\Place;
|
|
14
14
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
15
15
|
use Fleetbase\FleetOps\Models\Vendor;
|
|
16
|
+
use Fleetbase\FleetOps\Models\Waypoint;
|
|
17
|
+
use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
|
|
16
18
|
use Fleetbase\FleetOps\Orchestration\Engines\DriverAssignmentEngine;
|
|
17
19
|
use Fleetbase\FleetOps\Orchestration\Engines\RouteSequencingEngine;
|
|
18
20
|
use Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry;
|
|
21
|
+
use Fleetbase\FleetOps\Orchestration\Support\OrchestratorSettings;
|
|
22
|
+
use Fleetbase\FleetOps\Orchestration\Support\RouteMetrics;
|
|
19
23
|
use Fleetbase\FleetOps\Traits\AuthorizesMethods;
|
|
20
24
|
use Fleetbase\Http\Controllers\Controller;
|
|
21
|
-
use Fleetbase\Models\Setting;
|
|
22
25
|
use Illuminate\Http\JsonResponse;
|
|
23
26
|
use Illuminate\Http\Request;
|
|
24
27
|
use Illuminate\Support\Carbon;
|
|
@@ -42,6 +45,11 @@ class OrchestrationController extends Controller
|
|
|
42
45
|
{
|
|
43
46
|
use AuthorizesMethods;
|
|
44
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Engines that assign orders to vehicles without producing a stop sequence.
|
|
50
|
+
*/
|
|
51
|
+
protected const ASSIGNMENT_ONLY_ENGINES = ['greedy', 'capacity'];
|
|
52
|
+
|
|
45
53
|
public function __construct(protected OrchestrationEngineRegistry $registry)
|
|
46
54
|
{
|
|
47
55
|
$this->authorizeMethods([
|
|
@@ -281,7 +289,7 @@ class OrchestrationController extends Controller
|
|
|
281
289
|
// ── Run engine ────────────────────────────────────────────────────────
|
|
282
290
|
$engineId = $mode === 'assign_drivers'
|
|
283
291
|
? 'driver_assignment'
|
|
284
|
-
: ($request->input('options.engine')
|
|
292
|
+
: ($request->input('options.engine') ?: $this->orchestratorEngineSetting());
|
|
285
293
|
|
|
286
294
|
try {
|
|
287
295
|
if ($mode === 'assign_drivers') {
|
|
@@ -308,14 +316,13 @@ class OrchestrationController extends Controller
|
|
|
308
316
|
}
|
|
309
317
|
}
|
|
310
318
|
}
|
|
311
|
-
$
|
|
312
|
-
$result = $engine->sequence($orders, $options);
|
|
319
|
+
$result = $this->optimizeRoutes($engineId, $orders, $options);
|
|
313
320
|
} else {
|
|
314
|
-
$
|
|
315
|
-
$result = $engine->allocate($orders, $vehicles, $options);
|
|
321
|
+
$result = $this->registry->allocateWithFallback($engineId, $orders, $vehicles, $options);
|
|
316
322
|
}
|
|
317
323
|
} catch (\RuntimeException $e) {
|
|
318
|
-
// Engine is unavailable (e.g. VROOM not reachable)
|
|
324
|
+
// Engine is unavailable (e.g. VROOM not reachable) and there is no
|
|
325
|
+
// built-in engine to fall back to.
|
|
319
326
|
// Return a structured JSON 503 so the frontend can display a
|
|
320
327
|
// user-friendly message instead of an unhandled exception page.
|
|
321
328
|
return response()->json([
|
|
@@ -325,6 +332,10 @@ class OrchestrationController extends Controller
|
|
|
325
332
|
], 503);
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
if ($mode !== 'assign_drivers') {
|
|
336
|
+
$result = RouteMetrics::annotate($result, $orders, $vehicles);
|
|
337
|
+
}
|
|
338
|
+
|
|
328
339
|
return response()->json($result);
|
|
329
340
|
}
|
|
330
341
|
|
|
@@ -384,7 +395,71 @@ class OrchestrationController extends Controller
|
|
|
384
395
|
|
|
385
396
|
protected function orchestratorEngineSetting(): string
|
|
386
397
|
{
|
|
387
|
-
return
|
|
398
|
+
return OrchestratorSettings::engineForCompany($this->companyUuid());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Re-sequence each vehicle's already-assigned orders.
|
|
403
|
+
*
|
|
404
|
+
* A solver engine (e.g. VROOM) is run once per vehicle, restricted to
|
|
405
|
+
* that vehicle and its orders, so the stop order, arrival times and
|
|
406
|
+
* distances come from the selected engine. Engines that only assign
|
|
407
|
+
* orders (greedy, capacity) cannot sequence stops, and a solver that is
|
|
408
|
+
* missing or unavailable is replaced; in both cases the built-in route
|
|
409
|
+
* sequencer runs and the result says which engine ran and why.
|
|
410
|
+
*/
|
|
411
|
+
protected function optimizeRoutes(string $engineId, $orders, array $options): array
|
|
412
|
+
{
|
|
413
|
+
$warn = true;
|
|
414
|
+
if (in_array($engineId, self::ASSIGNMENT_ONLY_ENGINES, true)) {
|
|
415
|
+
$warn = false;
|
|
416
|
+
$reason = sprintf('The "%s" engine assigns orders but does not sequence stops; stops were sequenced by the built-in route sequencer.', $engineId);
|
|
417
|
+
} elseif (!$this->registry->has($engineId)) {
|
|
418
|
+
$reason = sprintf('No orchestration engine is registered as "%s".', $engineId);
|
|
419
|
+
} else {
|
|
420
|
+
try {
|
|
421
|
+
return $this->sequenceWithEngine($this->registry->resolve($engineId), $orders, $options);
|
|
422
|
+
} catch (\RuntimeException $e) {
|
|
423
|
+
$reason = $e->getMessage();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
$result = $this->routeSequencingEngine()->sequence($orders, $options);
|
|
428
|
+
|
|
429
|
+
return OrchestrationEngineRegistry::markFallback($result, $result['summary']['engine'] ?? 'route_sequencing', $engineId, $reason, $warn);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Run a solver engine once per vehicle group, keeping every order on its vehicle.
|
|
434
|
+
*/
|
|
435
|
+
protected function sequenceWithEngine(OrchestrationEngineInterface $engine, $orders, array $options): array
|
|
436
|
+
{
|
|
437
|
+
$options = array_merge($options, ['allocation_strategy' => 'route_aware']);
|
|
438
|
+
$assignments = [];
|
|
439
|
+
$unassigned = [];
|
|
440
|
+
|
|
441
|
+
foreach ($orders->groupBy(fn ($order) => $order->vehicle_assigned_uuid ?? '') as $vehicleUuid => $vehicleOrders) {
|
|
442
|
+
$vehicle = $vehicleUuid !== '' && $vehicleOrders->first()->relationLoaded('vehicle') ? $vehicleOrders->first()->vehicle : null;
|
|
443
|
+
if (!$vehicle) {
|
|
444
|
+
// Without an assigned vehicle there is no route to sequence.
|
|
445
|
+
array_push($unassigned, ...$vehicleOrders->pluck('public_id')->all());
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
$result = $engine->allocate($vehicleOrders->values(), collect([$vehicle]), $options);
|
|
450
|
+
array_push($assignments, ...($result['assignments'] ?? []));
|
|
451
|
+
array_push($unassigned, ...($result['unassigned'] ?? []));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return [
|
|
455
|
+
'assignments' => $assignments,
|
|
456
|
+
'unassigned' => array_values(array_unique($unassigned)),
|
|
457
|
+
'summary' => [
|
|
458
|
+
'engine' => $engine->getIdentifier(),
|
|
459
|
+
'assigned' => count($assignments),
|
|
460
|
+
'unassigned' => count(array_unique($unassigned)),
|
|
461
|
+
],
|
|
462
|
+
];
|
|
388
463
|
}
|
|
389
464
|
|
|
390
465
|
protected function driverAssignmentEngine(): DriverAssignmentEngine
|
|
@@ -415,9 +490,10 @@ class OrchestrationController extends Controller
|
|
|
415
490
|
return response()->json(['error' => 'No assignments provided.'], 422);
|
|
416
491
|
}
|
|
417
492
|
|
|
418
|
-
$committed
|
|
419
|
-
$failed
|
|
420
|
-
$manifests
|
|
493
|
+
$committed = [];
|
|
494
|
+
$failed = [];
|
|
495
|
+
$manifests = [];
|
|
496
|
+
$resequenced = false;
|
|
421
497
|
|
|
422
498
|
$this->beginOrchestrationTransaction();
|
|
423
499
|
try {
|
|
@@ -502,6 +578,7 @@ class OrchestrationController extends Controller
|
|
|
502
578
|
if (!empty($assignment['waypoint_sequence']) && $order->payload) {
|
|
503
579
|
foreach ($assignment['waypoint_sequence'] as $seq => $waypointId) {
|
|
504
580
|
$this->updateWaypointSequence($order->payload_uuid, $waypointId, $seq);
|
|
581
|
+
$resequenced = true;
|
|
505
582
|
}
|
|
506
583
|
}
|
|
507
584
|
|
|
@@ -523,6 +600,11 @@ class OrchestrationController extends Controller
|
|
|
523
600
|
return response()->json(['error' => 'Commit failed: ' . $e->getMessage()], 500);
|
|
524
601
|
}
|
|
525
602
|
|
|
603
|
+
// Waypoint sequences are written without model events
|
|
604
|
+
if ($resequenced) {
|
|
605
|
+
Waypoint::invalidateApiCacheManually($companyUuid);
|
|
606
|
+
}
|
|
607
|
+
|
|
526
608
|
return response()->json([
|
|
527
609
|
'committed' => $committed,
|
|
528
610
|
'failed' => $failed,
|
|
@@ -10,6 +10,7 @@ use Fleetbase\FleetOps\Events\OrderDispatchFailed;
|
|
|
10
10
|
use Fleetbase\FleetOps\Events\OrderStarted;
|
|
11
11
|
use Fleetbase\FleetOps\Events\WaypointActivityChanged;
|
|
12
12
|
use Fleetbase\FleetOps\Events\WaypointCompleted;
|
|
13
|
+
use Fleetbase\FleetOps\Exceptions\OrderEditLockedException;
|
|
13
14
|
use Fleetbase\FleetOps\Exports\OrderExport;
|
|
14
15
|
use Fleetbase\FleetOps\Flow\Activity;
|
|
15
16
|
use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
@@ -19,6 +20,7 @@ use Fleetbase\FleetOps\Http\Requests\Internal\CreateOrderRequest;
|
|
|
19
20
|
use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as OrderIndexResource;
|
|
20
21
|
use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
|
|
21
22
|
use Fleetbase\FleetOps\Http\Resources\v1\Proof as ProofResource;
|
|
23
|
+
use Fleetbase\FleetOps\Http\Resources\v1\PublicOrderTracking;
|
|
22
24
|
use Fleetbase\FleetOps\Imports\OrdersImport;
|
|
23
25
|
use Fleetbase\FleetOps\Jobs\FinalizeInternalOrderCreation;
|
|
24
26
|
use Fleetbase\FleetOps\Jobs\NotifyBulkAssignedDriver;
|
|
@@ -33,6 +35,7 @@ use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
|
33
35
|
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
34
36
|
use Fleetbase\FleetOps\Models\Waypoint;
|
|
35
37
|
use Fleetbase\FleetOps\Notifications\OrderPing;
|
|
38
|
+
use Fleetbase\FleetOps\Support\OrderEdit;
|
|
36
39
|
use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
|
|
37
40
|
use Fleetbase\FleetOps\Support\Utils;
|
|
38
41
|
use Fleetbase\Http\Requests\ExportRequest;
|
|
@@ -54,6 +57,12 @@ use Maatwebsite\Excel\Facades\Excel;
|
|
|
54
57
|
class OrderController extends FleetOpsController
|
|
55
58
|
{
|
|
56
59
|
use ResolvesOrderServiceStops;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The one answer the public tracking lookup gives for any number it cannot resolve.
|
|
63
|
+
*/
|
|
64
|
+
public const TRACKING_LOOKUP_ERROR = 'No order found using tracking number provided.';
|
|
65
|
+
|
|
57
66
|
/**
|
|
58
67
|
* Permissions for methods AuthorizationGuard cannot map to a schema action (see FleetOpsController).
|
|
59
68
|
*
|
|
@@ -163,8 +172,15 @@ class OrderController extends FleetOpsController
|
|
|
163
172
|
$input['type'] = 'transport';
|
|
164
173
|
}
|
|
165
174
|
|
|
175
|
+
// a configured lifecycle always starts at its initial activity; otherwise
|
|
166
176
|
// if no status is set its default to `created`
|
|
167
|
-
if (
|
|
177
|
+
if ($resolvedOrderConfig && $resolvedOrderConfig->hasConfiguredLifecycle()) {
|
|
178
|
+
$input['status'] = $resolvedOrderConfig->getInitialStatusCode();
|
|
179
|
+
if (!$resolvedOrderConfig->allowsDispatch()) {
|
|
180
|
+
$input['dispatched'] = false;
|
|
181
|
+
$input['adhoc'] = false;
|
|
182
|
+
}
|
|
183
|
+
} elseif (!isset($input['status'])) {
|
|
168
184
|
$input['status'] = 'created';
|
|
169
185
|
}
|
|
170
186
|
|
|
@@ -185,11 +201,13 @@ class OrderController extends FleetOpsController
|
|
|
185
201
|
$entities = Utils::get($input, 'payload.entities');
|
|
186
202
|
$uploads = Utils::get($input, 'files', []);
|
|
187
203
|
$customFieldValues = Utils::get($input, 'custom_field_values', []);
|
|
204
|
+
$orderConfig = $order->config();
|
|
205
|
+
$hasLifecycle = $orderConfig && $orderConfig->hasConfiguredLifecycle();
|
|
188
206
|
|
|
189
207
|
// save order route & payload with request input
|
|
190
208
|
$order
|
|
191
209
|
->setRoute($route)
|
|
192
|
-
->setStatus('created', false)
|
|
210
|
+
->setStatus($hasLifecycle ? $orderConfig->getInitialStatusCode() : 'created', false)
|
|
193
211
|
->insertPayload($payload)
|
|
194
212
|
->insertWaypoints($waypoints)
|
|
195
213
|
->insertEntities($entities);
|
|
@@ -212,8 +230,12 @@ class OrderController extends FleetOpsController
|
|
|
212
230
|
);
|
|
213
231
|
}
|
|
214
232
|
|
|
215
|
-
// Check dispatch flag with backward compatibility (default true)
|
|
233
|
+
// Check dispatch flag with backward compatibility (default true).
|
|
234
|
+
// Configs with a lifecycle that disables dispatch are never dispatched.
|
|
216
235
|
$shouldDispatch = isset($input['dispatched']) ? (bool) $input['dispatched'] : true;
|
|
236
|
+
if ($hasLifecycle && !$orderConfig->allowsDispatch()) {
|
|
237
|
+
$shouldDispatch = false;
|
|
238
|
+
}
|
|
217
239
|
|
|
218
240
|
// dispatch if flagged true, otherwise ensure order stays in created state
|
|
219
241
|
if ($shouldDispatch) {
|
|
@@ -300,6 +322,19 @@ class OrderController extends FleetOpsController
|
|
|
300
322
|
return $this->errorResponse('Unable to find order to update route for.');
|
|
301
323
|
}
|
|
302
324
|
|
|
325
|
+
// Refuse a route edit the order's status locks (completed, canceled, expired)
|
|
326
|
+
$edit = $this->beginOrderEdit($order);
|
|
327
|
+
try {
|
|
328
|
+
$edit->guard($edit->requestedChanges(array_filter([
|
|
329
|
+
'pickup' => $hasPickupInput ? true : null,
|
|
330
|
+
'dropoff' => $hasDropoffInput ? true : null,
|
|
331
|
+
'return' => $hasReturnInput ? true : null,
|
|
332
|
+
'waypoints' => ($hasWaypointsInput || $hasPickupInput || $hasDropoffInput || $hasReturnInput) ? true : null,
|
|
333
|
+
])));
|
|
334
|
+
} catch (OrderEditLockedException $exception) {
|
|
335
|
+
return $this->errorResponse($exception->getMessage(), 422, $exception->toResponseData());
|
|
336
|
+
}
|
|
337
|
+
|
|
303
338
|
if ($hasPickupInput) {
|
|
304
339
|
if ($pickup) {
|
|
305
340
|
$order->payload->setPickup($pickup, ['save' => true]);
|
|
@@ -334,14 +369,9 @@ class OrderController extends FleetOpsController
|
|
|
334
369
|
$order->payload->removeWaypoints();
|
|
335
370
|
}
|
|
336
371
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if ($startingDestination instanceof Place || $startingDestination instanceof Waypoint) {
|
|
343
|
-
$order->payload->setCurrentWaypoint($startingDestination);
|
|
344
|
-
}
|
|
372
|
+
// Only a changed route moves the current waypoint, and an order under way keeps
|
|
373
|
+
// the driver's stop; distance, time, tracking and `order.changed` follow the change
|
|
374
|
+
$this->commitOrderEdit($edit);
|
|
345
375
|
|
|
346
376
|
$order->load(['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints']);
|
|
347
377
|
|
|
@@ -483,7 +513,7 @@ class OrderController extends FleetOpsController
|
|
|
483
513
|
$successful = [];
|
|
484
514
|
|
|
485
515
|
foreach ($orders as $order) {
|
|
486
|
-
if ($order->status !== 'created') {
|
|
516
|
+
if ($order->status !== 'created' || $order->config()?->allowsDispatch() === false) {
|
|
487
517
|
$failed[] = $order->uuid;
|
|
488
518
|
continue;
|
|
489
519
|
}
|
|
@@ -582,7 +612,12 @@ class OrderController extends FleetOpsController
|
|
|
582
612
|
return $this->errorResponse('No order found to cancel.');
|
|
583
613
|
}
|
|
584
614
|
|
|
585
|
-
$
|
|
615
|
+
$reason = $request->input('reason');
|
|
616
|
+
if (is_string($reason) && trim($reason) !== '') {
|
|
617
|
+
$order->cancelWithReason($reason);
|
|
618
|
+
} else {
|
|
619
|
+
$order->cancel();
|
|
620
|
+
}
|
|
586
621
|
|
|
587
622
|
return $this->jsonResponse(
|
|
588
623
|
[
|
|
@@ -615,6 +650,10 @@ class OrderController extends FleetOpsController
|
|
|
615
650
|
return $this->errorResponse('No order config found for dispatch.');
|
|
616
651
|
}
|
|
617
652
|
|
|
653
|
+
if (!$order->config()->allowsDispatch()) {
|
|
654
|
+
return $this->errorResponse('Orders of this type are not dispatched.');
|
|
655
|
+
}
|
|
656
|
+
|
|
618
657
|
if (!$order->hasDriverAssigned && !$order->adhoc) {
|
|
619
658
|
return $this->errorResponse('No driver assigned to dispatch!');
|
|
620
659
|
}
|
|
@@ -748,10 +787,21 @@ class OrderController extends FleetOpsController
|
|
|
748
787
|
|
|
749
788
|
protected function assignDriverToOrders($orderUuids, Driver $driver): void
|
|
750
789
|
{
|
|
790
|
+
// Drivers being replaced are told once the assignment is written
|
|
791
|
+
$replaced = $this->scopedToCompany(Order::whereIn('uuid', $orderUuids))
|
|
792
|
+
->whereNotNull('driver_assigned_uuid')
|
|
793
|
+
->where('driver_assigned_uuid', '!=', $driver->uuid)
|
|
794
|
+
->get();
|
|
795
|
+
|
|
751
796
|
$this->scopedToCompany(Order::whereIn('uuid', $orderUuids))->update([
|
|
752
797
|
'driver_assigned_uuid' => $driver->uuid,
|
|
753
798
|
'updated_at' => now(),
|
|
754
799
|
]);
|
|
800
|
+
Order::invalidateApiCacheManually(session('company'));
|
|
801
|
+
|
|
802
|
+
foreach ($replaced as $order) {
|
|
803
|
+
$order->notifyDriverUnassigned($order->driver_assigned_uuid);
|
|
804
|
+
}
|
|
755
805
|
}
|
|
756
806
|
|
|
757
807
|
protected function dispatchBulkAssignedDriverNotification(array $orderUuids, Driver $driver): void
|
|
@@ -769,9 +819,19 @@ class OrderController extends FleetOpsController
|
|
|
769
819
|
return response()->json($payload);
|
|
770
820
|
}
|
|
771
821
|
|
|
772
|
-
protected function errorResponse(string $message, int $status = 400)
|
|
822
|
+
protected function errorResponse(string $message, int $status = 400, array $data = [])
|
|
823
|
+
{
|
|
824
|
+
return response()->error($message, $status, $data);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
protected function beginOrderEdit(Order $order): OrderEdit
|
|
773
828
|
{
|
|
774
|
-
return
|
|
829
|
+
return OrderEdit::begin($order);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
protected function commitOrderEdit(OrderEdit $edit): array
|
|
833
|
+
{
|
|
834
|
+
return $edit->commit();
|
|
775
835
|
}
|
|
776
836
|
|
|
777
837
|
/**
|
|
@@ -900,6 +960,19 @@ class OrderController extends FleetOpsController
|
|
|
900
960
|
$activity = $request->array('activity');
|
|
901
961
|
$activity = new Activity($activity, $order->getConfigFlow());
|
|
902
962
|
|
|
963
|
+
// Configs with strict transitions only accept a configured child of the current
|
|
964
|
+
// activity, and the server-side activity definition replaces the client payload.
|
|
965
|
+
$orderConfig = $order->config();
|
|
966
|
+
if ($orderConfig && $orderConfig->hasStrictTransitions()) {
|
|
967
|
+
$targetCode = $activity->get('code');
|
|
968
|
+
$configured = $targetCode ? $orderConfig->getActivityByCode($targetCode) : null;
|
|
969
|
+
if (!$configured || !$orderConfig->canTransition($order->status, $targetCode)) {
|
|
970
|
+
return response()->error('This activity is not a permitted next step for the order.', 422);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
$activity = $configured;
|
|
974
|
+
}
|
|
975
|
+
|
|
903
976
|
$requiresProof = Utils::isActivity($activity)
|
|
904
977
|
&& ($activity->get('require_pod') || ($activity->completesOrder() && $order->pod_required));
|
|
905
978
|
if ($requiresProof && !$proof && !$bypassProof) {
|
|
@@ -1875,27 +1948,43 @@ class OrderController extends FleetOpsController
|
|
|
1875
1948
|
return response()->json(OrderConfig::default());
|
|
1876
1949
|
}
|
|
1877
1950
|
|
|
1951
|
+
/**
|
|
1952
|
+
* Look up an order for the public Track Order page.
|
|
1953
|
+
*
|
|
1954
|
+
* Answers with `PublicOrderTracking`, never the full order resource: the
|
|
1955
|
+
* tracking number is the only credential here. A missing, malformed or
|
|
1956
|
+
* unknown tracking number all get the same error, so a caller cannot tell
|
|
1957
|
+
* which one they hit.
|
|
1958
|
+
*/
|
|
1878
1959
|
public function lookup(Request $request)
|
|
1879
1960
|
{
|
|
1880
|
-
$trackingNumber = $request->input('tracking');
|
|
1881
|
-
|
|
1882
|
-
return response()->error('No tracking number provided for lookup.');
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
$order = $this->findOrderByTrackingNumber($trackingNumber);
|
|
1961
|
+
$trackingNumber = $this->normalizeTrackingLookup($request->input('tracking'));
|
|
1962
|
+
$order = $trackingNumber ? $this->findOrderByTrackingNumber($trackingNumber) : null;
|
|
1886
1963
|
|
|
1887
1964
|
if (!$order) {
|
|
1888
|
-
return response()->error(
|
|
1965
|
+
return response()->error(static::TRACKING_LOOKUP_ERROR);
|
|
1889
1966
|
}
|
|
1890
1967
|
|
|
1891
|
-
|
|
1892
|
-
$order->loadMissing(['trackingNumber', 'payload', 'trackingStatuses']);
|
|
1893
|
-
|
|
1894
|
-
// load tracker data
|
|
1968
|
+
$order->loadMissing(['trackingNumber', 'trackingStatuses', 'payload.pickup', 'payload.dropoff', 'payload.waypoints', 'payload.entities']);
|
|
1895
1969
|
$order->tracker_data = $order->tracker()->toArray();
|
|
1896
|
-
$order->eta = $order->tracker()->eta();
|
|
1897
1970
|
|
|
1898
|
-
return new
|
|
1971
|
+
return new PublicOrderTracking($order);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/**
|
|
1975
|
+
* The tracking number to look up, or null when the input cannot be one:
|
|
1976
|
+
* not a string, blank, too long, or outside the characters tracking
|
|
1977
|
+
* numbers use.
|
|
1978
|
+
*/
|
|
1979
|
+
protected function normalizeTrackingLookup($input): ?string
|
|
1980
|
+
{
|
|
1981
|
+
if (!is_string($input)) {
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
$input = trim($input);
|
|
1986
|
+
|
|
1987
|
+
return preg_match('/^[A-Za-z0-9][A-Za-z0-9._\-]{0,99}$/', $input) === 1 ? $input : null;
|
|
1899
1988
|
}
|
|
1900
1989
|
|
|
1901
1990
|
/**
|
|
@@ -1952,6 +2041,7 @@ class OrderController extends FleetOpsController
|
|
|
1952
2041
|
}
|
|
1953
2042
|
|
|
1954
2043
|
$order->saveQuietly();
|
|
2044
|
+
$order->invalidateApiCache();
|
|
1955
2045
|
|
|
1956
2046
|
return response()->json([
|
|
1957
2047
|
'status' => 'OK',
|
|
@@ -6,6 +6,10 @@ use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
|
6
6
|
use Fleetbase\FleetOps\Jobs\ReplayPositions;
|
|
7
7
|
use Fleetbase\FleetOps\Models\Position;
|
|
8
8
|
use Fleetbase\FleetOps\Support\Utils;
|
|
9
|
+
use Fleetbase\Models\User;
|
|
10
|
+
use Fleetbase\Support\SocketCluster\ChannelAuthorizer;
|
|
11
|
+
use Fleetbase\Support\SocketCluster\SocketPrincipal;
|
|
12
|
+
use Fleetbase\Support\SocketCluster\SocketToken;
|
|
9
13
|
use Illuminate\Http\Request;
|
|
10
14
|
|
|
11
15
|
class PositionController extends FleetOpsController
|
|
@@ -37,6 +41,11 @@ class PositionController extends FleetOpsController
|
|
|
37
41
|
return response()->error('Position IDs are required');
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
// With socket authentication on, the replay may only publish to a channel the user could subscribe to.
|
|
45
|
+
if (SocketToken::enabled() && !$this->canReplayTo($request, $channelId)) {
|
|
46
|
+
return response()->error('You are not allowed to replay positions to this channel.', 403);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
$positions = Position::whereIn('uuid', $positionIds)
|
|
41
50
|
->where('company_uuid', session('company'))
|
|
42
51
|
->orderBy('created_at')
|
|
@@ -57,6 +66,19 @@ class PositionController extends FleetOpsController
|
|
|
57
66
|
]);
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Whether the requesting user may subscribe to, and so receive a replay on, the channel.
|
|
71
|
+
*/
|
|
72
|
+
protected function canReplayTo(Request $request, mixed $channelId): bool
|
|
73
|
+
{
|
|
74
|
+
$user = $request->user();
|
|
75
|
+
if (!$user instanceof User || !is_string($channelId) || $channelId === '' || strlen($channelId) > 255 || preg_match('/\s/', $channelId)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return app(ChannelAuthorizer::class)->authorize(SocketPrincipal::forUser($user, session('company')), $channelId)->allow;
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
/**
|
|
61
83
|
* Get position statistics/metrics.
|
|
62
84
|
*
|
|
@@ -21,6 +21,53 @@ class ProofController extends FleetOpsController
|
|
|
21
21
|
*/
|
|
22
22
|
public $resource = 'proof';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The most a dashboard can ask for in one request.
|
|
26
|
+
*/
|
|
27
|
+
public const RECENT_LIMIT_MAX = 24;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The latest photo proofs captured for the company's orders, newest first,
|
|
31
|
+
* for the dashboard's proof-of-delivery tile.
|
|
32
|
+
*/
|
|
33
|
+
public function recent(Request $request)
|
|
34
|
+
{
|
|
35
|
+
$limit = max(1, min(static::RECENT_LIMIT_MAX, (int) $request->input('limit', 12)));
|
|
36
|
+
$proofs = Proof::where('company_uuid', session('company'))
|
|
37
|
+
->whereNotNull('order_uuid')
|
|
38
|
+
->whereHas('file', fn ($query) => $query->where('type', 'photo'))
|
|
39
|
+
->with(['file', 'order.customer'])
|
|
40
|
+
->orderByDesc('created_at')
|
|
41
|
+
->limit($limit)
|
|
42
|
+
->get();
|
|
43
|
+
|
|
44
|
+
return $this->jsonResponse([
|
|
45
|
+
'proofs' => $proofs->map(fn (Proof $proof) => $this->recentProofPayload($proof))->values()->all(),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected function recentProofPayload(Proof $proof): array
|
|
50
|
+
{
|
|
51
|
+
$order = $proof->order;
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
'id' => $proof->public_id,
|
|
55
|
+
'order_id' => $order?->public_id,
|
|
56
|
+
'customer_name' => data_get($order, 'customer.name'),
|
|
57
|
+
'remarks' => $proof->remarks,
|
|
58
|
+
'url' => $this->photoUrl($proof->file),
|
|
59
|
+
'captured_at' => $proof->created_at?->toIso8601String(),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* File::url signs the object on s3/gcs, so a private bucket still serves the photo.
|
|
65
|
+
*/
|
|
66
|
+
protected function photoUrl(File $file): ?string
|
|
67
|
+
{
|
|
68
|
+
return $file->url;
|
|
69
|
+
}
|
|
70
|
+
|
|
24
71
|
/**
|
|
25
72
|
* Verify a QR code.
|
|
26
73
|
*
|
|
@@ -83,8 +130,8 @@ class ProofController extends FleetOpsController
|
|
|
83
130
|
// set the signature storage path
|
|
84
131
|
$path = $this->signatureStoragePath($proof);
|
|
85
132
|
|
|
86
|
-
// upload signature
|
|
87
|
-
$this->storeSignature($path, base64_decode($signature)
|
|
133
|
+
// upload signature (private object; File::url serves it through a signed URL)
|
|
134
|
+
$this->storeSignature($path, base64_decode($signature));
|
|
88
135
|
|
|
89
136
|
// create file record for upload
|
|
90
137
|
$file = $this->createSignatureFile($path, $signature, $proof);
|
|
@@ -121,9 +168,13 @@ class ProofController extends FleetOpsController
|
|
|
121
168
|
return Proof::create($attributes);
|
|
122
169
|
}
|
|
123
170
|
|
|
124
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Writes without an ACL: the media bucket enforces bucket-owner object ownership, so a
|
|
173
|
+
* 'public' visibility (public-read ACL) makes S3 reject the PUT and put() return false.
|
|
174
|
+
*/
|
|
175
|
+
protected function storeSignature(string $path, string|false $contents): void
|
|
125
176
|
{
|
|
126
|
-
Storage::disk('s3')->put($path, $contents
|
|
177
|
+
Storage::disk('s3')->put($path, $contents);
|
|
127
178
|
}
|
|
128
179
|
|
|
129
180
|
/**
|