@fleetbase/fleetops-engine 0.6.20 → 0.6.21

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 (120) hide show
  1. package/addon/components/custom-entity/form.hbs +14 -14
  2. package/addon/components/device/details.hbs +92 -43
  3. package/addon/components/device/form.hbs +108 -60
  4. package/addon/components/device/form.js +36 -8
  5. package/addon/components/device/panel-header.hbs +32 -0
  6. package/addon/components/device/panel-header.js +3 -0
  7. package/addon/components/driver/form.hbs +1 -1
  8. package/addon/components/driver/form.js +49 -47
  9. package/addon/components/entity/form.hbs +7 -5
  10. package/addon/components/layout/fleet-ops-sidebar.js +12 -12
  11. package/addon/components/map/drawer/device-event-listing.hbs +58 -0
  12. package/addon/components/map/drawer/device-event-listing.js +181 -0
  13. package/addon/components/map/drawer/position-listing.hbs +84 -0
  14. package/addon/components/map/drawer/position-listing.js +289 -0
  15. package/addon/components/map/drawer.js +2 -0
  16. package/addon/components/map/leaflet-live-map.hbs +7 -2
  17. package/addon/components/order/details/payload.hbs +6 -4
  18. package/addon/components/order/details/payload.js +2 -0
  19. package/addon/components/order-config-manager/custom-fields.js +1 -1
  20. package/addon/components/positions-replay.hbs +333 -0
  21. package/addon/components/positions-replay.js +372 -0
  22. package/addon/components/sensor/details.hbs +64 -38
  23. package/addon/components/sensor/form.hbs +112 -63
  24. package/addon/components/sensor/form.js +36 -24
  25. package/addon/components/sensor/panel-header.hbs +32 -0
  26. package/addon/components/sensor/panel-header.js +3 -0
  27. package/addon/components/telematic/details.hbs +40 -16
  28. package/addon/components/telematic/form.hbs +63 -64
  29. package/addon/components/telematic/form.js +73 -4
  30. package/addon/components/vehicle/card.hbs +1 -1
  31. package/addon/controllers/analytics/reports/index/edit.js +1 -1
  32. package/addon/controllers/connectivity/devices/index/details.js +22 -1
  33. package/addon/controllers/connectivity/devices/index/edit.js +66 -1
  34. package/addon/controllers/connectivity/devices/index.js +51 -9
  35. package/addon/controllers/connectivity/events/index.js +65 -16
  36. package/addon/controllers/connectivity/sensors/index/details.js +22 -1
  37. package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
  38. package/addon/controllers/connectivity/sensors/index.js +66 -6
  39. package/addon/controllers/connectivity/telematics/index/details.js +22 -1
  40. package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
  41. package/addon/controllers/connectivity/telematics/index.js +20 -11
  42. package/addon/controllers/management/fleets/index/details.js +26 -21
  43. package/addon/controllers/management/fleets/index/edit.js +9 -6
  44. package/addon/controllers/management/vehicles/index/details.js +21 -13
  45. package/addon/controllers/settings/custom-fields.js +6 -0
  46. package/addon/helpers/get-fleet-ops-option-label.js +11 -0
  47. package/addon/routes/connectivity/devices/index/details.js +27 -1
  48. package/addon/routes/connectivity/devices/index/edit.js +27 -1
  49. package/addon/routes/connectivity/sensors/index/details.js +27 -1
  50. package/addon/routes/connectivity/sensors/index/edit.js +27 -1
  51. package/addon/routes/connectivity/telematics/index/details.js +27 -1
  52. package/addon/routes/connectivity/telematics/index/edit.js +27 -1
  53. package/addon/routes/management/vehicles/index/details/positions.js +3 -0
  54. package/addon/routes.js +1 -0
  55. package/addon/services/movement-tracker.js +81 -30
  56. package/addon/styles/fleetops-engine.css +157 -0
  57. package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
  58. package/addon/templates/connectivity/devices/index/details.hbs +15 -2
  59. package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
  60. package/addon/templates/connectivity/events/index.hbs +1 -1
  61. package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
  62. package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
  63. package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
  64. package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
  65. package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
  66. package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
  67. package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
  68. package/addon/utils/fleet-ops-options.js +95 -0
  69. package/app/components/device/panel-header.js +1 -0
  70. package/app/components/map/drawer/device-event-listing.js +1 -0
  71. package/app/components/map/drawer/position-listing.js +1 -0
  72. package/app/components/positions-replay.js +1 -0
  73. package/app/components/sensor/panel-header.js +1 -0
  74. package/app/helpers/get-fleet-ops-option-label.js +1 -0
  75. package/app/routes/management/vehicles/index/details/positions.js +1 -0
  76. package/app/templates/management/vehicles/index/details/positions.js +1 -0
  77. package/composer.json +1 -1
  78. package/extension.json +1 -1
  79. package/package.json +4 -4
  80. package/server/config/telematics.php +111 -0
  81. package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
  82. package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
  83. package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
  84. package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
  85. package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
  86. package/server/src/Contracts/TelematicProviderInterface.php +119 -0
  87. package/server/src/Exceptions/TelematicProviderException.php +14 -0
  88. package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
  89. package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
  90. package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
  91. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
  92. package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
  93. package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
  94. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
  95. package/server/src/Http/Controllers/Internal/v1/TelematicWebhookController.php +170 -0
  96. package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
  97. package/server/src/Http/Filter/PositionFilter.php +35 -0
  98. package/server/src/Http/Resources/v1/Position.php +44 -0
  99. package/server/src/Jobs/ReplayPositions.php +64 -0
  100. package/server/src/Jobs/SendPositionReplay.php +65 -0
  101. package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
  102. package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
  103. package/server/src/Models/Device.php +72 -10
  104. package/server/src/Models/DeviceEvent.php +7 -0
  105. package/server/src/Models/Driver.php +28 -1
  106. package/server/src/Models/Payload.php +0 -1
  107. package/server/src/Models/Place.php +4 -1
  108. package/server/src/Models/Position.php +17 -17
  109. package/server/src/Models/Sensor.php +78 -13
  110. package/server/src/Models/Telematic.php +116 -6
  111. package/server/src/Models/Vehicle.php +8 -11
  112. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  113. package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
  114. package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
  115. package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
  116. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
  117. package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
  118. package/server/src/Support/Telematics/TelematicService.php +223 -0
  119. package/server/src/Support/Utils.php +1 -1
  120. package/server/src/routes.php +12 -1
