@fleetbase/fleetops-engine 0.6.52 → 0.6.54

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 (151) hide show
  1. package/addon/components/device/details.hbs +106 -135
  2. package/addon/components/device/details.js +174 -1
  3. package/addon/components/device/panel-header.hbs +2 -2
  4. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  5. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
  6. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  7. package/addon/components/layout/fleet-ops-sidebar.js +364 -395
  8. package/addon/components/maintenance-schedule/form.js +1 -0
  9. package/addon/components/order/form/details.hbs +1 -43
  10. package/addon/components/order/form/details.js +18 -53
  11. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  12. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  13. package/addon/components/order/form/payload.js +50 -2
  14. package/addon/components/order/form/route.js +15 -0
  15. package/addon/components/order/form/service-rate.hbs +62 -11
  16. package/addon/components/order/form/service-rate.js +124 -2
  17. package/addon/components/order/form.hbs +2 -0
  18. package/addon/components/telematic/details.hbs +7 -1
  19. package/addon/components/telematic/form.js +16 -8
  20. package/addon/components/work-order/details.hbs +5 -1
  21. package/addon/components/work-order/form.hbs +26 -4
  22. package/addon/components/work-order/form.js +5 -1
  23. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  24. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  25. package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
  26. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -1
  28. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  29. package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
  30. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  31. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  32. package/addon/controllers/maintenance/index.js +100 -0
  33. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  34. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  35. package/addon/controllers/management/index.js +88 -0
  36. package/addon/controllers/settings/index.js +142 -0
  37. package/addon/extension.js +21 -0
  38. package/addon/routes/analytics/index.js +3 -0
  39. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  40. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  41. package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
  42. package/addon/routes/connectivity/devices/index.js +3 -0
  43. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  44. package/addon/routes/connectivity/telematics/details/devices.js +4 -0
  45. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  46. package/addon/routes/connectivity/telematics/details.js +1 -1
  47. package/addon/routes/connectivity/telematics/edit.js +1 -1
  48. package/addon/routes/maintenance/index.js +3 -0
  49. package/addon/routes/maintenance/work-orders/index.js +3 -0
  50. package/addon/routes/settings/index.js +3 -0
  51. package/addon/routes.js +2 -0
  52. package/addon/services/order-creation.js +54 -1
  53. package/addon/services/order-validation.js +3 -2
  54. package/addon/services/service-rate-actions.js +1 -0
  55. package/addon/styles/fleetops-engine.css +1821 -6
  56. package/addon/templates/analytics/index.hbs +14 -0
  57. package/addon/templates/analytics/reports/index.hbs +4 -4
  58. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  59. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  60. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
  61. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  62. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  63. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  64. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  65. package/addon/templates/maintenance/index.hbs +113 -0
  66. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  67. package/addon/templates/management/index.hbs +109 -73
  68. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  69. package/addon/templates/operations/orders/index.hbs +1 -1
  70. package/addon/templates/operations/service-rates/index.hbs +1 -1
  71. package/addon/templates/settings/index.hbs +83 -0
  72. package/addon/templates/settings/routing.hbs +2 -2
  73. package/addon/utils/fleet-ops-options.js +38 -0
  74. package/app/components/order/form/orchestrator-constraints.js +1 -0
  75. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  76. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  77. package/app/controllers/maintenance/index.js +1 -0
  78. package/app/controllers/management/index.js +1 -0
  79. package/app/controllers/settings/index.js +1 -0
  80. package/app/routes/analytics/index.js +1 -0
  81. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  82. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  83. package/app/routes/maintenance/index.js +1 -0
  84. package/app/routes/settings/index.js +1 -0
  85. package/app/templates/analytics/index.js +1 -0
  86. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  87. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  88. package/app/templates/maintenance/index.js +1 -0
  89. package/app/templates/management/index.js +1 -0
  90. package/app/templates/settings/index.js +1 -0
  91. package/composer.json +1 -1
  92. package/extension.json +1 -1
  93. package/package.json +3 -3
  94. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  95. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  96. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  97. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  98. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  99. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  100. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  101. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  102. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  103. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
  104. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
  105. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  106. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  107. package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
  108. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  109. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  112. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  113. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  114. package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
  115. package/server/src/Http/Filter/DeviceFilter.php +131 -0
  116. package/server/src/Http/Filter/DriverFilter.php +6 -0
  117. package/server/src/Http/Filter/SensorFilter.php +125 -0
  118. package/server/src/Http/Filter/VehicleFilter.php +19 -0
  119. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  120. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  121. package/server/src/Http/Resources/v1/Driver.php +11 -2
  122. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  123. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  124. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  125. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  126. package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
  127. package/server/src/Models/Device.php +15 -1
  128. package/server/src/Models/DeviceEvent.php +1 -1
  129. package/server/src/Models/Driver.php +17 -1
  130. package/server/src/Models/Sensor.php +15 -2
  131. package/server/src/Models/Telematic.php +1 -1
  132. package/server/src/Models/Vehicle.php +5 -1
  133. package/server/src/Models/WorkOrder.php +5 -2
  134. package/server/src/Observers/DriverObserver.php +3 -3
  135. package/server/src/Observers/FleetObserver.php +23 -0
  136. package/server/src/Observers/VehicleObserver.php +3 -3
  137. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  138. package/server/src/Support/LiveCacheService.php +1 -1
  139. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
  140. package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
  141. package/server/src/Support/Telematics/TelematicService.php +330 -30
  142. package/server/src/routes.php +10 -0
  143. package/server/tests/DeviceFilterTest.php +36 -0
  144. package/server/tests/HubRoutesTest.php +62 -0
  145. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  146. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  147. package/server/tests/TelematicsHardeningTest.php +455 -3
  148. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  149. package/translations/ar-ae.yml +1 -1
  150. package/translations/en-us.yaml +1 -0
  151. package/translations/vi-vn.yaml +1 -1
