@fleetbase/fleetops-engine 0.6.17 → 0.6.18

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 (44) hide show
  1. package/addon/components/layout/fleet-ops-sidebar.hbs +25 -0
  2. package/addon/templates/virtual.hbs +3 -3
  3. package/composer.json +3 -2
  4. package/extension.json +1 -1
  5. package/package.json +1 -1
  6. package/server/migrations/2025_08_28_054920_create_warranties_table.php +56 -0
  7. package/server/migrations/2025_08_28_054921_create_telematics_table.php +60 -0
  8. package/server/migrations/2025_08_28_054922_create_assets_table.php +94 -0
  9. package/server/migrations/2025_08_28_054925_create_devices_table.php +190 -0
  10. package/server/migrations/2025_08_28_054926_create_device_events_table.php +99 -0
  11. package/server/migrations/2025_08_28_054926_create_sensors_table.php +62 -0
  12. package/server/migrations/2025_08_28_054927_create_parts_table.php +73 -0
  13. package/server/migrations/2025_08_28_054929_create_equipments_table.php +58 -0
  14. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +85 -0
  15. package/server/migrations/2025_08_28_054931_create_maintenances_table.php +79 -0
  16. package/server/migrations/2025_08_28_082002_update_vehicles_table_telematics.php +60 -0
  17. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -1
  18. package/server/src/Http/Resources/v1/Order.php +111 -60
  19. package/server/src/Models/Asset.php +548 -0
  20. package/server/src/Models/Contact.php +2 -0
  21. package/server/src/Models/Device.php +435 -0
  22. package/server/src/Models/DeviceEvent.php +501 -0
  23. package/server/src/Models/Driver.php +2 -0
  24. package/server/src/Models/Entity.php +2 -0
  25. package/server/src/Models/Equipment.php +483 -0
  26. package/server/src/Models/Fleet.php +2 -0
  27. package/server/src/Models/FuelReport.php +2 -0
  28. package/server/src/Models/Issue.php +2 -0
  29. package/server/src/Models/Maintenance.php +549 -0
  30. package/server/src/Models/Order.php +32 -112
  31. package/server/src/Models/OrderConfig.php +8 -0
  32. package/server/src/Models/Part.php +502 -0
  33. package/server/src/Models/Payload.php +101 -20
  34. package/server/src/Models/Place.php +10 -4
  35. package/server/src/Models/Sensor.php +510 -0
  36. package/server/src/Models/ServiceArea.php +1 -1
  37. package/server/src/Models/Telematic.php +336 -0
  38. package/server/src/Models/Vehicle.php +45 -1
  39. package/server/src/Models/VehicleDevice.php +1 -1
  40. package/server/src/Models/Vendor.php +2 -0
  41. package/server/src/Models/Warranty.php +413 -0
  42. package/server/src/Models/WorkOrder.php +532 -0
  43. package/server/src/Support/Utils.php +5 -0
  44. package/server/src/Traits/Maintainable.php +307 -0