@@ -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->location ?? 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,10 @@
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Casts\Point;
7
+ use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
8
+ use Fleetbase\LaravelMysqlSpatial\Types\Point as SpatialPoint;
9
+ use Fleetbase\Models\File;
6
10
  use Fleetbase\Models\Model;
7
11
  use Fleetbase\Models\User;
8
12
  use Fleetbase\Traits\HasApiModelBehavior;
@@ -15,6 +19,8 @@ use Fleetbase\Traits\TracksApiCredential;
15
19
  use Illuminate\Database\Eloquent\Relations\BelongsTo;
16
20
  use Illuminate\Database\Eloquent\Relations\HasMany;
17
21
  use Illuminate\Database\Eloquent\Relations\MorphTo;
22
+ use Illuminate\Support\Arr;
23
+ use Illuminate\Support\Str;
18
24
  use Spatie\Activitylog\LogOptions;
19
25
  use Spatie\Activitylog\Traits\LogsActivity;
20
26
  use Spatie\Sluggable\HasSlug;
@@ -37,6 +43,7 @@ class Device extends Model
37
43
  use HasMetaAttributes;
38
44
  use Searchable;
39
45
  use HasCustomFields;
46
+ use SpatialTrait;
40
47
 
41
48
  /**
42
49
  * The database table used by the model.
@@ -57,7 +64,7 @@ class Device extends Model
57
64
  *
58
65
  * @var array
59
66
  */
60
- protected $searchableColumns = ['name', 'model', 'serial_number', 'public_id'];
67
+ protected $searchableColumns = ['name', 'model', 'serial_number', 'manufacturer', 'public_id'];
61
68
 
