@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
|
@@ -12,11 +12,16 @@ use Fleetbase\FleetOps\Http\Resources\v1\Payload as PayloadResource;
|
|
|
12
12
|
use Fleetbase\FleetOps\Support\Utils;
|
|
13
13
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
14
14
|
use Fleetbase\Models\Model;
|
|
15
|
+
use Fleetbase\Support\ApiModelCache;
|
|
15
16
|
use Fleetbase\Traits\HasApiModelBehavior;
|
|
16
17
|
use Fleetbase\Traits\HasMetaAttributes;
|
|
17
18
|
use Fleetbase\Traits\HasPublicId;
|
|
18
19
|
use Fleetbase\Traits\HasUuid;
|
|
19
20
|
use Fleetbase\Traits\TracksApiCredential;
|
|
21
|
+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
22
|
+
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
23
|
+
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
|
24
|
+
use Illuminate\Support\Arr;
|
|
20
25
|
use Illuminate\Support\Facades\DB;
|
|
21
26
|
use Illuminate\Support\Str;
|
|
22
27
|
|
|
@@ -140,7 +145,7 @@ class Payload extends Model
|
|
|
140
145
|
/**
|
|
141
146
|
* Entities in the payload.
|
|
142
147
|
*/
|
|
143
|
-
public function entities()
|
|
148
|
+
public function entities(): HasMany
|
|
144
149
|
{
|
|
145
150
|
return $this->hasMany(Entity::class);
|
|
146
151
|
}
|
|
@@ -194,7 +199,7 @@ class Payload extends Model
|
|
|
194
199
|
/**
|
|
195
200
|
* The address the shipment will be delivered to.
|
|
196
201
|
*/
|
|
197
|
-
public function dropoff()
|
|
202
|
+
public function dropoff(): BelongsTo
|
|
198
203
|
{
|
|
199
204
|
return $this->belongsTo(Place::class, 'dropoff_uuid')->whereNull('deleted_at')->withoutGlobalScopes();
|
|
200
205
|
}
|
|
@@ -202,7 +207,7 @@ class Payload extends Model
|
|
|
202
207
|
/**
|
|
203
208
|
* The address the shipment will be delivered from.
|
|
204
209
|
*/
|
|
205
|
-
public function pickup()
|
|
210
|
+
public function pickup(): BelongsTo
|
|
206
211
|
{
|
|
207
212
|
return $this->belongsTo(Place::class, 'pickup_uuid')->whereNull('deleted_at')->withoutGlobalScopes();
|
|
208
213
|
}
|
|
@@ -210,7 +215,7 @@ class Payload extends Model
|
|
|
210
215
|
/**
|
|
211
216
|
* The address the shipment will be sent to upon failed delivery.
|
|
212
217
|
*/
|
|
213
|
-
public function return()
|
|
218
|
+
public function return(): BelongsTo
|
|
214
219
|
{
|
|
215
220
|
return $this->belongsTo(Place::class)->withoutGlobalScopes();
|
|
216
221
|
}
|
|
@@ -233,10 +238,8 @@ class Payload extends Model
|
|
|
233
238
|
|
|
234
239
|
/**
|
|
235
240
|
* Waypoints between start and end.
|
|
236
|
-
*
|
|
237
|
-
* @return \Illuminate\Database\Eloquent\Concerns\HasManyThrough
|
|
238
241
|
*/
|
|
239
|
-
public function waypoints()
|
|
242
|
+
public function waypoints(): HasManyThrough
|
|
240
243
|
{
|
|
241
244
|
return $this->hasManyThrough(Place::class, Waypoint::class, 'payload_uuid', 'uuid', 'uuid', 'place_uuid')->whereNull('waypoints.deleted_at')->withoutGlobalScopes();
|
|
242
245
|
}
|
|
@@ -249,6 +252,11 @@ class Payload extends Model
|
|
|
249
252
|
$this->attributes['cod_amount'] = Utils::numbersOnly($value);
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Add entities to the payload. Every entry creates a new entity, so sending
|
|
257
|
+
* the payload's current entities again duplicates them; use syncEntities()
|
|
258
|
+
* to replace the list.
|
|
259
|
+
*/
|
|
252
260
|
public function setEntities($entities = [])
|
|
253
261
|
{
|
|
254
262
|
if (empty($entities) || !is_array($entities)) {
|
|
@@ -256,49 +264,228 @@ class Payload extends Model
|
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
foreach ($entities as $attributes) {
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
$entity = new Entity($this->prepareEntityAttributes($attributes));
|
|
268
|
+
$this->entities()->save($entity);
|
|
269
|
+
}
|
|
261
270
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
271
|
+
return $this;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Replace the payload's entities with the given list: entries naming an
|
|
276
|
+
* existing entity of this payload (by `id` public id or `uuid`) update it,
|
|
277
|
+
* entries without one create a new entity, and entities left out are deleted.
|
|
278
|
+
*
|
|
279
|
+
* @return array{created: string[], updated: string[], deleted: string[]} entity uuids by outcome
|
|
280
|
+
*/
|
|
281
|
+
public function syncEntities($entities = []): array
|
|
282
|
+
{
|
|
283
|
+
$result = ['created' => [], 'updated' => [], 'deleted' => []];
|
|
284
|
+
if (!is_array($entities)) {
|
|
285
|
+
return $result;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
$existing = $this->entities()->get();
|
|
289
|
+
$kept = [];
|
|
290
|
+
|
|
291
|
+
foreach ($entities as $attributes) {
|
|
292
|
+
if (!is_array($attributes)) {
|
|
293
|
+
continue;
|
|
265
294
|
}
|
|
266
295
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
296
|
+
$entity = $this->findOwnEntity($existing, $attributes);
|
|
297
|
+
$values = $this->prepareEntityAttributes(Arr::except($attributes, ['id', 'uuid', 'public_id', 'payload_uuid', 'company_uuid']));
|
|
298
|
+
|
|
299
|
+
if ($entity) {
|
|
300
|
+
$entity->fill(Arr::only($values, $entity->getFillable()));
|
|
301
|
+
if ($entity->isDirty()) {
|
|
302
|
+
$entity->save();
|
|
303
|
+
$result['updated'][] = $entity->uuid;
|
|
273
304
|
}
|
|
305
|
+
$kept[] = $entity->uuid;
|
|
306
|
+
|
|
307
|
+
continue;
|
|
274
308
|
}
|
|
275
309
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
310
|
+
$entity = new Entity($values);
|
|
311
|
+
$this->entities()->save($entity);
|
|
312
|
+
$result['created'][] = $entity->uuid;
|
|
313
|
+
$kept[] = $entity->uuid;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
foreach ($existing as $entity) {
|
|
317
|
+
if (!in_array($entity->uuid, $kept, true)) {
|
|
318
|
+
$entity->delete();
|
|
319
|
+
$result['deleted'][] = $entity->uuid;
|
|
282
320
|
}
|
|
321
|
+
}
|
|
283
322
|
|
|
284
|
-
|
|
285
|
-
if (isset($attributes['photo'])) {
|
|
286
|
-
$path = 'uploads/' . session('company') . '/entities';
|
|
287
|
-
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($attributes['photo'], $path);
|
|
323
|
+
$this->unsetRelation('entities');
|
|
288
324
|
|
|
289
|
-
|
|
290
|
-
|
|
325
|
+
return $result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Whether every entry names an entity this payload already has, i.e. applying the
|
|
330
|
+
* list with syncEntities() would only update or remove entities, never add one.
|
|
331
|
+
*/
|
|
332
|
+
public function entitiesListOnlyNamesExisting(array $entities): bool
|
|
333
|
+
{
|
|
334
|
+
$existing = $this->entities()->get();
|
|
335
|
+
|
|
336
|
+
foreach ($entities as $attributes) {
|
|
337
|
+
if (!is_array($attributes) || !$this->findOwnEntity($existing, $attributes)) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Find the payload entity an entity entry refers to by `id` (public id) or `uuid`.
|
|
347
|
+
*/
|
|
348
|
+
protected function findOwnEntity($existing, array $attributes): ?Entity
|
|
349
|
+
{
|
|
350
|
+
$key = $attributes['id'] ?? $attributes['public_id'] ?? $attributes['uuid'] ?? null;
|
|
351
|
+
if (!is_string($key) || $key === '') {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return $existing->first(fn (Entity $entity) => $entity->public_id === $key || $entity->uuid === $key);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Resolve an entity entry's destination and photo into storable attributes.
|
|
360
|
+
*/
|
|
361
|
+
protected function prepareEntityAttributes(array $attributes): array
|
|
362
|
+
{
|
|
363
|
+
if (isset($attributes['_import_id'])) {
|
|
364
|
+
$waypoint = $this->waypoints->firstWhere('_import_id', $attributes['_import_id']);
|
|
365
|
+
|
|
366
|
+
if ($waypoint) {
|
|
367
|
+
$attributes['destination_uuid'] = $waypoint->uuid;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// if a destination or waypoint is explicitly set
|
|
372
|
+
if (empty($attributes['destination_uuid'])) {
|
|
373
|
+
$destinationKey = Utils::or($attributes, ['waypoint', 'destination']);
|
|
374
|
+
$destination = $this->findDestinationFromKey($destinationKey);
|
|
375
|
+
if ($destination instanceof Place) {
|
|
376
|
+
$attributes['destination_uuid'] = $destination->uuid;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Validate destination actually exists
|
|
381
|
+
if (isset($attributes['destination_uuid']) && Place::where('uuid', $attributes['destination_uuid'])->doesntExist()) {
|
|
382
|
+
$validDestination = $this->_findCorrectDestinationForEntity($attributes);
|
|
383
|
+
if ($validDestination) {
|
|
384
|
+
$attributes['destination_uuid'] = $validDestination->uuid;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Handle entity photo upload
|
|
389
|
+
if (isset($attributes['photo'])) {
|
|
390
|
+
$path = 'uploads/' . session('company') . '/entities';
|
|
391
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($attributes['photo'], $path);
|
|
392
|
+
|
|
393
|
+
if ($file) {
|
|
394
|
+
$attributes['photo_uuid'] = $file->uuid;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Clean up raw photo data
|
|
398
|
+
unset($attributes['photo']);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return $attributes;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* The payload's stops as stored: pickup, dropoff and return place uuids, the
|
|
406
|
+
* waypoint place uuids in order and the current waypoint. Compare two
|
|
407
|
+
* signatures with routeChanges() to see what an edit changed.
|
|
408
|
+
*/
|
|
409
|
+
public function routeSignature(): array
|
|
410
|
+
{
|
|
411
|
+
return [
|
|
412
|
+
'payload' => $this->uuid,
|
|
413
|
+
'pickup' => $this->pickup_uuid,
|
|
414
|
+
'dropoff' => $this->dropoff_uuid,
|
|
415
|
+
'return' => $this->return_uuid,
|
|
416
|
+
'waypoints' => $this->uuid ? Waypoint::where('payload_uuid', $this->uuid)->whereNull('deleted_at')->orderBy('order')->pluck('place_uuid')->values()->all() : [],
|
|
417
|
+
'current' => $this->current_waypoint_uuid,
|
|
418
|
+
];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The route parts (pickup, dropoff, return, waypoints) that differ between two route signatures.
|
|
423
|
+
*/
|
|
424
|
+
public static function routeChanges(array $before, array $after): array
|
|
425
|
+
{
|
|
426
|
+
$changes = [];
|
|
427
|
+
foreach (['pickup', 'dropoff', 'return', 'waypoints'] as $part) {
|
|
428
|
+
if (($before[$part] ?? null) !== ($after[$part] ?? null)) {
|
|
429
|
+
$changes[] = $part;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return $changes;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Point the payload at the right stop after its route changed.
|
|
438
|
+
*
|
|
439
|
+
* Before the order is under way this is the first stop, as it always was. Once
|
|
440
|
+
* the driver is under way the stop they are heading to is kept when it is still
|
|
441
|
+
* on the route; when it was replaced the stop that took its place is used (the
|
|
442
|
+
* new pickup, dropoff or return), and a removed waypoint moves them on to the
|
|
443
|
+
* next stop they have not completed.
|
|
444
|
+
*
|
|
445
|
+
* @param array $before the routeSignature() taken before the edit
|
|
446
|
+
*/
|
|
447
|
+
public function reconcileCurrentWaypoint(array $before, bool $inProgress): ?Place
|
|
448
|
+
{
|
|
449
|
+
$this->unsetRelation('waypoints');
|
|
450
|
+
$this->unsetRelation('waypointMarkers');
|
|
451
|
+
$this->loadMissing(['pickup', 'dropoff', 'return']);
|
|
452
|
+
|
|
453
|
+
$destination = null;
|
|
454
|
+
|
|
455
|
+
if ($inProgress) {
|
|
456
|
+
$after = $this->routeSignature();
|
|
457
|
+
$current = $before['current'] ?? null;
|
|
458
|
+
$stops = array_filter(array_merge([$after['pickup'], $after['dropoff'], $after['return']], $after['waypoints']));
|
|
459
|
+
|
|
460
|
+
if ($current && in_array($current, $stops, true)) {
|
|
461
|
+
$destination = Place::where('uuid', $current)->first();
|
|
462
|
+
} elseif ($current) {
|
|
463
|
+
foreach (['pickup', 'dropoff', 'return'] as $role) {
|
|
464
|
+
if ($current === ($before[$role] ?? null) && $this->{$role} instanceof Place) {
|
|
465
|
+
$destination = $this->{$role};
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
291
468
|
}
|
|
292
469
|
|
|
293
|
-
|
|
294
|
-
|
|
470
|
+
if (!$destination) {
|
|
471
|
+
$next = $this->waypointMarkers()->orderBy('order')->get()->first(function (Waypoint $waypoint) {
|
|
472
|
+
return !$waypoint->complete;
|
|
473
|
+
});
|
|
474
|
+
$destination = $next ? $next->getPlace() : ($this->dropoff instanceof Place ? $this->dropoff : null);
|
|
475
|
+
}
|
|
295
476
|
}
|
|
477
|
+
}
|
|
296
478
|
|
|
297
|
-
|
|
298
|
-
$this->
|
|
479
|
+
if (!$destination) {
|
|
480
|
+
$destination = $this->getPickupOrFirstWaypoint() ?? $this->getDropoffOrLastWaypoint();
|
|
299
481
|
}
|
|
300
482
|
|
|
301
|
-
|
|
483
|
+
if ($destination instanceof Place) {
|
|
484
|
+
$this->setCurrentWaypoint($destination);
|
|
485
|
+
$this->setRelation('currentWaypoint', $destination);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return $destination instanceof Place ? $destination : null;
|
|
302
489
|
}
|
|
303
490
|
|
|
304
491
|
public function insertEntities($entities = [])
|
|
@@ -365,6 +552,7 @@ class Payload extends Model
|
|
|
365
552
|
$this->current_waypoint_uuid = $destination->uuid;
|
|
366
553
|
if ($save) {
|
|
367
554
|
DB::table($this->getTable())->where('uuid', $this->uuid)->update(['current_waypoint_uuid' => $destination->uuid]);
|
|
555
|
+
ApiModelCache::invalidateModelCache($this, $this->company_uuid);
|
|
368
556
|
}
|
|
369
557
|
|
|
370
558
|
return $this;
|
|
@@ -868,6 +1056,7 @@ class Payload extends Model
|
|
|
868
1056
|
public function removeWaypoints()
|
|
869
1057
|
{
|
|
870
1058
|
Waypoint::where('payload_uuid', $this->uuid)->delete();
|
|
1059
|
+
Waypoint::invalidateApiCacheManually($this->company_uuid);
|
|
871
1060
|
$this->setRelation('waypoints', collect());
|
|
872
1061
|
|
|
873
1062
|
return $this;
|
|
@@ -895,6 +1084,7 @@ class Payload extends Model
|
|
|
895
1084
|
|
|
896
1085
|
if ($save) {
|
|
897
1086
|
$this->updateQuietly([$attr => null]);
|
|
1087
|
+
ApiModelCache::invalidateModelCache($this, $this->company_uuid);
|
|
898
1088
|
}
|
|
899
1089
|
|
|
900
1090
|
if (is_callable($callback)) {
|
|
@@ -928,6 +1118,7 @@ class Payload extends Model
|
|
|
928
1118
|
// If optioned to save
|
|
929
1119
|
if ($save) {
|
|
930
1120
|
$this->updateQuietly([$attr => $id]);
|
|
1121
|
+
ApiModelCache::invalidateModelCache($this, $this->company_uuid);
|
|
931
1122
|
}
|
|
932
1123
|
|
|
933
1124
|
if (is_callable($callback)) {
|
|
@@ -1000,6 +1191,7 @@ class Payload extends Model
|
|
|
1000
1191
|
|
|
1001
1192
|
$this->current_waypoint_uuid = $destination->uuid;
|
|
1002
1193
|
$this->saveQuietly();
|
|
1194
|
+
ApiModelCache::invalidateModelCache($this, $this->company_uuid);
|
|
1003
1195
|
$this->updateWaypointActivity($activity, $location);
|
|
1004
1196
|
|
|
1005
1197
|
return $this->load('currentWaypoint');
|
|
@@ -187,7 +187,8 @@ class Place extends Model
|
|
|
187
187
|
return static::getAvatar($value);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
// legacy rows hold an absolute URL; re-sign it if it points into the private media bucket
|
|
191
|
+
return Utils::signStoredFileUrl($value);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
/**
|
|
@@ -872,6 +873,11 @@ class Place extends Model
|
|
|
872
873
|
|
|
873
874
|
$result = static::insert($values);
|
|
874
875
|
|
|
876
|
+
// Inserted without model events
|
|
877
|
+
if ($result) {
|
|
878
|
+
static::invalidateApiCacheManually($values['company_uuid']);
|
|
879
|
+
}
|
|
880
|
+
|
|
875
881
|
return $result ? $uuid : false;
|
|
876
882
|
}
|
|
877
883
|
|
|
@@ -91,6 +91,13 @@ class ServiceArea extends Model
|
|
|
91
91
|
*/
|
|
92
92
|
protected $with = ['zones'];
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Relation paths the cached service area lists embed through `$with` (fleetbase/core-api#301).
|
|
96
|
+
*
|
|
97
|
+
* @var string[]
|
|
98
|
+
*/
|
|
99
|
+
protected $apiCacheDependencies = ['zones'];
|
|
100
|
+
|
|
94
101
|
/**
|
|
95
102
|
* Dynamic attributes that are appended to object.
|
|
96
103
|
*
|
|
@@ -12,6 +12,8 @@ use Fleetbase\Traits\HasPublicId;
|
|
|
12
12
|
use Fleetbase\Traits\HasUuid;
|
|
13
13
|
use Fleetbase\Traits\SendsWebhooks;
|
|
14
14
|
use Fleetbase\Traits\TracksApiCredential;
|
|
15
|
+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
16
|
+
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
15
17
|
|
|
16
18
|
class ServiceRate extends Model
|
|
17
19
|
{
|
|
@@ -110,42 +112,30 @@ class ServiceRate extends Model
|
|
|
110
112
|
*/
|
|
111
113
|
protected $filterParams = [];
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
115
|
-
*/
|
|
116
|
-
public function rateFees()
|
|
115
|
+
public function rateFees(): HasMany
|
|
117
116
|
{
|
|
118
117
|
return $this->hasMany(ServiceRateFee::class);
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
123
|
-
*/
|
|
124
|
-
public function parcelFees()
|
|
120
|
+
public function parcelFees(): HasMany
|
|
125
121
|
{
|
|
126
122
|
return $this->hasMany(ServiceRateParcelFee::class);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
/**
|
|
130
|
-
* @return
|
|
126
|
+
* @return BelongsTo
|
|
131
127
|
*/
|
|
132
128
|
public function orderConfig()
|
|
133
129
|
{
|
|
134
130
|
return $this->belongsTo(OrderConfig::class)->withTrashed();
|
|
135
131
|
}
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
139
|
-
*/
|
|
140
|
-
public function serviceArea()
|
|
133
|
+
public function serviceArea(): BelongsTo
|
|
141
134
|
{
|
|
142
135
|
return $this->belongsTo(ServiceArea::class)->whereNull('deleted_at');
|
|
143
136
|
}
|
|
144
137
|
|
|
145
|
-
|
|
146
|
-
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
147
|
-
*/
|
|
148
|
-
public function zone()
|
|
138
|
+
public function zone(): BelongsTo
|
|
149
139
|
{
|
|
150
140
|
return $this->belongsTo(Zone::class);
|
|
151
141
|
}
|
|
@@ -4,9 +4,11 @@ namespace Fleetbase\FleetOps\Models;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\PolymorphicType;
|
|
6
6
|
use Fleetbase\FleetOps\Casts\Point;
|
|
7
|
+
use Fleetbase\FleetOps\Support\TrackingCode;
|
|
7
8
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
9
|
use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
|
|
9
10
|
use Fleetbase\Models\Model;
|
|
11
|
+
use Fleetbase\Support\ApiModelCache;
|
|
10
12
|
use Fleetbase\Traits\HasApiModelBehavior;
|
|
11
13
|
use Fleetbase\Traits\HasPublicId;
|
|
12
14
|
use Fleetbase\Traits\HasUuid;
|
|
@@ -16,6 +18,7 @@ use Illuminate\Support\Carbon;
|
|
|
16
18
|
use Illuminate\Support\Facades\DB;
|
|
17
19
|
use Illuminate\Support\Facades\Schema;
|
|
18
20
|
use Illuminate\Support\Str;
|
|
21
|
+
use Milon\Barcode\Facades\DNS1DFacade as DNS1D;
|
|
19
22
|
use Milon\Barcode\Facades\DNS2DFacade as DNS2D;
|
|
20
23
|
|
|
21
24
|
class TrackingNumber extends Model
|
|
@@ -236,6 +239,74 @@ class TrackingNumber extends Model
|
|
|
236
239
|
throw (new \Illuminate\Database\Eloquent\ModelNotFoundException())->setModel(static::class, $id);
|
|
237
240
|
}
|
|
238
241
|
|
|
242
|
+
/**
|
|
243
|
+
* The record a scanned code points at, within one company: the order, waypoint,
|
|
244
|
+
* entity or place that owns the tracking number.
|
|
245
|
+
*
|
|
246
|
+
* Accepts every format TrackingCode::parse() reads, including the bare owner uuid
|
|
247
|
+
* printed on labels before codes carried a url, and a bare tracking number or public
|
|
248
|
+
* id from a barcode or typed by hand. Without a company nothing resolves.
|
|
249
|
+
*/
|
|
250
|
+
public static function findOwnerByCode(?string $code, ?string $companyUuid): ?\Illuminate\Database\Eloquent\Model
|
|
251
|
+
{
|
|
252
|
+
if (empty($companyUuid)) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
$parsed = TrackingCode::parse($code);
|
|
257
|
+
|
|
258
|
+
if ($parsed['uuid']) {
|
|
259
|
+
return static::ownerOf(static::firstForCode('owner_uuid', $parsed['uuid'], $companyUuid));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
$number = $parsed['tracking_number'] ?? $parsed['reference'];
|
|
263
|
+
if ($number) {
|
|
264
|
+
$owner = static::ownerOf(static::firstForCode('tracking_number', $number, $companyUuid));
|
|
265
|
+
|
|
266
|
+
// A url names its tracking number deliberately, so an unknown one is a miss,
|
|
267
|
+
// not a cue to fall back to the owner it also names, which must agree.
|
|
268
|
+
if ($owner || $parsed['tracking_number']) {
|
|
269
|
+
return $owner && (!$parsed['public_id'] || data_get($owner, 'public_id') === $parsed['public_id']) ? $owner : null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
$publicId = $parsed['public_id'] ?? $parsed['reference'];
|
|
274
|
+
|
|
275
|
+
return $publicId ? static::ownerOf(static::firstForOwnerPublicId($publicId, $companyUuid)) : null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected static function firstForCode(string $column, string $value, string $companyUuid): ?self
|
|
279
|
+
{
|
|
280
|
+
return static::where('company_uuid', $companyUuid)->where($column, $value)->first();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
protected static function firstForOwnerPublicId(string $publicId, string $companyUuid): ?self
|
|
284
|
+
{
|
|
285
|
+
// The public id's prefix names the owner's model, so only that table is searched.
|
|
286
|
+
$prefix = Str::contains($publicId, '_') ? Str::before($publicId, '_') : null;
|
|
287
|
+
$ownerClass = [
|
|
288
|
+
'order' => Order::class,
|
|
289
|
+
'waypoint' => Waypoint::class,
|
|
290
|
+
'entity' => Entity::class,
|
|
291
|
+
'place' => Place::class,
|
|
292
|
+
][$prefix] ?? null;
|
|
293
|
+
|
|
294
|
+
if (!$ownerClass) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return static::where('company_uuid', $companyUuid)
|
|
299
|
+
// Rows inserted raw (waypoints, entities) store the class with a leading slash.
|
|
300
|
+
->whereIn('owner_type', [$ownerClass, '\\' . $ownerClass])
|
|
301
|
+
->whereIn('owner_uuid', $ownerClass::withoutGlobalScopes()->select('uuid')->where('public_id', $publicId))
|
|
302
|
+
->first();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
protected static function ownerOf(?self $trackingNumber): ?\Illuminate\Database\Eloquent\Model
|
|
306
|
+
{
|
|
307
|
+
return $trackingNumber?->owner;
|
|
308
|
+
}
|
|
309
|
+
|
|
239
310
|
public function updateOwnerStatus(?TrackingStatus $trackingStatus = null)
|
|
240
311
|
{
|
|
241
312
|
$trackingStatus = $trackingStatus ?? $this->load(['status'])->getRelationValue('status');
|
|
@@ -254,6 +325,10 @@ class TrackingNumber extends Model
|
|
|
254
325
|
|
|
255
326
|
public static function insertGetUuid($values = [], ?Model $owner = null)
|
|
256
327
|
{
|
|
328
|
+
// Read before the fillable filter below drops it. Callers that insert the owner
|
|
329
|
+
// with a raw query, and so have no model to pass, hand its public_id over here.
|
|
330
|
+
$ownerPublicId = $owner ? data_get($owner, 'public_id') : ($values['owner_public_id'] ?? null);
|
|
331
|
+
|
|
257
332
|
$instance = new static();
|
|
258
333
|
$fillable = $instance->getFillable();
|
|
259
334
|
$insertKeys = array_keys($values);
|
|
@@ -276,8 +351,7 @@ class TrackingNumber extends Model
|
|
|
276
351
|
}
|
|
277
352
|
|
|
278
353
|
$values['tracking_number'] = static::newTrackingNumber($values['region'] ?? 'SG');
|
|
279
|
-
$values
|
|
280
|
-
$values['barcode'] = static::newBarcode((string) ($values['owner_uuid'] ?? ''), 'PDF417');
|
|
354
|
+
$values = array_merge($values, static::codeImages($values['tracking_number'], $ownerPublicId));
|
|
281
355
|
|
|
282
356
|
if (isset($values['meta']) && (is_object($values['meta']) || is_array($values['meta']))) {
|
|
283
357
|
$values['meta'] = json_encode($values['meta']);
|
|
@@ -314,8 +388,12 @@ class TrackingNumber extends Model
|
|
|
314
388
|
|
|
315
389
|
// silent update
|
|
316
390
|
static::updateOwnerStatusColumn($owner, $initialCode ?? 'created');
|
|
391
|
+
ApiModelCache::invalidateModelCache($owner, data_get($owner, 'company_uuid', $values['company_uuid']));
|
|
317
392
|
}
|
|
318
393
|
|
|
394
|
+
// Inserted and updated without model events
|
|
395
|
+
ApiModelCache::invalidateModelCache($instance, $values['company_uuid']);
|
|
396
|
+
|
|
319
397
|
return $uuid;
|
|
320
398
|
}
|
|
321
399
|
|
|
@@ -339,8 +417,29 @@ class TrackingNumber extends Model
|
|
|
339
417
|
return static::generateNumber($region);
|
|
340
418
|
}
|
|
341
419
|
|
|
420
|
+
/**
|
|
421
|
+
* The base64 PNGs printed on a label: a QR code carrying the tracking url (see
|
|
422
|
+
* TrackingCode) and a Code 128 barcode carrying the bare tracking number.
|
|
423
|
+
*
|
|
424
|
+
* @return array{qr_code: string, barcode: string}
|
|
425
|
+
*/
|
|
426
|
+
public static function codeImages(string $trackingNumber, ?string $ownerPublicId = null): array
|
|
427
|
+
{
|
|
428
|
+
return [
|
|
429
|
+
// Medium error correction: labels get scuffed, and the url fits comfortably.
|
|
430
|
+
'qr_code' => static::newBarcode(TrackingCode::qrContent($trackingNumber, $ownerPublicId), 'QRCODE,M'),
|
|
431
|
+
'barcode' => static::newBarcode(TrackingCode::barcodeContent($trackingNumber), 'C128'),
|
|
432
|
+
];
|
|
433
|
+
}
|
|
434
|
+
|
|
342
435
|
protected static function newBarcode(string $value, string $type): string
|
|
343
436
|
{
|
|
437
|
+
if ($type === 'C128') {
|
|
438
|
+
// 2px modules, 60px tall: a typical tracking number renders ~330px wide, inside
|
|
439
|
+
// the label's 360px cap, so it prints at its own resolution.
|
|
440
|
+
return DNS1D::getBarcodePNG($value, $type, 2, 60);
|
|
441
|
+
}
|
|
442
|
+
|
|
344
443
|
return DNS2D::getBarcodePNG($value, $type);
|
|
345
444
|
}
|
|
346
445
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Models;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Models\Model;
|
|
6
|
+
use Fleetbase\Traits\HasUuid;
|
|
7
|
+
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A device that has verified on the public tracking page. The cookie's value is never
|
|
11
|
+
* stored, only its SHA-256 in `token_hash`.
|
|
12
|
+
*/
|
|
13
|
+
class TrackingSession extends Model
|
|
14
|
+
{
|
|
15
|
+
use HasUuid;
|
|
16
|
+
|
|
17
|
+
protected $table = 'tracking_sessions';
|
|
18
|
+
|
|
19
|
+
protected $fillable = ['token_hash', 'ip', 'user_agent', 'last_seen_at', 'expires_at', 'revoked_at'];
|
|
20
|
+
|
|
21
|
+
protected $casts = [
|
|
22
|
+
'last_seen_at' => 'datetime',
|
|
23
|
+
'expires_at' => 'datetime',
|
|
24
|
+
'revoked_at' => 'datetime',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
protected $hidden = ['token_hash'];
|
|
28
|
+
|
|
29
|
+
public function grants(): HasMany
|
|
30
|
+
{
|
|
31
|
+
return $this->hasMany(TrackingSessionGrant::class, 'tracking_session_uuid', 'uuid');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Models;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Models\Model;
|
|
6
|
+
use Fleetbase\Traits\HasUuid;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What a verified device may see: one customer within one order, until `expires_at`.
|
|
10
|
+
*/
|
|
11
|
+
class TrackingSessionGrant extends Model
|
|
12
|
+
{
|
|
13
|
+
use HasUuid;
|
|
14
|
+
|
|
15
|
+
protected $table = 'tracking_session_grants';
|
|
16
|
+
|
|
17
|
+
protected $fillable = ['tracking_session_uuid', 'company_uuid', 'order_uuid', 'customer_type', 'customer_uuid', 'channel', 'expires_at'];
|
|
18
|
+
|
|
19
|
+
protected $casts = [
|
|
20
|
+
'expires_at' => 'datetime',
|
|
21
|
+
];
|
|
22
|
+
}
|