@@ -0,0 +1,47 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Models\Driver;
6
+ use Fleetbase\FleetOps\Models\Order;
7
+ use Illuminate\Bus\Queueable;
8
+ use Illuminate\Contracts\Queue\ShouldQueue;
9
+ use Illuminate\Foundation\Bus\Dispatchable;
10
+ use Illuminate\Queue\InteractsWithQueue;
11
+ use Illuminate\Queue\SerializesModels;
12
+
13
+ class NotifyBulkAssignedDriver implements ShouldQueue
14
+ {
15
+ use Dispatchable;
16
+ use InteractsWithQueue;
17
+ use Queueable;
18
+ use SerializesModels;
19
+
20
+ public function __construct(public array $orderUuids, public string $driverUuid)
21
+ {
22
+ }
23
+
24
+ public function handle(): void
25
+ {
26
+ $driver = Driver::where('uuid', $this->driverUuid)->first();
27
+ if (!$driver) {
28
+ return;
29
+ }
30
+
31
+ Order::whereIn('uuid', $this->orderUuids)
32
+ ->cursor()
33
+ ->each(function (Order $order) use ($driver): void {
34
+ $order->setRelation('driverAssigned', $driver);
35
+ $order->driver_assigned_uuid = $driver->uuid;
36
+
37
+ try {
38
+ $order->notifyDriverAssigned();
39
+ } catch (\Throwable $e) {
40
+ logger()->warning(
41
+ 'Failed notifying driver on order ' . $order->uuid,
42
+ ['error' => $e->getMessage()]
43
+ );
44
+ }
45
+ });
46
+ }
47
+ }
@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
10
10
  use Illuminate\Foundation\Bus\Dispatchable;
11
11
  use Illuminate\Queue\InteractsWithQueue;
12
12
  use Illuminate\Queue\SerializesModels;
13
+ use Illuminate\Support\Facades\Cache;
13
14
  use Illuminate\Support\Facades\Log;
14
15
 