@@ -0,0 +1,501 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Models;
4
+
5
+ use Fleetbase\Casts\Json;
6
+ use Fleetbase\Models\Alert;
7
+ use Fleetbase\Models\Model;
8
+ use Fleetbase\Models\User;
9
+ use Fleetbase\Traits\HasApiModelBehavior;
10
+ use Fleetbase\Traits\HasMetaAttributes;
11
+ use Fleetbase\Traits\HasPublicId;
12
+ use Fleetbase\Traits\HasUuid;
13
+ use Fleetbase\Traits\Searchable;
14
+ use Fleetbase\Traits\TracksApiCredential;
15
+ use Illuminate\Database\Eloquent\Relations\BelongsTo;
16
+ use Spatie\Activitylog\LogOptions;
17
+ use Spatie\Activitylog\Traits\LogsActivity;
18
+
19
+ /**
20
+ * Class DeviceEvent.
21
+ *
22
+ * Represents events generated by devices in the fleet management system.
23
+ * Events can include status changes, alerts, data transmissions, and other
24
+ * device-related activities.
25
+ */
26
+ class DeviceEvent extends Model
27
+ {
28
+ use HasUuid;
29
+ use HasPublicId;
30
+ use TracksApiCredential;
31
+ use HasApiModelBehavior;
32
+ use LogsActivity;
33
+ use HasMetaAttributes;
34
+ use Searchable;
35
+
36
+ /**
37
+ * The database table used by the model.
38
+ *
39
+ * @var string
40
+ */
41
+ protected $table = 'device_events';
42
+
43
+ /**
44
+ * The type of public Id to generate.
45
+ *
46
+ * @var string
47
+ */
48
+ protected $publicIdType = 'device_event';
49
+
50
+ /**
51
+ * The attributes that can be queried.
52
+ *
53
+ * @var array
54
+ */
55
+ protected $searchableColumns = ['event_type', 'message', 'public_id'];
56
+
57
+ /**
58
+ * The attributes that can be used for filtering.
59
+ *
60
+ * @var array
61
+ */
62
+ protected $filterParams = ['event_type', 'severity', 'device_uuid'];
63
+
64
+ /**
65
+ * The attributes that are mass assignable.
66
+ *
67
+ * @var array
68
+ */
69
+ protected $fillable = [
70
+ 'device_uuid',
71
+ 'payload',
72
+ 'meta',
73
+ 'location',
74
+ 'event_type',
75
+ 'severity',
76
+ 'ident',
77
+ 'protocol',
78
+ 'provider',
79
+ 'mileage',
80
+ 'state',
81
+ 'code',
82
+ 'reason',
83
+ 'comment',
84
+ 'slug',
85
+ ];
86
+
87
+ /**
88
+ * Dynamic attributes that are appended to object.
89
+ *
90
+ * @var array
91
+ */
92
+ protected $appends = [
93
+ 'device_name',
94
+ 'is_processed',
95
+ 'age_minutes',
96
+ 'processing_delay_minutes',
97
+ ];
98
+
99
+ /**
100
+ * The attributes excluded from the model's JSON form.
101
+ *
102
+ * @var array
103
+ */
104
+ protected $hidden = ['device'];
105
+
106
+ /**
107
+ * The attributes that should be cast to native types.
108
+ *
109
+ * @var array
110
+ */
111
+ protected $casts = [
112
+ 'data' => Json::class,
113
+ 'occurred_at' => 'datetime',
114
+ 'processed_at' => 'datetime',
115
+ ];
116
+
117
+ /**
118
+ * Properties which activity needs to be logged.
119
+ *
120
+ * @var array
121
+ */
122
+ protected static $logAttributes = '*';
123
+
124
+ /**
125
+ * Do not log empty changed.
126
+ *
127
+ * @var bool
128
+ */
129
+ protected static $submitEmptyLogs = false;
130
+
131
+ /**
132
+ * The name of the subject to log.
133
+ *
134
+ * @var string
135
+ */
136
+ protected static $logName = 'device_event';
137
+
138
+ /**
139
+ * Get the activity log options for the model.
140
+ */
141
+ public function getActivitylogOptions(): LogOptions
142
+ {
143
+ return LogOptions::defaults()->logAll();
144
+ }
145
+
146
+ public function device(): BelongsTo
147
+ {
148
+ return $this->belongsTo(Device::class, 'device_uuid', 'uuid');
149
+ }
150
+
151
+ public function createdBy(): BelongsTo
152
+ {
153
+ return $this->belongsTo(User::class, 'created_by_uuid', 'uuid');
154
+ }
155
+
156
+ /**
157
+ * Get the device name.
158
+ */
159
+ public function getDeviceNameAttribute(): ?string
160
+ {
161
+ return $this->device?->name;
162
+ }
163
+
164
+ /**
165
+ * Check if the event has been processed.
166
+ */
167
+ public function getIsProcessedAttribute(): bool
168
+ {
169
+ return !is_null($this->processed_at);
170
+ }
171
+
172
+ /**
173
+ * Get the age of the event in minutes.
174
+ */
175
+ public function getAgeMinutesAttribute(): int
176
+ {
177
+ $startTime = $this->occurred_at ?? $this->created_at;
178
+
179
+ return $startTime->diffInMinutes(now());
180
+ }
181
+
182
+ /**
183
+ * Get the processing delay in minutes.
184
+ */
185
+ public function getProcessingDelayMinutesAttribute(): ?int
186
+ {
187
+ if (!$this->processed_at) {
188
+ return null;
189
+ }
190
+
191
+ $startTime = $this->occurred_at ?? $this->created_at;
192
+
193
+ return $startTime->diffInMinutes($this->processed_at);
194
+ }
195
+
196
+ /**
197
+ * Scope to get events by type.
198
+ *
199
+ * @param \Illuminate\Database\Eloquent\Builder $query
200
+ *
201
+ * @return \Illuminate\Database\Eloquent\Builder
202
+ */
203
+ public function scopeByType($query, string $type)
204
+ {
205
+ return $query->where('event_type', $type);
206
+ }
207
+
208
+ /**
209
+ * Scope to get events by severity.
210
+ *
211
+ * @param \Illuminate\Database\Eloquent\Builder $query
212
+ *
213
+ * @return \Illuminate\Database\Eloquent\Builder
214
+ */
215
+ public function scopeBySeverity($query, string $severity)
216
+ {
217
+ return $query->where('severity', $severity);
218
+ }
219
+
220
+ /**
221
+ * Scope to get processed events.
222
+ *
223
+ * @param \Illuminate\Database\Eloquent\Builder $query
224
+ *
225
+ * @return \Illuminate\Database\Eloquent\Builder
226
+ */
227
+ public function scopeProcessed($query)
228
+ {
229
+ return $query->whereNotNull('processed_at');
230
+ }
231
+
232
+ /**
233
+ * Scope to get unprocessed events.
234
+ *
235
+ * @param \Illuminate\Database\Eloquent\Builder $query
236
+ *
237
+ * @return \Illuminate\Database\Eloquent\Builder
238
+ */
239
+ public function scopeUnprocessed($query)
240
+ {
241
+ return $query->whereNull('processed_at');
242
+ }
243
+
244
+ /**
245
+ * Scope to get recent events.
246
+ *
247
+ * @param \Illuminate\Database\Eloquent\Builder $query
248
+ *
249
+ * @return \Illuminate\Database\Eloquent\Builder
250
+ */
251
+ public function scopeRecent($query, int $minutes = 60)
252
+ {
253
+ return $query->where('occurred_at', '>=', now()->subMinutes($minutes));
254
+ }
255
+
256
+ /**
257
+ * Scope to get critical events.
258
+ *
259
+ * @param \Illuminate\Database\Eloquent\Builder $query
260
+ *
261
+ * @return \Illuminate\Database\Eloquent\Builder
262
+ */
263
+ public function scopeCritical($query)
264
+ {
265
+ return $query->where('severity', 'critical');
266
+ }
267
+
268
+ /**
269
+ * Mark the event as processed.
270
+ */
271
+ public function markAsProcessed(): bool
272
+ {
273
+ if ($this->is_processed) {
274
+ return false;
275
+ }
276
+
277
+ $updated = $this->update(['processed_at' => now()]);
278
+
279
+ if ($updated) {
280
+ activity('device_event_processed')
281
+ ->performedOn($this)
282
+ ->withProperties([
283
+ 'event_type' => $this->event_type,
284
+ 'processing_delay_minutes' => $this->processing_delay_minutes,
285
+ ])
286
+ ->log('Device event processed');
287
+ }
288
+
289
+ return $updated;
290
+ }
291
+
292
+ /**
293
+ * Get a specific data field from the event.
294
+ */
295
+ public function getData(string $field, $default = null)
296
+ {
297
+ $data = $this->data ?? [];
298
+
299
+ // Support dot notation for nested data
300
+ $keys = explode('.', $field);
301
+ $value = $data;
302
+
303
+ foreach ($keys as $key) {
304
+ if (is_array($value) && isset($value[$key])) {
305
+ $value = $value[$key];
306
+ } else {
307
+ return $default;
308
+ }
309
+ }
310
+
311
+ return $value;
312
+ }
313
+
314
+ /**
315
+ * Set a specific data field in the event.
316
+ */
317
+ public function setData(string $field, $value): bool
318
+ {
319
+ $data = $this->data ?? [];
320
+
321
+ // Support dot notation for nested data
322
+ $keys = explode('.', $field);
323
+ $current = &$data;
324
+
325
+ foreach ($keys as $i => $key) {
326
+ if ($i === count($keys) - 1) {
327
+ $current[$key] = $value;
328
+ } else {
329
+ if (!isset($current[$key]) || !is_array($current[$key])) {
330
+ $current[$key] = [];
331
+ }
332
+ $current = &$current[$key];
333
+ }
334
+ }
335
+
336
+ return $this->update(['data' => $data]);
337
+ }
338
+
339
+ /**
340
+ * Check if the event should trigger an alert.
341
+ */
342
+ public function shouldTriggerAlert(): bool
343
+ {
344
+ // Define event types that should trigger alerts
345
+ $alertTriggerTypes = [
346
+ 'error',
347
+ 'warning',
348
+ 'critical_failure',
349
+ 'security_breach',
350
+ 'maintenance_required',
351
+ 'threshold_exceeded',
352
+ ];
353
+
354
+ if (in_array($this->event_type, $alertTriggerTypes)) {
355
+ return true;
356
+ }
357
+
358
+ // Check severity
359
+ if (in_array($this->severity, ['high', 'critical'])) {
360
+ return true;
361
+ }
362
+
363
+ return false;
364
+ }
365
+
366
+ /**
367
+ * Create an alert based on this event.
368
+ */
369
+ public function createAlert(): ?Alert
370
+ {
371
+ if (!$this->shouldTriggerAlert()) {
372
+ return null;
373
+ }
374
+
375
+ // Check if an alert already exists for this event
376
+ $existingAlert = Alert::where('subject_type', static::class)
377
+ ->where('subject_uuid', $this->uuid)
378
+ ->where('status', 'open')
379
+ ->first();
380
+
381
+ if ($existingAlert) {
382
+ return $existingAlert;
383
+ }
384
+
385
+ $alert = Alert::create([
386
+ 'company_uuid' => $this->company_uuid,
387
+ 'type' => 'device_event',
388
+ 'severity' => $this->severity,
389
+ 'status' => 'open',
390
+ 'subject_type' => static::class,
391
+ 'subject_uuid' => $this->uuid,
392
+ 'message' => $this->generateAlertMessage(),
393
+ 'context' => [
394
+ 'device_uuid' => $this->device_uuid,
395
+ 'device_name' => $this->device_name,
396
+ 'event_type' => $this->event_type,
397
+ 'event_data' => $this->data,
398
+ 'occurred_at' => $this->occurred_at,
399
+ ],
400
+ 'triggered_at' => $this->occurred_at ?? now(),
401
+ ]);
402
+
403
+ return $alert;
404
+ }
405
+
406
+ /**
407
+ * Generate an alert message for this event.
408
+ */
409
+ protected function generateAlertMessage(): string
410
+ {
411
+ $deviceName = $this->device_name ?? 'Unknown Device';
412
+
413
+ switch ($this->event_type) {
414
+ case 'error':
415
+ return "Device '{$deviceName}' reported an error: {$this->message}";
416
+ case 'warning':
417
+ return "Device '{$deviceName}' issued a warning: {$this->message}";
418
+ case 'critical_failure':
419
+ return "Critical failure detected on device '{$deviceName}': {$this->message}";
420
+ case 'security_breach':
421
+ return "Security breach detected on device '{$deviceName}': {$this->message}";
422
+ case 'maintenance_required':
423
+ return "Device '{$deviceName}' requires maintenance: {$this->message}";
424
+ case 'threshold_exceeded':
425
+ return "Threshold exceeded on device '{$deviceName}': {$this->message}";
426
+ default:
427
+ return "Device '{$deviceName}' event ({$this->event_type}): {$this->message}";
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Get the severity level as a numeric value for sorting.
433
+ */
434
+ public function getSeverityLevel(): int
435
+ {
436
+ switch ($this->severity) {
437
+ case 'critical':
438
+ return 4;
439
+ case 'high':
440
+ return 3;
441
+ case 'medium':
442
+ return 2;
443
+ case 'low':
444
+ return 1;
445
+ default:
446
+ return 0;
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Get events that occurred around the same time as this event.
452
+ *
453
+ * @return \Illuminate\Database\Eloquent\Collection
454
+ */
455
+ public function getCorrelatedEvents(int $minuteWindow = 5)
456
+ {
457
+ $startTime = ($this->occurred_at ?? $this->created_at)->subMinutes($minuteWindow);
458
+ $endTime = ($this->occurred_at ?? $this->created_at)->addMinutes($minuteWindow);
459
+
460
+ return static::where('device_uuid', $this->device_uuid)
461
+ ->where('uuid', '!=', $this->uuid)
462
+ ->whereBetween('occurred_at', [$startTime, $endTime])
463
+ ->orderBy('occurred_at')
464
+ ->get();
465
+ }
466
+
467
+ /**
468
+ * Check if this event is part of a pattern.
469
+ */
470
+ public function isPartOfPattern(int $lookbackHours = 24, int $minimumOccurrences = 3): bool
471
+ {
472
+ $startTime = now()->subHours($lookbackHours);
473
+
474
+ $similarEvents = static::where('device_uuid', $this->device_uuid)
475
+ ->where('event_type', $this->event_type)
476
+ ->where('occurred_at', '>=', $startTime)
477
+ ->count();
478
+
479
+ return $similarEvents >= $minimumOccurrences;
480
+ }
481
+
482
+ /**
483
+ * Export the event data for analysis.
484
+ */
485
+ public function exportForAnalysis(): array
486
+ {
487
+ return [
488
+ 'event_id' => $this->public_id,
489
+ 'device_uuid' => $this->device_uuid,
490
+ 'device_name' => $this->device_name,
491
+ 'event_type' => $this->event_type,
492
+ 'severity' => $this->severity,
493
+ 'message' => $this->message,
494
+ 'occurred_at' => $this->occurred_at?->toISOString(),
495
+ 'processed_at' => $this->processed_at?->toISOString(),
496
+ 'processing_delay_minutes' => $this->processing_delay_minutes,
497
+ 'data' => $this->data,
498
+ 'created_at' => $this->created_at?->toISOString(),
499
+ ];
500
+ }
501
+ }
@@ -12,6 +12,7 @@ use Fleetbase\Models\File;
12
12
  use Fleetbase\Models\Model;
13
13
  use Fleetbase\Models\User;
14
14
  use Fleetbase\Traits\HasApiModelBehavior;
15
+ use Fleetbase\Traits\HasCustomFields;
15
16
  use Fleetbase\Traits\HasInternalId;
16
17
  use Fleetbase\Traits\HasPublicId;
17
18
  use Fleetbase\Traits\HasUuid;
@@ -46,6 +47,7 @@ class Driver extends Model
46
47
  use HasSlug;
47
48
  use LogsActivity;
48
49
  use CausesActivity;
50
+ use HasCustomFields;
49
51
 
50
52
  /**
51
53
  * The database table used by the model.
@@ -10,6 +10,7 @@ use Fleetbase\FleetOps\Traits\HasTrackingNumber;
10
10
  use Fleetbase\FleetOps\Traits\PayloadAccessors;
11
11
  use Fleetbase\Models\Model;
12
12
  use Fleetbase\Traits\HasApiModelBehavior;
13
+ use Fleetbase\Traits\HasCustomFields;
13
14
  use Fleetbase\Traits\HasInternalId;
14
15
  use Fleetbase\Traits\HasMetaAttributes;
15
16
  use Fleetbase\Traits\HasPublicId;
@@ -36,6 +37,7 @@ class Entity extends Model
36
37
  use HasApiModelBehavior;
37
38
  use HasMetaAttributes;
38
39
  use PayloadAccessors;
40
+ use HasCustomFields;
39
41
 
40
42
  /**
41
43
  * The database table used by the model.