@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.
Files changed (266) hide show
  1. package/addon/components/customer/create-order-form.hbs +2 -2
  2. package/addon/components/customer/form.hbs +2 -2
  3. package/addon/components/customer/order-form.hbs +2 -2
  4. package/addon/components/customer/orders.hbs +2 -2
  5. package/addon/components/display-place.hbs +2 -2
  6. package/addon/components/driver/details.hbs +5 -1
  7. package/addon/components/driver/form.hbs +11 -1
  8. package/addon/components/fleet-listing-panel.hbs +2 -2
  9. package/addon/components/fleet-listing-panel.js +10 -0
  10. package/addon/components/map/google-live-map.js +18 -75
  11. package/addon/components/map/leaflet-live-map.hbs +187 -37
  12. package/addon/components/map/leaflet-live-map.js +184 -20
  13. package/addon/components/modals/order-change-destination.hbs +46 -0
  14. package/addon/components/modals/order-change-destination.js +106 -0
  15. package/addon/components/modals/stop-activity.hbs +28 -0
  16. package/addon/components/modals/stop-activity.js +52 -0
  17. package/addon/components/modals/update-order-activity.hbs +45 -3
  18. package/addon/components/modals/update-order-activity.js +143 -5
  19. package/addon/components/orchestrator/phase-builder.hbs +21 -0
  20. package/addon/components/orchestrator/phase-builder.js +13 -0
  21. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  22. package/addon/components/orchestrator/resource-panel.js +2 -4
  23. package/addon/components/orchestrator-workbench.js +1 -0
  24. package/addon/components/order/activity-list.hbs +2 -2
  25. package/addon/components/order/activity-timeline.hbs +31 -29
  26. package/addon/components/order/details/activity.hbs +3 -3
  27. package/addon/components/order/details/activity.js +11 -0
  28. package/addon/components/order/details/documents.js +2 -0
  29. package/addon/components/order/details/purchase-rate.hbs +2 -2
  30. package/addon/components/order/details/route.hbs +11 -3
  31. package/addon/components/order/details/route.js +91 -14
  32. package/addon/components/order/details/tracking.hbs +101 -72
  33. package/addon/components/order/details/tracking.js +149 -2
  34. package/addon/components/order/form/details.hbs +3 -3
  35. package/addon/components/order/form/documents.js +11 -4
  36. package/addon/components/order/form/route.hbs +108 -104
  37. package/addon/components/order/form/route.js +37 -1
  38. package/addon/components/order/form/service-rate.hbs +2 -2
  39. package/addon/components/order/form.hbs +2 -2
  40. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  41. package/addon/components/order-config-manager/activity-flow.js +178 -12
  42. package/addon/components/place/address.js +1 -1
  43. package/addon/components/route-list.hbs +17 -17
  44. package/addon/components/route-list.js +42 -10
  45. package/addon/components/service-rate/details.hbs +41 -0
  46. package/addon/components/service-rate/form.hbs +118 -0
  47. package/addon/components/service-rate/form.js +55 -0
  48. package/addon/components/vehicle/form.js +1 -1
  49. package/addon/components/vehicle/pill.hbs +1 -1
  50. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
  51. package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
  52. package/addon/components/widget/fuel-efficiency.hbs +26 -0
  53. package/addon/components/widget/fuel-efficiency.js +43 -0
  54. package/addon/components/widget/geofence-violations.hbs +44 -0
  55. package/addon/components/widget/geofence-violations.js +55 -0
  56. package/addon/components/widget/issues-insights.hbs +43 -0
  57. package/addon/components/widget/issues-insights.js +62 -0
  58. package/addon/components/widget/kpi-active-orders.hbs +10 -0
  59. package/addon/components/widget/kpi-active-orders.js +5 -0
  60. package/addon/components/widget/kpi-aov.hbs +10 -0
  61. package/addon/components/widget/kpi-aov.js +5 -0
  62. package/addon/components/widget/kpi-distance.hbs +10 -0
  63. package/addon/components/widget/kpi-distance.js +5 -0
  64. package/addon/components/widget/kpi-drivers-online.hbs +10 -0
  65. package/addon/components/widget/kpi-drivers-online.js +5 -0
  66. package/addon/components/widget/kpi-earnings.hbs +10 -0
  67. package/addon/components/widget/kpi-earnings.js +5 -0
  68. package/addon/components/widget/kpi-open-issues.hbs +11 -0
  69. package/addon/components/widget/kpi-open-issues.js +5 -0
  70. package/addon/components/widget/kpi-tile.hbs +59 -0
  71. package/addon/components/widget/kpi-tile.js +168 -0
  72. package/addon/components/widget/live-fleet.hbs +122 -0
  73. package/addon/components/widget/live-fleet.js +365 -0
  74. package/addon/components/widget/maintenance-overview.hbs +45 -0
  75. package/addon/components/widget/maintenance-overview.js +40 -0
  76. package/addon/components/widget/on-time-delivery.hbs +27 -0
  77. package/addon/components/widget/on-time-delivery.js +73 -0
  78. package/addon/components/widget/operations-pulse.hbs +51 -0
  79. package/addon/components/widget/operations-pulse.js +173 -0
  80. package/addon/components/widget/orders-by-status.hbs +26 -0
  81. package/addon/components/widget/orders-by-status.js +54 -0
  82. package/addon/components/widget/revenue-trend.hbs +34 -0
  83. package/addon/components/widget/revenue-trend.js +67 -0
  84. package/addon/components/widget/top-drivers.hbs +44 -0
  85. package/addon/components/widget/top-drivers.js +54 -0
  86. package/addon/controllers/management/vehicles/index/new.js +1 -1
  87. package/addon/controllers/operations/orders/index/details.js +4 -4
  88. package/addon/controllers/operations/orders/index.js +14 -0
  89. package/addon/controllers/settings/routing.js +26 -0
  90. package/addon/extension.js +172 -3
  91. package/addon/helpers/place-address-html.js +2 -2
  92. package/addon/routes/operations/service-rates/index.js +1 -1
  93. package/addon/services/geofence.js +163 -19
  94. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  95. package/addon/services/map-adapter/google.js +13 -2
  96. package/addon/services/vehicle-actions.js +1 -1
  97. package/addon/styles/fleetops-engine.css +205 -88
  98. package/addon/templates/maintenance/equipment/index.hbs +1 -1
  99. package/addon/templates/maintenance/parts/index.hbs +1 -1
  100. package/addon/templates/maintenance/schedules/index.hbs +1 -1
  101. package/addon/templates/management/vehicles/index.hbs +1 -1
  102. package/addon/templates/settings/avatars.hbs +1 -1
  103. package/addon/templates/settings/custom-fields.hbs +1 -1
  104. package/addon/templates/settings/map.hbs +1 -1
  105. package/addon/templates/settings/navigator-app.hbs +1 -1
  106. package/addon/templates/settings/notifications.hbs +1 -1
  107. package/addon/templates/settings/orchestrator.hbs +1 -1
  108. package/addon/templates/settings/payments/index.hbs +1 -1
  109. package/addon/templates/settings/payments/onboard.hbs +1 -1
  110. package/addon/templates/settings/routing.hbs +22 -2
  111. package/addon/templates/settings/scheduling.hbs +1 -1
  112. package/addon/utils/fleet-ops-options.js +2 -1
  113. package/addon/utils/live-map-card-content.js +171 -0
  114. package/addon/utils/normalize-order-config-flow.js +41 -0
  115. package/addon/utils/place-address-html.js +3 -2
  116. package/addon/utils/route-visualization.js +54 -7
  117. package/app/components/modals/order-change-destination.js +1 -0
  118. package/app/components/modals/stop-activity.js +1 -0
  119. package/app/components/widget/fuel-efficiency.js +1 -0
  120. package/app/components/widget/geofence-violations.js +1 -0
  121. package/app/components/widget/issues-insights.js +1 -0
  122. package/app/components/widget/kpi-active-orders.js +1 -0
  123. package/app/components/widget/kpi-aov.js +1 -0
  124. package/app/components/widget/kpi-distance.js +1 -0
  125. package/app/components/widget/kpi-drivers-online.js +1 -0
  126. package/app/components/widget/kpi-earnings.js +1 -0
  127. package/app/components/widget/kpi-open-issues.js +1 -0
  128. package/app/components/widget/kpi-tile.js +1 -0
  129. package/app/components/widget/live-fleet.js +1 -0
  130. package/app/components/widget/maintenance-overview.js +1 -0
  131. package/app/components/widget/on-time-delivery.js +1 -0
  132. package/app/components/widget/operations-pulse.js +1 -0
  133. package/app/components/widget/orders-by-status.js +1 -0
  134. package/app/components/widget/revenue-trend.js +1 -0
  135. package/app/components/widget/top-drivers.js +1 -0
  136. package/app/utils/live-map-card-content.js +1 -0
  137. package/composer.json +1 -1
  138. package/extension.json +1 -1
  139. package/package.json +3 -3
  140. package/pnpm-workspace.yaml +1 -0
  141. package/server/config/telematics.php +111 -1
  142. package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
  143. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  144. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  145. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  146. package/server/resources/views/labels/default.php +57 -49
  147. package/server/resources/views/labels/waypoint-label.php +27 -20
  148. package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
  149. package/server/seeders/Testing/ConnectivitySeeder.php +213 -0
  150. package/server/seeders/Testing/FleetSeeder.php +341 -0
  151. package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
  152. package/server/seeders/Testing/NetworkSeeder.php +250 -0
  153. package/server/seeders/Testing/OrdersSeeder.php +198 -0
  154. package/server/seeders/Testing/TestingSeeder.php +40 -0
  155. package/server/seeders/{VroomOrchestrationSeeder.php → Testing/VroomOrchestrationSeeder.php} +29 -22
  156. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  157. package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
  158. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  159. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  160. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  161. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  162. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  163. package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
  164. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  165. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  166. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
  167. package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
  168. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  169. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
  170. package/server/src/Http/Controllers/Internal/v1/OrderController.php +441 -32
  171. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  172. package/server/src/Http/Controllers/Internal/v1/SettingController.php +46 -1
  173. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  174. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  175. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  176. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  177. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  178. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  179. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  180. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  181. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  182. package/server/src/Http/Resources/v1/Customer.php +79 -0
  183. package/server/src/Http/Resources/v1/Driver.php +2 -0
  184. package/server/src/Http/Resources/v1/Fleet.php +4 -0
  185. package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
  186. package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
  187. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  188. package/server/src/Http/Resources/v1/Payload.php +23 -20
  189. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  190. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  191. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  192. package/server/src/Models/Contact.php +92 -15
  193. package/server/src/Models/Customer.php +64 -0
  194. package/server/src/Models/Driver.php +14 -3
  195. package/server/src/Models/Entity.php +2 -2
  196. package/server/src/Models/Order.php +29 -4
  197. package/server/src/Models/OrderConfig.php +14 -11
  198. package/server/src/Models/Payload.php +17 -1
  199. package/server/src/Models/ServiceRate.php +273 -1
  200. package/server/src/Models/ServiceRateFee.php +39 -9
  201. package/server/src/Models/TrackingNumber.php +8 -2
  202. package/server/src/Models/Vehicle.php +18 -2
  203. package/server/src/Models/Waypoint.php +2 -2
  204. package/server/src/Notifications/LateDeparture.php +48 -0
  205. package/server/src/Notifications/ProlongedStoppage.php +48 -0
  206. package/server/src/Notifications/RouteDeviation.php +48 -0
  207. package/server/src/Observers/ContactObserver.php +2 -0
  208. package/server/src/Observers/ServiceRateObserver.php +6 -6
  209. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +29 -2
  210. package/server/src/Providers/FleetOpsServiceProvider.php +6 -0
  211. package/server/src/Providers/NotificationServiceProvider.php +3 -0
  212. package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
  213. package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
  214. package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
  215. package/server/src/Support/Analytics/IssuesInsights.php +67 -0
  216. package/server/src/Support/Analytics/LiveFleet.php +127 -0
  217. package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
  218. package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
  219. package/server/src/Support/Analytics/OperationsPulse.php +86 -0
  220. package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
  221. package/server/src/Support/Analytics/RevenueTrend.php +78 -0
  222. package/server/src/Support/Analytics/TopDrivers.php +92 -0
  223. package/server/src/Support/CustomerAuth.php +98 -0
  224. package/server/src/Support/LabelPdf.php +52 -0
  225. package/server/src/Support/Metrics/AbstractMetric.php +170 -0
  226. package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
  227. package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
  228. package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
  229. package/server/src/Support/Metrics/EarningsMetric.php +30 -0
  230. package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
  231. package/server/src/Support/Metrics/MoneyMetric.php +30 -0
  232. package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
  233. package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
  234. package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
  235. package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
  236. package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
  237. package/server/src/Support/Metrics/Registry.php +46 -0
  238. package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
  239. package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
  240. package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
  241. package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
  242. package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
  243. package/server/src/Support/Metrics.php +46 -249
  244. package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
  245. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
  246. package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
  247. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  248. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  249. package/server/src/Tracking/TrackingStop.php +13 -11
  250. package/server/src/routes.php +50 -0
  251. package/server/tests/AnalyticsRoutesTest.php +41 -0
  252. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  253. package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
  254. package/server/tests/CustomerEndpointTest.php +199 -0
  255. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  256. package/server/tests/LabelPdfRenderingTest.php +23 -0
  257. package/server/tests/LiveControllerViewportTest.php +60 -0
  258. package/server/tests/MetricsRegistryTest.php +84 -0
  259. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  260. package/server/tests/OrderConfigEndpointTest.php +86 -0
  261. package/server/tests/OrderFileAttachmentTest.php +20 -0
  262. package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
  263. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  264. package/server/tests/VroomOrchestrationEngineTest.php +91 -0
  265. package/translations/en-us.yaml +38 -0
  266. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -0,0 +1,279 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Console\Commands;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Fleetbase\FleetOps\Models\Position;