15
16
  /**
@@ -27,7 +28,7 @@ class SyncTelematicDevicesJob implements ShouldQueue
27
28
  public Telematic $telematic;
28
29
  public array $options;
29
30
  public string $jobId;
30
- public int $tries = 3;
31
+ public int $tries = 1;
31
32
  public int $timeout = 300;
32
33
 
33
34
  /**
@@ -46,83 +47,186 @@ class SyncTelematicDevicesJob implements ShouldQueue
46
47
  public function handle(TelematicProviderRegistry $registry, TelematicService $service): void
47
48
  {
48
49
  $correlationId = \Illuminate\Support\Str::uuid()->toString();
50
+ $lockKey = 'fleetops:sync-telematic-devices:' . $this->telematic->uuid;
51
+ $lock = Cache::lock($lockKey, $this->timeout + 60);
49
52
 
50
- Log::info('Device discovery started', [
51
- 'correlation_id' => $correlationId,
52
- 'telematic_uuid' => $this->telematic->uuid,
53
- 'provider' => $this->telematic->provider,
54
- ]);
53
+ if (!$lock->get()) {
54
+ Log::info('Device discovery skipped because another sync is already running', [
55
+ 'correlation_id' => $correlationId,
56
+ 'telematic_uuid' => $this->telematic->uuid,
57
+ 'provider' => $this->telematic->provider,
58
+ 'lock_key' => $lockKey,
59
+ ]);
55
60
 
56
- try {
57
- $provider = $registry->resolve($this->telematic->provider);
58
- $provider->connect($this->telematic);
61
+ $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
62
+ 'last_sync_job_id' => $this->jobId,
63
+ 'last_sync_result' => 'skipped',
64
+ 'last_sync_skipped_reason' => 'sync_already_running',
65
+ 'last_sync_skipped_at' => now()->toDateTimeString(),
66
+ ]);
67
+ $this->telematic->save();
59
68
 
60
- $cursor = null;
61
- $totalSynced = 0;
69
+ return;
70
+ }
62
71
 
63
- do {
64
- $response = $provider->fetchDevices([
65
- 'limit' => $this->options['limit'] ?? 100,
66
- 'cursor' => $cursor,
67
- 'filters' => $this->options['filters'] ?? [],
68
- ]);
72
+ try {
73
+ Log::info('Device discovery started', [
74
+ 'correlation_id' => $correlationId,
75
+ 'telematic_uuid' => $this->telematic->uuid,
76
+ 'provider' => $this->telematic->provider,
77
+ ]);
69
78
 
70
- foreach ($response['devices'] as $devicePayload) {
71
- $normalizedDevice = $provider->normalizeDevice($devicePayload);
72
- try {
73
- $service->linkDevice($this->telematic, $normalizedDevice);
74
- $totalSynced++;
75
- } catch (\Illuminate\Validation\ValidationException $e) {
76
- Log::warning('Skipping telematics device without provider identity', [
77
- 'correlation_id' => $correlationId,
78
- 'provider' => $this->telematic->provider,
79
- ]);
79
+ $cursor = null;
80
+ $totalFetched = 0;
81
+ $totalLinked = 0;
82
+ $totalEvents = 0;
83
+ $totalSensors = 0;
84
+ $totalSkipped = 0;
85
+ $pageCount = 0;
86
+ $lastProviderAllCount = null;
87
+ $lastProviderFiltersCount = null;
88
+
89
+ try {
90
+ $provider = $registry->resolve($this->telematic->provider);
91
+ $provider->connect($this->telematic);
92
+
93
+ do {
94
+ $response = $provider->fetchDevices([
95
+ 'limit' => $this->options['limit'] ?? null,
96
+ 'cursor' => $cursor,
97
+ 'filters' => $this->options['filters'] ?? [],
98
+ ]);
99
+
100
+ $devices = $response['devices'] ?? [];
101
+ $pagination = $response['pagination'] ?? [];
102
+ $pageCount++;
103
+ $totalFetched += count($devices);
104
+
105
+ if (isset($pagination['allCount'])) {
106
+ $lastProviderAllCount = $pagination['allCount'];
80
107
  }
81
- }
82
108
 
83
- $cursor = $response['next_cursor'];
84
-
85
- Log::info('Device discovery progress', [
86
- 'correlation_id' => $correlationId,
87
- 'synced' => $totalSynced,
88
- 'has_more' => $response['has_more'],
89
- ]);
109
+ if (isset($pagination['filtersCount'])) {
110
+ $lastProviderFiltersCount = $pagination['filtersCount'];
111
+ }
90
112
 
91
- // Broadcast progress (TODO: implement WebSocket broadcasting)
92
- } while ($response['has_more'] && $cursor);
113
+ Log::info('Device discovery page fetched', [
114
+ 'correlation_id' => $correlationId,
115
+ 'telematic_uuid' => $this->telematic->uuid,
116
+ 'provider' => $this->telematic->provider,
117
+ 'page' => $pageCount,
118
+ 'cursor' => $cursor,
119
+ 'limit' => $pagination['limit'] ?? ($this->options['limit'] ?? null),
120
+ 'result_count' => count($devices),
121
+ 'all_count' => $lastProviderAllCount,
122
+ 'filters_count' => $lastProviderFiltersCount,
123
+ 'next_cursor' => $response['next_cursor'] ?? null,
124
+ 'has_more' => $response['has_more'] ?? false,
125
+ ]);
126
+
127
+ foreach ($devices as $devicePayload) {
128
+ $normalizedDevice = $provider->normalizeDevice($devicePayload);
129
+ try {
130
+ $result = $service->ingestDeviceSnapshot($this->telematic, $provider, $devicePayload);
131
+ $totalLinked++;
132
+ if ($result['event']) {
133
+ $totalEvents++;
134
+ }
135
+ $totalSensors += $result['sensors'] ?? 0;
136
+ } catch (\Illuminate\Validation\ValidationException $e) {
137
+ $totalSkipped++;
138
+ Log::warning('Skipping telematics device without provider identity', [
139
+ 'correlation_id' => $correlationId,
140
+ 'provider' => $this->telematic->provider,
141
+ 'provider_unit_id' => $devicePayload['_id'] ?? $devicePayload['id'] ?? null,
142
+ 'device_id' => $normalizedDevice['device_id'] ?? null,
143
+ 'name' => $normalizedDevice['name'] ?? $devicePayload['name'] ?? null,
144
+ 'imei' => $devicePayload['imei'] ?? null,
145
+ ]);
146
+ }
147
+ }
93
148
 
94
- Log::info('Device discovery completed', [
95
- 'correlation_id' => $correlationId,
96
- 'total_synced' => $totalSynced,
97
- ]);
149
+ $cursor = $response['next_cursor'] ?? null;
150
+
151
+ Log::info('Device discovery progress', [
152
+ 'correlation_id' => $correlationId,
153
+ 'fetched' => $totalFetched,
154
+ 'linked' => $totalLinked,
155
+ 'events' => $totalEvents,
156
+ 'sensors' => $totalSensors,
157
+ 'skipped' => $totalSkipped,
158
+ 'has_more' => $response['has_more'] ?? false,
159
+ ]);
160
+
161
+ // Broadcast progress (TODO: implement WebSocket broadcasting)
162
+ } while (($response['has_more'] ?? false) && $cursor);
163
+
164
+ Log::info('Device discovery completed', [
165
+ 'correlation_id' => $correlationId,
166
+ 'total_fetched' => $totalFetched,
167
+ 'total_linked' => $totalLinked,
168
+ 'total_events' => $totalEvents,
169
+ 'total_sensors' => $totalSensors,
170
+ 'total_skipped' => $totalSkipped,
171
+ 'page_count' => $pageCount,
172
+ 'provider_all_count' => $lastProviderAllCount,
173
+ 'provider_filter_count' => $lastProviderFiltersCount,
174
+ ]);
98
175
 
99
- $this->telematic->status = 'active';
100
- $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
101
- 'last_sync_job_id' => $this->jobId,
102
- 'last_sync_completed_at' => now()->toDateTimeString(),
103
- 'last_sync_result' => 'success',
104
- 'last_sync_total' => $totalSynced,
105
- 'last_sync_error' => null,
106
- ]);
107
- $this->telematic->save();
108
- } catch (\Exception $e) {
109
- Log::error('Device discovery failed', [
110
- 'correlation_id' => $correlationId,
111
- 'error' => $e->getMessage(),
112
- 'exception' => get_class($e),
113
- ]);
176
+ $this->telematic->status = 'active';
177
+ $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
178
+ 'last_sync_job_id' => $this->jobId,
179
+ 'last_sync_completed_at' => now()->toDateTimeString(),
180
+ 'last_sync_result' => 'success',
181
+ 'last_sync_total' => $totalLinked,
182
+ 'last_sync_fetched_total' => $totalFetched,
183
+ 'last_sync_linked_total' => $totalLinked,
184
+ 'last_sync_events_total' => $totalEvents,
185
+ 'last_sync_sensors_total' => $totalSensors,
186
+ 'last_sync_skipped_total' => $totalSkipped,
187
+ 'last_sync_page_count' => $pageCount,
188
+ 'last_sync_provider_total' => $lastProviderFiltersCount ?? $lastProviderAllCount,
189
+ 'last_sync_provider_all_count' => $lastProviderAllCount,
190
+ 'last_sync_provider_filters_count' => $lastProviderFiltersCount,
191
+ 'last_sync_error' => null,
192
+ 'last_sync_error_context' => null,
193
+ ]);
194
+ $this->telematic->save();
195
+ } catch (\Exception $e) {
196
+ $failureContext = method_exists($e, 'context') ? $e->context() : [];
197
+ $failureMessage = $this->safeSyncErrorMessage($e);
198
+
199
+ Log::error('Device discovery failed', [
200
+ 'correlation_id' => $correlationId,
201
+ 'error' => $failureMessage,
202
+ 'exception' => get_class($e),
203
+ 'provider_context' => $failureContext,
204
+ ]);
114
205
 
115
- $this->telematic->status = 'error';
116
- $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
117
- 'last_sync_job_id' => $this->jobId,
118
- 'last_sync_result' => 'failed',
119
- 'last_sync_error' => 'Device sync failed. Review the provider connection and server logs, then try again.',
120
- 'last_sync_error_type' => class_basename($e),
121
- 'last_sync_failed_at' => now()->toDateTimeString(),
122
- ]);
123
- $this->telematic->save();
206
+ $this->telematic->status = 'error';
207
+ $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
208
+ 'last_sync_job_id' => $this->jobId,
209
+ 'last_sync_result' => 'failed',
210
+ 'last_sync_fetched_total' => $totalFetched,
211
+ 'last_sync_linked_total' => $totalLinked,
212
+ 'last_sync_events_total' => $totalEvents,
213
+ 'last_sync_sensors_total' => $totalSensors,
214
+ 'last_sync_skipped_total' => $totalSkipped,
215
+ 'last_sync_page_count' => $pageCount,
216
+ 'last_sync_provider_total' => $lastProviderFiltersCount ?? $lastProviderAllCount,
217
+ 'last_sync_provider_all_count' => $lastProviderAllCount,
218
+ 'last_sync_provider_filters_count' => $lastProviderFiltersCount,
219
+ 'last_sync_error' => $failureMessage,
220
+ 'last_sync_error_type' => class_basename($e),
221
+ 'last_sync_error_context' => $failureContext,
222
+ 'last_sync_failed_at' => now()->toDateTimeString(),
223
+ ]);
224
+ $this->telematic->save();
124
225
 
125
- throw $e;
226
+ throw $e;
227
+ }
228
+ } finally {
229
+ $lock->release();
126
230
  }
127
231
  }
128
232
 
@@ -133,4 +237,15 @@ class SyncTelematicDevicesJob implements ShouldQueue
133
237
  {
134
238
  return $this->jobId;
135
239
  }
240
+
241
+ protected function safeSyncErrorMessage(\Throwable $e): string
242
+ {
243
+ $message = $e->getMessage();
244
+
245
+ if (!$message || preg_match('/(token=|password|client_secret|authorization|bearer\s+[a-z0-9._-]+)/i', $message)) {
246
+ return 'Device sync failed. Review the provider connection and safe sync metadata, then try again.';
247
+ }
248
+
249
+ return $message;
250
+ }
136
251
  }
@@ -74,7 +74,21 @@ class Device extends Model
74
74
  *
75
75
  * @var array
76
76
  */
