@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.
- package/addon/components/custom-entity/form.hbs +14 -14
- package/addon/components/device/details.hbs +92 -43
- package/addon/components/device/form.hbs +108 -60
- package/addon/components/device/form.js +36 -8
- package/addon/components/device/panel-header.hbs +32 -0
- package/addon/components/device/panel-header.js +3 -0
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/driver/form.js +49 -47
- package/addon/components/entity/form.hbs +7 -5
- package/addon/components/layout/fleet-ops-sidebar.js +12 -12
- package/addon/components/map/drawer/device-event-listing.hbs +58 -0
- package/addon/components/map/drawer/device-event-listing.js +181 -0
- package/addon/components/map/drawer/position-listing.hbs +84 -0
- package/addon/components/map/drawer/position-listing.js +289 -0
- package/addon/components/map/drawer.js +2 -0
- package/addon/components/map/leaflet-live-map.hbs +7 -2
- package/addon/components/order/details/payload.hbs +6 -4
- package/addon/components/order/details/payload.js +2 -0
- package/addon/components/order-config-manager/custom-fields.js +1 -1
- package/addon/components/positions-replay.hbs +333 -0
- package/addon/components/positions-replay.js +372 -0
- package/addon/components/sensor/details.hbs +64 -38
- package/addon/components/sensor/form.hbs +112 -63
- package/addon/components/sensor/form.js +36 -24
- package/addon/components/sensor/panel-header.hbs +32 -0
- package/addon/components/sensor/panel-header.js +3 -0
- package/addon/components/telematic/details.hbs +40 -16
- package/addon/components/telematic/form.hbs +63 -64
- package/addon/components/telematic/form.js +73 -4
- package/addon/components/vehicle/card.hbs +1 -1
- package/addon/controllers/analytics/reports/index/edit.js +1 -1
- package/addon/controllers/connectivity/devices/index/details.js +22 -1
- package/addon/controllers/connectivity/devices/index/edit.js +66 -1
- package/addon/controllers/connectivity/devices/index.js +51 -9
- package/addon/controllers/connectivity/events/index.js +65 -16
- package/addon/controllers/connectivity/sensors/index/details.js +22 -1
- package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
- package/addon/controllers/connectivity/sensors/index.js +66 -6
- package/addon/controllers/connectivity/telematics/index/details.js +22 -1
- package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
- package/addon/controllers/connectivity/telematics/index.js +20 -11
- package/addon/controllers/management/fleets/index/details.js +26 -21
- package/addon/controllers/management/fleets/index/edit.js +9 -6
- package/addon/controllers/management/vehicles/index/details.js +21 -13
- package/addon/controllers/settings/custom-fields.js +6 -0
- package/addon/helpers/get-fleet-ops-option-label.js +11 -0
- package/addon/routes/connectivity/devices/index/details.js +27 -1
- package/addon/routes/connectivity/devices/index/edit.js +27 -1
- package/addon/routes/connectivity/sensors/index/details.js +27 -1
- package/addon/routes/connectivity/sensors/index/edit.js +27 -1
- package/addon/routes/connectivity/telematics/index/details.js +27 -1
- package/addon/routes/connectivity/telematics/index/edit.js +27 -1
- package/addon/routes/management/vehicles/index/details/positions.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/movement-tracker.js +81 -30
- package/addon/styles/fleetops-engine.css +157 -0
- package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/devices/index/details.hbs +15 -2
- package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
- package/addon/templates/connectivity/events/index.hbs +1 -1
- package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
- package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
- package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
- package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
- package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
- package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
- package/addon/utils/fleet-ops-options.js +95 -0
- package/app/components/device/panel-header.js +1 -0
- package/app/components/map/drawer/device-event-listing.js +1 -0
- package/app/components/map/drawer/position-listing.js +1 -0
- package/app/components/positions-replay.js +1 -0
- package/app/components/sensor/panel-header.js +1 -0
- package/app/helpers/get-fleet-ops-option-label.js +1 -0
- package/app/routes/management/vehicles/index/details/positions.js +1 -0
- package/app/templates/management/vehicles/index/details/positions.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/config/telematics.php +111 -0
- package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
- package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
- package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
- package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
- package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
- package/server/src/Contracts/TelematicProviderInterface.php +119 -0
- package/server/src/Exceptions/TelematicProviderException.php +14 -0
- package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
- package/server/src/Http/Controllers/Internal/v1/TelematicWebhookController.php +170 -0
- package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
- package/server/src/Http/Filter/PositionFilter.php +35 -0
- package/server/src/Http/Resources/v1/Position.php +44 -0
- package/server/src/Jobs/ReplayPositions.php +64 -0
- package/server/src/Jobs/SendPositionReplay.php +65 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
- package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
- package/server/src/Models/Device.php +72 -10
- package/server/src/Models/DeviceEvent.php +7 -0
- package/server/src/Models/Driver.php +28 -1
- package/server/src/Models/Payload.php +0 -1
- package/server/src/Models/Place.php +4 -1
- package/server/src/Models/Position.php +17 -17
- package/server/src/Models/Sensor.php +78 -13
- package/server/src/Models/Telematic.php +116 -6
- package/server/src/Models/Vehicle.php +8 -11
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
- package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
- package/server/src/Support/Telematics/TelematicService.php +223 -0
- package/server/src/Support/Utils.php +1 -1
- 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
|
-
'
|
|
85
|
+
'photo_uuid',
|
|
86
|
+
'type',
|
|
79
87
|
'device_id',
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
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 = [
|
|
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'
|
|
129
|
-
'
|
|
130
|
-
'
|
|
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' =>
|
|
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
|