62
69
  /**
63
70
  * The attributes that can be used for filtering.
@@ -75,14 +82,20 @@ class Device extends Model
75
82
  'company_uuid',
76
83
  'telematic_uuid',
77
84
  'warranty_uuid',
78
- 'device_type',
85
+ 'photo_uuid',
86
+ 'type',
79
87
  'device_id',
80
- 'device_provider',
81
- 'device_name',
82
- 'device_model',
83
- 'device_location',
88
+ 'internal_id',
89
+ 'imei',
90
+ 'imsi',
91
+ 'firmware_version',
92
+ 'provider',
93
+ 'name',
94
+ 'model',
95
+ 'location',
84
96
  'manufacturer',
85
97
  'serial_number',
98
+ 'last_position',
86
99
  'installation_date',
87
100
  'last_maintenance_date',
88
101
  'meta',
@@ -110,6 +123,7 @@ class Device extends Model
110
123
  'is_online',
111
124
  'attached_to_name',
112
125
  'connection_status',
126
+ 'photo_url',
113
127
  ];
114
128
 
115
129
  /**
@@ -117,7 +131,14 @@ class Device extends Model
117
131
  *
118
132
  * @var array
119
133
  */
120
- protected $hidden = ['warranty', 'telematic', 'attachable'];
134
+ protected $hidden = [];
135
+
136
+ /**
137
+ * The attributes that are spatial columns.
138
+ *
139
+ * @var array
140
+ */
141
+ protected $spatialFields = ['last_position'];
121
142
 
122
143
  /**
123
144
  * The attributes that should be cast to native types.
@@ -125,9 +146,10 @@ class Device extends Model
125
146
  * @var array
126
147
  */
127
148
  protected $casts = [
128
- 'last_online_at' => 'datetime',
129
- 'meta' => Json::class,
130
- 'options' => Json::class,
149
+ 'last_online_at' => 'datetime',
150
+ 'last_position' => Point::class,
151
+ 'meta' => Json::class,
152
+ 'options' => Json::class,
131
153
  ];
132
154
 
133
155
  /**
@@ -204,6 +226,21 @@ class Device extends Model
204
226
  return $this->hasMany(Sensor::class, 'device_uuid', 'uuid');
205
227
  }
206
228
 
229
+ public function photo(): BelongsTo
230
+ {
231
+ return $this->belongsTo(File::class);
232
+ }
233
+
234
+ /**
235
+ * Get photo URL attribute.
236
+ *
237
+ * @return string
238
+ */
239
+ public function getPhotoUrlAttribute()
240
+ {
241
+ return data_get($this, 'photo.url', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/image-file-icon.png');
242
+ }
243
+
207
244
  /**
208
245
  * Get the warranty name.
209
246
  */
@@ -432,4 +469,29 @@ class Device extends Model
432
469
  ->limit($limit)
433
470
  ->get();
434
471
  }
472
+
473
+ /**
474
+ * Creates a new position for the vehicle.
475
+ */
476
+ public function createPosition(array $attributes = [], Model|string|null $destination = null): ?Position
477
+ {
478
+ if (!isset($attributes['coordinates']) && isset($attributes['location'])) {
479
+ $attributes['coordinates'] = $attributes['location'];
480
+ }
481
+
482
+ if (!isset($attributes['coordinates']) && isset($attributes['latitude']) && isset($attributes['longitude'])) {
483
+ $attributes['coordinates'] = new SpatialPoint($attributes['latitude'], $attributes['longitude']);
484
+ }
485
+
486
+ // handle destination if set
487
+ $destinationUuid = Str::isUuid($destination) ? $destination : data_get($destination, 'uuid');
488
+
489
+ return Position::create([
490
+ ...Arr::only($attributes, ['coordinates', 'heading', 'bearing', 'speed', 'altitude', 'order_uuid']),
491
+ 'subject_uuid' => $this->uuid,
492
+ 'subject_type' => $this->getMorphClass(),
493
+ 'company_uuid' => $this->company_uuid,
494
+ 'destination_uuid' => $destinationUuid,
495
+ ]);
496
+ }
435
497
  }
@@ -4,6 +4,7 @@ namespace Fleetbase\FleetOps\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
6
  use Fleetbase\Models\Alert;
7
+ use Fleetbase\Models\Company;
7
8
  use Fleetbase\Models\Model;
8
9
  use Fleetbase\Models\User;
9
10
  use Fleetbase\Traits\HasApiModelBehavior;
@@ -67,6 +68,7 @@ class DeviceEvent extends Model
67
68
  * @var array
68
69
  */
69
70
  protected $fillable = [
71
+ 'company_uuid',
70
72
  'device_uuid',
71
73
  'payload',
72
74
  'meta',
@@ -143,6 +145,11 @@ class DeviceEvent extends Model
143
145
  return LogOptions::defaults()->logAll();
144
146
  }
145
147
 