77
- protected $filterParams = ['status', 'warranty_uuid', 'attachable_type', 'attachable_uuid', 'telematic_uuid', 'provider'];
77
+ protected $filterParams = [
78
+ 'status',
79
+ 'attachment_state',
80
+ 'vehicle',
81
+ 'connection_status',
82
+ 'device_id',
83
+ 'last_online_at',
84
+ 'updated_at',
85
+ 'warranty_uuid',
86
+ 'attachable_type',
87
+ 'attachable_uuid',
88
+ 'telematic',
89
+ 'telematic_uuid',
90
+ 'provider',
91
+ ];
78
92
 
79
93
  /**
80
94
  * The attributes that are mass assignable.
@@ -65,7 +65,7 @@ class DeviceEvent extends Model
65
65
  *
66
66
  * @var array
67
67
  */
68
- protected $filterParams = ['event_type', 'severity', 'device_uuid', 'provider', 'code'];
68
+ protected $filterParams = ['event_type', 'severity', 'device_uuid', 'provider', 'code', 'processed', 'occurred_at', 'created_at', 'updated_at', 'telematic'];
69
69
 
70
70
  /**
71
71
  * The attributes that are mass assignable.
@@ -32,6 +32,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
32
32
  use Illuminate\Database\Eloquent\Relations\MorphOne;
33
33
  use Illuminate\Notifications\Notifiable;
34
34
  use Illuminate\Support\Arr;
35
+ use Illuminate\Support\Carbon;
35
36
  use Illuminate\Support\Collection;
36
37
  use Illuminate\Support\Facades\DB;
37
38
  use Illuminate\Support\Str;
@@ -196,7 +197,7 @@ class Driver extends Model
196
197
  *
197
198
  * @var array
198
199
  */
