@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
|
@@ -13,12 +13,15 @@ use Fleetbase\FleetOps\Models\Payload;
|
|
|
13
13
|
use Fleetbase\FleetOps\Models\Place;
|
|
14
14
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
15
15
|
use Fleetbase\FleetOps\Models\Vendor;
|
|
16
|
+
use Fleetbase\FleetOps\Models\Waypoint;
|
|
17
|
+
use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
|
|
16
18
|
use Fleetbase\FleetOps\Orchestration\Engines\DriverAssignmentEngine;
|
|
17
19
|
use Fleetbase\FleetOps\Orchestration\Engines\RouteSequencingEngine;
|
|
18
20
|
use Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry;
|
|
21
|
+
use Fleetbase\FleetOps\Orchestration\Support\OrchestratorSettings;
|
|
22
|
+
use Fleetbase\FleetOps\Orchestration\Support\RouteMetrics;
|
|
19
23
|
use Fleetbase\FleetOps\Traits\AuthorizesMethods;
|
|
20
24
|
use Fleetbase\Http\Controllers\Controller;
|
|
21
|
-
use Fleetbase\Models\Setting;
|
|
22
25
|
use Illuminate\Http\JsonResponse;
|
|
23
26
|
use Illuminate\Http\Request;
|
|
24
27
|
use Illuminate\Support\Carbon;
|
|
@@ -42,6 +45,11 @@ class OrchestrationController extends Controller
|
|
|
42
45
|
{
|
|
43
46
|
use AuthorizesMethods;
|
|
44
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Engines that assign orders to vehicles without producing a stop sequence.
|
|
50
|
+
*/
|
|
51
|
+
protected const ASSIGNMENT_ONLY_ENGINES = ['greedy', 'capacity'];
|
|
52
|
+
|
|
45
53
|
public function __construct(protected OrchestrationEngineRegistry $registry)
|
|
46
54
|
{
|
|
47
55
|
$this->authorizeMethods([
|
|
@@ -281,7 +289,7 @@ class OrchestrationController extends Controller
|
|
|
281
289
|
// ── Run engine ────────────────────────────────────────────────────────
|
|
282
290
|
$engineId = $mode === 'assign_drivers'
|
|
283
291
|
? 'driver_assignment'
|
|
284
|
-
: ($request->input('options.engine')
|
|
292
|
+
: ($request->input('options.engine') ?: $this->orchestratorEngineSetting());
|
|
285
293
|
|
|
286
294
|
try {
|
|
287
295
|
if ($mode === 'assign_drivers') {
|
|
@@ -308,14 +316,13 @@ class OrchestrationController extends Controller
|
|
|
308
316
|
}
|
|
309
317
|
}
|
|
310
318
|
}
|
|
311
|
-
$
|
|
312
|
-
$result = $engine->sequence($orders, $options);
|
|
319
|
+
$result = $this->optimizeRoutes($engineId, $orders, $options);
|
|
313
320
|
} else {
|
|
314
|
-
$
|
|
315
|
-
$result = $engine->allocate($orders, $vehicles, $options);
|
|
321
|
+
$result = $this->registry->allocateWithFallback($engineId, $orders, $vehicles, $options);
|
|
316
322
|
}
|
|
317
323
|
} catch (\RuntimeException $e) {
|
|
318
|
-
// Engine is unavailable (e.g. VROOM not reachable)
|
|
324
|
+
// Engine is unavailable (e.g. VROOM not reachable) and there is no
|
|
325
|
+
// built-in engine to fall back to.
|
|
319
326
|
// Return a structured JSON 503 so the frontend can display a
|
|
320
327
|
// user-friendly message instead of an unhandled exception page.
|
|
321
328
|
return response()->json([
|
|
@@ -325,6 +332,10 @@ class OrchestrationController extends Controller
|
|
|
325
332
|
], 503);
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
if ($mode !== 'assign_drivers') {
|
|
336
|
+
$result = RouteMetrics::annotate($result, $orders, $vehicles);
|
|
337
|
+
}
|
|
338
|
+
|
|
328
339
|
return response()->json($result);
|
|
329
340
|
}
|
|
330
341
|
|
|
@@ -384,7 +395,71 @@ class OrchestrationController extends Controller
|
|
|
384
395
|
|
|
385
396
|
protected function orchestratorEngineSetting(): string
|
|
386
397
|
{
|
|
387
|
-
return
|
|
398
|
+
return OrchestratorSettings::engineForCompany($this->companyUuid());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Re-sequence each vehicle's already-assigned orders.
|
|
403
|
+
*
|
|
404
|
+
* A solver engine (e.g. VROOM) is run once per vehicle, restricted to
|
|
405
|
+
* that vehicle and its orders, so the stop order, arrival times and
|
|
406
|
+
* distances come from the selected engine. Engines that only assign
|
|
407
|
+
* orders (greedy, capacity) cannot sequence stops, and a solver that is
|
|
408
|
+
* missing or unavailable is replaced; in both cases the built-in route
|
|
409
|
+
* sequencer runs and the result says which engine ran and why.
|
|
410
|
+
*/
|
|
411
|
+
protected function optimizeRoutes(string $engineId, $orders, array $options): array
|
|
412
|
+
{
|
|
413
|
+
$warn = true;
|
|
414
|
+
if (in_array($engineId, self::ASSIGNMENT_ONLY_ENGINES, true)) {
|
|
415
|
+
$warn = false;
|
|
416
|
+
$reason = sprintf('The "%s" engine assigns orders but does not sequence stops; stops were sequenced by the built-in route sequencer.', $engineId);
|
|
417
|
+
} elseif (!$this->registry->has($engineId)) {
|
|
418
|
+
$reason = sprintf('No orchestration engine is registered as "%s".', $engineId);
|
|
419
|
+
} else {
|
|
420
|
+
try {
|
|
421
|
+
return $this->sequenceWithEngine($this->registry->resolve($engineId), $orders, $options);
|
|
422
|
+
} catch (\RuntimeException $e) {
|
|
423
|
+
$reason = $e->getMessage();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
$result = $this->routeSequencingEngine()->sequence($orders, $options);
|
|
428
|
+
|
|
429
|
+
return OrchestrationEngineRegistry::markFallback($result, $result['summary']['engine'] ?? 'route_sequencing', $engineId, $reason, $warn);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Run a solver engine once per vehicle group, keeping every order on its vehicle.
|
|
434
|
+
*/
|
|
435
|
+
protected function sequenceWithEngine(OrchestrationEngineInterface $engine, $orders, array $options): array
|
|
436
|
+
{
|
|
437
|
+
$options = array_merge($options, ['allocation_strategy' => 'route_aware']);
|
|
438
|
+
$assignments = [];
|
|
439
|
+
$unassigned = [];
|
|
440
|
+
|
|
441
|
+
foreach ($orders->groupBy(fn ($order) => $order->vehicle_assigned_uuid ?? '') as $vehicleUuid => $vehicleOrders) {
|
|
442
|
+
$vehicle = $vehicleUuid !== '' && $vehicleOrders->first()->relationLoaded('vehicle') ? $vehicleOrders->first()->vehicle : null;
|
|
443
|
+
if (!$vehicle) {
|
|
444
|
+
// Without an assigned vehicle there is no route to sequence.
|
|
445
|
+
array_push($unassigned, ...$vehicleOrders->pluck('public_id')->all());
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
$result = $engine->allocate($vehicleOrders->values(), collect([$vehicle]), $options);
|
|
450
|
+
array_push($assignments, ...($result['assignments'] ?? []));
|
|
451
|
+
array_push($unassigned, ...($result['unassigned'] ?? []));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return [
|
|
455
|
+
'assignments' => $assignments,
|
|
456
|
+
'unassigned' => array_values(array_unique($unassigned)),
|
|
457
|
+
'summary' => [
|
|
458
|
+
'engine' => $engine->getIdentifier(),
|
|
459
|
+
'assigned' => count($assignments),
|
|
460
|
+
'unassigned' => count(array_unique($unassigned)),
|
|
461
|
+
],
|
|
462
|
+
];
|
|
388
463
|
}
|
|
389
464
|
|
|
390
465
|
protected function driverAssignmentEngine(): DriverAssignmentEngine
|
|
@@ -415,9 +490,10 @@ class OrchestrationController extends Controller
|
|
|
415
490
|
return response()->json(['error' => 'No assignments provided.'], 422);
|
|
416
491
|
}
|
|
417
492
|
|
|
418
|
-
$committed
|
|
419
|
-
$failed
|
|
420
|
-
$manifests
|
|
493
|
+
$committed = [];
|
|
494
|
+
$failed = [];
|
|
495
|
+
$manifests = [];
|
|
496
|
+
$resequenced = false;
|
|
421
497
|
|
|
422
498
|
$this->beginOrchestrationTransaction();
|
|
423
499
|
try {
|
|
@@ -502,6 +578,7 @@ class OrchestrationController extends Controller
|
|
|
502
578
|
if (!empty($assignment['waypoint_sequence']) && $order->payload) {
|
|
503
579
|
foreach ($assignment['waypoint_sequence'] as $seq => $waypointId) {
|
|
504
580
|
$this->updateWaypointSequence($order->payload_uuid, $waypointId, $seq);
|
|
581
|
+
$resequenced = true;
|
|
505
582
|
}
|
|
506
583
|
}
|
|
507
584
|
|
|
@@ -523,6 +600,11 @@ class OrchestrationController extends Controller
|
|
|
523
600
|
return response()->json(['error' => 'Commit failed: ' . $e->getMessage()], 500);
|
|
524
601
|
}
|
|
525
602
|
|
|
603
|
+
// Waypoint sequences are written without model events
|
|
604
|
+
if ($resequenced) {
|
|
605
|
+
Waypoint::invalidateApiCacheManually($companyUuid);
|
|
606
|
+
}
|
|
607
|
+
|
|
526
608
|
return response()->json([
|
|
527
609
|
'committed' => $committed,
|
|
528
610
|
'failed' => $failed,
|
|
@@ -10,6 +10,7 @@ use Fleetbase\FleetOps\Events\OrderDispatchFailed;
|
|
|
10
10
|
use Fleetbase\FleetOps\Events\OrderStarted;
|
|
11
11
|
use Fleetbase\FleetOps\Events\WaypointActivityChanged;
|
|
12
12
|
use Fleetbase\FleetOps\Events\WaypointCompleted;
|
|
13
|
+
use Fleetbase\FleetOps\Exceptions\OrderEditLockedException;
|
|
13
14
|
use Fleetbase\FleetOps\Exports\OrderExport;
|
|
14
15
|
use Fleetbase\FleetOps\Flow\Activity;
|
|
15
16
|
use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
@@ -19,6 +20,7 @@ use Fleetbase\FleetOps\Http\Requests\Internal\CreateOrderRequest;
|
|
|
19
20
|
use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as OrderIndexResource;
|
|
20
21
|
use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
|
|
21
22
|
use Fleetbase\FleetOps\Http\Resources\v1\Proof as ProofResource;
|
|
23
|
+
use Fleetbase\FleetOps\Http\Resources\v1\PublicOrderTracking;
|
|
22
24
|
use Fleetbase\FleetOps\Imports\OrdersImport;
|
|
23
25
|
use Fleetbase\FleetOps\Jobs\FinalizeInternalOrderCreation;
|
|
24
26
|
use Fleetbase\FleetOps\Jobs\NotifyBulkAssignedDriver;
|
|
@@ -33,6 +35,7 @@ use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
|
33
35
|
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
34
36
|
use Fleetbase\FleetOps\Models\Waypoint;
|
|
35
37
|
use Fleetbase\FleetOps\Notifications\OrderPing;
|
|
38
|
+
use Fleetbase\FleetOps\Support\OrderEdit;
|
|
36
39
|
use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
|
|
37
40
|
use Fleetbase\FleetOps\Support\Utils;
|
|
38
41
|
use Fleetbase\Http\Requests\ExportRequest;
|
|
@@ -54,6 +57,12 @@ use Maatwebsite\Excel\Facades\Excel;
|
|
|
54
57
|
class OrderController extends FleetOpsController
|
|
55
58
|
{
|
|
56
59
|
use ResolvesOrderServiceStops;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The one answer the public tracking lookup gives for any number it cannot resolve.
|
|
63
|
+
*/
|
|
64
|
+
public const TRACKING_LOOKUP_ERROR = 'No order found using tracking number provided.';
|
|
65
|
+
|
|
57
66
|
/**
|
|
58
67
|
* Permissions for methods AuthorizationGuard cannot map to a schema action (see FleetOpsController).
|
|
59
68
|
*
|
|
@@ -313,6 +322,19 @@ class OrderController extends FleetOpsController
|
|
|
313
322
|
return $this->errorResponse('Unable to find order to update route for.');
|
|
314
323
|
}
|
|
315
324
|
|
|
325
|
+
// Refuse a route edit the order's status locks (completed, canceled, expired)
|
|
326
|
+
$edit = $this->beginOrderEdit($order);
|
|
327
|
+
try {
|
|
328
|
+
$edit->guard($edit->requestedChanges(array_filter([
|
|
329
|
+
'pickup' => $hasPickupInput ? true : null,
|
|
330
|
+
'dropoff' => $hasDropoffInput ? true : null,
|
|
331
|
+
'return' => $hasReturnInput ? true : null,
|
|
332
|
+
'waypoints' => ($hasWaypointsInput || $hasPickupInput || $hasDropoffInput || $hasReturnInput) ? true : null,
|
|
333
|
+
])));
|
|
334
|
+
} catch (OrderEditLockedException $exception) {
|
|
335
|
+
return $this->errorResponse($exception->getMessage(), 422, $exception->toResponseData());
|
|
336
|
+
}
|
|
337
|
+
|
|
316
338
|
if ($hasPickupInput) {
|
|
317
339
|
if ($pickup) {
|
|
318
340
|
$order->payload->setPickup($pickup, ['save' => true]);
|
|
@@ -347,14 +369,9 @@ class OrderController extends FleetOpsController
|
|
|
347
369
|
$order->payload->removeWaypoints();
|
|
348
370
|
}
|
|
349
371
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if ($startingDestination instanceof Place || $startingDestination instanceof Waypoint) {
|
|
356
|
-
$order->payload->setCurrentWaypoint($startingDestination);
|
|
357
|
-
}
|
|
372
|
+
// Only a changed route moves the current waypoint, and an order under way keeps
|
|
373
|
+
// the driver's stop; distance, time, tracking and `order.changed` follow the change
|
|
374
|
+
$this->commitOrderEdit($edit);
|
|
358
375
|
|
|
359
376
|
$order->load(['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints']);
|
|
360
377
|
|
|
@@ -595,7 +612,12 @@ class OrderController extends FleetOpsController
|
|
|
595
612
|
return $this->errorResponse('No order found to cancel.');
|
|
596
613
|
}
|
|
597
614
|
|
|
598
|
-
$
|
|
615
|
+
$reason = $request->input('reason');
|
|
616
|
+
if (is_string($reason) && trim($reason) !== '') {
|
|
617
|
+
$order->cancelWithReason($reason);
|
|
618
|
+
} else {
|
|
619
|
+
$order->cancel();
|
|
620
|
+
}
|
|
599
621
|
|
|
600
622
|
return $this->jsonResponse(
|
|
601
623
|
[
|
|
@@ -765,10 +787,21 @@ class OrderController extends FleetOpsController
|
|
|
765
787
|
|
|
766
788
|
protected function assignDriverToOrders($orderUuids, Driver $driver): void
|
|
767
789
|
{
|
|
790
|
+
// Drivers being replaced are told once the assignment is written
|
|
791
|
+
$replaced = $this->scopedToCompany(Order::whereIn('uuid', $orderUuids))
|
|
792
|
+
->whereNotNull('driver_assigned_uuid')
|
|
793
|
+
->where('driver_assigned_uuid', '!=', $driver->uuid)
|
|
794
|
+
->get();
|
|
795
|
+
|
|
768
796
|
$this->scopedToCompany(Order::whereIn('uuid', $orderUuids))->update([
|
|
769
797
|
'driver_assigned_uuid' => $driver->uuid,
|
|
770
798
|
'updated_at' => now(),
|
|
771
799
|
]);
|
|
800
|
+
Order::invalidateApiCacheManually(session('company'));
|
|
801
|
+
|
|
802
|
+
foreach ($replaced as $order) {
|
|
803
|
+
$order->notifyDriverUnassigned($order->driver_assigned_uuid);
|
|
804
|
+
}
|
|
772
805
|
}
|
|
773
806
|
|
|
774
807
|
protected function dispatchBulkAssignedDriverNotification(array $orderUuids, Driver $driver): void
|
|
@@ -786,9 +819,19 @@ class OrderController extends FleetOpsController
|
|
|
786
819
|
return response()->json($payload);
|
|
787
820
|
}
|
|
788
821
|
|
|
789
|
-
protected function errorResponse(string $message, int $status = 400)
|
|
822
|
+
protected function errorResponse(string $message, int $status = 400, array $data = [])
|
|
823
|
+
{
|
|
824
|
+
return response()->error($message, $status, $data);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
protected function beginOrderEdit(Order $order): OrderEdit
|
|
790
828
|
{
|
|
791
|
-
return
|
|
829
|
+
return OrderEdit::begin($order);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
protected function commitOrderEdit(OrderEdit $edit): array
|
|
833
|
+
{
|
|
834
|
+
return $edit->commit();
|
|
792
835
|
}
|
|
793
836
|
|
|
794
837
|
/**
|
|
@@ -1905,27 +1948,43 @@ class OrderController extends FleetOpsController
|
|
|
1905
1948
|
return response()->json(OrderConfig::default());
|
|
1906
1949
|
}
|
|
1907
1950
|
|
|
1951
|
+
/**
|
|
1952
|
+
* Look up an order for the public Track Order page.
|
|
1953
|
+
*
|
|
1954
|
+
* Answers with `PublicOrderTracking`, never the full order resource: the
|
|
1955
|
+
* tracking number is the only credential here. A missing, malformed or
|
|
1956
|
+
* unknown tracking number all get the same error, so a caller cannot tell
|
|
1957
|
+
* which one they hit.
|
|
1958
|
+
*/
|
|
1908
1959
|
public function lookup(Request $request)
|
|
1909
1960
|
{
|
|
1910
|
-
$trackingNumber = $request->input('tracking');
|
|
1911
|
-
|
|
1912
|
-
return response()->error('No tracking number provided for lookup.');
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
$order = $this->findOrderByTrackingNumber($trackingNumber);
|
|
1961
|
+
$trackingNumber = $this->normalizeTrackingLookup($request->input('tracking'));
|
|
1962
|
+
$order = $trackingNumber ? $this->findOrderByTrackingNumber($trackingNumber) : null;
|
|
1916
1963
|
|
|
1917
1964
|
if (!$order) {
|
|
1918
|
-
return response()->error(
|
|
1965
|
+
return response()->error(static::TRACKING_LOOKUP_ERROR);
|
|
1919
1966
|
}
|
|
1920
1967
|
|
|
1921
|
-
|
|
1922
|
-
$order->loadMissing(['trackingNumber', 'payload', 'trackingStatuses']);
|
|
1923
|
-
|
|
1924
|
-
// load tracker data
|
|
1968
|
+
$order->loadMissing(['trackingNumber', 'trackingStatuses', 'payload.pickup', 'payload.dropoff', 'payload.waypoints', 'payload.entities']);
|
|
1925
1969
|
$order->tracker_data = $order->tracker()->toArray();
|
|
1926
|
-
$order->eta = $order->tracker()->eta();
|
|
1927
1970
|
|
|
1928
|
-
return new
|
|
1971
|
+
return new PublicOrderTracking($order);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/**
|
|
1975
|
+
* The tracking number to look up, or null when the input cannot be one:
|
|
1976
|
+
* not a string, blank, too long, or outside the characters tracking
|
|
1977
|
+
* numbers use.
|
|
1978
|
+
*/
|
|
1979
|
+
protected function normalizeTrackingLookup($input): ?string
|
|
1980
|
+
{
|
|
1981
|
+
if (!is_string($input)) {
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
$input = trim($input);
|
|
1986
|
+
|
|
1987
|
+
return preg_match('/^[A-Za-z0-9][A-Za-z0-9._\-]{0,99}$/', $input) === 1 ? $input : null;
|
|
1929
1988
|
}
|
|
1930
1989
|
|
|
1931
1990
|
/**
|
|
@@ -1982,6 +2041,7 @@ class OrderController extends FleetOpsController
|
|
|
1982
2041
|
}
|
|
1983
2042
|
|
|
1984
2043
|
$order->saveQuietly();
|
|
2044
|
+
$order->invalidateApiCache();
|
|
1985
2045
|
|
|
1986
2046
|
return response()->json([
|
|
1987
2047
|
'status' => 'OK',
|
|
@@ -6,6 +6,10 @@ use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
|
6
6
|
use Fleetbase\FleetOps\Jobs\ReplayPositions;
|
|
7
7
|
use Fleetbase\FleetOps\Models\Position;
|
|
8
8
|
use Fleetbase\FleetOps\Support\Utils;
|
|
9
|
+
use Fleetbase\Models\User;
|
|
10
|
+
use Fleetbase\Support\SocketCluster\ChannelAuthorizer;
|
|
11
|
+
use Fleetbase\Support\SocketCluster\SocketPrincipal;
|
|
12
|
+
use Fleetbase\Support\SocketCluster\SocketToken;
|
|
9
13
|
use Illuminate\Http\Request;
|
|
10
14
|
|
|
11
15
|
class PositionController extends FleetOpsController
|
|
@@ -37,6 +41,11 @@ class PositionController extends FleetOpsController
|
|
|
37
41
|
return response()->error('Position IDs are required');
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
// With socket authentication on, the replay may only publish to a channel the user could subscribe to.
|
|
45
|
+
if (SocketToken::enabled() && !$this->canReplayTo($request, $channelId)) {
|
|
46
|
+
return response()->error('You are not allowed to replay positions to this channel.', 403);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
$positions = Position::whereIn('uuid', $positionIds)
|
|
41
50
|
->where('company_uuid', session('company'))
|
|
42
51
|
->orderBy('created_at')
|
|
@@ -57,6 +66,19 @@ class PositionController extends FleetOpsController
|
|
|
57
66
|
]);
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Whether the requesting user may subscribe to, and so receive a replay on, the channel.
|
|
71
|
+
*/
|
|
72
|
+
protected function canReplayTo(Request $request, mixed $channelId): bool
|
|
73
|
+
{
|
|
74
|
+
$user = $request->user();
|
|
75
|
+
if (!$user instanceof User || !is_string($channelId) || $channelId === '' || strlen($channelId) > 255 || preg_match('/\s/', $channelId)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return app(ChannelAuthorizer::class)->authorize(SocketPrincipal::forUser($user, session('company')), $channelId)->allow;
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
/**
|
|
61
83
|
* Get position statistics/metrics.
|
|
62
84
|
*
|
|
@@ -21,6 +21,53 @@ class ProofController extends FleetOpsController
|
|
|
21
21
|
*/
|
|
22
22
|
public $resource = 'proof';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The most a dashboard can ask for in one request.
|
|
26
|
+
*/
|
|
27
|
+
public const RECENT_LIMIT_MAX = 24;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The latest photo proofs captured for the company's orders, newest first,
|
|
31
|
+
* for the dashboard's proof-of-delivery tile.
|
|
32
|
+
*/
|
|
33
|
+
public function recent(Request $request)
|
|
34
|
+
{
|
|
35
|
+
$limit = max(1, min(static::RECENT_LIMIT_MAX, (int) $request->input('limit', 12)));
|
|
36
|
+
$proofs = Proof::where('company_uuid', session('company'))
|
|
37
|
+
->whereNotNull('order_uuid')
|
|
38
|
+
->whereHas('file', fn ($query) => $query->where('type', 'photo'))
|
|
39
|
+
->with(['file', 'order.customer'])
|
|
40
|
+
->orderByDesc('created_at')
|
|
41
|
+
->limit($limit)
|
|
42
|
+
->get();
|
|
43
|
+
|
|
44
|
+
return $this->jsonResponse([
|
|
45
|
+
'proofs' => $proofs->map(fn (Proof $proof) => $this->recentProofPayload($proof))->values()->all(),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected function recentProofPayload(Proof $proof): array
|
|
50
|
+
{
|
|
51
|
+
$order = $proof->order;
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
'id' => $proof->public_id,
|
|
55
|
+
'order_id' => $order?->public_id,
|
|
56
|
+
'customer_name' => data_get($order, 'customer.name'),
|
|
57
|
+
'remarks' => $proof->remarks,
|
|
58
|
+
'url' => $this->photoUrl($proof->file),
|
|
59
|
+
'captured_at' => $proof->created_at?->toIso8601String(),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* File::url signs the object on s3/gcs, so a private bucket still serves the photo.
|
|
65
|
+
*/
|
|
66
|
+
protected function photoUrl(File $file): ?string
|
|
67
|
+
{
|
|
68
|
+
return $file->url;
|
|
69
|
+
}
|
|
70
|
+
|
|
24
71
|
/**
|
|
25
72
|
* Verify a QR code.
|
|
26
73
|
*
|
|
@@ -83,8 +130,8 @@ class ProofController extends FleetOpsController
|
|
|
83
130
|
// set the signature storage path
|
|
84
131
|
$path = $this->signatureStoragePath($proof);
|
|
85
132
|
|
|
86
|
-
// upload signature
|
|
87
|
-
$this->storeSignature($path, base64_decode($signature)
|
|
133
|
+
// upload signature (private object; File::url serves it through a signed URL)
|
|
134
|
+
$this->storeSignature($path, base64_decode($signature));
|
|
88
135
|
|
|
89
136
|
// create file record for upload
|
|
90
137
|
$file = $this->createSignatureFile($path, $signature, $proof);
|
|
@@ -121,9 +168,13 @@ class ProofController extends FleetOpsController
|
|
|
121
168
|
return Proof::create($attributes);
|
|
122
169
|
}
|
|
123
170
|
|
|
124
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Writes without an ACL: the media bucket enforces bucket-owner object ownership, so a
|
|
173
|
+
* 'public' visibility (public-read ACL) makes S3 reject the PUT and put() return false.
|
|
174
|
+
*/
|
|
175
|
+
protected function storeSignature(string $path, string|false $contents): void
|
|
125
176
|
{
|
|
126
|
-
Storage::disk('s3')->put($path, $contents
|
|
177
|
+
Storage::disk('s3')->put($path, $contents);
|
|
127
178
|
}
|
|
128
179
|
|
|
129
180
|
/**
|