@fleetbase/fleetops-engine 0.6.16 → 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 (49) 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_11_170800_add_company_index_to_routes_table.php +21 -0
  7. package/server/migrations/2025_08_28_054920_create_warranties_table.php +56 -0
  8. package/server/migrations/2025_08_28_054921_create_telematics_table.php +60 -0
  9. package/server/migrations/2025_08_28_054922_create_assets_table.php +94 -0
  10. package/server/migrations/2025_08_28_054925_create_devices_table.php +190 -0
  11. package/server/migrations/2025_08_28_054926_create_device_events_table.php +99 -0
  12. package/server/migrations/2025_08_28_054926_create_sensors_table.php +62 -0
  13. package/server/migrations/2025_08_28_054927_create_parts_table.php +73 -0
  14. package/server/migrations/2025_08_28_054929_create_equipments_table.php +58 -0
  15. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +85 -0
  16. package/server/migrations/2025_08_28_054931_create_maintenances_table.php +79 -0
  17. package/server/migrations/2025_08_28_082002_update_vehicles_table_telematics.php +60 -0
  18. package/server/src/Http/Controllers/Api/v1/OrderController.php +19 -1
  19. package/server/src/Http/Controllers/Internal/v1/OrderController.php +31 -8
  20. package/server/src/Http/Resources/v1/Order.php +111 -60
  21. package/server/src/Models/Asset.php +548 -0
  22. package/server/src/Models/Contact.php +2 -0
  23. package/server/src/Models/Device.php +435 -0
  24. package/server/src/Models/DeviceEvent.php +501 -0
  25. package/server/src/Models/Driver.php +2 -0
  26. package/server/src/Models/Entity.php +27 -50
  27. package/server/src/Models/Equipment.php +483 -0
  28. package/server/src/Models/Fleet.php +2 -0
  29. package/server/src/Models/FuelReport.php +2 -0
  30. package/server/src/Models/Issue.php +2 -0
  31. package/server/src/Models/Maintenance.php +549 -0
  32. package/server/src/Models/Order.php +32 -112
  33. package/server/src/Models/OrderConfig.php +8 -0
  34. package/server/src/Models/Part.php +502 -0
  35. package/server/src/Models/Payload.php +101 -20
  36. package/server/src/Models/Place.php +10 -4
  37. package/server/src/Models/Sensor.php +510 -0
  38. package/server/src/Models/ServiceArea.php +1 -1
  39. package/server/src/Models/Telematic.php +336 -0
  40. package/server/src/Models/Vehicle.php +45 -1
  41. package/server/src/Models/VehicleDevice.php +1 -1
  42. package/server/src/Models/Vendor.php +2 -0
  43. package/server/src/Models/Warranty.php +413 -0
  44. package/server/src/Models/Waypoint.php +2 -0
  45. package/server/src/Models/WorkOrder.php +532 -0
  46. package/server/src/Support/Utils.php +5 -0
  47. package/server/src/Traits/HasTrackingNumber.php +64 -10
  48. package/server/src/Traits/Maintainable.php +307 -0
  49. package/server/src/Traits/PayloadAccessors.php +126 -0