199
- protected $filterParams = ['vendor', 'facilitator', 'customer', 'fleet', 'photo_uuid', 'avatar_uuid', 'avatar_value'];
200
+ protected $filterParams = ['vendor', 'facilitator', 'customer', 'fleet', 'vehicle', 'photo_uuid', 'avatar_uuid', 'avatar_value'];
200
201
 
201
202
  /**
202
203
  * The session-agnostic columns for the model.
@@ -235,6 +236,21 @@ class Driver extends Model
235
236
  static::addGlobalScope(new DriverScope());
236
237
  }
237
238
 
239
+ public function setLicenseExpiryAttribute($value): void
240
+ {
241
+ if (empty($value)) {
242
+ if ($this->exists && !empty($this->getOriginal('license_expiry'))) {
243
+ return;
244
+ }
245
+
246
+ $this->attributes['license_expiry'] = null;
247
+
248
+ return;
249
+ }
250
+
251
+ $this->attributes['license_expiry'] = Carbon::parse($value)->toDateString();
252
+ }
253
+
238
254
  /**
239
255
  * @return BelongsTo
240
256
  */
@@ -66,14 +66,27 @@ class Sensor extends Model
66
66
  *
67
67
  * @var array
68
68
  */
69
- protected $searchableColumns = ['name', 'type', 'internal_id', 'unit', 'public_id'];
69
+ protected $searchableColumns = ['name', 'type', 'internal_id', 'serial_number', 'imei', 'unit', 'public_id'];
70
70
 
