@fleetbase/fleetops-engine 0.6.71 → 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 +15 -18
- 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/form/route.hbs +1 -1
- package/addon/components/order/form/route.js +13 -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/operations/orders/index/details.js +19 -3
- package/addon/controllers/operations/scheduler/index.js +45 -14
- 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 +65 -1
- package/addon/helpers/parcel-box-image.js +16 -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/location.js +69 -21
- package/addon/services/map-settings.js +28 -0
- package/addon/services/order-allocation.js +1 -1
- package/addon/styles/fleetops-engine.css +1391 -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/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/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 +84 -24
- 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 +200 -9
- 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 +101 -2
- 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/OrderControllerContractsTest.php +110 -14
- 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/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/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 +287 -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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
if (!function_exists('Fleetbase\FleetOps\Models\dispatch')) {
|
|
4
|
+
eval('namespace Fleetbase\FleetOps\Models; function dispatch($job) { return \FleetOpsOrderUnitDispatchRecorder::record($job); }');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (!function_exists('Fleetbase\FleetOps\Models\event')) {
|
|
8
|
+
eval('namespace Fleetbase\FleetOps\Models; function event($event = null) { \FleetOpsOrderUnitDispatchRecorder::$events[] = $event; return $event; }');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (!function_exists('Fleetbase\FleetOps\Models\now')) {
|
|
12
|
+
eval('namespace Fleetbase\FleetOps\Models; function now($tz = null) { return \Illuminate\Support\Carbon::now($tz); }');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!class_exists('Fleetbase\FleetOps\Events\OrderDispatched', false)) {
|
|
16
|
+
eval('namespace Fleetbase\FleetOps\Events; class OrderDispatched { public function __construct(public $order) {} }');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!class_exists('FleetOpsOrderUnitDispatchRecorder', false)) {
|
|
20
|
+
class FleetOpsOrderUnitDispatchRecorder
|
|
21
|
+
{
|
|
22
|
+
public static array $jobs = [];
|
|
23
|
+
public static array $events = [];
|
|
24
|
+
|
|
25
|
+
public static function reset(): void
|
|
26
|
+
{
|
|
27
|
+
static::$jobs = [];
|
|
28
|
+
static::$events = [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static function record($job): object
|
|
32
|
+
{
|
|
33
|
+
static::$jobs[] = $job;
|
|
34
|
+
|
|
35
|
+
if ($job instanceof Closure) {
|
|
36
|
+
$job();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new class {
|
|
40
|
+
public function afterCommit(): self
|
|
41
|
+
{
|
|
42
|
+
return $this;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
use Fleetbase\FleetOps\Events\OrderDispatched;
|
|
50
|
+
use Fleetbase\FleetOps\Flow\Activity;
|
|
51
|
+
use Fleetbase\FleetOps\Jobs\FinalizeApiOrderCreation;
|
|
52
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
53
|
+
use Fleetbase\FleetOps\Models\OrderConfig;
|
|
54
|
+
use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
55
|
+
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
56
|
+
use Illuminate\Support\Carbon;
|
|
57
|
+
|
|
58
|
+
class FleetOpsQueuedDispatchOrderFake extends Order
|
|
59
|
+
{
|
|
60
|
+
public bool $quietSaved = false;
|
|
61
|
+
public array $calls = [];
|
|
62
|
+
public array $activityRows = [];
|
|
63
|
+
public array $statuses = [];
|
|
64
|
+
public ?OrderConfig $fakeConfig = null;
|
|
65
|
+
|
|
66
|
+
public function getDateFormat()
|
|
67
|
+
{
|
|
68
|
+
return 'Y-m-d H:i:s';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public function load($relations)
|
|
72
|
+
{
|
|
73
|
+
return $this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public function saveQuietly(array $options = []): bool
|
|
77
|
+
{
|
|
78
|
+
$this->quietSaved = true;
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public function flushAttributesCache(): bool
|
|
84
|
+
{
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public function config(): ?OrderConfig
|
|
89
|
+
{
|
|
90
|
+
return $this->fakeConfig;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public function notifyDriverAssigned(): void
|
|
94
|
+
{
|
|
95
|
+
$this->calls[] = 'notifyDriverAssigned';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public function setPreliminaryDistanceAndTime(): void
|
|
99
|
+
{
|
|
100
|
+
$this->calls[] = 'setPreliminaryDistanceAndTime';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public function purchaseServiceQuote($serviceQuote, $meta = [])
|
|
104
|
+
{
|
|
105
|
+
$this->calls[] = 'purchaseServiceQuote';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public function insertActivity(Activity $activity, $location = [], $proof = null): string
|
|
109
|
+
{
|
|
110
|
+
$this->activityRows[] = $activity->code;
|
|
111
|
+
|
|
112
|
+
return 'tracking_status_public';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public function setStatus(?string $status, $andSave = true)
|
|
116
|
+
{
|
|
117
|
+
$this->statuses[] = $status;
|
|
118
|
+
$this->status = $status;
|
|
119
|
+
|
|
120
|
+
return $this;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class FleetOpsQueuedDispatchConfigFake extends OrderConfig
|
|
125
|
+
{
|
|
126
|
+
public function getDispatchActivity(): ?Activity
|
|
127
|
+
{
|
|
128
|
+
return new Activity(['code' => 'dispatched']);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class FleetOpsQueuedDispatchJobProbe extends FinalizeApiOrderCreation
|
|
133
|
+
{
|
|
134
|
+
public ?Order $order = null;
|
|
135
|
+
public array $ready = [];
|
|
136
|
+
|
|
137
|
+
protected function findOrder(): ?Order
|
|
138
|
+
{
|
|
139
|
+
return $this->order;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
protected function findServiceQuote(): ?ServiceQuote
|
|
143
|
+
{
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected function fireOrderReady(Order $order): void
|
|
148
|
+
{
|
|
149
|
+
$this->ready[] = $order->uuid;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function fleetopsQueuedDispatchTrackingStatus(string $code): TrackingStatus
|
|
154
|
+
{
|
|
155
|
+
$status = new TrackingStatus();
|
|
156
|
+
$status->code = $code;
|
|
157
|
+
|
|
158
|
+
return $status;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function fleetopsQueuedDispatchOrder(array $attributes = [], array $trackingCodes = ['CREATED'], array $lifecycle = []): FleetOpsQueuedDispatchOrderFake
|
|
162
|
+
{
|
|
163
|
+
$config = new FleetOpsQueuedDispatchConfigFake();
|
|
164
|
+
$config->setRawAttributes(['meta' => json_encode($lifecycle ? ['lifecycle' => $lifecycle] : [])], true);
|
|
165
|
+
|
|
166
|
+
$order = new FleetOpsQueuedDispatchOrderFake();
|
|
167
|
+
$order->setRawAttributes(array_merge([
|
|
168
|
+
'uuid' => 'order-uuid',
|
|
169
|
+
'tracking_number_uuid' => 'tracking-number-uuid',
|
|
170
|
+
'status' => 'created',
|
|
171
|
+
'dispatched' => false,
|
|
172
|
+
'started' => false,
|
|
173
|
+
], $attributes), true);
|
|
174
|
+
$order->fakeConfig = $config;
|
|
175
|
+
$order->setRelation('trackingStatuses', collect(array_map('fleetopsQueuedDispatchTrackingStatus', $trackingCodes)));
|
|
176
|
+
$order->setRelation('driverAssigned', null);
|
|
177
|
+
$order->setRelation('payload', null);
|
|
178
|
+
|
|
179
|
+
return $order;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function fleetopsRunQueuedDispatch(Order $order): FleetOpsQueuedDispatchJobProbe
|
|
183
|
+
{
|
|
184
|
+
$job = new FleetOpsQueuedDispatchJobProbe($order->uuid, null, true);
|
|
185
|
+
$job->order = $order;
|
|
186
|
+
$job->handle();
|
|
187
|
+
|
|
188
|
+
return $job;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
beforeEach(function () {
|
|
192
|
+
FleetOpsOrderUnitDispatchRecorder::reset();
|
|
193
|
+
Carbon::setTestNow(Carbon::parse('2026-10-11 09:30:00'));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
afterEach(function () {
|
|
197
|
+
Carbon::setTestNow();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('queued api dispatch still dispatches an order that is waiting at created', function () {
|
|
201
|
+
$order = fleetopsQueuedDispatchOrder();
|
|
202
|
+
|
|
203
|
+
$job = fleetopsRunQueuedDispatch($order);
|
|
204
|
+
|
|
205
|
+
expect($order->dispatched)->toBeTrue()
|
|
206
|
+
->and($order->dispatched_at->toDateTimeString())->toBe('2026-10-11 09:30:00')
|
|
207
|
+
->and($order->status)->toBe('dispatched')
|
|
208
|
+
->and($order->activityRows)->toBe(['dispatched'])
|
|
209
|
+
->and(FleetOpsOrderUnitDispatchRecorder::$events)->toHaveCount(1)
|
|
210
|
+
->and(FleetOpsOrderUnitDispatchRecorder::$events[0])->toBeInstanceOf(OrderDispatched::class)
|
|
211
|
+
->and($job->ready)->toBe(['order-uuid']);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('queued api dispatch is a no-op once the order was dispatched and started', function () {
|
|
215
|
+
$dispatchedAt = Carbon::parse('2026-10-11 09:00:00');
|
|
216
|
+
$startedAt = Carbon::parse('2026-10-11 09:10:00');
|
|
217
|
+
$order = fleetopsQueuedDispatchOrder([
|
|
218
|
+
'status' => 'started',
|
|
219
|
+
'dispatched' => true,
|
|
220
|
+
'dispatched_at' => $dispatchedAt->toDateTimeString(),
|
|
221
|
+
'started' => true,
|
|
222
|
+
'started_at' => $startedAt->toDateTimeString(),
|
|
223
|
+
], ['CREATED', 'DISPATCHED', 'STARTED']);
|
|
224
|
+
|
|
225
|
+
$job = fleetopsRunQueuedDispatch($order);
|
|
226
|
+
|
|
227
|
+
expect($order->status)->toBe('started')
|
|
228
|
+
->and($order->dispatched_at->toDateTimeString())->toBe('2026-10-11 09:00:00')
|
|
229
|
+
->and($order->started_at->toDateTimeString())->toBe('2026-10-11 09:10:00')
|
|
230
|
+
->and($order->quietSaved)->toBeFalse()
|
|
231
|
+
->and($order->activityRows)->toBe([])
|
|
232
|
+
->and($order->statuses)->toBe([])
|
|
233
|
+
->and(FleetOpsOrderUnitDispatchRecorder::$jobs)->toBe([])
|
|
234
|
+
->and(FleetOpsOrderUnitDispatchRecorder::$events)->toBe([])
|
|
235
|
+
->and($order->calls)->toBe(['notifyDriverAssigned', 'setPreliminaryDistanceAndTime', 'purchaseServiceQuote'])
|
|
236
|
+
->and($job->ready)->toBe(['order-uuid']);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('an order is only awaiting dispatch at its initial status with nothing recorded', function () {
|
|
240
|
+
expect(fleetopsQueuedDispatchOrder()->isAwaitingDispatch())->toBeTrue()
|
|
241
|
+
->and(fleetopsQueuedDispatchOrder(['status' => null])->isAwaitingDispatch())->toBeTrue()
|
|
242
|
+
->and(fleetopsQueuedDispatchOrder(['status' => 'pending'], ['PENDING'], ['initial' => 'pending'])->isAwaitingDispatch())->toBeTrue()
|
|
243
|
+
->and(fleetopsQueuedDispatchOrder(['dispatched' => true])->isAwaitingDispatch())->toBeFalse()
|
|
244
|
+
->and(fleetopsQueuedDispatchOrder(['dispatched_at' => '2026-10-11 09:00:00'])->isAwaitingDispatch())->toBeFalse()
|
|
245
|
+
->and(fleetopsQueuedDispatchOrder(['started' => true])->isAwaitingDispatch())->toBeFalse()
|
|
246
|
+
->and(fleetopsQueuedDispatchOrder(['started_at' => '2026-10-11 09:10:00'])->isAwaitingDispatch())->toBeFalse()
|
|
247
|
+
->and(fleetopsQueuedDispatchOrder(['status' => 'enroute'])->isAwaitingDispatch())->toBeFalse()
|
|
248
|
+
->and(fleetopsQueuedDispatchOrder(['status' => 'completed'])->isAwaitingDispatch())->toBeFalse()
|
|
249
|
+
->and(fleetopsQueuedDispatchOrder([], ['CREATED', 'DISPATCHED'])->isAwaitingDispatch())->toBeFalse();
|
|
250
|
+
|
|
251
|
+
$withoutConfig = fleetopsQueuedDispatchOrder(['status' => 'pending']);
|
|
252
|
+
$withoutConfig->fakeConfig = null;
|
|
253
|
+
|
|
254
|
+
expect($withoutConfig->isAwaitingDispatch())->toBeFalse();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('a terminal or later status on a custom lifecycle is never dispatched by the queued job', function () {
|
|
258
|
+
$order = fleetopsQueuedDispatchOrder(['status' => 'delivered'], ['PENDING', 'DELIVERED'], ['initial' => 'pending', 'completed' => 'delivered']);
|
|
259
|
+
|
|
260
|
+
fleetopsRunQueuedDispatch($order);
|
|
261
|
+
|
|
262
|
+
expect($order->status)->toBe('delivered')
|
|
263
|
+
->and($order->dispatched)->toBeFalse()
|
|
264
|
+
->and($order->activityRows)->toBe([])
|
|
265
|
+
->and(FleetOpsOrderUnitDispatchRecorder::$events)->toBe([]);
|
|
266
|
+
});
|
|
@@ -14,8 +14,11 @@ use Fleetbase\FleetOps\Listeners\HandleGeofenceEntered;
|
|
|
14
14
|
use Fleetbase\FleetOps\Models\Driver;
|
|
15
15
|
use Fleetbase\FleetOps\Models\GeofenceEventLog;
|
|
16
16
|
use Fleetbase\FleetOps\Models\Order;
|
|
17
|
+
use Fleetbase\FleetOps\Models\Payload;
|
|
18
|
+
use Fleetbase\FleetOps\Models\Place;
|
|
17
19
|
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
18
20
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
21
|
+
use Fleetbase\FleetOps\Models\Waypoint;
|
|
19
22
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
20
23
|
use Illuminate\Database\ConnectionResolver;
|
|
21
24
|
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|
@@ -96,8 +99,12 @@ function fleetopsHandleGeofenceEnteredConnection(): SQLiteConnection
|
|
|
96
99
|
$connection = new SQLiteConnection($pdo, 'default');
|
|
97
100
|
$connection->statement('create table geofence_events_log (uuid varchar(64) primary key, company_uuid varchar(64), driver_uuid varchar(64) null, vehicle_uuid varchar(64) null, order_uuid varchar(64) null, subject_uuid varchar(64) null, subject_type varchar(255) null, subject_name varchar(255) null, geofence_uuid varchar(64), geofence_type varchar(64), geofence_name varchar(255) null, event_type varchar(64), latitude numeric null, longitude numeric null, speed_kmh numeric null, dwell_duration_minutes integer null, occurred_at datetime null, created_at datetime null, updated_at datetime null)');
|
|
98
101
|
|
|
102
|
+
$connection->statement('create table tracking_numbers (id integer primary key autoincrement, uuid varchar(64), public_id varchar(64) null, company_uuid varchar(64) null, status_uuid varchar(64) null, created_at datetime null, updated_at datetime null, deleted_at datetime null)');
|
|
103
|
+
$connection->statement('create table tracking_statuses (id integer primary key autoincrement, uuid varchar(64), public_id varchar(64) null, company_uuid varchar(64) null, tracking_number_uuid varchar(64) null, code varchar(64) null, complete integer null, created_at datetime null, updated_at datetime null, deleted_at datetime null)');
|
|
104
|
+
|
|
99
105
|
$resolver = new ConnectionResolver([
|
|
100
106
|
'default' => $connection,
|
|
107
|
+
'mysql' => $connection,
|
|
101
108
|
]);
|
|
102
109
|
$resolver->setDefaultConnection('default');
|
|
103
110
|
EloquentModel::setConnectionResolver($resolver);
|
|
@@ -212,7 +219,73 @@ test('geofence entered listener writes vehicle entry logs with current order con
|
|
|
212
219
|
->and($log->subject_name)->toBe('Dock Van');
|
|
213
220
|
});
|
|
214
221
|
|
|
222
|
+
function fleetopsEnteredZone(string $name, float $lat, float $lng): object
|
|
223
|
+
{
|
|
224
|
+
return new class($name, $lat, $lng) {
|
|
225
|
+
public string $uuid;
|
|
226
|
+
public string $public_id;
|
|
227
|
+
|
|
228
|
+
public function __construct(public string $name, private float $lat, private float $lng)
|
|
229
|
+
{
|
|
230
|
+
$this->uuid = $name . '-uuid';
|
|
231
|
+
$this->public_id = $name . '-public';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public function getLatitudeAttribute(): float
|
|
235
|
+
{
|
|
236
|
+
return $this->lat;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public function getLongitudeAttribute(): float
|
|
240
|
+
{
|
|
241
|
+
return $this->lng;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function fleetopsEnteredPlace(string $uuid, ?Point $location): Place
|
|
247
|
+
{
|
|
248
|
+
$place = new Place();
|
|
249
|
+
$place->setRawAttributes(['uuid' => $uuid, 'public_id' => $uuid . '-public', 'location' => $location], true);
|
|
250
|
+
|
|
251
|
+
return $place;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Build a pickup -> dropoff payload. The pickup sits on the default event
|
|
256
|
+
* zone centroid; the dropoff is roughly 9km away.
|
|
257
|
+
*/
|
|
258
|
+
function fleetopsEnteredEndpointPayload(?string $currentWaypointUuid = null, ?string $pickupTrackingNumberUuid = null): Payload
|
|
259
|
+
{
|
|
260
|
+
$payload = new Payload();
|
|
261
|
+
$payload->setRawAttributes([
|
|
262
|
+
'uuid' => 'payload-uuid',
|
|
263
|
+
'current_waypoint_uuid' => $currentWaypointUuid,
|
|
264
|
+
'pickup_tracking_number_uuid' => $pickupTrackingNumberUuid,
|
|
265
|
+
], true);
|
|
266
|
+
$payload->setRelation('pickup', fleetopsEnteredPlace('pickup-place', new Point(1.3521, 103.8198)));
|
|
267
|
+
$payload->setRelation('dropoff', fleetopsEnteredPlace('dropoff-place', new Point(1.3000, 103.9000)));
|
|
268
|
+
$payload->setRelation('waypoints', collect());
|
|
269
|
+
$payload->setRelation('waypointMarkers', collect());
|
|
270
|
+
|
|
271
|
+
return $payload;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function fleetopsEnteredCompleteStatus(SQLiteConnection $connection, string $trackingNumberUuid): void
|
|
275
|
+
{
|
|
276
|
+
$connection->table('tracking_numbers')->insert(['uuid' => $trackingNumberUuid, 'status_uuid' => $trackingNumberUuid . '-status']);
|
|
277
|
+
$connection->table('tracking_statuses')->insert(['uuid' => $trackingNumberUuid . '-status', 'tracking_number_uuid' => $trackingNumberUuid, 'code' => 'completed', 'complete' => 1]);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function fleetopsEnteredArrive(Order $order, object $zone): void
|
|
281
|
+
{
|
|
282
|
+
$driver = fleetopsEnteredDriver($order);
|
|
283
|
+
|
|
284
|
+
(new HandleGeofenceEntered())->handle(fleetopsEnteredEvent($driver, $zone));
|
|
285
|
+
}
|
|
286
|
+
|
|
215
287
|
test('geofence entered arrival handles destination and status failures', function () {
|
|
288
|
+
fleetopsHandleGeofenceEnteredConnection();
|
|
216
289
|
$listener = new HandleGeofenceEntered();
|
|
217
290
|
$arrival = new ReflectionMethod(HandleGeofenceEntered::class, 'handleOrderArrival');
|
|
218
291
|
$arrival->setAccessible(true);
|
|
@@ -220,37 +293,124 @@ test('geofence entered arrival handles destination and status failures', functio
|
|
|
220
293
|
$driver = fleetopsEnteredDriver();
|
|
221
294
|
$event = fleetopsEnteredEvent($driver);
|
|
222
295
|
|
|
223
|
-
$
|
|
224
|
-
public function
|
|
296
|
+
$throwingPayload = new class extends Payload {
|
|
297
|
+
public function loadMissing($relations)
|
|
225
298
|
{
|
|
226
299
|
throw new RuntimeException('destination failed');
|
|
227
300
|
}
|
|
228
|
-
}
|
|
301
|
+
};
|
|
302
|
+
$destinationFailure = fleetopsEnteredOrder($throwingPayload);
|
|
229
303
|
$arrival->invoke($listener, $driver, $event->geofence, $destinationFailure, $event);
|
|
230
304
|
|
|
231
|
-
$
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
$arrival->invoke($listener, $driver, $event->geofence, $placeFailure, $event);
|
|
305
|
+
$emptyPayload = new Payload();
|
|
306
|
+
$emptyPayload->setRelation('pickup', null);
|
|
307
|
+
$emptyPayload->setRelation('dropoff', null);
|
|
308
|
+
$emptyPayload->setRelation('waypoints', collect());
|
|
309
|
+
$emptyPayload->setRelation('waypointMarkers', collect());
|
|
310
|
+
$noStops = fleetopsEnteredOrder($emptyPayload);
|
|
311
|
+
$arrival->invoke($listener, $driver, $event->geofence, $noStops, $event);
|
|
243
312
|
|
|
244
|
-
$statusFailure
|
|
245
|
-
public function getPickupOrCurrentWaypoint(): object
|
|
246
|
-
{
|
|
247
|
-
return (object) ['place' => (object) ['location' => new Point(1.3521, 103.8198)]];
|
|
248
|
-
}
|
|
249
|
-
});
|
|
313
|
+
$statusFailure = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload());
|
|
250
314
|
$statusFailure->throwOnStatus = true;
|
|
251
315
|
$arrival->invoke($listener, $driver, $event->geofence, $statusFailure, $event);
|
|
252
316
|
|
|
253
317
|
expect($destinationFailure->calls)->toBe([])
|
|
254
|
-
->and($
|
|
318
|
+
->and($noStops->calls)->toBe([])
|
|
255
319
|
->and($statusFailure->calls)->toBe([]);
|
|
256
320
|
});
|
|
321
|
+
|
|
322
|
+
test('geofence entered arrival targets the pickup before the pickup is completed', function () {
|
|
323
|
+
fleetopsHandleGeofenceEnteredConnection();
|
|
324
|
+
|
|
325
|
+
$atDropoff = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload());
|
|
326
|
+
fleetopsEnteredArrive($atDropoff, fleetopsEnteredZone('dropoff-zone', 1.3000, 103.9000));
|
|
327
|
+
|
|
328
|
+
$atPickup = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload('pickup-place'));
|
|
329
|
+
fleetopsEnteredArrive($atPickup, fleetopsEnteredZone('pickup-zone', 1.3521, 103.8198));
|
|
330
|
+
|
|
331
|
+
expect($atDropoff->calls)->toBe([])
|
|
332
|
+
->and($atDropoff->status)->toBe('dispatched')
|
|
333
|
+
->and($atPickup->calls[0])->toBe(['setStatus', 'arrived', true])
|
|
334
|
+
->and($atPickup->calls[1][1]->get('details'))->toBe('Driver entered destination geofence "pickup-zone".');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('geofence entered arrival targets the dropoff once the order has advanced past the pickup', function () {
|
|
338
|
+
fleetopsHandleGeofenceEnteredConnection();
|
|
339
|
+
|
|
340
|
+
$atPickup = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload('dropoff-place'));
|
|
341
|
+
fleetopsEnteredArrive($atPickup, fleetopsEnteredZone('pickup-zone', 1.3521, 103.8198));
|
|
342
|
+
|
|
343
|
+
$atDropoff = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload('dropoff-place'));
|
|
344
|
+
fleetopsEnteredArrive($atDropoff, fleetopsEnteredZone('dropoff-zone', 1.3000, 103.9000));
|
|
345
|
+
|
|
346
|
+
expect($atPickup->calls)->toBe([])
|
|
347
|
+
->and($atDropoff->calls[0])->toBe(['setStatus', 'arrived', true])
|
|
348
|
+
->and($atDropoff->calls[1][1]->get('details'))->toBe('Driver entered destination geofence "dropoff-zone".');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('geofence entered arrival skips a completed pickup the payload pointer has not advanced past', function () {
|
|
352
|
+
$connection = fleetopsHandleGeofenceEnteredConnection();
|
|
353
|
+
fleetopsEnteredCompleteStatus($connection, 'pickup-tracking');
|
|
354
|
+
|
|
355
|
+
$atPickup = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload('pickup-place', 'pickup-tracking'));
|
|
356
|
+
fleetopsEnteredArrive($atPickup, fleetopsEnteredZone('pickup-zone', 1.3521, 103.8198));
|
|
357
|
+
|
|
358
|
+
$atDropoff = fleetopsEnteredOrder(fleetopsEnteredEndpointPayload(null, 'pickup-tracking'));
|
|
359
|
+
fleetopsEnteredArrive($atDropoff, fleetopsEnteredZone('dropoff-zone', 1.3000, 103.9000));
|
|
360
|
+
|
|
361
|
+
expect($atPickup->calls)->toBe([])
|
|
362
|
+
->and($atDropoff->calls[0])->toBe(['setStatus', 'arrived', true]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('geofence entered arrival targets the current stop of a multi-waypoint order', function () {
|
|
366
|
+
$connection = fleetopsHandleGeofenceEnteredConnection();
|
|
367
|
+
fleetopsEnteredCompleteStatus($connection, 'first-tracking');
|
|
368
|
+
|
|
369
|
+
$multiDropPayload = function (?string $currentWaypointUuid): Payload {
|
|
370
|
+
$payload = new Payload();
|
|
371
|
+
$payload->setRawAttributes(['uuid' => 'multi-payload-uuid', 'current_waypoint_uuid' => $currentWaypointUuid], true);
|
|
372
|
+
$payload->setRelation('pickup', null);
|
|
373
|
+
$payload->setRelation('dropoff', null);
|
|
374
|
+
$payload->setRelation('waypoints', collect());
|
|
375
|
+
|
|
376
|
+
$markers = collect([
|
|
377
|
+
['first', 'first-tracking', new Point(1.3521, 103.8198)],
|
|
378
|
+
['second', null, new Point(1.3000, 103.9000)],
|
|
379
|
+
['third', null, new Point(1.4000, 103.7000)],
|
|
380
|
+
])->map(function (array $stop, int $index) {
|
|
381
|
+
[$name, $trackingNumberUuid, $location] = $stop;
|
|
382
|
+
|
|
383
|
+
$waypoint = new Waypoint();
|
|
384
|
+
$waypoint->setRawAttributes([
|
|
385
|
+
'uuid' => $name . '-waypoint',
|
|
386
|
+
'place_uuid' => $name . '-place',
|
|
387
|
+
'tracking_number_uuid' => $trackingNumberUuid,
|
|
388
|
+
'order' => $index,
|
|
389
|
+
], true);
|
|
390
|
+
$waypoint->setRelation('place', fleetopsEnteredPlace($name . '-place', $location));
|
|
391
|
+
$waypoint->setRelation('trackingNumber', null);
|
|
392
|
+
|
|
393
|
+
return $waypoint;
|
|
394
|
+
});
|
|
395
|
+
$payload->setRelation('waypointMarkers', $markers);
|
|
396
|
+
|
|
397
|
+
return $payload;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
$atCompletedStop = fleetopsEnteredOrder($multiDropPayload('second-place'));
|
|
401
|
+
fleetopsEnteredArrive($atCompletedStop, fleetopsEnteredZone('first-zone', 1.3521, 103.8198));
|
|
402
|
+
|
|
403
|
+
$atLaterStop = fleetopsEnteredOrder($multiDropPayload('second-place'));
|
|
404
|
+
fleetopsEnteredArrive($atLaterStop, fleetopsEnteredZone('third-zone', 1.4000, 103.7000));
|
|
405
|
+
|
|
406
|
+
$atCurrentStop = fleetopsEnteredOrder($multiDropPayload('second-place'));
|
|
407
|
+
fleetopsEnteredArrive($atCurrentStop, fleetopsEnteredZone('second-zone', 1.3000, 103.9000));
|
|
408
|
+
|
|
409
|
+
$staleFirstStop = fleetopsEnteredOrder($multiDropPayload(null));
|
|
410
|
+
fleetopsEnteredArrive($staleFirstStop, fleetopsEnteredZone('second-zone', 1.3000, 103.9000));
|
|
411
|
+
|
|
412
|
+
expect($atCompletedStop->calls)->toBe([])
|
|
413
|
+
->and($atLaterStop->calls)->toBe([])
|
|
414
|
+
->and($atCurrentStop->calls[0])->toBe(['setStatus', 'arrived', true])
|
|
415
|
+
->and($staleFirstStop->calls[0])->toBe(['setStatus', 'arrived', true]);
|
|
416
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
4
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
5
|
+
use Fleetbase\Tests\Support\TrackingPageDatabase;
|
|
6
|
+
use Illuminate\Cache\ArrayStore;
|
|
7
|
+
use Illuminate\Cache\Repository;
|
|
8
|
+
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|
9
|
+
use Illuminate\Database\SQLiteConnection;
|
|
10
|
+
use Illuminate\Support\Facades\Cache;
|
|
11
|
+
|
|
12
|
+
beforeEach(function () {
|
|
13
|
+
Cache::swap(new Repository(new ArrayStore()));
|
|
14
|
+
session(['company' => 'co-1']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(function () {
|
|
18
|
+
EloquentModel::unsetConnectionResolver();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function driverAssignVehicleDatabase(): SQLiteConnection
|
|
22
|
+
{
|
|
23
|
+
$db = TrackingPageDatabase::boot();
|
|
24
|
+
|
|
25
|
+
$db->getSchemaBuilder()->create('users', function ($table) {
|
|
26
|
+
$table->increments('id');
|
|
27
|
+
$table->string('uuid')->nullable();
|
|
28
|
+
$table->string('company_uuid')->nullable();
|
|
29
|
+
$table->timestamps();
|
|
30
|
+
$table->softDeletes();
|
|
31
|
+
});
|
|
32
|
+
$db->getSchemaBuilder()->create('drivers', function ($table) {
|
|
33
|
+
$table->increments('id');
|
|
34
|
+
$table->string('uuid')->nullable();
|
|
35
|
+
$table->string('public_id')->nullable();
|
|
36
|
+
$table->string('company_uuid')->nullable();
|
|
37
|
+
$table->string('user_uuid')->nullable();
|
|
38
|
+
$table->string('vehicle_uuid')->nullable();
|
|
39
|
+
$table->timestamps();
|
|
40
|
+
$table->softDeletes();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
foreach (['user-1', 'user-2'] as $user) {
|
|
44
|
+
TrackingPageDatabase::insert($db, 'users', ['uuid' => $user, 'company_uuid' => 'co-1']);
|
|
45
|
+
}
|
|
46
|
+
TrackingPageDatabase::insert($db, 'drivers', ['uuid' => 'driver-1', 'company_uuid' => 'co-1', 'user_uuid' => 'user-1', 'vehicle_uuid' => 'vehicle-1']);
|
|
47
|
+
TrackingPageDatabase::insert($db, 'drivers', ['uuid' => 'driver-2', 'company_uuid' => 'co-1', 'user_uuid' => 'user-2']);
|
|
48
|
+
|
|
49
|
+
return $db;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function driverAssignVehicleVehicle(): Vehicle
|
|
53
|
+
{
|
|
54
|
+
$vehicle = new Vehicle();
|
|
55
|
+
$vehicle->setRawAttributes(['uuid' => 'vehicle-1', 'company_uuid' => 'co-1'], true);
|
|
56
|
+
$vehicle->exists = true;
|
|
57
|
+
|
|
58
|
+
return $vehicle;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function driverAssignVehicleAssignments(SQLiteConnection $db): array
|
|
62
|
+
{
|
|
63
|
+
return $db->table('drivers')->orderBy('uuid')->pluck('vehicle_uuid', 'uuid')->all();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
test('assigning a driver the vehicle it already has keeps the assignment', function () {
|
|
67
|
+
$db = driverAssignVehicleDatabase();
|
|
68
|
+
|
|
69
|
+
Driver::where('uuid', 'driver-1')->first()->assignVehicle(driverAssignVehicleVehicle());
|
|
70
|
+
|
|
71
|
+
expect(driverAssignVehicleAssignments($db))->toBe(['driver-1' => 'vehicle-1', 'driver-2' => null]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('assigning a vehicle to another driver moves it from its current driver', function () {
|
|
75
|
+
$db = driverAssignVehicleDatabase();
|
|
76
|
+
|
|
77
|
+
$driver = Driver::where('uuid', 'driver-2')->first()->assignVehicle(driverAssignVehicleVehicle());
|
|
78
|
+
|
|
79
|
+
expect(driverAssignVehicleAssignments($db))->toBe(['driver-1' => null, 'driver-2' => 'vehicle-1'])
|
|
80
|
+
->and($driver->vehicle->uuid)->toBe('vehicle-1');
|
|
81
|
+
});
|
|
@@ -33,7 +33,8 @@ use Illuminate\Database\SQLiteConnection;
|
|
|
33
33
|
use Spatie\Activitylog\LogOptions;
|
|
34
34
|
use Spatie\Sluggable\SlugOptions;
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// Extends the relation the model method now declares, so the override stays compatible; the parent constructor is skipped on purpose.
|
|
37
|
+
class FleetOpsFleetUnitCountingRelationFake extends HasManyThrough
|
|
37
38
|
{
|
|
38
39
|
public array $wheres = [];
|
|
39
40
|
|
|
@@ -59,7 +60,7 @@ class FleetOpsFleetUnitCountingFake extends Fleet
|
|
|
59
60
|
public array $driverRelations = [];
|
|
60
61
|
public array $vehicleRelations = [];
|
|
61
62
|
|
|
62
|
-
public function drivers()
|
|
63
|
+
public function drivers(): HasManyThrough
|
|
63
64
|
{
|
|
64
65
|
$relation = new FleetOpsFleetUnitCountingRelationFake(5);
|
|
65
66
|
$this->driverRelations[] = $relation;
|
|
@@ -67,7 +68,7 @@ class FleetOpsFleetUnitCountingFake extends Fleet
|
|
|
67
68
|
return $relation;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
public function vehicles()
|
|
71
|
+
public function vehicles(): HasManyThrough
|
|
71
72
|
{
|
|
72
73
|
$relation = new FleetOpsFleetUnitCountingRelationFake(8);
|
|
73
74
|
$this->vehicleRelations[] = $relation;
|