@fleetbase/fleetops-engine 0.6.48 → 0.6.50
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/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/form.hbs +2 -2
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +2 -2
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/details.hbs +5 -1
- package/addon/components/driver/form.hbs +11 -1
- package/addon/components/fleet-listing-panel.hbs +2 -2
- package/addon/components/fleet-listing-panel.js +10 -0
- package/addon/components/map/google-live-map.js +18 -75
- package/addon/components/map/leaflet-live-map.hbs +187 -37
- package/addon/components/map/leaflet-live-map.js +184 -20
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +45 -3
- package/addon/components/modals/update-order-activity.js +143 -5
- package/addon/components/orchestrator/phase-builder.hbs +21 -0
- package/addon/components/orchestrator/phase-builder.js +13 -0
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/orchestrator-workbench.js +1 -0
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +11 -0
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order/form.hbs +2 -2
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -17
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +41 -0
- package/addon/components/service-rate/form.hbs +118 -0
- package/addon/components/service-rate/form.js +55 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
- package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
- package/addon/components/widget/fuel-efficiency.hbs +26 -0
- package/addon/components/widget/fuel-efficiency.js +43 -0
- package/addon/components/widget/geofence-violations.hbs +44 -0
- package/addon/components/widget/geofence-violations.js +55 -0
- package/addon/components/widget/issues-insights.hbs +43 -0
- package/addon/components/widget/issues-insights.js +62 -0
- package/addon/components/widget/kpi-active-orders.hbs +10 -0
- package/addon/components/widget/kpi-active-orders.js +5 -0
- package/addon/components/widget/kpi-aov.hbs +10 -0
- package/addon/components/widget/kpi-aov.js +5 -0
- package/addon/components/widget/kpi-distance.hbs +10 -0
- package/addon/components/widget/kpi-distance.js +5 -0
- package/addon/components/widget/kpi-drivers-online.hbs +10 -0
- package/addon/components/widget/kpi-drivers-online.js +5 -0
- package/addon/components/widget/kpi-earnings.hbs +10 -0
- package/addon/components/widget/kpi-earnings.js +5 -0
- package/addon/components/widget/kpi-open-issues.hbs +11 -0
- package/addon/components/widget/kpi-open-issues.js +5 -0
- package/addon/components/widget/kpi-tile.hbs +59 -0
- package/addon/components/widget/kpi-tile.js +168 -0
- package/addon/components/widget/live-fleet.hbs +122 -0
- package/addon/components/widget/live-fleet.js +365 -0
- package/addon/components/widget/maintenance-overview.hbs +45 -0
- package/addon/components/widget/maintenance-overview.js +40 -0
- package/addon/components/widget/on-time-delivery.hbs +27 -0
- package/addon/components/widget/on-time-delivery.js +73 -0
- package/addon/components/widget/operations-pulse.hbs +51 -0
- package/addon/components/widget/operations-pulse.js +173 -0
- package/addon/components/widget/orders-by-status.hbs +26 -0
- package/addon/components/widget/orders-by-status.js +54 -0
- package/addon/components/widget/revenue-trend.hbs +34 -0
- package/addon/components/widget/revenue-trend.js +67 -0
- package/addon/components/widget/top-drivers.hbs +44 -0
- package/addon/components/widget/top-drivers.js +54 -0
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/operations/orders/index/details.js +4 -4
- package/addon/controllers/operations/orders/index.js +14 -0
- package/addon/controllers/settings/routing.js +26 -0
- package/addon/extension.js +172 -3
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/map-adapter/google.js +13 -2
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +205 -88
- package/addon/templates/maintenance/equipment/index.hbs +1 -1
- package/addon/templates/maintenance/parts/index.hbs +1 -1
- package/addon/templates/maintenance/schedules/index.hbs +1 -1
- package/addon/templates/management/vehicles/index.hbs +1 -1
- package/addon/templates/settings/avatars.hbs +1 -1
- package/addon/templates/settings/custom-fields.hbs +1 -1
- package/addon/templates/settings/map.hbs +1 -1
- package/addon/templates/settings/navigator-app.hbs +1 -1
- package/addon/templates/settings/notifications.hbs +1 -1
- package/addon/templates/settings/orchestrator.hbs +1 -1
- package/addon/templates/settings/payments/index.hbs +1 -1
- package/addon/templates/settings/payments/onboard.hbs +1 -1
- package/addon/templates/settings/routing.hbs +22 -2
- package/addon/templates/settings/scheduling.hbs +1 -1
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/live-map-card-content.js +171 -0
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/widget/fuel-efficiency.js +1 -0
- package/app/components/widget/geofence-violations.js +1 -0
- package/app/components/widget/issues-insights.js +1 -0
- package/app/components/widget/kpi-active-orders.js +1 -0
- package/app/components/widget/kpi-aov.js +1 -0
- package/app/components/widget/kpi-distance.js +1 -0
- package/app/components/widget/kpi-drivers-online.js +1 -0
- package/app/components/widget/kpi-earnings.js +1 -0
- package/app/components/widget/kpi-open-issues.js +1 -0
- package/app/components/widget/kpi-tile.js +1 -0
- package/app/components/widget/live-fleet.js +1 -0
- package/app/components/widget/maintenance-overview.js +1 -0
- package/app/components/widget/on-time-delivery.js +1 -0
- package/app/components/widget/operations-pulse.js +1 -0
- package/app/components/widget/orders-by-status.js +1 -0
- package/app/components/widget/revenue-trend.js +1 -0
- package/app/components/widget/top-drivers.js +1 -0
- package/app/utils/live-map-card-content.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +1 -0
- package/server/config/telematics.php +111 -1
- package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
- package/server/seeders/Testing/ConnectivitySeeder.php +213 -0
- package/server/seeders/Testing/FleetSeeder.php +341 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
- package/server/seeders/Testing/NetworkSeeder.php +250 -0
- package/server/seeders/Testing/OrdersSeeder.php +198 -0
- package/server/seeders/Testing/TestingSeeder.php +40 -0
- package/server/seeders/{VroomOrchestrationSeeder.php → Testing/VroomOrchestrationSeeder.php} +29 -22
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +441 -32
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +46 -1
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/Driver.php +2 -0
- package/server/src/Http/Resources/v1/Fleet.php +4 -0
- package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
- package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Models/Contact.php +92 -15
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +14 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Order.php +29 -4
- package/server/src/Models/OrderConfig.php +14 -11
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Notifications/LateDeparture.php +48 -0
- package/server/src/Notifications/ProlongedStoppage.php +48 -0
- package/server/src/Notifications/RouteDeviation.php +48 -0
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +29 -2
- package/server/src/Providers/FleetOpsServiceProvider.php +6 -0
- package/server/src/Providers/NotificationServiceProvider.php +3 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
- package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
- package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
- package/server/src/Support/Analytics/IssuesInsights.php +67 -0
- package/server/src/Support/Analytics/LiveFleet.php +127 -0
- package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
- package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
- package/server/src/Support/Analytics/OperationsPulse.php +86 -0
- package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
- package/server/src/Support/Analytics/RevenueTrend.php +78 -0
- package/server/src/Support/Analytics/TopDrivers.php +92 -0
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +170 -0
- package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
- package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
- package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
- package/server/src/Support/Metrics/EarningsMetric.php +30 -0
- package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
- package/server/src/Support/Metrics/MoneyMetric.php +30 -0
- package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
- package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
- package/server/src/Support/Metrics/Registry.php +46 -0
- package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
- package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
- package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
- package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
- package/server/src/Support/Metrics.php +46 -249
- package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +50 -0
- package/server/tests/AnalyticsRoutesTest.php +41 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +84 -0
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/server/tests/VroomOrchestrationEngineTest.php +91 -0
- package/translations/en-us.yaml +38 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -216,6 +216,14 @@ class ServiceRate extends Model
|
|
|
216
216
|
return $this->rate_calculation_method === 'per_meter';
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Check if the rate calculation method is "multi_zone_distance".
|
|
221
|
+
*/
|
|
222
|
+
public function isMultiZoneDistance(): bool
|
|
223
|
+
{
|
|
224
|
+
return $this->rate_calculation_method === 'multi_zone_distance';
|
|
225
|
+
}
|
|
226
|
+
|
|
219
227
|
/**
|
|
220
228
|
* Check if the rate calculation method is "per_drop".
|
|
221
229
|
*/
|
|
@@ -329,6 +337,16 @@ class ServiceRate extends Model
|
|
|
329
337
|
return $this;
|
|
330
338
|
}
|
|
331
339
|
|
|
340
|
+
$serviceRateFees = collect($serviceRateFees)
|
|
341
|
+
->map(fn ($fee) => $this->normalizeServiceRateFeePayload($fee))
|
|
342
|
+
->filter()
|
|
343
|
+
->values()
|
|
344
|
+
->toArray();
|
|
345
|
+
|
|
346
|
+
if (!$serviceRateFees) {
|
|
347
|
+
return $this;
|
|
348
|
+
}
|
|
349
|
+
|
|
332
350
|
$iterate = count($serviceRateFees);
|
|
333
351
|
|
|
334
352
|
for ($i = 0; $i < $iterate; $i++) {
|
|
@@ -356,6 +374,43 @@ class ServiceRate extends Model
|
|
|
356
374
|
return $this;
|
|
357
375
|
}
|
|
358
376
|
|
|
377
|
+
/**
|
|
378
|
+
* Normalize embedded service-rate fee payloads before persistence.
|
|
379
|
+
*/
|
|
380
|
+
public function normalizeServiceRateFeePayload($fee): ?array
|
|
381
|
+
{
|
|
382
|
+
if (!is_array($fee)) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
$fee['service_area_uuid'] ??= data_get($fee, 'service_area.uuid');
|
|
387
|
+
$fee['zone_uuid'] ??= data_get($fee, 'zone.uuid');
|
|
388
|
+
|
|
389
|
+
if (Utils::castBoolean($fee['is_fallback'] ?? false)) {
|
|
390
|
+
$fee['service_area_uuid'] = null;
|
|
391
|
+
$fee['zone_uuid'] = null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return collect($fee)
|
|
395
|
+
->only([
|
|
396
|
+
'uuid',
|
|
397
|
+
'service_rate_uuid',
|
|
398
|
+
'service_area_uuid',
|
|
399
|
+
'zone_uuid',
|
|
400
|
+
'label',
|
|
401
|
+
'priority',
|
|
402
|
+
'is_fallback',
|
|
403
|
+
'distance',
|
|
404
|
+
'distance_unit',
|
|
405
|
+
'min',
|
|
406
|
+
'max',
|
|
407
|
+
'unit',
|
|
408
|
+
'fee',
|
|
409
|
+
'currency',
|
|
410
|
+
])
|
|
411
|
+
->toArray();
|
|
412
|
+
}
|
|
413
|
+
|
|
359
414
|
/**
|
|
360
415
|
* Set the service rate parcel fees for the current object.
|
|
361
416
|
*
|
|
@@ -506,7 +561,7 @@ class ServiceRate extends Model
|
|
|
506
561
|
public static function getServicableForPlaces($places = [], $service = null, $currency = null, ?\Closure $queryCallback = null): array
|
|
507
562
|
{
|
|
508
563
|
$reader = new GeoJSONReader();
|
|
509
|
-
$serviceRatesQuery = static::with(['zone', 'serviceArea', 'rateFees', 'parcelFees']);
|
|
564
|
+
$serviceRatesQuery = static::with(['zone', 'serviceArea', 'rateFees.zone', 'rateFees.serviceArea', 'parcelFees']);
|
|
510
565
|
|
|
511
566
|
if ($currency) {
|
|
512
567
|
$serviceRatesQuery->whereRaw('lower(currency) = ?', [strtolower($currency)]);
|
|
@@ -738,6 +793,12 @@ class ServiceRate extends Model
|
|
|
738
793
|
]);
|
|
739
794
|
}
|
|
740
795
|
|
|
796
|
+
if ($this->isMultiZoneDistance()) {
|
|
797
|
+
[$multiZoneFee, $multiZoneLines] = $this->quoteMultiZoneDistance($waypoints, $totalDistance);
|
|
798
|
+
$subTotal += $multiZoneFee;
|
|
799
|
+
$lines = $lines->merge($multiZoneLines);
|
|
800
|
+
}
|
|
801
|
+
|
|
741
802
|
if ($this->isAlgorithm()) {
|
|
742
803
|
$resolvedEndpointCount = $endpointCount ?? $this->inferEndpointCountFromStops($waypoints);
|
|
743
804
|
$rateFee = $this->normalizeCalculatedMoney(Algo::exec(
|
|
@@ -927,6 +988,12 @@ class ServiceRate extends Model
|
|
|
927
988
|
]);
|
|
928
989
|
}
|
|
929
990
|
|
|
991
|
+
if ($this->isMultiZoneDistance()) {
|
|
992
|
+
[$multiZoneFee, $multiZoneLines] = $this->quoteMultiZoneDistance($waypoints, $totalDistance);
|
|
993
|
+
$subTotal += $multiZoneFee;
|
|
994
|
+
$lines = $lines->merge($multiZoneLines);
|
|
995
|
+
}
|
|
996
|
+
|
|
930
997
|
if ($this->isAlgorithm()) {
|
|
931
998
|
$rateFee = $this->normalizeCalculatedMoney(Algo::exec(
|
|
932
999
|
$this->algorithm,
|
|
@@ -1086,6 +1153,211 @@ class ServiceRate extends Model
|
|
|
1086
1153
|
return 2;
|
|
1087
1154
|
}
|
|
1088
1155
|
|
|
1156
|
+
protected function quoteMultiZoneDistance($waypoints = [], ?int $totalDistance = 0): array
|
|
1157
|
+
{
|
|
1158
|
+
$this->loadMissing(['rateFees.zone', 'rateFees.serviceArea']);
|
|
1159
|
+
|
|
1160
|
+
$rules = $this->rateFees
|
|
1161
|
+
->filter(fn ($rule) => !$rule->is_fallback && ($rule->zone || $rule->serviceArea))
|
|
1162
|
+
->sortByDesc(fn ($rule) => (int) ($rule->priority ?? 0))
|
|
1163
|
+
->values();
|
|
1164
|
+
|
|
1165
|
+
$fallbackRule = $this->rateFees
|
|
1166
|
+
->filter(fn ($rule) => (bool) $rule->is_fallback)
|
|
1167
|
+
->sortByDesc(fn ($rule) => (int) ($rule->priority ?? 0))
|
|
1168
|
+
->first();
|
|
1169
|
+
|
|
1170
|
+
if ($rules->isEmpty() && !$fallbackRule) {
|
|
1171
|
+
return [0, collect()];
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
$pricedDistances = $this->calculateMultiZoneDistances($waypoints, $rules, $fallbackRule, $totalDistance);
|
|
1175
|
+
$lines = collect();
|
|
1176
|
+
$subTotal = 0;
|
|
1177
|
+
|
|
1178
|
+
foreach ($pricedDistances as $entry) {
|
|
1179
|
+
$rule = $entry['rule'];
|
|
1180
|
+
if (!$rule instanceof ServiceRateFee) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
$distanceInMeters = (float) ($entry['distance_m'] ?? 0);
|
|
1185
|
+
if ($distanceInMeters <= 0) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
$unit = strtolower($rule->distance_unit ?: 'km');
|
|
1190
|
+
$distance = $this->normalizeDistanceForUnit($distanceInMeters, $unit);
|
|
1191
|
+
$fee = $this->normalizeCalculatedMoney($distance * Utils::numbersOnly($rule->fee ?? 0));
|
|
1192
|
+
$subTotal += $fee;
|
|
1193
|
+
|
|
1194
|
+
if ($rule->is_fallback) {
|
|
1195
|
+
$label = 'Out-of-zone distance charge';
|
|
1196
|
+
} else {
|
|
1197
|
+
$label = data_get($rule, 'zone.name') ?: data_get($rule, 'serviceArea.name') ?: $rule->label ?: 'Geographic';
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
if (!$rule->is_fallback && !str_ends_with(strtolower($label), 'distance charge')) {
|
|
1201
|
+
$label .= ' distance charge';
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
$lines->push([
|
|
1205
|
+
'details' => sprintf('%s (%s %s x %s)', $label, round($distance, 2), $unit, Utils::moneyFormat($rule->fee, $this->currency)),
|
|
1206
|
+
'amount' => $fee,
|
|
1207
|
+
'formatted_amount' => Utils::moneyFormat($fee, $this->currency),
|
|
1208
|
+
'currency' => $this->currency,
|
|
1209
|
+
'code' => 'MULTI_ZONE_DISTANCE_FEE',
|
|
1210
|
+
]);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
return [$subTotal, $lines];
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
protected function calculateMultiZoneDistances($waypoints, $rules, ?ServiceRateFee $fallbackRule = null, ?int $totalDistance = 0): array
|
|
1217
|
+
{
|
|
1218
|
+
$places = collect($waypoints)->map(fn ($place) => Place::createFromMixed($place))->filter()->values();
|
|
1219
|
+
|
|
1220
|
+
if ($places->count() < 2 || (int) $totalDistance <= 0) {
|
|
1221
|
+
return $fallbackRule ? [['rule' => $fallbackRule, 'distance_m' => (float) ($totalDistance ?? 0)]] : [];
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
$reader = new GeoJSONReader();
|
|
1225
|
+
$ruleGeometries = $rules->map(function ($rule) use ($reader) {
|
|
1226
|
+
return [
|
|
1227
|
+
'rule' => $rule,
|
|
1228
|
+
'geometry' => $this->readRateRuleGeometry($rule, $reader),
|
|
1229
|
+
];
|
|
1230
|
+
})->filter(fn ($entry) => $entry['geometry'])->values();
|
|
1231
|
+
|
|
1232
|
+
if ($ruleGeometries->isEmpty()) {
|
|
1233
|
+
return $fallbackRule ? [['rule' => $fallbackRule, 'distance_m' => (float) $totalDistance]] : [];
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
$distances = [];
|
|
1237
|
+
$sampledTotal = 0;
|
|
1238
|
+
|
|
1239
|
+
for ($i = 0; $i < $places->count() - 1; $i++) {
|
|
1240
|
+
$start = $this->getLngLatFromPlace($places[$i]);
|
|
1241
|
+
$end = $this->getLngLatFromPlace($places[$i + 1]);
|
|
1242
|
+
|
|
1243
|
+
if (!$start || !$end) {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
$legDistance = $this->haversineDistanceInMeters($start['lat'], $start['lng'], $end['lat'], $end['lng']);
|
|
1248
|
+
$steps = max(1, min(500, (int) ceil($legDistance / 1000)));
|
|
1249
|
+
|
|
1250
|
+
for ($step = 0; $step < $steps; $step++) {
|
|
1251
|
+
$fromRatio = $step / $steps;
|
|
1252
|
+
$toRatio = ($step + 1) / $steps;
|
|
1253
|
+
$midRatio = ($fromRatio + $toRatio) / 2;
|
|
1254
|
+
|
|
1255
|
+
$from = $this->interpolateLngLat($start, $end, $fromRatio);
|
|
1256
|
+
$to = $this->interpolateLngLat($start, $end, $toRatio);
|
|
1257
|
+
$mid = $this->interpolateLngLat($start, $end, $midRatio);
|
|
1258
|
+
|
|
1259
|
+
$sampleDistance = $this->haversineDistanceInMeters($from['lat'], $from['lng'], $to['lat'], $to['lng']);
|
|
1260
|
+
$sampledTotal += $sampleDistance;
|
|
1261
|
+
|
|
1262
|
+
$matchedRule = $this->matchMultiZoneRule($mid, $ruleGeometries) ?: $fallbackRule;
|
|
1263
|
+
if (!$matchedRule instanceof ServiceRateFee) {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
$key = $matchedRule->uuid ?? spl_object_hash($matchedRule);
|
|
1268
|
+
if (!isset($distances[$key])) {
|
|
1269
|
+
$distances[$key] = ['rule' => $matchedRule, 'distance_m' => 0];
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
$distances[$key]['distance_m'] += $sampleDistance;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if ($sampledTotal > 0 && $totalDistance > 0) {
|
|
1277
|
+
$scale = $totalDistance / $sampledTotal;
|
|
1278
|
+
foreach ($distances as &$entry) {
|
|
1279
|
+
$entry['distance_m'] *= $scale;
|
|
1280
|
+
}
|
|
1281
|
+
unset($entry);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return array_values($distances);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
protected function readRateRuleGeometry(ServiceRateFee $rule, GeoJSONReader $reader)
|
|
1288
|
+
{
|
|
1289
|
+
$border = data_get($rule, 'zone.border') ?: data_get($rule, 'serviceArea.border');
|
|
1290
|
+
|
|
1291
|
+
if (!$border) {
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
try {
|
|
1296
|
+
if (is_object($border) && method_exists($border, 'toJson')) {
|
|
1297
|
+
return $reader->read($border->toJson());
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (is_array($border) || is_object($border)) {
|
|
1301
|
+
return $reader->read(json_encode($border, JSON_UNESCAPED_UNICODE));
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (is_string($border)) {
|
|
1305
|
+
return $reader->read($border);
|
|
1306
|
+
}
|
|
1307
|
+
} catch (\Throwable $e) {
|
|
1308
|
+
return null;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
protected function matchMultiZoneRule(array $point, $ruleGeometries): ?ServiceRateFee
|
|
1315
|
+
{
|
|
1316
|
+
$brickPoint = \Brick\Geo\Point::fromText(sprintf('POINT (%F %F)', $point['lng'], $point['lat']), 4326);
|
|
1317
|
+
|
|
1318
|
+
foreach ($ruleGeometries as $entry) {
|
|
1319
|
+
if ($entry['geometry']->contains($brickPoint)) {
|
|
1320
|
+
return $entry['rule'];
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
protected function getLngLatFromPlace(?Place $place): ?array
|
|
1328
|
+
{
|
|
1329
|
+
if (!$place instanceof Place) {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
$point = $place->getLocationAsPoint();
|
|
1334
|
+
|
|
1335
|
+
if (!$point || !method_exists($point, 'getLat') || !method_exists($point, 'getLng')) {
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return ['lat' => (float) $point->getLat(), 'lng' => (float) $point->getLng()];
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
protected function interpolateLngLat(array $start, array $end, float $ratio): array
|
|
1343
|
+
{
|
|
1344
|
+
return [
|
|
1345
|
+
'lat' => $start['lat'] + (($end['lat'] - $start['lat']) * $ratio),
|
|
1346
|
+
'lng' => $start['lng'] + (($end['lng'] - $start['lng']) * $ratio),
|
|
1347
|
+
];
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
protected function haversineDistanceInMeters(float $lat1, float $lng1, float $lat2, float $lng2): float
|
|
1351
|
+
{
|
|
1352
|
+
$earthRadius = 6371000;
|
|
1353
|
+
$latDelta = deg2rad($lat2 - $lat1);
|
|
1354
|
+
$lngDelta = deg2rad($lng2 - $lng1);
|
|
1355
|
+
|
|
1356
|
+
$a = sin($latDelta / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($lngDelta / 2) ** 2;
|
|
1357
|
+
|
|
1358
|
+
return $earthRadius * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1089
1361
|
/**
|
|
1090
1362
|
* Find the ServiceRateFee based on the total distance.
|
|
1091
1363
|
*
|
|
@@ -7,6 +7,7 @@ use Fleetbase\FleetOps\Support\Utils;
|
|
|
7
7
|
use Fleetbase\Models\Model;
|
|
8
8
|
use Fleetbase\Traits\HasUuid;
|
|
9
9
|
use Fleetbase\Traits\TracksApiCredential;
|
|
10
|
+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
10
11
|
|
|
11
12
|
class ServiceRateFee extends Model
|
|
12
13
|
{
|
|
@@ -32,7 +33,22 @@ class ServiceRateFee extends Model
|
|
|
32
33
|
*
|
|
33
34
|
* @var array
|
|
34
35
|
*/
|
|
35
|
-
protected $fillable = [
|
|
36
|
+
protected $fillable = [
|
|
37
|
+
'_key',
|
|
38
|
+
'service_rate_uuid',
|
|
39
|
+
'service_area_uuid',
|
|
40
|
+
'zone_uuid',
|
|
41
|
+
'label',
|
|
42
|
+
'priority',
|
|
43
|
+
'is_fallback',
|
|
44
|
+
'distance',
|
|
45
|
+
'distance_unit',
|
|
46
|
+
'min',
|
|
47
|
+
'max',
|
|
48
|
+
'unit',
|
|
49
|
+
'fee',
|
|
50
|
+
'currency',
|
|
51
|
+
];
|
|
36
52
|
|
|
37
53
|
/**
|
|
38
54
|
* The attributes that should be cast to native types.
|
|
@@ -40,10 +56,12 @@ class ServiceRateFee extends Model
|
|
|
40
56
|
* @var array
|
|
41
57
|
*/
|
|
42
58
|
protected $casts = [
|
|
43
|
-
'min'
|
|
44
|
-
'max'
|
|
45
|
-
'fee'
|
|
46
|
-
'distance'
|
|
59
|
+
'min' => 'integer',
|
|
60
|
+
'max' => 'integer',
|
|
61
|
+
'fee' => Money::class,
|
|
62
|
+
'distance' => 'integer',
|
|
63
|
+
'priority' => 'integer',
|
|
64
|
+
'is_fallback' => 'boolean',
|
|
47
65
|
];
|
|
48
66
|
|
|
49
67
|
/**
|
|
@@ -62,14 +80,26 @@ class ServiceRateFee extends Model
|
|
|
62
80
|
|
|
63
81
|
public static function onRowInsert($row)
|
|
64
82
|
{
|
|
65
|
-
$row['fee']
|
|
66
|
-
$row['distance']
|
|
67
|
-
$row['min']
|
|
68
|
-
$row['max']
|
|
83
|
+
$row['fee'] = Money::apply($row['fee'] ?? 0);
|
|
84
|
+
$row['distance'] = Utils::numbersOnly($row['distance'] ?? null);
|
|
85
|
+
$row['min'] = Utils::numbersOnly($row['min'] ?? null);
|
|
86
|
+
$row['max'] = Utils::numbersOnly($row['max'] ?? null);
|
|
87
|
+
$row['priority'] = Utils::numbersOnly($row['priority'] ?? 0);
|
|
88
|
+
$row['is_fallback'] = Utils::castBoolean($row['is_fallback'] ?? false);
|
|
69
89
|
|
|
70
90
|
return $row;
|
|
71
91
|
}
|
|
72
92
|
|
|
93
|
+
public function serviceArea(): BelongsTo
|
|
94
|
+
{
|
|
95
|
+
return $this->belongsTo(ServiceArea::class, 'service_area_uuid', 'uuid');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public function zone(): BelongsTo
|
|
99
|
+
{
|
|
100
|
+
return $this->belongsTo(Zone::class, 'zone_uuid', 'uuid');
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
public function setDistanceAttribute($value)
|
|
74
104
|
{
|
|
75
105
|
$this->attributes['distance'] = Utils::numbersOnly($value);
|
|
@@ -14,6 +14,7 @@ use Fleetbase\Traits\SendsWebhooks;
|
|
|
14
14
|
use Fleetbase\Traits\TracksApiCredential;
|
|
15
15
|
use Illuminate\Support\Carbon;
|
|
16
16
|
use Illuminate\Support\Facades\DB;
|
|
17
|
+
use Illuminate\Support\Facades\Schema;
|
|
17
18
|
use Illuminate\Support\Str;
|
|
18
19
|
use Milon\Barcode\Facades\DNS2DFacade as DNS2D;
|
|
19
20
|
|
|
@@ -242,7 +243,7 @@ class TrackingNumber extends Model
|
|
|
242
243
|
$status = strtolower($trackingStatus->code);
|
|
243
244
|
$owner = $this->load(['owner'])->getRelationValue('owner');
|
|
244
245
|
|
|
245
|
-
if ($owner && $owner->isFillable('status') && $owner->status !== $status) {
|
|
246
|
+
if ($owner && static::ownerHasStatusColumn($owner) && $owner->isFillable('status') && $owner->status !== $status) {
|
|
246
247
|
$this->owner->status = $status;
|
|
247
248
|
|
|
248
249
|
$this->owner->save();
|
|
@@ -303,7 +304,7 @@ class TrackingNumber extends Model
|
|
|
303
304
|
TrackingNumber::where('uuid', $uuid)->update(['status_uuid' => $trackingStatusId]);
|
|
304
305
|
|
|
305
306
|
// update owner status
|
|
306
|
-
if ($owner && $owner instanceof Model && $owner->isFillable('status')) {
|
|
307
|
+
if ($owner && $owner instanceof Model && static::ownerHasStatusColumn($owner) && $owner->isFillable('status')) {
|
|
307
308
|
// runs event cycles
|
|
308
309
|
// $model->update([ 'status' => 'created' ]);
|
|
309
310
|
|
|
@@ -313,4 +314,9 @@ class TrackingNumber extends Model
|
|
|
313
314
|
|
|
314
315
|
return $uuid;
|
|
315
316
|
}
|
|
317
|
+
|
|
318
|
+
protected static function ownerHasStatusColumn(Model $owner): bool
|
|
319
|
+
{
|
|
320
|
+
return Schema::hasColumn($owner->getTable(), 'status');
|
|
321
|
+
}
|
|
316
322
|
}
|
|
@@ -223,6 +223,7 @@ class Vehicle extends Model
|
|
|
223
223
|
*/
|
|
224
224
|
protected $attributes = [
|
|
225
225
|
'avatar_url' => 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg',
|
|
226
|
+
'status' => 'available',
|
|
226
227
|
];
|
|
227
228
|
|
|
228
229
|
/**
|
|
@@ -324,6 +325,14 @@ class Vehicle extends Model
|
|
|
324
325
|
return $this->hasOne(Driver::class, 'vehicle_uuid', 'uuid')->without(['vehicle']);
|
|
325
326
|
}
|
|
326
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Set the vehicle status attribute.
|
|
330
|
+
*/
|
|
331
|
+
public function setStatusAttribute(?string $status = 'available'): void
|
|
332
|
+
{
|
|
333
|
+
$this->attributes['status'] = $status === null || $status === 'active' ? 'available' : $status;
|
|
334
|
+
}
|
|
335
|
+
|
|
327
336
|
public function category(): BelongsTo
|
|
328
337
|
{
|
|
329
338
|
return $this->belongsTo(Category::class, 'category_uuid', 'uuid');
|
|
@@ -597,6 +606,14 @@ class Vehicle extends Model
|
|
|
597
606
|
return $this;
|
|
598
607
|
}
|
|
599
608
|
|
|
609
|
+
public function unassignDriver(): self
|
|
610
|
+
{
|
|
611
|
+
Driver::where('vehicle_uuid', $this->uuid)->update(['vehicle_uuid' => null]);
|
|
612
|
+
$this->unsetRelation('driver');
|
|
613
|
+
|
|
614
|
+
return $this;
|
|
615
|
+
}
|
|
616
|
+
|
|
600
617
|
/**
|
|
601
618
|
* Retrieves the last known position of the driver within the current company context.
|
|
602
619
|
*
|
|
@@ -727,9 +744,8 @@ class Vehicle extends Model
|
|
|
727
744
|
'plate_number' => $plateNumber,
|
|
728
745
|
'vin' => $vin,
|
|
729
746
|
'type' => $type,
|
|
730
|
-
'status' => '
|
|
747
|
+
'status' => 'available',
|
|
731
748
|
'online' => 0,
|
|
732
|
-
'status' => 'active',
|
|
733
749
|
]);
|
|
734
750
|
|
|
735
751
|
// If driver was resolved assign driver to vehicle
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
|
-
use Barryvdh\DomPDF\Facade\Pdf;
|
|
6
5
|
use Fleetbase\Casts\PolymorphicType;
|
|
6
|
+
use Fleetbase\FleetOps\Support\LabelPdf;
|
|
7
7
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
8
|
use Fleetbase\FleetOps\Traits\HasTrackingNumber;
|
|
9
9
|
use Fleetbase\FleetOps\Traits\PayloadAccessors;
|
|
@@ -94,7 +94,7 @@ class Waypoint extends Model
|
|
|
94
94
|
*/
|
|
95
95
|
public function pdfLabel()
|
|
96
96
|
{
|
|
97
|
-
return
|
|
97
|
+
return LabelPdf::fromHtml($this->label());
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Notifications;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Notifications\Messages\MailMessage;
|
|
9
|
+
use Illuminate\Notifications\Notification;
|
|
10
|
+
|
|
11
|
+
class LateDeparture extends Notification implements ShouldQueue
|
|
12
|
+
{
|
|
13
|
+
use Queueable;
|
|
14
|
+
|
|
15
|
+
public static string $name = 'Late Departure';
|
|
16
|
+
public static string $description = 'Notify when an order has not departed after the configured grace period.';
|
|
17
|
+
public static string $package = 'fleet-ops';
|
|
18
|
+
|
|
19
|
+
public function __construct(public Order $order, public array $context = [])
|
|
20
|
+
{
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function via($notifiable): array
|
|
24
|
+
{
|
|
25
|
+
return ['mail', 'database'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public function toMail($notifiable): MailMessage
|
|
29
|
+
{
|
|
30
|
+
$orderId = $this->order->tracking ?? $this->order->public_id;
|
|
31
|
+
|
|
32
|
+
return (new MailMessage())
|
|
33
|
+
->subject("Late departure detected for {$orderId}")
|
|
34
|
+
->line("Order {$orderId} has not departed within the configured grace period.")
|
|
35
|
+
->line('Please review the order and dispatch status.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function toArray($notifiable): array
|
|
39
|
+
{
|
|
40
|
+
return [
|
|
41
|
+
'event' => 'order.late_departure',
|
|
42
|
+
'order_id' => $this->order->public_id,
|
|
43
|
+
'order_uuid' => $this->order->uuid,
|
|
44
|
+
'context' => $this->context,
|
|
45
|
+
'message' => sprintf('Order %s has not departed within the configured grace period.', $this->order->tracking ?? $this->order->public_id),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Notifications;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Notifications\Messages\MailMessage;
|
|
9
|
+
use Illuminate\Notifications\Notification;
|
|
10
|
+
|
|
11
|
+
class ProlongedStoppage extends Notification implements ShouldQueue
|
|
12
|
+
{
|
|
13
|
+
use Queueable;
|
|
14
|
+
|
|
15
|
+
public static string $name = 'Prolonged Stoppage';
|
|
16
|
+
public static string $description = 'Notify when a vehicle remains stopped during an active trip beyond the configured threshold.';
|
|
17
|
+
public static string $package = 'fleet-ops';
|
|
18
|
+
|
|
19
|
+
public function __construct(public Order $order, public array $context = [])
|
|
20
|
+
{
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function via($notifiable): array
|
|
24
|
+
{
|
|
25
|
+
return ['mail', 'database'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public function toMail($notifiable): MailMessage
|
|
29
|
+
{
|
|
30
|
+
$orderId = $this->order->tracking ?? $this->order->public_id;
|
|
31
|
+
|
|
32
|
+
return (new MailMessage())
|
|
33
|
+
->subject("Prolonged stoppage detected for {$orderId}")
|
|
34
|
+
->line("The vehicle assigned to order {$orderId} has remained stopped beyond the configured threshold.")
|
|
35
|
+
->line('Please review the active trip and vehicle location.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function toArray($notifiable): array
|
|
39
|
+
{
|
|
40
|
+
return [
|
|
41
|
+
'event' => 'order.prolonged_stoppage',
|
|
42
|
+
'order_id' => $this->order->public_id,
|
|
43
|
+
'order_uuid' => $this->order->uuid,
|
|
44
|
+
'context' => $this->context,
|
|
45
|
+
'message' => sprintf('The vehicle assigned to order %s has remained stopped beyond the configured threshold.', $this->order->tracking ?? $this->order->public_id),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Notifications;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Notifications\Messages\MailMessage;
|
|
9
|
+
use Illuminate\Notifications\Notification;
|
|
10
|
+
|
|
11
|
+
class RouteDeviation extends Notification implements ShouldQueue
|
|
12
|
+
{
|
|
13
|
+
use Queueable;
|
|
14
|
+
|
|
15
|
+
public static string $name = 'Route Deviation';
|
|
16
|
+
public static string $description = 'Notify when tracked vehicle location deviates from the active route beyond the configured threshold.';
|
|
17
|
+
public static string $package = 'fleet-ops';
|
|
18
|
+
|
|
19
|
+
public function __construct(public Order $order, public array $context = [])
|
|
20
|
+
{
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function via($notifiable): array
|
|
24
|
+
{
|
|
25
|
+
return ['mail', 'database'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public function toMail($notifiable): MailMessage
|
|
29
|
+
{
|
|
30
|
+
$orderId = $this->order->tracking ?? $this->order->public_id;
|
|
31
|
+
|
|
32
|
+
return (new MailMessage())
|
|
33
|
+
->subject("Route deviation detected for {$orderId}")
|
|
34
|
+
->line("The vehicle assigned to order {$orderId} has deviated from the active route beyond the configured threshold.")
|
|
35
|
+
->line('Please review the route and current vehicle position.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function toArray($notifiable): array
|
|
39
|
+
{
|
|
40
|
+
return [
|
|
41
|
+
'event' => 'order.route_deviation',
|
|
42
|
+
'order_id' => $this->order->public_id,
|
|
43
|
+
'order_uuid' => $this->order->uuid,
|
|
44
|
+
'context' => $this->context,
|
|
45
|
+
'message' => sprintf('The vehicle assigned to order %s has deviated from the active route beyond the configured threshold.', $this->order->tracking ?? $this->order->public_id),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|