71
71
  /**
72
72
  * The attributes that can be used for filtering.
73
73
  *
74
74
  * @var array
75
75
  */
76
- protected $filterParams = ['type', 'sensor_type', 'status', 'device_uuid', 'warranty_uuid', 'sensorable_type', 'telematic_uuid'];
76
+ protected $filterParams = [
77
+ 'type',
78
+ 'sensor_type',
79
+ 'status',
80
+ 'device_uuid',
81
+ 'serial_number',
82
+ 'imei',
83
+ 'warranty_uuid',
84
+ 'sensorable_type',
85
+ 'telematic_uuid',
86
+ 'last_reading_at',
87
+ 'created_at',
88
+ 'updated_at',
89
+ ];
77
90
 
78
91
  /**
79
92
  * The attributes that are mass assignable.
@@ -63,7 +63,7 @@ class Telematic extends Model
63
63
  *
64
64
  * @var array
65
65
  */
66
- protected $filterParams = ['provider', 'status', 'warranty_uuid'];
66
+ protected $filterParams = ['public_id', 'provider', 'status', 'warranty_uuid'];
67
67
 
68
68
  /**
69
69
  * The attributes that are mass assignable.
@@ -72,7 +72,7 @@ class Vehicle extends Model
72
72
  *
73
73
  * @var array
74
74
  */
75
- protected $filterParams = ['vendor', 'driver', 'driver_uuid', 'vehicle_make', 'vehicle_model'];
75
+ protected $filterParams = ['vendor', 'driver', 'driver_uuid', 'vehicle_make', 'vehicle_model', 'telematic_uuid'];
76
76
 
77
77
  /**
78
78
  * Relationships to auto load with driver.
@@ -407,6 +407,10 @@ class Vehicle extends Model
407
407
  */
408
408
  public function getDisplayNameAttribute()
