@fleetbase/fleetops-engine 0.6.20 → 0.6.22

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 (162) hide show
  1. package/addon/components/custom-entity/form.hbs +14 -14
  2. package/addon/components/device/card.hbs +1 -0
  3. package/addon/components/device/card.js +3 -0
  4. package/addon/components/device/details.hbs +92 -43
  5. package/addon/components/device/form.hbs +108 -60
  6. package/addon/components/device/form.js +36 -8
  7. package/addon/components/device/manager.hbs +29 -0
  8. package/addon/components/device/manager.js +95 -0
  9. package/addon/components/device/panel-header.hbs +32 -0
  10. package/addon/components/device/panel-header.js +3 -0
  11. package/addon/components/device/pill.hbs +16 -0
  12. package/addon/components/device/pill.js +3 -0
  13. package/addon/components/driver/details.hbs +4 -0
  14. package/addon/components/driver/details.js +19 -1
  15. package/addon/components/driver/form.hbs +14 -3
  16. package/addon/components/driver/form.js +49 -47
  17. package/addon/components/driver/pill.hbs +17 -0
  18. package/addon/components/driver/pill.js +3 -0
  19. package/addon/components/entity/form.hbs +7 -5
  20. package/addon/components/layout/fleet-ops-sidebar.js +12 -12
  21. package/addon/components/map/drawer/device-event-listing.hbs +64 -0
  22. package/addon/components/map/drawer/device-event-listing.js +181 -0
  23. package/addon/components/map/drawer/position-listing.hbs +100 -0
  24. package/addon/components/map/drawer/position-listing.js +455 -0
  25. package/addon/components/map/drawer.js +2 -0
  26. package/addon/components/map/leaflet-live-map.hbs +7 -2
  27. package/addon/components/modals/attach-device.hbs +18 -0
  28. package/addon/components/modals/attach-device.js +3 -0
  29. package/addon/components/order/details/detail.hbs +2 -54
  30. package/addon/components/order/details/detail.js +1 -0
  31. package/addon/components/order/details/payload.hbs +6 -4
  32. package/addon/components/order/details/payload.js +2 -0
  33. package/addon/components/order/pill.hbs +34 -0
  34. package/addon/components/order/pill.js +3 -0
  35. package/addon/components/order-config-manager/custom-fields.js +1 -1
  36. package/addon/components/positions-replay.hbs +339 -0
  37. package/addon/components/positions-replay.js +409 -0
  38. package/addon/components/sensor/details.hbs +64 -38
  39. package/addon/components/sensor/form.hbs +112 -63
  40. package/addon/components/sensor/form.js +36 -24
  41. package/addon/components/sensor/panel-header.hbs +32 -0
  42. package/addon/components/sensor/panel-header.js +3 -0
  43. package/addon/components/telematic/details.hbs +40 -16
  44. package/addon/components/telematic/form.hbs +63 -64
  45. package/addon/components/telematic/form.js +73 -4
  46. package/addon/components/vehicle/card.hbs +2 -2
  47. package/addon/components/vehicle/details.hbs +4 -0
  48. package/addon/components/vehicle/details.js +19 -1
  49. package/addon/components/vehicle/form.hbs +4 -0
  50. package/addon/components/vehicle/pill.hbs +34 -0
  51. package/addon/components/vehicle/pill.js +3 -0
  52. package/addon/controllers/analytics/reports/index/edit.js +1 -1
  53. package/addon/controllers/connectivity/devices/index/details.js +22 -1
  54. package/addon/controllers/connectivity/devices/index/edit.js +66 -1
  55. package/addon/controllers/connectivity/devices/index.js +51 -9
  56. package/addon/controllers/connectivity/events/index.js +65 -16
  57. package/addon/controllers/connectivity/sensors/index/details.js +22 -1
  58. package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
  59. package/addon/controllers/connectivity/sensors/index.js +66 -6
  60. package/addon/controllers/connectivity/telematics/index/details.js +22 -1
  61. package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
  62. package/addon/controllers/connectivity/telematics/index.js +20 -11
  63. package/addon/controllers/management/fleets/index/details.js +26 -21
  64. package/addon/controllers/management/fleets/index/edit.js +9 -6
  65. package/addon/controllers/management/vehicles/index/details.js +26 -13
  66. package/addon/controllers/settings/custom-fields.js +6 -0
  67. package/addon/helpers/get-fleet-ops-option-label.js +11 -0
  68. package/addon/routes/connectivity/devices/index/details.js +27 -1
  69. package/addon/routes/connectivity/devices/index/edit.js +27 -1
  70. package/addon/routes/connectivity/sensors/index/details.js +27 -1
  71. package/addon/routes/connectivity/sensors/index/edit.js +27 -1
  72. package/addon/routes/connectivity/telematics/index/details.js +27 -1
  73. package/addon/routes/connectivity/telematics/index/edit.js +27 -1
  74. package/addon/routes/management/drivers/index/details/positions.js +3 -0
  75. package/addon/routes/management/vehicles/index/details/positions.js +3 -0
  76. package/addon/routes.js +4 -0
  77. package/addon/services/movement-tracker.js +81 -30
  78. package/addon/services/position-playback.js +486 -0
  79. package/addon/services/resource-metadata.js +46 -0
  80. package/addon/styles/fleetops-engine.css +157 -0
  81. package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
  82. package/addon/templates/connectivity/devices/index/details.hbs +15 -2
  83. package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
  84. package/addon/templates/connectivity/events/index.hbs +1 -1
  85. package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
  86. package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
  87. package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
  88. package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
  89. package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
  90. package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
  91. package/addon/templates/management/drivers/index/details/positions.hbs +2 -0
  92. package/addon/templates/management/vehicles/index/details/devices.hbs +1 -2
  93. package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
  94. package/addon/utils/fleet-ops-options.js +95 -0
  95. package/app/components/device/card.js +1 -0
  96. package/app/components/device/manager.js +1 -0
  97. package/app/components/device/panel-header.js +1 -0
  98. package/app/components/device/pill.js +1 -0
  99. package/app/components/driver/pill.js +1 -0
  100. package/app/components/map/drawer/device-event-listing.js +1 -0
  101. package/app/components/map/drawer/position-listing.js +1 -0
  102. package/app/components/modals/attach-device.js +1 -0
  103. package/app/components/order/pill.js +1 -0
  104. package/app/components/positions-replay.js +1 -0
  105. package/app/components/sensor/panel-header.js +1 -0
  106. package/app/components/vehicle/pill.js +1 -0
  107. package/app/helpers/get-fleet-ops-option-label.js +1 -0
  108. package/app/routes/management/drivers/index/details/positions.js +1 -0
  109. package/app/routes/management/vehicles/index/details/positions.js +1 -0
  110. package/app/services/position-playback.js +1 -0
  111. package/app/services/resource-metadata.js +1 -0
  112. package/app/templates/management/drivers/index/details/positions.js +1 -0
  113. package/app/templates/management/vehicles/index/details/positions.js +1 -0
  114. package/composer.json +1 -1
  115. package/extension.json +1 -1
  116. package/package.json +4 -4
  117. package/server/config/telematics.php +111 -0
  118. package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
  119. package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
  120. package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
  121. package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
  122. package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
  123. package/server/src/Contracts/TelematicProviderInterface.php +119 -0
  124. package/server/src/Exceptions/TelematicProviderException.php +14 -0
  125. package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
  126. package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
  127. package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
  128. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
  129. package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
  130. package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
  131. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
  132. package/server/src/Http/Controllers/TelematicWebhookController.php +169 -0
  133. package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
  134. package/server/src/Http/Filter/PositionFilter.php +35 -0
  135. package/server/src/Http/Resources/v1/Position.php +44 -0
  136. package/server/src/Jobs/ReplayPositions.php +64 -0
  137. package/server/src/Jobs/SendPositionReplay.php +65 -0
  138. package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
  139. package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
  140. package/server/src/Models/Asset.php +10 -8
  141. package/server/src/Models/Device.php +79 -12
  142. package/server/src/Models/DeviceEvent.php +33 -3
  143. package/server/src/Models/Driver.php +28 -1
  144. package/server/src/Models/Maintenance.php +15 -12
  145. package/server/src/Models/Part.php +2 -0
  146. package/server/src/Models/Payload.php +0 -1
  147. package/server/src/Models/Place.php +4 -1
  148. package/server/src/Models/Position.php +27 -17
  149. package/server/src/Models/Sensor.php +78 -13
  150. package/server/src/Models/Telematic.php +116 -6
  151. package/server/src/Models/TrackingNumber.php +3 -1
  152. package/server/src/Models/Vehicle.php +8 -11
  153. package/server/src/Models/WorkOrder.php +8 -5
  154. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  155. package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
  156. package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
  157. package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
  158. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
  159. package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
  160. package/server/src/Support/Telematics/TelematicService.php +223 -0
  161. package/server/src/Support/Utils.php +1 -1
  162. package/server/src/routes.php +24 -1