148
+ public function company(): BelongsTo
149
+ {
150
+ return $this->belongsTo(Company::class, 'company_uuid', 'uuid');
151
+ }
152
+
146
153
  public function device(): BelongsTo
147
154
  {
148
155
  return $this->belongsTo(Device::class, 'device_uuid', 'uuid');
@@ -8,6 +8,7 @@ use Fleetbase\FleetOps\Scopes\DriverScope;
8
8
  use Fleetbase\FleetOps\Support\Utils;
9
9
  use Fleetbase\FleetOps\Support\Utils as FleetOpsUtils;
10
10
  use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
11
+ use Fleetbase\LaravelMysqlSpatial\Types\Point as SpatialPoint;
11
12
  use Fleetbase\Models\File;
12
13
  use Fleetbase\Models\Model;
13
14
  use Fleetbase\Models\User;
@@ -24,6 +25,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
24
25
  use Illuminate\Database\Eloquent\Relations\HasMany;
25
26
  use Illuminate\Database\Eloquent\Relations\HasManyThrough;
26
27
  use Illuminate\Notifications\Notifiable;
28
+ use Illuminate\Support\Arr;
27
29
  use Illuminate\Support\Collection;
28
30
  use Illuminate\Support\Facades\DB;
29
31
  use Illuminate\Support\Str;
@@ -645,7 +647,7 @@ class Driver extends Model
645
647
  $positionData = [
646
648
  'company_uuid' => session('company', $this->company_uuid),
647
649
  'subject_uuid' => $this->uuid,
648
- 'subject_type' => get_class($this),
650
+ 'subject_type' => $this->getMorphClass(),
649
651
  'coordinates' => $this->location,
650
652
  'altitude' => $this->altitude,
651
653
  'heading' => $this->heading,
@@ -661,6 +663,31 @@ class Driver extends Model
661
663
  return ($isFirstPosition || $isPast50Meters) ? Position::create($positionData) : null;
662
664
  }
663
665
 
666
+ /**
667
+ * Creates a new position for the vehicle.
668
+ */
669
+ public function createPosition(array $attributes = [], Model|string|null $destination = null): ?Position
670
+ {
671
+ if (!isset($attributes['coordinates']) && isset($attributes['location'])) {
672
+ $attributes['coordinates'] = $attributes['location'];
673
+ }
674
+
675
+ if (!isset($attributes['coordinates']) && isset($attributes['latitude']) && isset($attributes['longitude'])) {
676
+ $attributes['coordinates'] = new SpatialPoint($attributes['latitude'], $attributes['longitude']);
677
+ }
678
+
679
+ // handle destination if set
680
+ $destinationUuid = Str::isUuid($destination) ? $destination : data_get($destination, 'uuid');
681
+
682
+ return Position::create([
683
+ ...Arr::only($attributes, ['coordinates', 'heading', 'bearing', 'speed', 'altitude', 'order_uuid']),
684
+ 'subject_uuid' => $this->uuid,
685
+ 'subject_type' => $this->getMorphClass(),
686
+ 'company_uuid' => $this->company_uuid,
687
+ 'destination_uuid' => $destinationUuid,
688
+ ]);
689
+ }
690
+
664
691
  /**
665
692
  * Get the user relationship from the driver.
666
693
  */
@@ -388,7 +388,6 @@ class Payload extends Model
388
388
  } else {
389
389
  $place = Place::createFromMixed($attributes);
390
390
 
391
-
392
391
  // Store temp search UUID for traceability if present and different
393
392
  if ($place instanceof Place && isset($attributes['uuid']) && $place->uuid !== $attributes['uuid']) {
394
393
  $place->updateMeta('search_uuid', $attributes['uuid']);
@@ -366,6 +366,7 @@ class Place extends Model
366
366
  if ($saveInstance) {
367
367
  $place->save();
368
368
  }
369
+
369
370
  return $place;
370
371
  }
371
372
 
@@ -554,8 +555,10 @@ class Place extends Model
554
555
  }
555
556
 
556
557
  // If has $attributes['address']
558
+ $address = $place['address'];
557
559
  if (!empty($place['address'])) {
558
- return static::createFromGeocodingLookup($place['address'], $saveInstance);
560
+ // return static::createFromGeocodingLookup($place['address'], $saveInstance);
561
+ return static::create(array_merge($place, static::getValuesFromGeocodingLookup($address)));
559
562
  }
560
563
 
561
564
  // Perform google lookup to fill address