409
409
  {
410
+ if (isset($this->name) && strlen($this->name)) {
411
+ return $this->name;
412
+ }
413
+
410
414
  // Initialize an empty array to hold the name segments
411
415
  $nameSegments = [];
412
416
 
@@ -59,14 +59,14 @@ class WorkOrder extends Model
59
59
  *
60
60
  * @var array
61
61
  */
62
- protected $searchableColumns = ['code', 'subject', 'instructions', 'public_id'];
62
+ protected $searchableColumns = ['code', 'subject', 'category', 'instructions', 'public_id'];
63
63
 
64
64
  /**
65
65
  * The attributes that can be used for filtering.
66
66
  *
67
67
  * @var array
68
68
  */
69
- protected $filterParams = ['status', 'priority', 'target_type', 'assignee_type'];
69
+ protected $filterParams = ['category', 'status', 'priority', 'target_type', 'assignee_type'];
70
70
 
71
71
  /**
72
72
  * The attributes that are mass assignable.
@@ -77,6 +77,7 @@ class WorkOrder extends Model
77
77
  'company_uuid',
78
78
  'code',
79
79
  'subject',
80
+ 'category',
80
81
  'status',
81
82
  'priority',
82
83
  'target_type',
@@ -575,6 +576,7 @@ class WorkOrder extends Model
575
576
  $row = array_filter($row);
576
577
 
577
578
  $subject = Utils::or($row, ['subject', 'title', 'name']);
579
+ $category = Utils::or($row, ['category', 'work_order_category', 'work_type', 'type']);
578
580
  $status = Utils::or($row, ['status'], 'open');
579
581
  $priority = Utils::or($row, ['priority'], 'normal');
580
582
  $instructions = Utils::or($row, ['instructions', 'description', 'notes']);
@@ -592,6 +594,7 @@ class WorkOrder extends Model
592
594
  $workOrder = new static([
593
595
  'company_uuid' => session('company'),
594
596
  'subject' => $subject,
597
+ 'category' => $category,
595
598
  'status' => $status,
596
599
  'priority' => $priority,
597
600
  'instructions' => $instructions,
@@ -30,7 +30,7 @@ class DriverObserver
30
30
  */
31
31
  public function created(Driver $driver)
32
32
  {
33
- LiveCacheService::invalidate('drivers');
33
+ LiveCacheService::invalidateMultiple(['drivers', 'operations-monitor']);
34
34
  }
35
35
 
36
36
  /**
@@ -40,7 +40,7 @@ class DriverObserver
40
40
  */
41
41
  public function updated(Driver $driver)
42
42
  {
43
- LiveCacheService::invalidate('drivers');
43
+ LiveCacheService::invalidateMultiple(['drivers', 'operations-monitor']);
44
44
  }
45
45
 
46
46
  /**
@@ -70,6 +70,6 @@ class DriverObserver
70
70
  $user->delete();
71
71
  }
72
72
 
73
- LiveCacheService::invalidate('drivers');
73
+ LiveCacheService::invalidateMultiple(['drivers', 'operations-monitor']);
74
74
  }
75
75
  }
@@ -3,9 +3,30 @@
3
3
  namespace Fleetbase\FleetOps\Observers;
4
4
 
5
5
  use Fleetbase\FleetOps\Models\Fleet;
6
+ use Fleetbase\FleetOps\Support\LiveCacheService;
6
7
 
7
8
  class FleetObserver
8
9
  {
10
+ /**
11
+ * Handle the Fleet "created" event.
12
+ *
13
+ * @return void
14
+ */
15
+ public function created(Fleet $fleet)
16
+ {
17
+ LiveCacheService::invalidate('operations-monitor');
18
+ }
19
+
20
+ /**
21
+ * Handle the Fleet "updated" event.
22
+ *
23
+ * @return void
24
+ */
25
+ public function updated(Fleet $fleet)
26
+ {
27
+ LiveCacheService::invalidate('operations-monitor');
28
+ }
29
+
9
30
  /**
10
31
  * Handle the Driver "deleted" event.
11
32
  *
@@ -15,5 +36,7 @@ class FleetObserver
15
36
  {
16
37
  // If the fleet being deleted is set as parent fleet, remove it as the parent fleet
17
38
  $subFleets = Fleet::where(['parent_fleet_uuid' => $fleet->uuid])->update(['parent_fleet_uuid' => null]);
39
+
40
+ LiveCacheService::invalidate('operations-monitor');
18
41
  }
19
42
  }
@@ -30,7 +30,7 @@ class VehicleObserver
30
30
  }
31
31
  }
32
32
 
33
- LiveCacheService::invalidate('vehicles');
33
+ LiveCacheService::invalidateMultiple(['vehicles', 'operations-monitor']);
34
34
  }
35
35
 
36
36
  /**
@@ -55,7 +55,7 @@ class VehicleObserver
55
55
  }
56
56
  }
57
57
 
58
- LiveCacheService::invalidate('vehicles');
58
+ LiveCacheService::invalidateMultiple(['vehicles', 'operations-monitor']);
59
59
  }
60
60
 
61
61
  /**
@@ -68,6 +68,6 @@ class VehicleObserver
68
68
  // Unassign the deleted vehicle from matching driver/(s)
69
69
  Driver::where(['vehicle_uuid' => $vehicle->uuid])->delete();
70
70
 
71
- LiveCacheService::invalidate('vehicles');
71
+ LiveCacheService::invalidateMultiple(['vehicles', 'operations-monitor']);
72
72
  }
73
73
  }
@@ -7,6 +7,7 @@ use Brick\Geo\Engine\GEOSEngine;
7
7
  use Fleetbase\Providers\CoreServiceProvider;
8
8
  use Fleetbase\Support\NotificationRegistry;
9
9
  use Fleetbase\Support\Utils;
10
+ use Illuminate\Database\Eloquent\Relations\Relation;
10
11
 
11
12
  if (!Utils::classExists(CoreServiceProvider::class)) {
12
13
  throw new \Exception('FleetOps cannot be loaded without `fleetbase/core-api` installed!');
@@ -68,6 +69,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
68
69
  \Fleetbase\FleetOps\Console\Commands\ProcessMaintenanceTriggers::class,
69
70
  \Fleetbase\FleetOps\Console\Commands\SendMaintenanceReminders::class,
70
71
  \Fleetbase\FleetOps\Console\Commands\ProcessOperationalAlerts::class,
72
+ \Fleetbase\FleetOps\Console\Commands\SyncTelematics::class,
71
73
  ];
72
74
 
73
75
  /**
@@ -129,6 +131,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
129
131
  */
130
132
  public function boot()
131
133
  {
134
+ $this->registerMorphMap();
132
135
  $this->registerObservers();
133
136
  $this->registerCommands();
134
137
  $this->scheduleCommands(function ($schedule) {
@@ -139,6 +142,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
139
142
  $schedule->command('fleetops:process-maintenance-triggers')->daily()->withoutOverlapping()->storeOutputInDb();
140
143
  $schedule->command('fleetops:send-maintenance-reminders')->daily()->withoutOverlapping()->storeOutputInDb();
141
144
  $schedule->command('fleetops:process-operational-alerts')->everyMinute()->withoutOverlapping()->storeOutputInDb();
145
+ $schedule->command('fleetops:sync-telematics')->everyMinute()->withoutOverlapping()->storeOutputInDb();
142
146
  });
143
147
  $this->registerNotifications();
144
148
  $this->registerExpansionsFrom(__DIR__ . '/../Expansions');
@@ -194,6 +198,14 @@ class FleetOpsServiceProvider extends CoreServiceProvider
194
198
  }
195
199
  }
196
200
 
201
+ public function registerMorphMap(): void
202
+ {
203
+ Relation::morphMap([
204
+ 'Fleetbase\\Models\\Vehicle' => \Fleetbase\FleetOps\Models\Vehicle::class,
205
+ '\\Fleetbase\\Models\\Vehicle' => \Fleetbase\FleetOps\Models\Vehicle::class,
206
+ ]);
207
+ }
208
+
197
209
  public function registerNotifications()
198
210
  {
199
211
  // Register Notifications
@@ -108,7 +108,7 @@ class LiveCacheService
108
108
  }
109
109
  } else {
110
110
  // Invalidate all endpoints
111
- $endpoints = ['orders', 'routes', 'coordinates', 'drivers', 'vehicles', 'places'];
111
+ $endpoints = ['orders', 'routes', 'coordinates', 'drivers', 'vehicles', 'places', 'operations-monitor'];
112
112
  foreach ($endpoints as $ep) {
113
113
  static::incrementVersion($ep);
114
114
  }