@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
|
@@ -11,7 +11,9 @@ use Fleetbase\Models\Company;
|
|
|
11
11
|
use Fleetbase\Models\CompanyUser;
|
|
12
12
|
use Fleetbase\Models\User;
|
|
13
13
|
use Fleetbase\Services\SmsService;
|
|
14
|
+
use Illuminate\Support\Facades\DB;
|
|
14
15
|
use Illuminate\Support\Facades\Mail;
|
|
16
|
+
use Illuminate\Support\Facades\Schema;
|
|
15
17
|
use Illuminate\Support\Str;
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -174,7 +176,15 @@ class ProfileAccountManager
|
|
|
174
176
|
$user->save();
|
|
175
177
|
$user->setType($type);
|
|
176
178
|
|
|
177
|
-
|
|
179
|
+
try {
|
|
180
|
+
static::attachToCompany($user, $companyUuid, $type);
|
|
181
|
+
} catch (\Throwable $e) {
|
|
182
|
+
// Leave no account behind for a profile that was never created
|
|
183
|
+
static::removeSandboxCopy($user);
|
|
184
|
+
$user->forceDelete();
|
|
185
|
+
|
|
186
|
+
throw $e;
|
|
187
|
+
}
|
|
178
188
|
|
|
179
189
|
return $user;
|
|
180
190
|
}
|
|
@@ -355,6 +365,8 @@ class ProfileAccountManager
|
|
|
355
365
|
return null;
|
|
356
366
|
}
|
|
357
367
|
|
|
368
|
+
static::copyAccountToSandbox($user, $companyUuid);
|
|
369
|
+
|
|
358
370
|
$role = static::ROLES[$type] ?? static::ROLES['contact'];
|
|
359
371
|
$companyUser = CompanyUser::where(['company_uuid' => $companyUuid, 'user_uuid' => $user->uuid])->first();
|
|
360
372
|
if (!$companyUser) {
|
|
@@ -372,6 +384,47 @@ class ProfileAccountManager
|
|
|
372
384
|
return $companyUser;
|
|
373
385
|
}
|
|
374
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Copy the account and its organization into the sandbox database for a
|
|
389
|
+
* test-key request. Users and companies always live in the live database,
|
|
390
|
+
* but memberships follow the request into the sandbox, whose foreign keys
|
|
391
|
+
* need both rows there too.
|
|
392
|
+
*/
|
|
393
|
+
public static function copyAccountToSandbox(User $user, ?string $companyUuid = null): void
|
|
394
|
+
{
|
|
395
|
+
if (!session('is_sandbox')) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
$live = DB::connection($user->getConnectionName());
|
|
400
|
+
$sandbox = DB::connection('sandbox');
|
|
401
|
+
$account = (array) $live->table($user->getTable())->where('uuid', $user->uuid)->first();
|
|
402
|
+
$company = $companyUuid ? (array) $live->table('companies')->where('uuid', $companyUuid)->first() : [];
|
|
403
|
+
|
|
404
|
+
Schema::connection('sandbox')->disableForeignKeyConstraints();
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
if ($company) {
|
|
408
|
+
$sandbox->table('companies')->updateOrInsert(['uuid' => $company['uuid']], array_diff_key($company, ['id' => true]));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
$account['company_uuid'] = ($account['company_uuid'] ?? null) ?: $companyUuid;
|
|
412
|
+
$sandbox->table($user->getTable())->updateOrInsert(['uuid' => $user->uuid], array_diff_key($account, ['id' => true]));
|
|
413
|
+
} finally {
|
|
414
|
+
Schema::connection('sandbox')->enableForeignKeyConstraints();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Remove the sandbox copy of an account that is being discarded.
|
|
420
|
+
*/
|
|
421
|
+
public static function removeSandboxCopy(User $user): void
|
|
422
|
+
{
|
|
423
|
+
if (session('is_sandbox')) {
|
|
424
|
+
DB::connection('sandbox')->table($user->getTable())->where('uuid', $user->uuid)->delete();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
375
428
|
public static function normalizeEmail(?string $email): ?string
|
|
376
429
|
{
|
|
377
430
|
$email = is_string($email) ? strtolower(trim($email)) : null;
|
|
@@ -15,6 +15,7 @@ use Fleetbase\FleetOps\Models\TrackingNumber;
|
|
|
15
15
|
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
16
16
|
use Fleetbase\FleetOps\Models\Waypoint;
|
|
17
17
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
18
|
+
use Fleetbase\Support\ApiModelCache;
|
|
18
19
|
use Fleetbase\Support\TemplateString;
|
|
19
20
|
use Illuminate\Support\Collection;
|
|
20
21
|
use Illuminate\Support\Facades\DB;
|
|
@@ -185,6 +186,7 @@ trait ResolvesOrderServiceStops
|
|
|
185
186
|
|
|
186
187
|
$payload->current_waypoint_uuid = $place->uuid;
|
|
187
188
|
$payload->saveQuietly();
|
|
189
|
+
ApiModelCache::invalidateModelCache($payload, $payload->company_uuid);
|
|
188
190
|
$payload->setRelation('currentWaypoint', $place);
|
|
189
191
|
|
|
190
192
|
if (($stop['waypoint'] ?? null) instanceof Waypoint) {
|
|
@@ -254,6 +256,7 @@ trait ResolvesOrderServiceStops
|
|
|
254
256
|
$activityId = $waypoint->insertActivity($activity, $location, $proof);
|
|
255
257
|
if ($activityId && $waypoint->tracking_number_uuid) {
|
|
256
258
|
DB::table((new TrackingNumber())->getTable())->where('uuid', $waypoint->tracking_number_uuid)->update(['status_uuid' => $activityId]);
|
|
259
|
+
ApiModelCache::invalidateModelCache(new TrackingNumber(), $payload->company_uuid ?? $order->company_uuid);
|
|
257
260
|
if ($waypoint->relationLoaded('trackingNumber')) {
|
|
258
261
|
$waypoint->trackingNumber->status_uuid = $activityId;
|
|
259
262
|
$waypoint->trackingNumber->unsetRelation('status');
|
|
@@ -391,6 +394,7 @@ trait ResolvesOrderServiceStops
|
|
|
391
394
|
// @codeCoverageIgnoreEnd
|
|
392
395
|
|
|
393
396
|
DB::table($payload->getTable())->where('uuid', $payload->uuid)->update([$column => $trackingNumberUuid]);
|
|
397
|
+
ApiModelCache::invalidateModelCache($payload, $payload->company_uuid ?? $order->company_uuid);
|
|
394
398
|
$payload->{$column} = $trackingNumberUuid;
|
|
395
399
|
|
|
396
400
|
return TrackingNumber::where('uuid', $trackingNumberUuid)->first();
|
|
@@ -417,6 +421,7 @@ trait ResolvesOrderServiceStops
|
|
|
417
421
|
|
|
418
422
|
if ($activityId) {
|
|
419
423
|
DB::table($trackingNumber->getTable())->where('uuid', $trackingNumber->uuid)->update(['status_uuid' => $activityId]);
|
|
424
|
+
ApiModelCache::invalidateModelCache($trackingNumber, $payload->company_uuid ?? $order->company_uuid);
|
|
420
425
|
$trackingNumber->status_uuid = $activityId;
|
|
421
426
|
}
|
|
422
427
|
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Contact;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Device;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
8
|
+
use Fleetbase\FleetOps\Models\Entity;
|
|
9
|
+
use Fleetbase\FleetOps\Models\Fleet;
|
|
10
|
+
use Fleetbase\FleetOps\Models\IntegratedVendor;
|
|
11
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
12
|
+
use Fleetbase\FleetOps\Models\Payload;
|
|
13
|
+
use Fleetbase\FleetOps\Models\Place;
|
|
14
|
+
use Fleetbase\FleetOps\Models\Position;
|
|
15
|
+
use Fleetbase\FleetOps\Models\PurchaseRate;
|
|
16
|
+
use Fleetbase\FleetOps\Models\ServiceArea;
|
|
17
|
+
use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
18
|
+
use Fleetbase\FleetOps\Models\ServiceRate;
|
|
19
|
+
use Fleetbase\FleetOps\Models\TrackingNumber;
|
|
20
|
+
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
21
|
+
use Fleetbase\FleetOps\Models\Trailer;
|
|
22
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
23
|
+
use Fleetbase\FleetOps\Models\Vendor;
|
|
24
|
+
use Fleetbase\FleetOps\Models\Waypoint;
|
|
25
|
+
use Fleetbase\FleetOps\Models\Zone;
|
|
26
|
+
use Fleetbase\Models\Company;
|
|
27
|
+
use Fleetbase\Models\User;
|
|
28
|
+
use Fleetbase\Support\SocketCluster\SocketChannelRegistry;
|
|
29
|
+
use Fleetbase\Support\SocketCluster\SocketPrincipal;
|
|
30
|
+
use Illuminate\Database\Eloquent\Builder;
|
|
31
|
+
use Illuminate\Database\Eloquent\Model;
|
|
32
|
+
use Illuminate\Http\Request;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Who may subscribe to FleetOps realtime channels.
|
|
36
|
+
*
|
|
37
|
+
* Channel names are "{prefix}.{id}" where the prefix is the snake_case model name the lifecycle
|
|
38
|
+
* events broadcast on (plus `driverAssigned`, which the order's assigned-driver channel uses) and
|
|
39
|
+
* the id is the model's uuid or public_id.
|
|
40
|
+
*
|
|
41
|
+
* Console users and API credentials may subscribe to any of their own company's records. Drivers
|
|
42
|
+
* and customers get narrow access only:
|
|
43
|
+
*
|
|
44
|
+
* - a driver: their own driver record, their current vehicle and its devices and positions, and
|
|
45
|
+
* orders assigned to them (directly, or through one of the order's payload entities);
|
|
46
|
+
* - a customer: orders they are the customer of, and the driver and vehicle of those orders while
|
|
47
|
+
* they are active.
|
|
48
|
+
*
|
|
49
|
+
* Public tracking channels (`tracking.{opaque}`) are denied here to everyone: only a `tracking`
|
|
50
|
+
* token, whose scope names the channel exactly, can subscribe to one.
|
|
51
|
+
*/
|
|
52
|
+
class SocketChannels
|
|
53
|
+
{
|
|
54
|
+
/**
|
|
55
|
+
* Order statuses after which a customer no longer sees the order's driver or vehicle.
|
|
56
|
+
*/
|
|
57
|
+
public const TERMINAL_ORDER_STATUSES = ['completed', 'canceled'];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Prefixes resolved by the generic model resolver, with their model.
|
|
61
|
+
*/
|
|
62
|
+
public const MODEL_PREFIXES = [
|
|
63
|
+
'order' => Order::class,
|
|
64
|
+
'driver' => Driver::class,
|
|
65
|
+
'driverAssigned' => Driver::class,
|
|
66
|
+
'vehicle' => Vehicle::class,
|
|
67
|
+
'trailer' => Trailer::class,
|
|
68
|
+
'device' => Device::class,
|
|
69
|
+
'waypoint' => Waypoint::class,
|
|
70
|
+
'entity' => Entity::class,
|
|
71
|
+
'place' => Place::class,
|
|
72
|
+
'contact' => Contact::class,
|
|
73
|
+
'fleet' => Fleet::class,
|
|
74
|
+
'zone' => Zone::class,
|
|
75
|
+
'service_area' => ServiceArea::class,
|
|
76
|
+
'service_rate' => ServiceRate::class,
|
|
77
|
+
'service_quote' => ServiceQuote::class,
|
|
78
|
+
'purchase_rate' => PurchaseRate::class,
|
|
79
|
+
'tracking_status' => TrackingStatus::class,
|
|
80
|
+
'tracking_number' => TrackingNumber::class,
|
|
81
|
+
'payload' => Payload::class,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Prefixes whose id may name more than one kind of record, with the models tried in order.
|
|
86
|
+
*/
|
|
87
|
+
public const MORPH_PREFIXES = [
|
|
88
|
+
'customer' => [Contact::class, Vendor::class],
|
|
89
|
+
'vendor' => [Vendor::class, IntegratedVendor::class],
|
|
90
|
+
'facilitator' => [Vendor::class, IntegratedVendor::class, Contact::class],
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Register every FleetOps prefix and the driver principal resolver.
|
|
95
|
+
*/
|
|
96
|
+
public static function register(SocketChannelRegistry $registry): void
|
|
97
|
+
{
|
|
98
|
+
foreach (self::MODEL_PREFIXES as $prefix => $modelClass) {
|
|
99
|
+
$registry->registerModel($prefix, $modelClass, static::narrowFor($prefix));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
foreach (self::MORPH_PREFIXES as $prefix => $modelClasses) {
|
|
103
|
+
$registry->register($prefix, static::resolver($modelClasses, [static::class, 'narrowSelf']));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Positions have no public_id, so they are looked up by uuid only.
|
|
107
|
+
$registry->register('position', static::resolver([Position::class], [static::class, 'narrowPosition'], false));
|
|
108
|
+
|
|
109
|
+
// tracking.{opaque} is reachable only through a tracking token's exact scope.
|
|
110
|
+
$registry->register(TrackingChannel::PREFIX, static fn (): bool => false);
|
|
111
|
+
|
|
112
|
+
$registry->registerPrincipalResolver([static::class, 'driverPrincipal']);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The narrow rule used for driver and customer principals on a generic model prefix.
|
|
117
|
+
*/
|
|
118
|
+
public static function narrowFor(string $prefix): ?callable
|
|
119
|
+
{
|
|
120
|
+
return match ($prefix) {
|
|
121
|
+
'order' => [static::class, 'narrowOrder'],
|
|
122
|
+
'driver', 'driverAssigned' => [static::class, 'narrowDriver'],
|
|
123
|
+
'vehicle' => [static::class, 'narrowVehicle'],
|
|
124
|
+
'device' => [static::class, 'narrowDevice'],
|
|
125
|
+
'contact' => [static::class, 'narrowSelf'],
|
|
126
|
+
default => null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A resolver over one or more models: company match for user and API principals, the narrow
|
|
132
|
+
* rule for driver and customer principals, deny for everyone else.
|
|
133
|
+
*
|
|
134
|
+
* @param array<int, class-string<Model>> $modelClasses
|
|
135
|
+
*/
|
|
136
|
+
public static function resolver(array $modelClasses, ?callable $narrow = null, bool $byPublicId = true): \Closure
|
|
137
|
+
{
|
|
138
|
+
return static function (SocketPrincipal $principal, string $id, string $channel = '') use ($modelClasses, $narrow, $byPublicId): bool {
|
|
139
|
+
$narrowed = $principal->kind === 'driver' || $principal->kind === 'customer';
|
|
140
|
+
if ((!$principal->isCompanyScoped() && !$narrowed) || $id === '') {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
$model = static::find($modelClasses, $principal, $id, $byPublicId);
|
|
145
|
+
if ($model === null) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if ($principal->isCompanyScoped()) {
|
|
150
|
+
return $principal->cid !== null && $model->company_uuid === $principal->cid;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return $narrow !== null && (bool) $narrow($principal, $model);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The first of the models whose uuid (or public_id) is the id, in the principal's environment.
|
|
159
|
+
*
|
|
160
|
+
* @param array<int, class-string<Model>> $modelClasses
|
|
161
|
+
*/
|
|
162
|
+
public static function find(array $modelClasses, SocketPrincipal $principal, string $id, bool $byPublicId = true): ?Model
|
|
163
|
+
{
|
|
164
|
+
foreach ($modelClasses as $modelClass) {
|
|
165
|
+
$model = $modelClass::on(static::connection($principal))
|
|
166
|
+
->where(function ($query) use ($id, $byPublicId) {
|
|
167
|
+
$query->where('uuid', $id);
|
|
168
|
+
if ($byPublicId) {
|
|
169
|
+
$query->orWhere('public_id', $id);
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
->first();
|
|
173
|
+
|
|
174
|
+
if ($model) {
|
|
175
|
+
return $model;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The database connection for the principal's environment; null is the default one.
|
|
184
|
+
*/
|
|
185
|
+
public static function connection(SocketPrincipal $principal): ?string
|
|
186
|
+
{
|
|
187
|
+
return $principal->env === 'test' ? 'sandbox' : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Orders: a driver's when assigned to them (directly or on one of its payload entities), a customer's own.
|
|
192
|
+
*/
|
|
193
|
+
public static function narrowOrder(SocketPrincipal $principal, Model $order): bool
|
|
194
|
+
{
|
|
195
|
+
if ($principal->kind === 'customer') {
|
|
196
|
+
return static::isSelf($principal, $order->customer_uuid);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if ($principal->kind !== 'driver') {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (static::isSelf($principal, $order->driver_assigned_uuid)) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return !empty($order->payload_uuid)
|
|
208
|
+
&& Entity::on($order->getConnectionName())
|
|
209
|
+
->where('payload_uuid', $order->payload_uuid)
|
|
210
|
+
->where('driver_assigned_uuid', $principal->sub)
|
|
211
|
+
->exists();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Drivers: a driver only themselves; a customer the driver of one of their active orders.
|
|
216
|
+
*/
|
|
217
|
+
public static function narrowDriver(SocketPrincipal $principal, Model $driver): bool
|
|
218
|
+
{
|
|
219
|
+
if ($principal->kind === 'driver') {
|
|
220
|
+
return static::isSelf($principal, $driver->uuid);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return $principal->kind === 'customer'
|
|
224
|
+
&& static::activeCustomerOrders($principal, $driver->getConnectionName())
|
|
225
|
+
->where('driver_assigned_uuid', $driver->uuid)
|
|
226
|
+
->exists();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Vehicles: a driver their current vehicle; a customer the vehicle of one of their active orders,
|
|
231
|
+
* whether assigned to the order or driven by the order's driver.
|
|
232
|
+
*/
|
|
233
|
+
public static function narrowVehicle(SocketPrincipal $principal, Model $vehicle): bool
|
|
234
|
+
{
|
|
235
|
+
if ($principal->kind === 'driver') {
|
|
236
|
+
return static::driverVehicleUuid($principal, $vehicle->getConnectionName()) === $vehicle->uuid;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if ($principal->kind !== 'customer') {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
$connection = $vehicle->getConnectionName();
|
|
244
|
+
$drivers = Driver::on($connection)->where('vehicle_uuid', $vehicle->uuid)->select('uuid')->toBase();
|
|
245
|
+
|
|
246
|
+
return static::activeCustomerOrders($principal, $connection)
|
|
247
|
+
->where(function ($query) use ($vehicle, $drivers) {
|
|
248
|
+
$query->where('vehicle_assigned_uuid', $vehicle->uuid)->orWhereIn('driver_assigned_uuid', $drivers);
|
|
249
|
+
})
|
|
250
|
+
->exists();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Devices: a driver those attached to themselves or to their current vehicle.
|
|
255
|
+
*/
|
|
256
|
+
public static function narrowDevice(SocketPrincipal $principal, Model $device): bool
|
|
257
|
+
{
|
|
258
|
+
return $principal->kind === 'driver' && static::ownsDriverSubject($principal, $device->attachable_uuid, $device->getConnectionName());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Positions: a driver those recorded for themselves or for their current vehicle.
|
|
263
|
+
*/
|
|
264
|
+
public static function narrowPosition(SocketPrincipal $principal, Model $position): bool
|
|
265
|
+
{
|
|
266
|
+
return $principal->kind === 'driver' && static::ownsDriverSubject($principal, $position->subject_uuid, $position->getConnectionName());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Contacts, customers, vendors, facilitators: a customer only themselves.
|
|
271
|
+
*/
|
|
272
|
+
public static function narrowSelf(SocketPrincipal $principal, Model $model): bool
|
|
273
|
+
{
|
|
274
|
+
return $principal->kind === 'customer' && static::isSelf($principal, $model->uuid);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Claim a Sanctum-authenticated user who is a driver of the current company as a driver principal.
|
|
279
|
+
*/
|
|
280
|
+
public static function driverPrincipal(Request $request, $user): ?SocketPrincipal
|
|
281
|
+
{
|
|
282
|
+
if (!$user instanceof User || !$user->uuid) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
$companyUuid = session('company') ?: $user->company_uuid;
|
|
287
|
+
if (!$companyUuid) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
$driver = Driver::where('user_uuid', $user->uuid)->where('company_uuid', $companyUuid)->first();
|
|
292
|
+
if (!$driver) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
$connection = $driver->getConnectionName();
|
|
297
|
+
$cpid = Company::on($connection)->where('uuid', $companyUuid)->value('public_id');
|
|
298
|
+
|
|
299
|
+
return new SocketPrincipal(
|
|
300
|
+
kind: 'driver',
|
|
301
|
+
sub: (string) $driver->uuid,
|
|
302
|
+
cid: (string) $companyUuid,
|
|
303
|
+
cpid: is_string($cpid) && $cpid !== '' ? $cpid : null,
|
|
304
|
+
env: $connection === 'sandbox' ? 'test' : 'live',
|
|
305
|
+
ids: array_values(array_filter([$driver->uuid, $driver->public_id, $user->uuid, $user->public_id], fn ($id) => is_string($id) && $id !== '')),
|
|
306
|
+
adm: false,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The customer's orders that are not completed or canceled.
|
|
312
|
+
*/
|
|
313
|
+
protected static function activeCustomerOrders(SocketPrincipal $principal, ?string $connection): Builder
|
|
314
|
+
{
|
|
315
|
+
return Order::on($connection)
|
|
316
|
+
->whereIn('customer_uuid', array_values(array_unique([$principal->sub, ...$principal->ids])))
|
|
317
|
+
->whereNotIn('status', self::TERMINAL_ORDER_STATUSES);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Whether a record's subject is the driver principal or the driver's current vehicle.
|
|
322
|
+
*/
|
|
323
|
+
protected static function ownsDriverSubject(SocketPrincipal $principal, ?string $subjectUuid, ?string $connection): bool
|
|
324
|
+
{
|
|
325
|
+
if (!$subjectUuid) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return static::isSelf($principal, $subjectUuid) || static::driverVehicleUuid($principal, $connection) === $subjectUuid;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Whether an id is the principal's own (its subject or one of its ids).
|
|
334
|
+
*/
|
|
335
|
+
protected static function isSelf(SocketPrincipal $principal, mixed $id): bool
|
|
336
|
+
{
|
|
337
|
+
return is_string($id) && $id !== '' && ($id === $principal->sub || $principal->owns($id));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
protected static function driverVehicleUuid(SocketPrincipal $principal, ?string $connection): ?string
|
|
341
|
+
{
|
|
342
|
+
$vehicleUuid = Driver::on($connection)->where('uuid', $principal->sub)->value('vehicle_uuid');
|
|
343
|
+
|
|
344
|
+
return is_string($vehicleUuid) && $vehicleUuid !== '' ? $vehicleUuid : null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -13,6 +13,8 @@ use Fleetbase\FleetOps\Models\Sensor;
|
|
|
13
13
|
use Fleetbase\FleetOps\Models\Telematic;
|
|
14
14
|
use Fleetbase\FleetOps\Models\Trailer;
|
|
15
15
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
16
|
+
use Fleetbase\FleetOps\Support\ApiCacheThrottle;
|
|
17
|
+
use Fleetbase\FleetOps\Support\TrackingPublisher;
|
|
16
18
|
use Fleetbase\LaravelMysqlSpatial\Types\Point as SpatialPoint;
|
|
17
19
|
use Illuminate\Support\Carbon;
|
|
18
20
|
use Illuminate\Support\Facades\Crypt;
|
|
@@ -759,7 +761,8 @@ class TelematicService
|
|
|
759
761
|
'last_provider' => $event->provider,
|
|
760
762
|
'last_telemetry_data' => array_filter($eventData, fn ($value) => $value !== null),
|
|
761
763
|
]);
|
|
762
|
-
|
|
764
|
+
// Every telemetry event saves the trailer: model events fire, cache invalidation is coalesced
|
|
765
|
+
ApiCacheThrottle::telemetryWrite($trailer, fn () => $trailer->save());
|
|
763
766
|
|
|
764
767
|
$broadcast = new TrailerLocationChanged($trailer, ['source' => 'telematics', 'device_event_uuid' => $event->uuid, 'provider' => $event->provider, 'position_at' => $event->occurred_at?->toISOString()]);
|
|
765
768
|
$this->broadcastTelemetry($broadcast, $eventData['_ordered_telemetry'] ?? false);
|
|
@@ -802,7 +805,8 @@ class TelematicService
|
|
|
802
805
|
'fuel_level' => $eventData['fuel_level'] ?? null,
|
|
803
806
|
], fn ($value) => $value !== null),
|
|
804
807
|
]);
|
|
805
|
-
|
|
808
|
+
// Every telemetry event saves the vehicle: model events fire, cache invalidation is coalesced
|
|
809
|
+
ApiCacheThrottle::telemetryWrite($vehicle, fn () => $vehicle->save());
|
|
806
810
|
|
|
807
811
|
$broadcast = new VehicleLocationChanged($vehicle, [
|
|
808
812
|
'source' => 'telematics',
|
|
@@ -811,6 +815,7 @@ class TelematicService
|
|
|
811
815
|
'position_at' => $event->occurred_at?->toISOString(),
|
|
812
816
|
]);
|
|
813
817
|
$this->broadcastTelemetry($broadcast, $eventData['_ordered_telemetry'] ?? false);
|
|
818
|
+
app(TrackingPublisher::class)->vehicleMoved($vehicle);
|
|
814
819
|
}
|
|
815
820
|
|
|
816
821
|
protected function broadcastTelemetry(object $event, bool $afterCommit): void
|
|
@@ -4,6 +4,7 @@ namespace Fleetbase\FleetOps\Support\Telematics\Telemetry;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Jobs\ProcessTelematicDelivery;
|
|
6
6
|
use Fleetbase\FleetOps\Models\Telematic;
|
|
7
|
+
use Fleetbase\Support\ApiModelCache;
|
|
7
8
|
use Illuminate\Support\Facades\Crypt;
|
|
8
9
|
use Illuminate\Support\Facades\DB;
|
|
9
10
|
use Illuminate\Support\Str;
|
|
@@ -98,10 +99,16 @@ class Inbox
|
|
|
98
99
|
return;
|
|
99
100
|
}
|
|
100
101
|
// Written without model events: sweeps run every minute and the model logs every save.
|
|
102
|
+
$connectionStatus = in_array($connection->status, ['error', 'synchronizing'], true) ? 'active' : $connection->status;
|
|
101
103
|
DB::table('telematics')->where('uuid', $connection->uuid)->update([
|
|
102
|
-
'status' =>
|
|
104
|
+
'status' => $connectionStatus,
|
|
103
105
|
'meta' => json_encode(array_merge($meta, $result), JSON_THROW_ON_ERROR),
|
|
104
106
|
]);
|
|
107
|
+
// Cached device lists embed the telematic. Only a status change invalidates
|
|
108
|
+
// them: the sync meta changes on every sweep and may lag by the list TTL.
|
|
109
|
+
if ($connectionStatus !== $connection->status) {
|
|
110
|
+
ApiModelCache::invalidateModelCache($connection, $connection->company_uuid);
|
|
111
|
+
}
|
|
105
112
|
});
|
|
106
113
|
}
|
|
107
114
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Support\SocketCluster\SocketToken;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The public tracking channel of one customer on one order: `tracking.{opaque}`.
|
|
9
|
+
*
|
|
10
|
+
* The opaque id is derived by core-api (SocketToken::trackingId): lowercase, unpadded base32 of
|
|
11
|
+
* HMAC-SHA256(tracking key, "{order_uuid}:{customer_type}:{customer_uuid}"), first 26 characters,
|
|
12
|
+
* where the tracking key is derived from SOCKETCLUSTER_AUTH_KEY. Nobody can guess or enumerate it
|
|
13
|
+
* without the auth key, and it reveals nothing about the order.
|
|
14
|
+
*
|
|
15
|
+
* The channel registry denies the `tracking` prefix to every principal; the only way in is a
|
|
16
|
+
* `tracking` token from token(), whose scope names exactly this channel.
|
|
17
|
+
*/
|
|
18
|
+
final class TrackingChannel
|
|
19
|
+
{
|
|
20
|
+
public const PREFIX = 'tracking';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The channel name for a customer's scope.
|
|
24
|
+
*
|
|
25
|
+
* @throws \RuntimeException when socket authentication is not configured
|
|
26
|
+
*/
|
|
27
|
+
public static function name(TrackingScope $scope): string
|
|
28
|
+
{
|
|
29
|
+
return self::PREFIX . '.' . static::opaqueId($scope);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The opaque id for a customer's scope.
|
|
34
|
+
*
|
|
35
|
+
* @throws \RuntimeException when socket authentication is not configured
|
|
36
|
+
*/
|
|
37
|
+
public static function opaqueId(TrackingScope $scope): string
|
|
38
|
+
{
|
|
39
|
+
return SocketToken::trackingId($scope->order_uuid, $scope->customer_type, $scope->customer_uuid);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Mint the token (kind `tracking`, 1800 s) that may subscribe to this scope's channel and nothing else.
|
|
44
|
+
*
|
|
45
|
+
* The caller (the public tracking API) checks its own grant for the scope first.
|
|
46
|
+
*
|
|
47
|
+
* @return array{token: string, expires_in: int, expires_at: string}
|
|
48
|
+
*
|
|
49
|
+
* @throws \RuntimeException when socket authentication is not configured
|
|
50
|
+
*/
|
|
51
|
+
public static function token(TrackingScope $scope): array
|
|
52
|
+
{
|
|
53
|
+
return SocketToken::forTracking($scope);
|
|
54
|
+
}
|
|
55
|
+
}
|