@@ -0,0 +1,548 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Models;
4
+
5
+ use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Casts\Point;
7
+ use Fleetbase\Models\Category;
8
+ use Fleetbase\Models\File;
9
+ use Fleetbase\Models\Model;
10
+ use Fleetbase\Models\User;
11
+ use Fleetbase\Traits\HasApiModelBehavior;
12
+ use Fleetbase\Traits\HasCustomFields;
13
+ use Fleetbase\Traits\HasMetaAttributes;
14
+ use Fleetbase\Traits\HasPublicId;
15
+ use Fleetbase\Traits\HasUuid;
16
+ use Fleetbase\Traits\Searchable;
17
+ use Fleetbase\Traits\TracksApiCredential;
18
+ use Illuminate\Database\Eloquent\Relations\BelongsTo;
19
+ use Illuminate\Database\Eloquent\Relations\HasMany;
20
+ use Illuminate\Database\Eloquent\Relations\MorphMany;
21
+ use Illuminate\Database\Eloquent\Relations\MorphTo;
22
+ use Spatie\Activitylog\LogOptions;
23
+ use Spatie\Activitylog\Traits\LogsActivity;
24
+ use Spatie\Sluggable\HasSlug;
25
+ use Spatie\Sluggable\SlugOptions;
26
+
27
+ /**
28
+ * Class Asset.
29
+ *
30
+ * Represents a physical asset in the fleet (truck, trailer, container, drone, forklift, etc.).
31
+ * Assets are the primary operational units that can be tracked, maintained, and managed.
32
+ */
33
+ class Asset extends Model
34
+ {
35
+ use HasUuid;
36
+ use HasPublicId;
37
+ use TracksApiCredential;
38
+ use HasApiModelBehavior;
39
+ use HasSlug;
40
+ use LogsActivity;
41
+ use HasMetaAttributes;
42
+ use Searchable;
43
+ use HasCustomFields;
44
+
45
+ /**
46
+ * The database table used by the model.
47
+ *
48
+ * @var string
49
+ */
50
+ protected $table = 'assets';
51
+
52
+ /**
53
+ * The type of public Id to generate.
54
+ *
55
+ * @var string
56
+ */
57
+ protected $publicIdType = 'asset';
58
+
59
+ /**
60
+ * The attributes that can be queried.
61
+ *
62
+ * @var array
63
+ */
64
+ protected $searchableColumns = ['name', 'code', 'vin', 'plate_number', 'make', 'model', 'serial_number', 'public_id'];
65
+
66
+ /**
67
+ * The attributes that can be used for filtering.
68
+ *
69
+ * @var array
70
+ */
71
+ protected $filterParams = ['category_uuid', 'vendor_uuid', 'type', 'status', 'make', 'model', 'year'];
72
+
73
+ /**
74
+ * The attributes that are spatial columns.
75
+ *
76
+ * @var array
77
+ */
78
+ protected $spatialFields = ['location'];
79
+
80
+ /**
81
+ * The attributes that are mass assignable.
82
+ *
83
+ * @var array
84
+ */
85
+ protected $fillable = [
86
+ 'company_uuid',
87
+ 'category_uuid',
88
+ 'vendor_uuid',
89
+ 'warranty_uuid',
90
+ 'telematic_uuid',
91
+ 'assigned_to_uuid',
92
+ 'assigned_to_type',
93
+ 'operator_uuid',
94
+ 'operator_type',
95
+ 'current_place_uuid',
96
+ 'photo_uuid',
97
+ 'name',
98
+ 'code',
99
+ 'type',
100
+ 'location',
101
+ 'speed',
102
+ 'heading',
103
+ 'altitude',
104
+ 'status',
105
+ 'vin',
106
+ 'plate_number',
107
+ 'make',
108
+ 'model',
109
+ 'year',
110
+ 'color',
111
+ 'serial_number',
112
+ 'odometer',
113
+ 'engine_hours',
114
+ 'gvw',
115
+ 'capacity',
116
+ 'specs',
117
+ 'attributes',
118
+ 'slug',
119
+ ];
120
+
121
+ /**
122
+ * Dynamic attributes that are appended to object.
123
+ *
124
+ * @var array
125
+ */
126
+ protected $appends = [
127
+ 'category_name',
128
+ 'vendor_name',
129
+ 'warranty_name',
130
+ 'current_location',
131
+ 'photo_url',
132
+ 'display_name',
133
+ 'is_online',
134
+ 'last_maintenance',
135
+ 'next_maintenance_due',
136
+ ];
137
+
138
+ /**
139
+ * The attributes excluded from the model's JSON form.
140
+ *
141
+ * @var array
142
+ */
143
+ protected $hidden = ['category', 'vendor', 'warranty', 'telematic', 'currentPlace', 'photo'];
144
+
145
+ /**
146
+ * The attributes that should be cast to native types.
147
+ *
148
+ * @var array
149
+ */
150
+ protected $casts = [
151
+ 'year' => 'integer',
152
+ 'odometer' => 'integer',
153
+ 'engine_hours' => 'integer',
154
+ 'gvw' => 'decimal:2',
155
+ 'capacity' => Json::class,
156
+ 'specs' => Json::class,
157
+ 'attributes' => Json::class,
158
+ 'location' => Point::class,
159
+ ];
160
+
161
+ /**
162
+ * Properties which activity needs to be logged.
163
+ *
164
+ * @var array
165
+ */
166
+ protected static $logAttributes = '*';
167
+
168
+ /**
169
+ * Do not log empty changed.
170
+ *
171
+ * @var bool
172
+ */
173
+ protected static $submitEmptyLogs = false;
174
+
175
+ /**
176
+ * The name of the subject to log.
177
+ *
178
+ * @var string
179
+ */
180
+ protected static $logName = 'asset';
181
+
182
+ /**
183
+ * Get the options for generating the slug.
184
+ */
185
+ public function getSlugOptions(): SlugOptions
186
+ {
187
+ return SlugOptions::create()
188
+ ->generateSlugsFrom(['name', 'code'])
189
+ ->saveSlugsTo('slug');
190
+ }
191
+
192
+ /**
193
+ * Get the activity log options for the model.
194
+ */
195
+ public function getActivitylogOptions(): LogOptions
196
+ {
197
+ return LogOptions::defaults()->logAll();
198
+ }
199
+
200
+ public function assignedTo(): MorphTo
201
+ {
202
+ return $this->morphTo();
203
+ }
204
+
205
+ public function operator(): MorphTo
206
+ {
207
+ return $this->morphTo();
208
+ }
209
+
210
+ public function category(): BelongsTo
211
+ {
212
+ return $this->belongsTo(Category::class, 'category_uuid', 'uuid');
213
+ }
214
+
215
+ public function vendor(): BelongsTo
216
+ {
217
+ return $this->belongsTo(Vendor::class, 'vendor_uuid', 'uuid');
218
+ }
219
+
220
+ public function warranty(): BelongsTo
221
+ {
222
+ return $this->belongsTo(Warranty::class, 'warranty_uuid', 'uuid');
223
+ }
224
+
225
+ public function telematic(): BelongsTo
226
+ {
227
+ return $this->belongsTo(Telematic::class, 'telematic_uuid', 'uuid');
228
+ }
229
+
230
+ public function currentPlace(): BelongsTo
231
+ {
232
+ return $this->belongsTo(Place::class, 'current_place_uuid', 'uuid');
233
+ }
234
+
235
+ public function photo(): BelongsTo
236
+ {
237
+ return $this->belongsTo(File::class, 'photo_uuid', 'uuid');
238
+ }
239
+
240
+ public function createdBy(): BelongsTo
241
+ {
242
+ return $this->belongsTo(User::class, 'created_by_uuid', 'uuid');
243
+ }
244
+
245
+ public function updatedBy(): BelongsTo
246
+ {
247
+ return $this->belongsTo(User::class, 'updated_by_uuid', 'uuid');
248
+ }
249
+
250
+ public function devices(): HasMany
251
+ {
252
+ return $this->hasMany(Device::class, 'attachable_uuid');
253
+ }
254
+
255
+ public function equipments(): HasMany
256
+ {
257
+ return $this->hasMany(Equipment::class, 'equipable_uuid', 'uuid')
258
+ ->where('equipable_type', static::class);
259
+ }
260
+
261
+ public function maintenances(): HasMany
262
+ {
263
+ return $this->hasMany(Maintenance::class, 'maintainable_uuid', 'uuid')
264
+ ->where('maintainable_type', static::class);
265
+ }
266
+
267
+ public function sensors(): HasMany
268
+ {
269
+ return $this->hasMany(Sensor::class, 'sensorable_uuid', 'uuid')
270
+ ->where('sensorable_type', static::class);
271
+ }
272
+
273
+ public function parts(): MorphMany
274
+ {
275
+ return $this->morphMany(Part::class, 'asset');
276
+ }
277
+
278
+ public function positions(): HasMany
279
+ {
280
+ return $this->hasMany(Position::class, 'subject_uuid');
281
+ }
282
+
283
+ /**
284
+ * Get the category name.
285
+ */
286
+ public function getCategoryNameAttribute(): ?string
287
+ {
288
+ return $this->category?->name;
289
+ }
290
+
291
+ /**
292
+ * Get the vendor name.
293
+ */
294
+ public function getVendorNameAttribute(): ?string
295
+ {
296
+ return $this->vendor?->name;
297
+ }
298
+
299
+ /**
300
+ * Get the warranty name.
301
+ */
302
+ public function getWarrantyNameAttribute(): ?string
303
+ {
304
+ return $this->warranty?->name;
305
+ }
306
+
307
+ /**
308
+ * Get the current location information.
309
+ */
310
+ public function getCurrentLocationAttribute(): ?array
311
+ {
312
+ if ($this->currentPlace) {
313
+ return [
314
+ 'name' => $this->currentPlace->name,
315
+ 'address' => $this->currentPlace->address,
316
+ 'latitude' => $this->currentPlace->latitude,
317
+ 'longitude' => $this->currentPlace->longitude,
318
+ ];
319
+ }
320
+
321
+ // Try to get location from telematic
322
+ if ($this->telematic && $this->telematic->last_location) {
323
+ return $this->telematic->last_location;
324
+ }
325
+
326
+ return null;
327
+ }
328
+
329
+ /**
330
+ * Get the photo URL.
331
+ */
332
+ public function getPhotoUrlAttribute(): ?string
333
+ {
334
+ return $this->photo?->url;
335
+ }
336
+
337
+ /**
338
+ * Get the display name for the asset.
339
+ */
340
+ public function getDisplayNameAttribute(): string
341
+ {
342
+ if ($this->name) {
343
+ return $this->name;
344
+ }
345
+
346
+ $parts = array_filter([
347
+ $this->make,
348
+ $this->model,
349
+ $this->year,
350
+ $this->code,
351
+ ]);
352
+
353
+ return implode(' ', $parts) ?: 'Asset #' . $this->public_id;
354
+ }
355
+
356
+ /**
357
+ * Check if the asset is currently online (via telematic).
358
+ */
359
+ public function getIsOnlineAttribute(): bool
360
+ {
361
+ return $this->telematic?->is_online ?? false;
362
+ }
363
+
364
+ /**
365
+ * Get the last maintenance record.
366
+ */
367
+ public function getLastMaintenanceAttribute(): ?Maintenance
368
+ {
369
+ return $this->maintenances()
370
+ ->where('status', 'completed')
371
+ ->orderBy('completed_at', 'desc')
372
+ ->first();
373
+ }
374
+
375
+ /**
376
+ * Get the next maintenance due date.
377
+ */
378
+ public function getNextMaintenanceDueAttribute(): ?string
379
+ {
380
+ $nextMaintenance = $this->maintenances()
381
+ ->where('status', 'scheduled')
382
+ ->orderBy('scheduled_at', 'asc')
383
+ ->first();
384
+
385
+ return $nextMaintenance?->scheduled_at?->toDateString();
386
+ }
387
+
388
+ /**
389
+ * Scope to get assets by type.
390
+ *
391
+ * @param \Illuminate\Database\Eloquent\Builder $query
392
+ *
393
+ * @return \Illuminate\Database\Eloquent\Builder
394
+ */
395
+ public function scopeByType($query, string $type)
396
+ {
397
+ return $query->where('type', $type);
398
+ }
399
+
400
+ /**
401
+ * Scope to get active assets.
402
+ *
403
+ * @param \Illuminate\Database\Eloquent\Builder $query
404
+ *
405
+ * @return \Illuminate\Database\Eloquent\Builder
406
+ */
407
+ public function scopeActive($query)
408
+ {
409
+ return $query->where('status', 'active');
410
+ }
411
+
412
+ /**
413
+ * Scope to get assets with telematics.
414
+ *
415
+ * @param \Illuminate\Database\Eloquent\Builder $query
416
+ *
417
+ * @return \Illuminate\Database\Eloquent\Builder
418
+ */
419
+ public function scopeWithTelematics($query)
420
+ {
421
+ return $query->whereNotNull('telematic_uuid');
422
+ }
423
+
424
+ /**
425
+ * Scope to get assets that are online.
426
+ *
427
+ * @param \Illuminate\Database\Eloquent\Builder $query
428
+ *
429
+ * @return \Illuminate\Database\Eloquent\Builder
430
+ */
431
+ public function scopeOnline($query)
432
+ {
433
+ return $query->whereHas('telematic', function ($q) {
434
+ $q->online();
435
+ });
436
+ }
437
+
438
+ /**
439
+ * Update the asset's odometer reading.
440
+ */
441
+ public function updateOdometer(int $reading, ?string $source = null): bool
442
+ {
443
+ if ($reading > $this->odometer) {
444
+ $updated = $this->update(['odometer' => $reading]);
445
+
446
+ if ($updated) {
447
+ activity('odometer_update')
448
+ ->performedOn($this)
449
+ ->withProperties([
450
+ 'old_reading' => $this->getOriginal('odometer'),
451
+ 'new_reading' => $reading,
452
+ 'source' => $source ?? 'manual',
453
+ ])
454
+ ->log('Odometer updated');
455
+ }
456
+
457
+ return $updated;
458
+ }
459
+
460
+ return false;
461
+ }
462
+
463
+ /**
464
+ * Update the asset's engine hours.
465
+ */
466
+ public function updateEngineHours(int $hours, ?string $source = null): bool
467
+ {
468
+ if ($hours > $this->engine_hours) {
469
+ $updated = $this->update(['engine_hours' => $hours]);
470
+
471
+ if ($updated) {
472
+ activity('engine_hours_update')
473
+ ->performedOn($this)
474
+ ->withProperties([
475
+ 'old_hours' => $this->getOriginal('engine_hours'),
476
+ 'new_hours' => $hours,
477
+ 'source' => $source ?? 'manual',
478
+ ])
479
+ ->log('Engine hours updated');
480
+ }
481
+
482
+ return $updated;
483
+ }
484
+
485
+ return false;
486
+ }
487
+
488
+ /**
489
+ * Check if the asset needs maintenance.
490
+ */
491
+ public function needsMaintenance(): bool
492
+ {
493
+ // Check if there's overdue maintenance
494
+ $overdueMaintenance = $this->maintenances()
495
+ ->where('status', 'scheduled')
496
+ ->where('scheduled_at', '<', now())
497
+ ->exists();
498
+
499
+ if ($overdueMaintenance) {
500
+ return true;
501
+ }
502
+
503
+ // Check maintenance intervals based on odometer or engine hours
504
+ $specs = $this->specs ?? [];
505
+ $maintenanceInterval = $specs['maintenance_interval'] ?? null;
506
+
507
+ if ($maintenanceInterval && $this->last_maintenance) {
508
+ $milesSinceLastMaintenance = $this->odometer - $this->last_maintenance->odometer;
509
+
510
+ return $milesSinceLastMaintenance >= $maintenanceInterval;
511
+ }
512
+
513
+ return false;
514
+ }
515
+
516
+ /**
517
+ * Get the asset's utilization rate.
518
+ */
519
+ public function getUtilizationRate(int $days = 30): float
520
+ {
521
+ // This would calculate based on actual usage data
522
+ // For now, return a placeholder calculation
523
+ $totalHours = $days * 24;
524
+ $usedHours = $this->engine_hours ?? 0;
525
+
526
+ return min(($usedHours / $totalHours) * 100, 100);
527
+ }
528
+
529
+ /**
530
+ * Schedule maintenance for the asset.
531
+ */
532
+ public function scheduleMaintenance(string $type, \DateTime $scheduledAt, array $details = []): Maintenance
533
+ {
534
+ return Maintenance::create([
535
+ 'company_uuid' => $this->company_uuid,
536
+ 'maintainable_type' => static::class,
537
+ 'maintainable_uuid' => $this->uuid,
538
+ 'type' => $type,
539
+ 'status' => 'scheduled',
540
+ 'scheduled_at' => $scheduledAt,
541
+ 'odometer' => $this->odometer,
542
+ 'engine_hours' => $this->engine_hours,
543
+ 'summary' => $details['summary'] ?? null,
544
+ 'notes' => $details['notes'] ?? null,
545
+ 'created_by_uuid' => auth()->id(),
546
+ ]);
547
+ }
548
+ }
@@ -10,6 +10,7 @@ use Fleetbase\Models\Model;
10
10
  use Fleetbase\Models\User;
11
11
  use Fleetbase\Notifications\UserInvited;
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;
@@ -42,6 +43,7 @@ class Contact extends Model
42
43
  use LogsActivity;
43
44
  use CausesActivity;
44
45
  use Notifiable;
46
+ use HasCustomFields;
45
47
 
46
48
  /**
47
49
  * The database table used by the model.