@@ -0,0 +1,169 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers;
4
+
5
+ use Fleetbase\FleetOps\Models\Telematic;
6
+ use Fleetbase\FleetOps\Support\Telematics\TelematicProviderRegistry;
7
+ use Fleetbase\FleetOps\Support\Telematics\TelematicService;
8
+ use Fleetbase\Support\IdempotencyManager;
9
+ use Illuminate\Http\JsonResponse;
10
+ use Illuminate\Http\Request;
11
+ use Illuminate\Routing\Controller;
12
+ use Illuminate\Support\Facades\Log;
13
+ use Illuminate\Support\Str;
14
+
15
+ /**
16
+ * Class TelematicWebhookController.
17
+ *
18
+ * Handles webhook ingestion from telematics providers.
19
+ */
20
+ class TelematicWebhookController extends Controller
21
+ {
22
+ protected TelematicProviderRegistry $registry;
23
+ protected TelematicService $service;
24
+ protected IdempotencyManager $idempotency;
25
+
26
+ public function __construct(
27
+ TelematicProviderRegistry $registry,
28
+ TelematicService $service,
29
+ IdempotencyManager $idempotency,
30
+ ) {
31
+ $this->registry = $registry;
32
+ $this->service = $service;
33
+ $this->idempotency = $idempotency;
34
+ }
35
+
36
+ /**
37
+ * Handle provider webhook.
38
+ */
39
+ public function handle(Request $request, string $providerKey): JsonResponse
40
+ {
41
+ $correlationId = Str::uuid()->toString();
42
+
43
+ Log::info('Webhook received', [
44
+ 'correlation_id' => $correlationId,
45
+ 'provider' => $providerKey,
46
+ 'headers' => $request->headers->all(),
47
+ ]);
48
+
49
+ // Check idempotency
50
+ $idempotencyKey = $request->header('X-Idempotency-Key');
51
+ if ($idempotencyKey && $this->idempotency->isDuplicate($idempotencyKey)) {
52
+ Log::info('Duplicate webhook detected', [
53
+ 'correlation_id' => $correlationId,
54
+ 'idempotency_key' => $idempotencyKey,
55
+ ]);
56
+
57
+ return response()->json(['status' => 'duplicate'], 200);
58
+ }
59
+
60
+ // Get provider
61
+ $provider = $this->registry->resolve($providerKey);
62
+
63
+ // Find telematic for this provider
64
+ $telematic = Telematic::where('provider', $providerKey)->first();
65
+
66
+ if (!$telematic) {
67
+ Log::warning('No telematic found for provider', [
68
+ 'correlation_id' => $correlationId,
69
+ 'provider' => $providerKey,
70
+ ]);
71
+
72
+ return response()->json(['error' => 'No telematic configured'], 404);
73
+ }
74
+
75
+ // Validate signature
76
+ $signature = $request->header('X-Webhook-Signature');
77
+ $credentials = $this->service->getCredentials($telematic);
78
+
79
+ if ($signature && !$provider->validateWebhookSignature($request->getContent(), $signature, $credentials)) {
80
+ Log::warning('Invalid webhook signature', [
81
+ 'correlation_id' => $correlationId,
82
+ 'provider' => $providerKey,
83
+ ]);
84
+
85
+ return response()->json(['error' => 'Invalid signature'], 403);
86
+ }
87
+
88
+ // Process webhook
89
+ try {
90
+ $result = $provider->processWebhook($request->all(), $request->headers->all());
91
+
92
+ // Link devices
93
+ foreach ($result['devices'] as $deviceData) {
94
+ $this->service->linkDevice($telematic, $deviceData);
95
+ }
96
+
97
+ // Store events (TODO: implement event storage)
98
+ // Store sensors (TODO: implement sensor storage)
99
+
100
+ // Mark as processed
101
+ if ($idempotencyKey) {
102
+ $this->idempotency->markProcessed($idempotencyKey);
103
+ }
104
+
105
+ Log::info('Webhook processed successfully', [
106
+ 'correlation_id' => $correlationId,
107
+ 'devices_count' => count($result['devices']),
108
+ 'events_count' => count($result['events']),
109
+ ]);
110
+
111
+ return response()->json(['status' => 'processed'], 200);
112
+ } catch (\Exception $e) {
113
+ Log::error('Webhook processing failed', [
114
+ 'correlation_id' => $correlationId,
115
+ 'error' => $e->getMessage(),
116
+ ]);
117
+
118
+ return response()->json(['error' => 'Processing failed'], 500);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Handle custom provider ingest.
124
+ */
125
+ public function ingest(Request $request, string $id): JsonResponse
126
+ {
127
+ $telematic = Telematic::where('uuid', $id)->orWhere('public_id', $id)->firstOrFail();
128
+ $correlationId = Str::uuid()->toString();
129
+
130
+ Log::info('Custom ingest received', [
131
+ 'correlation_id' => $correlationId,
132
+ 'telematic_uuid' => $id,
133
+ ]);
134
+
135
+ // Check idempotency
136
+ $idempotencyKey = $request->header('X-Idempotency-Key');
137
+ if ($idempotencyKey && $this->idempotency->isDuplicate($idempotencyKey)) {
138
+ return response()->json(['status' => 'duplicate'], 200);
139
+ }
140
+
141
+ try {
142
+ // Process devices
143
+ if ($request->has('devices')) {
144
+ foreach ($request->input('devices') as $deviceData) {
145
+ $this->service->linkDevice($telematic, $deviceData);
146
+ }
147
+ }
148
+
149
+ // Mark as processed
150
+ if ($idempotencyKey) {
151
+ $this->idempotency->markProcessed($idempotencyKey);
152
+ }
153
+
154
+ Log::info('Custom ingest processed', [
155
+ 'correlation_id' => $correlationId,
156
+ 'devices_count' => count($request->input('devices', [])),
157
+ ]);
158
+
159
+ return response()->json(['status' => 'ingested'], 200);
160
+ } catch (\Exception $e) {
161
+ Log::error('Custom ingest failed', [
162
+ 'correlation_id' => $correlationId,
163
+ 'error' => $e->getMessage(),
164
+ ]);
165
+
166
+ return response()->json(['error' => 'Ingest failed'], 500);
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,68 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Filter;
4
+
5
+ use Fleetbase\FleetOps\Support\Utils;
6
+ use Fleetbase\Http\Filter\Filter;
7
+
8
+ class DeviceEventFilter extends Filter
9
+ {
10
+ public function queryForInternal()
11
+ {
12
+ $this->builder->where(
13
+ function ($query) {
14
+ $query->where('company_uuid', $this->session->get('company'));
15
+ }
16
+ );
17
+ }
18
+
19
+ public function queryForPublic()
20
+ {
21
+ $this->builder->where('company_uuid', $this->session->get('company'));
22
+ }
23
+
24
+ public function query(?string $searchQuery)
25
+ {
26
+ $this->builder->search($searchQuery);
27
+ }
28
+
29
+ public function telematic(?string $telematic)
30
+ {
31
+ $this->builder->whereHas('device', function ($query) use ($telematic) {
32
+ $query->whereHas('telematic', function ($query) use ($telematic) {
33
+ $query->where('uuid', $telematic);
34
+ $query->orWhere('public_id', $telematic);
35
+ });
36
+ });
37
+ }
38
+
39
+ public function device(?string $device)
40
+ {
41
+ $this->builder->whereHas('device', function ($query) use ($device) {
42
+ $query->where('uuid', $device);
43
+ $query->orWhere('public_id', $device);
44
+ });
45
+ }
46
+
47
+ public function createdAt($createdAt)
48
+ {
49
+ $createdAt = Utils::dateRange($createdAt);
50
+
51
+ if (is_array($createdAt)) {
52
+ $this->builder->whereBetween('created_at', $createdAt);
53
+ } else {
54
+ $this->builder->whereDate('created_at', $createdAt);
55
+ }
56
+ }
57
+
58
+ public function updatedAt($updatedAt)
59
+ {
60
+ $updatedAt = Utils::dateRange($updatedAt);
61
+
62
+ if (is_array($updatedAt)) {
63
+ $this->builder->whereBetween('updated_at', $updatedAt);
64
+ } else {
65
+ $this->builder->whereDate('updated_at', $updatedAt);
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Filter;
4
+
5
+ use Fleetbase\FleetOps\Support\Utils;
6
+ use Fleetbase\Http\Filter\Filter;
7
+
8
+ class PositionFilter extends Filter
9
+ {
10
+ public function queryForInternal()
11
+ {
12
+ $this->builder->where('company_uuid', $this->session->get('company'));
13
+ }
14
+
15
+ public function queryForPublic()
16
+ {
17
+ $this->builder->where('company_uuid', $this->session->get('company'));
18
+ }
19
+
20
+ public function query(?string $searchQuery)
21
+ {
22
+ $this->builder->search($searchQuery);
23
+ }
24
+
25
+ public function createdAt($createdAt)
26
+ {
27
+ $createdAt = Utils::dateRange($createdAt);
28
+
29
+ if (is_array($createdAt)) {
30
+ $this->builder->whereBetween('created_at', $createdAt);
31
+ } else {
32
+ $this->builder->whereDate('created_at', $createdAt);
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,44 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Resources\v1;
4
+
5
+ use Fleetbase\Http\Resources\FleetbaseResource;
6
+ use Fleetbase\LaravelMysqlSpatial\Types\Point;
7
+ use Fleetbase\Support\Http;
8
+ use Fleetbase\Support\Resolve;
9
+
10
+ class Position extends FleetbaseResource
11
+ {
12
+ /**
13
+ * Transform the resource into an array.
14
+ *
15
+ * @param \Illuminate\Http\Request $request
16
+ *
17
+ * @return array
18
+ */
19
+ public function toArray($request)
20
+ {
21
+ return [
22
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
23
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
24
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
25
+ 'order_uuid' => $this->when(Http::isInternalRequest(), $this->order_uuid),
26
+ 'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
27
+ 'destination_uuid' => $this->when(Http::isInternalRequest(), $this->destination_uuid),
28
+ 'subject_uuid' => $this->when(Http::isInternalRequest(), $this->subject_uuid),
29
+ 'subject_type' => $this->subject_type,
30
+ 'subject' => $this->whenLoaded('subject', fn () => Resolve::httpResourceForModel($this->subject)),
31
+ 'order' => $this->whenLoaded('order', fn () => new Order($this->order)),
32
+ 'destination' => $this->whenLoaded('destination', fn () => new Place($this->destination)),
33
+ 'heading' => $this->heading ?? 0,
34
+ 'bearing' => $this->bearing ?? 0,
35
+ 'speed' => $this->speed ?? 0,
36
+ 'altitude' => $this->altitude ?? 0,
37
+ 'latitude' => $this->latitude ?? 0,
38
+ 'longitude' => $this->longitude ?? 0,
39
+ 'coordinates' => $this->coordinates ?? new Point(0, 0),
40
+ 'updated_at' => $this->updated_at,
41
+ 'created_at' => $this->created_at,
42
+ ];
43
+ }
44
+ }
@@ -0,0 +1,64 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Carbon\Carbon;
6
+ use Fleetbase\FleetOps\Models\Position;
7
+ use Fleetbase\Support\SocketCluster\SocketClusterService;
8
+ use Illuminate\Bus\Queueable;
9
+ use Illuminate\Contracts\Queue\ShouldQueue;
10
+ use Illuminate\Foundation\Bus\Dispatchable;
11
+ use Illuminate\Queue\InteractsWithQueue;
12
+ use Illuminate\Queue\SerializesModels;
13
+ use Illuminate\Support\Facades\Log;
14
+
15
+ class ReplayPositions implements ShouldQueue
16
+ {
17
+ use Dispatchable;
18
+ use InteractsWithQueue;
19
+ use Queueable;
20
+ use SerializesModels;
21
+
22
+ /** @var \Illuminate\Support\Collection<int, Position> */
23
+ protected $positions;
24
+ protected string $channelId;
25
+ protected float $speed;
26
+ protected ?string $subjectUuid;
27
+
28
+ /** Max runtime per worker (seconds) */
29
+ public $timeout = 3600;
30
+
31
+ /**
32
+ * @param \Illuminate\Support\Collection<int, Position> $positions
33
+ */
34
+ public function __construct($positions, string $channelId, float $speed = 1, ?string $subjectUuid = null)
35
+ {
36
+ $this->positions = $positions;
37
+ $this->channelId = $channelId;
38
+ $this->speed = max($speed, 0.1);
39
+ $this->subjectUuid = $subjectUuid;
40
+ }
41
+
42
+ public function handle(): void
43
+ {
44
+ $socket = new SocketClusterService();
45
+
46
+ // Base timestamp to compute relative offsets
47
+ $baseTime = Carbon::parse($this->positions->first()->created_at);
48
+
49
+ foreach ($this->positions as $index => $position) {
50
+ $currentTime = Carbon::parse($position->created_at);
51
+ $offset = $baseTime->diffInSeconds($currentTime, false) / $this->speed;
52
+
53
+ // Schedule a small job for each event with its own delay
54
+ SendPositionReplay::dispatch(
55
+ $this->channelId,
56
+ $position,
57
+ $index,
58
+ $this->subjectUuid
59
+ )->delay(now()->addSeconds(max(0, $offset)));
60
+ }
61
+
62
+ Log::info("Replay scheduled for {$this->positions->count()} positions on channel {$this->channelId}");
63
+ }
64
+ }
@@ -0,0 +1,65 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Models\Position;
6
+ use Fleetbase\Support\SocketCluster\SocketClusterService;
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
+ use Illuminate\Support\Facades\Log;
13
+
14
+ class SendPositionReplay implements ShouldQueue
15
+ {
16
+ use Dispatchable;
17
+ use InteractsWithQueue;
18
+ use Queueable;
19
+ use SerializesModels;
20
+
21
+ protected string $channelId;
22
+ protected Position $position;
23
+ protected int $index;
24
+ protected ?string $subjectUuid;
25
+
26
+ public $tries = 3;
27
+ public $timeout = 60;
28
+
29
+ public function __construct(string $channelId, Position $position, int $index, ?string $subjectUuid = null)
30
+ {
31
+ $this->channelId = $channelId;
32
+ $this->position = $position;
33
+ $this->index = $index;
34
+ $this->subjectUuid = $subjectUuid;
35
+ }
36
+
37
+ public function handle(): void
38
+ {
39
+ $socket = new SocketClusterService();
40
+
41
+ $eventData = [
42
+ 'id' => uniqid('event_'),
43
+ 'api_version' => config('api.version'),
44
+ 'event' => 'position.simulated',
45
+ 'created_at' => $this->position->created_at->toDateTimeString(),
46
+ 'data' => [
47
+ 'id' => $this->subjectUuid ?? $this->position->subject_uuid,
48
+ 'location' => $this->position->coordinates,
49
+ 'heading' => $this->position->heading ?? 0,
50
+ 'speed' => $this->position->speed ?? 0,
51
+ 'altitude' => $this->position->altitude ?? 0,
52
+ 'additionalData' => [
53
+ 'index' => $this->index,
54
+ 'position_uuid' => $this->position->uuid,
55
+ ],
56
+ ],
57
+ ];
58
+
59
+ try {
60
+ $socket->send($this->channelId, $eventData);
61
+ } catch (\Throwable $e) {
62
+ Log::error("Failed to send replay event [{$this->position->uuid}]: {$e->getMessage()}");
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,106 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Models\Telematic;
6
+ use Fleetbase\FleetOps\Support\Telematics\TelematicService;
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
+ use Illuminate\Support\Facades\Log;
13
+
14
+ /**
15
+ * Class SyncTelematicDevicesJob.
16
+ *
17
+ * Job for discovering and syncing devices from a provider.
18
+ */
19
+ class SyncTelematicDevicesJob implements ShouldQueue
20
+ {
21
+ use Dispatchable;
22
+ use InteractsWithQueue;
23
+ use Queueable;
24
+ use SerializesModels;
25
+
26
+ public Telematic $telematic;
27
+ public array $options;
28
+ public int $tries = 3;
29
+ public int $timeout = 300;
30
+
31
+ /**
32
+ * Create a new job instance.
33
+ */
34
+ public function __construct(Telematic $telematic, array $options = [])
35
+ {
36
+ $this->telematic = $telematic;
37
+ $this->options = $options;
38
+ $this->queue = 'telematics-sync';
39
+ }
40
+
41
+ /**
42
+ * Execute the job.
43
+ */
44
+ public function handle(ProviderRegistry $registry, TelematicService $service): void
45
+ {
46
+ $correlationId = \Illuminate\Support\Str::uuid()->toString();
47
+
48
+ Log::info('Device discovery started', [
49
+ 'correlation_id' => $correlationId,
50
+ 'telematic_uuid' => $this->telematic->uuid,
51
+ 'provider' => $this->telematic->provider,
52
+ ]);
53
+
54
+ try {
55
+ $provider = $registry->resolve($this->telematic->provider);
56
+ $provider->connect($this->telematic);
57
+
58
+ $cursor = null;
59
+ $totalSynced = 0;
60
+
61
+ do {
62
+ $response = $provider->fetchDevices([
63
+ 'limit' => $this->options['limit'] ?? 100,
64
+ 'cursor' => $cursor,
65
+ 'filters' => $this->options['filters'] ?? [],
66
+ ]);
67
+
68
+ foreach ($response['devices'] as $devicePayload) {
69
+ $normalizedDevice = $provider->normalizeDevice($devicePayload);
70
+ $service->linkDevice($this->telematic, $normalizedDevice);
71
+ $totalSynced++;
72
+ }
73
+
74
+ $cursor = $response['next_cursor'];
75
+
76
+ Log::info('Device discovery progress', [
77
+ 'correlation_id' => $correlationId,
78
+ 'synced' => $totalSynced,
79
+ 'has_more' => $response['has_more'],
80
+ ]);
81
+
82
+ // Broadcast progress (TODO: implement WebSocket broadcasting)
83
+ } while ($response['has_more'] && $cursor);
84
+
85
+ Log::info('Device discovery completed', [
86
+ 'correlation_id' => $correlationId,
87
+ 'total_synced' => $totalSynced,
88
+ ]);
89
+ } catch (\Exception $e) {
90
+ Log::error('Device discovery failed', [
91
+ 'correlation_id' => $correlationId,
92
+ 'error' => $e->getMessage(),
93
+ ]);
94
+
95
+ throw $e;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get the job ID.
101
+ */
102
+ public function getJobId(): string
103
+ {
104
+ return $this->job->getJobId() ?? \Illuminate\Support\Str::uuid()->toString();
105
+ }
106
+ }
@@ -0,0 +1,102 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Models\Telematic;
6
+ use Illuminate\Bus\Queueable;
7
+ use Illuminate\Contracts\Queue\ShouldQueue;
8
+ use Illuminate\Foundation\Bus\Dispatchable;
9
+ use Illuminate\Queue\InteractsWithQueue;
10
+ use Illuminate\Queue\SerializesModels;
11
+ use Illuminate\Support\Facades\Crypt;
12
+ use Illuminate\Support\Facades\Log;
13
+ use Illuminate\Support\Str;
14
+
15
+ /**
16
+ * Class TestTelematicConnectionJob.
17
+ *
18
+ * Job for testing connection to a provider asynchronously.
19
+ */
20
+ class TestTelematicConnectionJob implements ShouldQueue
21
+ {
22
+ use Dispatchable;
23
+ use InteractsWithQueue;
24
+ use Queueable;
25
+ use SerializesModels;
26
+
27
+ public Telematic $telematic;
28
+ public int $tries = 1;
29
+ public int $timeout = 30;
30
+
31
+ /**
32
+ * Create a new job instance.
33
+ */
34
+ public function __construct(Telematic $telematic)
35
+ {
36
+ $this->telematic = $telematic;
37
+ $this->queue = 'telematics-priority';
38
+ }
39
+
40
+ /**
41
+ * Execute the job.
42
+ */
43
+ public function handle(ProviderRegistry $registry): void
44
+ {
45
+ $correlationId = Str::uuid()->toString();
46
+
47
+ Log::info('Connection test started', [
48
+ 'correlation_id' => $correlationId,
49
+ 'telematic_uuid' => $this->telematic->uuid,
50
+ 'provider' => $this->telematic->provider,
51
+ ]);
52
+
53
+ try {
54
+ $provider = $registry->resolve($this->telematic->provider);
55
+ $credentials = json_decode(Crypt::decryptString($this->telematic->credentials), true);
56
+
57
+ $result = $provider->testConnection($credentials);
58
+
59
+ if ($result['success']) {
60
+ $this->telematic->status = 'active';
61
+ $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
62
+ 'last_connection_test' => now()->toDateTimeString(),
63
+ 'last_test_result' => 'success',
64
+ ]);
65
+ } else {
66
+ $this->telematic->status = 'error';
67
+ $this->telematic->meta = array_merge($this->telematic->meta ?? [], [
68
+ 'last_connection_test' => now()->toDateTimeString(),
69
+ 'last_test_result' => 'failed',
70
+ 'last_error' => $result['message'],
71
+ ]);
72
+ }
73
+
74
+ $this->telematic->save();
75
+
76
+ Log::info('Connection test completed', [
77
+ 'correlation_id' => $correlationId,
78
+ 'success' => $result['success'],
79
+ ]);
80
+
81
+ // Broadcast result (TODO: implement WebSocket broadcasting)
82
+ } catch (\Exception $e) {
83
+ Log::error('Connection test failed', [
84
+ 'correlation_id' => $correlationId,
85
+ 'error' => $e->getMessage(),
86
+ ]);
87
+
88
+ $this->telematic->status = 'error';
89
+ $this->telematic->save();
90
+
91
+ throw $e;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Get the job ID.
97
+ */
98
+ public function getJobId(): string
99
+ {
100
+ return $this->job->getJobId() ?? Str::uuid()->toString();
101
+ }
102
+ }
@@ -3,6 +3,7 @@
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\Casts\PolymorphicType;
6
7
  use Fleetbase\FleetOps\Casts\Point;
7
8
  use Fleetbase\Models\Category;
8
9
  use Fleetbase\Models\File;
@@ -157,14 +158,15 @@ class Asset extends Model
157
158
  * @var array
158
159
  */
159
160
  protected $casts = [
160
- 'year' => 'integer',
161
- 'odometer' => 'integer',
162
- 'engine_hours' => 'integer',
163
- 'gvw' => 'decimal:2',
164
- 'capacity' => Json::class,
165
- 'specs' => Json::class,
166
- 'attributes' => Json::class,
167
- 'location' => Point::class,
161
+ 'year' => 'integer',
162
+ 'odometer' => 'integer',
163
+ 'engine_hours' => 'integer',
164
+ 'gvw' => 'decimal:2',
165
+ 'capacity' => Json::class,
166
+ 'specs' => Json::class,
167
+ 'attributes' => Json::class,
168
+ 'location' => Point::class,
169
+ 'assigned_to_type' => PolymorphicType::class,
168
170
  ];
169
171
 
170
172
  /**