7
+ use Fleetbase\FleetOps\Notifications\LateDeparture;
8
+ use Fleetbase\FleetOps\Notifications\ProlongedStoppage;
9
+ use Fleetbase\FleetOps\Notifications\RouteDeviation;
10
+ use Fleetbase\FleetOps\Support\Utils;
11
+ use Fleetbase\LaravelMysqlSpatial\Types\Point;
12
+ use Fleetbase\Models\Setting;
13
+ use Fleetbase\Support\NotificationRegistry;
14
+ use Illuminate\Console\Command;
15
+ use Illuminate\Support\Carbon;
16
+ use Illuminate\Support\Facades\Cache;
17
+
18
+ class ProcessOperationalAlerts extends Command
19
+ {
20
+ protected $signature = 'fleetops:process-operational-alerts
21
+ {--days=2 : Only include orders created in the last N days}
22
+ {--chunk=250 : How many orders to process per chunk}
23
+ {--no-lock : Skip process locking}
24
+ {--dry : Dry run without updating notification markers}';
25
+
26
+ protected $description = 'Evaluate FleetOps operational alerts for late departures, route deviations, and prolonged stoppages.';
27
+
28
+ public function handle(): int
29
+ {
30
+ $days = max(1, (int) $this->option('days'));
31
+ $perChunk = max(50, (int) $this->option('chunk'));
32
+ $dryRun = (bool) $this->option('dry');
33
+ $useLock = !$this->option('no-lock');
34
+ $lock = null;
35
+
36
+ if ($useLock) {
37
+ $lock = Cache::lock('fleetops:process-operational-alerts', 600);
38
+ if (!$lock->get()) {
39
+ $this->warn('Another operational alert run appears to be in progress.');
40
+
41
+ return self::SUCCESS;
42
+ }
43
+ }
44
+
45
+ try {
46
+ $cutoff = Carbon::now()->subDays($days);
47
+ $query = $this->ordersQuery($cutoff);
48
+ $total = (clone $query)->count('id');
49
+
50
+ if ($total === 0) {
51
+ $this->info('No qualifying orders found.');
52
+
53
+ return self::SUCCESS;
54
+ }
55
+
56
+ $triggered = 0;
57
+
58
+ $query->orderBy('id')->chunkById($perChunk, function ($orders) use ($dryRun, &$triggered) {
59
+ $orders->loadMissing(['driverAssigned', 'vehicleAssigned', 'route']);
60
+
61
+ foreach ($orders as $order) {
62
+ session(['company' => $order->company_uuid]);
63
+ $settings = $this->alertSettings();
64
+
65
+ if ($this->processLateDeparture($order, $settings, $dryRun)) {
66
+ $triggered++;
67
+ }
68
+
69
+ if ($this->processRouteDeviation($order, $settings, $dryRun)) {
70
+ $triggered++;
71
+ }
72
+
73
+ if ($this->processProlongedStoppage($order, $settings, $dryRun)) {
74
+ $triggered++;
75
+ }
76
+ }
77
+ });
78
+
79
+ $this->info(($dryRun ? '[Dry Run] ' : '') . "Operational alerts triggered: {$triggered}");
80
+
81
+ return self::SUCCESS;
82
+ } finally {
83
+ if ($lock) {
84
+ $lock->release();
85
+ }
86
+ }
87
+ }
88
+
89
+ protected function ordersQuery(Carbon $cutoff)
90
+ {
91
+ return Order::query()
92
+ ->withoutGlobalScopes()
93
+ ->whereNotIn('status', ['completed', 'canceled'])
94
+ ->whereNull('deleted_at')
95
+ ->whereNotNull('company_uuid')
96
+ ->where('created_at', '>=', $cutoff);
97
+ }
98
+
99
+ protected function processLateDeparture(Order $order, array $settings, bool $dryRun): bool
100
+ {
101
+ if (!data_get($settings, 'late_departures.enabled')) {
102
+ return false;
103
+ }
104
+
105
+ if (!$order->scheduled_at || $order->started_at || $order->started) {
106
+ return false;
107
+ }
108
+
109
+ $graceMinutes = (int) data_get($settings, 'late_departures.grace_period_minutes', 15);
110
+ if (Carbon::parse($order->scheduled_at)->addMinutes($graceMinutes)->isFuture()) {
111
+ return false;
112
+ }
113
+
114
+ return $this->notifyOnce($order, 'late_departure', LateDeparture::class, [
115
+ 'scheduled_at' => $order->scheduled_at,
116
+ 'grace_period_minutes' => $graceMinutes,
117
+ ], $dryRun);
118
+ }
119
+
120
+ protected function processRouteDeviation(Order $order, array $settings, bool $dryRun): bool
121
+ {
122
+ if (!data_get($settings, 'route_deviations.enabled')) {
123
+ return false;
124
+ }
125
+
126
+ $latestPosition = $this->latestPositionForOrder($order);
127
+ if (!$latestPosition?->coordinates) {
128
+ return false;
129
+ }
130
+
131
+ $routePoints = $this->routePoints($order);
132
+ if (count($routePoints) < 2) {
133
+ return false;
134
+ }
135
+
136
+ $thresholdMeters = (int) data_get($settings, 'route_deviations.distance_threshold_meters', 500);
137
+ $distanceMeters = $this->minimumDistanceToRoute($latestPosition->coordinates, $routePoints);
138
+
139
+ if ($distanceMeters <= $thresholdMeters) {
140
+ return false;
141
+ }
142
+
143
+ return $this->notifyOnce($order, 'route_deviation', RouteDeviation::class, [
144
+ 'distance_meters' => round($distanceMeters, 2),
145
+ 'distance_threshold_meters' => $thresholdMeters,
146
+ 'position_id' => $latestPosition->uuid,
147
+ ], $dryRun);
148
+ }
149
+
150
+ protected function processProlongedStoppage(Order $order, array $settings, bool $dryRun): bool
151
+ {
152
+ if (!data_get($settings, 'prolonged_stoppages.enabled')) {
153
+ return false;
154
+ }
155
+
156
+ if (!$order->started_at && !$order->started) {
157
+ return false;
158
+ }
159
+
160
+ $latestPosition = $this->latestPositionForOrder($order);
161
+ if (!$latestPosition || ((float) $latestPosition->speed) > 1) {
162
+ return false;
163
+ }
164
+
165
+ $thresholdMinutes = (int) data_get($settings, 'prolonged_stoppages.duration_threshold_minutes', 30);
166
+ if (Carbon::parse($latestPosition->created_at)->addMinutes($thresholdMinutes)->isFuture()) {
167
+ return false;
168
+ }
169
+
170
+ return $this->notifyOnce($order, 'prolonged_stoppage', ProlongedStoppage::class, [
171
+ 'duration_threshold_minutes' => $thresholdMinutes,
172
+ 'position_id' => $latestPosition->uuid,
173
+ 'stopped_since' => $latestPosition->created_at,
174
+ ], $dryRun);
175
+ }
176
+
177
+ protected function notifyOnce(Order $order, string $key, string $notificationClass, array $context, bool $dryRun): bool
178
+ {
179
+ $metaKey = "operational_alerts.{$key}.notified_at";
180
+ if ($order->getMeta($metaKey)) {
181
+ return false;
182
+ }
183
+
184
+ NotificationRegistry::notify($notificationClass, $order, $context);
185
+
186
+ if (!$dryRun) {
187
+ $order->setMeta("operational_alerts.{$key}", [
188
+ 'notified_at' => Carbon::now()->toDateTimeString(),
189
+ 'context' => $context,
190
+ ]);
191
+ $order->saveQuietly();
192
+ }
193
+
194
+ return true;
195
+ }
196
+
197
+ protected function latestPositionForOrder(Order $order): ?Position
198
+ {
199
+ return Position::where('company_uuid', $order->company_uuid)
200
+ ->where('order_uuid', $order->uuid)
201
+ ->latest()
202
+ ->first();
203
+ }
204
+
205
+ protected function routePoints(Order $order): array
206
+ {
207
+ $details = data_get($order, 'route.details', []);
208
+
209
+ return $this->collectPoints($details);
210
+ }
211
+
212
+ protected function collectPoints(mixed $value): array
213
+ {
214
+ if (!is_array($value)) {
215
+ return [];
216
+ }
217
+
218
+ if ($point = $this->pointFromPair($value)) {
219
+ return [$point];
220
+ }
221
+
222
+ $points = [];
223
+ foreach ($value as $item) {
224
+ foreach ($this->collectPoints($item) as $point) {
225
+ $points[] = $point;
226
+ }
227
+ }
228
+
229
+ return $points;
230
+ }
231
+
232
+ protected function pointFromPair(array $value): ?Point
233
+ {
234
+ if (count($value) !== 2 || !is_numeric($value[0]) || !is_numeric($value[1])) {
235
+ return null;
236
+ }
237
+
238
+ $first = (float) $value[0];
239
+ $second = (float) $value[1];
240
+
241
+ if (abs($first) <= 90 && abs($second) <= 180) {
242
+ return new Point($first, $second);
243
+ }
244
+
245
+ if (abs($first) <= 180 && abs($second) <= 90) {
246
+ return new Point($second, $first);
247
+ }
248
+
249
+ return null;
250
+ }
251
+
252
+ protected function minimumDistanceToRoute(Point $position, array $routePoints): float
253
+ {
254
+ return collect($routePoints)
255
+ ->map(fn (Point $point) => Utils::vincentyGreatCircleDistance($position, $point))
256
+ ->min() ?? 0;
257
+ }
258
+
259
+ protected function alertSettings(): array
260
+ {
261
+ $settings = Setting::lookupCompany('tracking', []);
262
+ $alerts = data_get($settings, 'alerts', []);
263
+
264
+ return [
265
+ 'late_departures' => [
266
+ 'enabled' => (bool) data_get($alerts, 'late_departures.enabled', true),
267
+ 'grace_period_minutes' => (int) data_get($alerts, 'late_departures.grace_period_minutes', 15),
268
+ ],
269
+ 'route_deviations' => [
270
+ 'enabled' => (bool) data_get($alerts, 'route_deviations.enabled', true),
271
+ 'distance_threshold_meters' => (int) data_get($alerts, 'route_deviations.distance_threshold_meters', 500),
272
+ ],
273
+ 'prolonged_stoppages' => [
274
+ 'enabled' => (bool) data_get($alerts, 'prolonged_stoppages.enabled', true),
275
+ 'duration_threshold_minutes' => (int) data_get($alerts, 'prolonged_stoppages.duration_threshold_minutes', 30),
276
+ ],
277
+ ];
278
+ }
279
+ }
@@ -0,0 +1,13 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Exceptions;
4
+
5
+ use Fleetbase\Models\User;
6
+
7
+ class CustomerUserConflictException extends UserAlreadyExistsException
8
+ {
9
+ public function __construct(string $message = '', ?User $user = null, int $code = 0, ?\Throwable $previous = null)
10
+ {
11
+ parent::__construct($message, $user, $code, $previous);
12
+ }
13
+ }
@@ -39,6 +39,10 @@ class ContactController extends Controller
39
39
  }
40
40
 
41
41
  try {
42
+ $contactCandidate = new Contact($input);
43
+ $contactCandidate->company_uuid = session('company');
44
+ $contactCandidate->assertCustomerIdentityIsAvailable();
45
+
42
46
  // create the contact
43
47
  $contact = Contact::updateOrCreate(
44
48
  [
@@ -106,8 +110,19 @@ class ContactController extends Controller
106
110
  }
107
111
  }
108
112
 
109
- // update the contact
110
- $contact->update($input);
113
+ try {
114
+ // update the contact
115
+ $contactCandidate = $contact->replicate();
116
+ $contactCandidate->forceFill($contact->getAttributes());
117
+ $contactCandidate->exists = $contact->exists;
118
+ $contactCandidate->forceFill($input);
119
+ $contactCandidate->assertCustomerIdentityIsAvailable();
120
+
121
+ $contact->update($input);
122
+ } catch (\Exception $e) {
123
+ return response()->apiError($e->getMessage());
124
+ }
125
+
111
126
  $contact->flushAttributesCache();
112
127
 
113
128
  // response the contact resource