@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,483 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Models;
4
+
5
+ use Fleetbase\Casts\Json;
6
+ use Fleetbase\Models\File;
7
+ use Fleetbase\Models\Model;
8
+ use Fleetbase\Models\User;
9
+ use Fleetbase\Traits\HasApiModelBehavior;
10
+ use Fleetbase\Traits\HasCustomFields;
11
+ use Fleetbase\Traits\HasMetaAttributes;
12
+ use Fleetbase\Traits\HasPublicId;
13
+ use Fleetbase\Traits\HasUuid;
14
+ use Fleetbase\Traits\Searchable;
15
+ use Fleetbase\Traits\TracksApiCredential;
16
+ use Illuminate\Database\Eloquent\Relations\BelongsTo;
17
+ use Illuminate\Database\Eloquent\Relations\HasMany;
18
+ use Illuminate\Database\Eloquent\Relations\MorphTo;
19
+ use Spatie\Activitylog\LogOptions;
20
+ use Spatie\Activitylog\Traits\LogsActivity;
21
+ use Spatie\Sluggable\HasSlug;
22
+ use Spatie\Sluggable\SlugOptions;
23
+
24
+ /**
25
+ * Class Equipment.
26
+ *
27
+ * Represents auxiliary gear that can be assigned to assets or other entities.
28
+ * Examples include PPE, refrigeration units, tools, liftgates, and other equipment.
29
+ */
30
+ class Equipment extends Model
31
+ {
32
+ use HasUuid;
33
+ use HasPublicId;
34
+ use TracksApiCredential;
35
+ use HasApiModelBehavior;
36
+ use HasSlug;
37
+ use LogsActivity;
38
+ use HasMetaAttributes;
39
+ use Searchable;
40
+ use HasCustomFields;
41
+
42
+ /**
43
+ * The database table used by the model.
44
+ *
45
+ * @var string
46
+ */
47
+ protected $table = 'equipments';
48
+
49
+ /**
50
+ * The type of public Id to generate.
51
+ *
52
+ * @var string
53
+ */
54
+ protected $publicIdType = 'equipment';
55
+
56
+ /**
57
+ * The attributes that can be queried.
58
+ *
59
+ * @var array
60
+ */
61
+ protected $searchableColumns = ['name', 'code', 'type', 'serial_number', 'manufacturer', 'model', 'public_id'];
62
+
63
+ /**
64
+ * The attributes that can be used for filtering.
65
+ *
66
+ * @var array
67
+ */
68
+ protected $filterParams = ['type', 'status', 'manufacturer', 'warranty_uuid', 'equipable_type'];
69
+
70
+ /**
71
+ * The attributes that are mass assignable.
72
+ *
73
+ * @var array
74
+ */
75
+ protected $fillable = [
76
+ 'company_uuid',
77
+ 'warranty_uuid',
78
+ 'photo_uuid',
79
+ 'name',
80
+ 'code',
81
+ 'type',
82
+ 'status',
83
+ 'serial_number',
84
+ 'manufacturer',
85
+ 'model',
86
+ 'equipable_type',
87
+ 'equipable_uuid',
88
+ 'purchased_at',
89
+ 'purchase_price',
90
+ 'meta',
91
+ 'slug',
92
+ ];
93
+
94
+ /**
95
+ * Dynamic attributes that are appended to object.
96
+ *
97
+ * @var array
98
+ */
99
+ protected $appends = [
100
+ 'warranty_name',
101
+ 'photo_url',
102
+ 'equipped_to_name',
103
+ 'is_equipped',
104
+ 'age_in_days',
105
+ 'depreciated_value',
106
+ ];
107
+
108
+ /**
109
+ * The attributes excluded from the model's JSON form.
110
+ *
111
+ * @var array
112
+ */
113
+ protected $hidden = ['warranty', 'photo', 'equipable'];
114
+
115
+ /**
116
+ * The attributes that should be cast to native types.
117
+ *
118
+ * @var array
119
+ */
120
+ protected $casts = [
121
+ 'purchased_at' => 'date',
122
+ 'purchase_price' => 'decimal:2',
123
+ 'meta' => Json::class,
124
+ ];
125
+
126
+ /**
127
+ * Properties which activity needs to be logged.
128
+ *
129
+ * @var array
130
+ */
131
+ protected static $logAttributes = '*';
132
+
133
+ /**
134
+ * Do not log empty changed.
135
+ *
136
+ * @var bool
137
+ */
138
+ protected static $submitEmptyLogs = false;
139
+
140
+ /**
141
+ * The name of the subject to log.
142
+ *
143
+ * @var string
144
+ */
145
+ protected static $logName = 'equipment';
146
+
147
+ /**
148
+ * Get the options for generating the slug.
149
+ */
150
+ public function getSlugOptions(): SlugOptions
151
+ {
152
+ return SlugOptions::create()
153
+ ->generateSlugsFrom(['name', 'code'])
154
+ ->saveSlugsTo('slug');
155
+ }
156
+
157
+ /**
158
+ * Get the activity log options for the model.
159
+ */
160
+ public function getActivitylogOptions(): LogOptions
161
+ {
162
+ return LogOptions::defaults()->logAll();
163
+ }
164
+
165
+ public function warranty(): BelongsTo
166
+ {
167
+ return $this->belongsTo(Warranty::class, 'warranty_uuid', 'uuid');
168
+ }
169
+
170
+ public function photo(): BelongsTo
171
+ {
172
+ return $this->belongsTo(File::class, 'photo_uuid', 'uuid');
173
+ }
174
+
175
+ public function createdBy(): BelongsTo
176
+ {
177
+ return $this->belongsTo(User::class, 'created_by_uuid', 'uuid');
178
+ }
179
+
180
+ public function updatedBy(): BelongsTo
181
+ {
182
+ return $this->belongsTo(User::class, 'updated_by_uuid', 'uuid');
183
+ }
184
+
185
+ public function equipable(): MorphTo
186
+ {
187
+ return $this->morphTo();
188
+ }
189
+
190
+ public function maintenances(): HasMany
191
+ {
192
+ return $this->hasMany(Maintenance::class, 'maintainable_uuid', 'uuid')
193
+ ->where('maintainable_type', static::class);
194
+ }
195
+
196
+ /**
197
+ * Get the warranty name.
198
+ */
199
+ public function getWarrantyNameAttribute(): ?string
200
+ {
201
+ return $this->warranty?->name;
202
+ }
203
+
204
+ /**
205
+ * Get the photo URL.
206
+ */
207
+ public function getPhotoUrlAttribute(): ?string
208
+ {
209
+ return $this->photo?->url;
210
+ }
211
+
212
+ /**
213
+ * Get the name of what the equipment is equipped to.
214
+ */
215
+ public function getEquippedToNameAttribute(): ?string
216
+ {
217
+ if ($this->equipable) {
218
+ return $this->equipable->name ?? $this->equipable->display_name ?? null;
219
+ }
220
+
221
+ return null;
222
+ }
223
+
224
+ /**
225
+ * Check if the equipment is currently equipped to something.
226
+ */
227
+ public function getIsEquippedAttribute(): bool
228
+ {
229
+ return !is_null($this->equipable_uuid) && !is_null($this->equipable_type);
230
+ }
231
+
232
+ /**
233
+ * Get the age of the equipment in days.
234
+ */
235
+ public function getAgeInDaysAttribute(): ?int
236
+ {
237
+ if ($this->purchased_at) {
238
+ return $this->purchased_at->diffInDays(now());
239
+ }
240
+
241
+ return null;
242
+ }
243
+
244
+ /**
245
+ * Get the depreciated value of the equipment.
246
+ */
247
+ public function getDepreciatedValueAttribute(): ?float
248
+ {
249
+ if (!$this->purchase_price || !$this->purchased_at) {
250
+ return null;
251
+ }
252
+
253
+ $meta = $this->meta ?? [];
254
+ $depreciationRate = $meta['depreciation_rate'] ?? 0.1; // 10% per year default
255
+ $yearsOld = $this->purchased_at->diffInYears(now());
256
+
257
+ $depreciatedAmount = $this->purchase_price * ($depreciationRate * $yearsOld);
258
+ $currentValue = $this->purchase_price - $depreciatedAmount;
259
+
260
+ return max($currentValue, 0); // Don't go below 0
261
+ }
262
+
263
+ /**
264
+ * Scope to get equipment by type.
265
+ *
266
+ * @param \Illuminate\Database\Eloquent\Builder $query
267
+ *
268
+ * @return \Illuminate\Database\Eloquent\Builder
269
+ */
270
+ public function scopeByType($query, string $type)
271
+ {
272
+ return $query->where('type', $type);
273
+ }
274
+
275
+ /**
276
+ * Scope to get active equipment.
277
+ *
278
+ * @param \Illuminate\Database\Eloquent\Builder $query
279
+ *
280
+ * @return \Illuminate\Database\Eloquent\Builder
281
+ */
282
+ public function scopeActive($query)
283
+ {
284
+ return $query->where('status', 'active');
285
+ }
286
+
287
+ /**
288
+ * Scope to get equipped equipment.
289
+ *
290
+ * @param \Illuminate\Database\Eloquent\Builder $query
291
+ *
292
+ * @return \Illuminate\Database\Eloquent\Builder
293
+ */
294
+ public function scopeEquipped($query)
295
+ {
296
+ return $query->whereNotNull('equipable_uuid')
297
+ ->whereNotNull('equipable_type');
298
+ }
299
+
300
+ /**
301
+ * Scope to get unequipped equipment.
302
+ *
303
+ * @param \Illuminate\Database\Eloquent\Builder $query
304
+ *
305
+ * @return \Illuminate\Database\Eloquent\Builder
306
+ */
307
+ public function scopeUnequipped($query)
308
+ {
309
+ return $query->whereNull('equipable_uuid')
310
+ ->orWhereNull('equipable_type');
311
+ }
312
+
313
+ /**
314
+ * Scope to get equipment by manufacturer.
315
+ *
316
+ * @param \Illuminate\Database\Eloquent\Builder $query
317
+ *
318
+ * @return \Illuminate\Database\Eloquent\Builder
319
+ */
320
+ public function scopeByManufacturer($query, string $manufacturer)
321
+ {
322
+ return $query->where('manufacturer', $manufacturer);
323
+ }
324
+
325
+ /**
326
+ * Equip this equipment to an entity.
327
+ */
328
+ public function equipTo(Model $equipable): bool
329
+ {
330
+ $updated = $this->update([
331
+ 'equipable_type' => get_class($equipable),
332
+ 'equipable_uuid' => $equipable->uuid,
333
+ ]);
334
+
335
+ if ($updated) {
336
+ activity('equipment_equipped')
337
+ ->performedOn($this)
338
+ ->withProperties([
339
+ 'equipped_to_type' => get_class($equipable),
340
+ 'equipped_to_uuid' => $equipable->uuid,
341
+ 'equipped_to_name' => $equipable->name ?? $equipable->display_name ?? null,
342
+ ])
343
+ ->log('Equipment equipped');
344
+ }
345
+
346
+ return $updated;
347
+ }
348
+
349
+ /**
350
+ * Unequip this equipment.
351
+ */
352
+ public function unequip(): bool
353
+ {
354
+ $oldEquipableType = $this->equipable_type;
355
+ $oldEquipableUuid = $this->equipable_uuid;
356
+
357
+ $updated = $this->update([
358
+ 'equipable_type' => null,
359
+ 'equipable_uuid' => null,
360
+ ]);
361
+
362
+ if ($updated) {
363
+ activity('equipment_unequipped')
364
+ ->performedOn($this)
365
+ ->withProperties([
366
+ 'previous_equipped_to_type' => $oldEquipableType,
367
+ 'previous_equipped_to_uuid' => $oldEquipableUuid,
368
+ ])
369
+ ->log('Equipment unequipped');
370
+ }
371
+
372
+ return $updated;
373
+ }
374
+
375
+ /**
376
+ * Check if the equipment needs maintenance.
377
+ */
378
+ public function needsMaintenance(): bool
379
+ {
380
+ // Check if there's overdue maintenance
381
+ $overdueMaintenance = $this->maintenances()
382
+ ->where('status', 'scheduled')
383
+ ->where('scheduled_at', '<', now())
384
+ ->exists();
385
+
386
+ if ($overdueMaintenance) {
387
+ return true;
388
+ }
389
+
390
+ // Check maintenance intervals based on age or usage
391
+ $meta = $this->meta ?? [];
392
+ $maintenanceIntervalDays = $meta['maintenance_interval_days'] ?? null;
393
+
394
+ if ($maintenanceIntervalDays && $this->purchased_at) {
395
+ $lastMaintenance = $this->maintenances()
396
+ ->where('status', 'completed')
397
+ ->orderBy('completed_at', 'desc')
398
+ ->first();
399
+
400
+ $lastMaintenanceDate = $lastMaintenance?->completed_at ?? $this->purchased_at;
401
+ $daysSinceLastMaintenance = $lastMaintenanceDate->diffInDays(now());
402
+
403
+ return $daysSinceLastMaintenance >= $maintenanceIntervalDays;
404
+ }
405
+
406
+ return false;
407
+ }
408
+
409
+ /**
410
+ * Schedule maintenance for the equipment.
411
+ */
412
+ public function scheduleMaintenance(string $type, \DateTime $scheduledAt, array $details = []): Maintenance
413
+ {
414
+ return Maintenance::create([
415
+ 'company_uuid' => $this->company_uuid,
416
+ 'maintainable_type' => static::class,
417
+ 'maintainable_uuid' => $this->uuid,
418
+ 'type' => $type,
419
+ 'status' => 'scheduled',
420
+ 'scheduled_at' => $scheduledAt,
421
+ 'summary' => $details['summary'] ?? null,
422
+ 'notes' => $details['notes'] ?? null,
423
+ 'created_by_uuid' => auth()->id(),
424
+ ]);
425
+ }
426
+
427
+ /**
428
+ * Get the utilization rate of the equipment.
429
+ */
430
+ public function getUtilizationRate(int $days = 30): float
431
+ {
432
+ // This would calculate based on actual usage data
433
+ // For now, return a placeholder calculation based on equipped status
434
+ if ($this->is_equipped) {
435
+ return 75.0; // Assume 75% utilization when equipped
436
+ }
437
+
438
+ return 0.0;
439
+ }
440
+
441
+ /**
442
+ * Get the maintenance cost for a period.
443
+ */
444
+ public function getMaintenanceCost(int $days = 365): float
445
+ {
446
+ $startDate = now()->subDays($days);
447
+
448
+ return $this->maintenances()
449
+ ->where('status', 'completed')
450
+ ->where('completed_at', '>=', $startDate)
451
+ ->sum('total_cost') ?? 0;
452
+ }
453
+
454
+ /**
455
+ * Check if the equipment is under warranty.
456
+ */
457
+ public function isUnderWarranty(): bool
458
+ {
459
+ return $this->warranty && $this->warranty->is_active;
460
+ }
461
+
462
+ /**
463
+ * Get the replacement cost estimate.
464
+ */
465
+ public function getReplacementCostEstimate(): ?float
466
+ {
467
+ $meta = $this->meta ?? [];
468
+
469
+ if (isset($meta['replacement_cost'])) {
470
+ return $meta['replacement_cost'];
471
+ }
472
+
473
+ // Estimate based on purchase price with inflation
474
+ if ($this->purchase_price && $this->purchased_at) {
475
+ $yearsOld = $this->purchased_at->diffInYears(now());
476
+ $inflationRate = $meta['inflation_rate'] ?? 0.03; // 3% default
477
+
478
+ return $this->purchase_price * pow(1 + $inflationRate, $yearsOld);
479
+ }
480
+
481
+ return null;
482
+ }
483
+ }
@@ -5,6 +5,7 @@ namespace Fleetbase\FleetOps\Models;
5
5
  use Fleetbase\FleetOps\Support\Utils;
6
6
  use Fleetbase\Models\Model;
7
7
  use Fleetbase\Traits\HasApiModelBehavior;
8
+ use Fleetbase\Traits\HasCustomFields;
8
9
  use Fleetbase\Traits\HasPublicId;
9
10
  use Fleetbase\Traits\HasUuid;
10
11
  use Fleetbase\Traits\Searchable;
@@ -27,6 +28,7 @@ class Fleet extends Model
27
28
  use HasSlug;
28
29
  use LogsActivity;
29
30
  use Notifiable;
31
+ use HasCustomFields;
30
32
 
31
33
  /**
32
34
  * The database table used by the model.
@@ -9,6 +9,7 @@ use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
9
9
  use Fleetbase\Models\Model;
10
10
  use Fleetbase\Models\User;
11
11
  use Fleetbase\Traits\HasApiModelBehavior;
12
+ use Fleetbase\Traits\HasCustomFields;
12
13
  use Fleetbase\Traits\HasPublicId;
13
14
  use Fleetbase\Traits\HasUuid;
14
15
  use Fleetbase\Traits\Searchable;
@@ -22,6 +23,7 @@ class FuelReport extends Model
22
23
  use HasApiModelBehavior;
23
24
  use SpatialTrait;
24
25
  use Searchable;
26
+ use HasCustomFields;
25
27
 
26
28
  /**
27
29
  * The database table used by the model.
@@ -9,6 +9,7 @@ use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
9
9
  use Fleetbase\Models\Model;
10
10
  use Fleetbase\Models\User;
11
11
  use Fleetbase\Traits\HasApiModelBehavior;
12
+ use Fleetbase\Traits\HasCustomFields;
12
13
  use Fleetbase\Traits\HasPublicId;
13
14
  use Fleetbase\Traits\HasUuid;
14
15
  use Fleetbase\Traits\TracksApiCredential;
@@ -21,6 +22,7 @@ class Issue extends Model
21
22
  use TracksApiCredential;
22
23
  use SpatialTrait;
23
24
  use HasApiModelBehavior;
25
+ use HasCustomFields;
24
26
 
25
27
  /**
26
28
  * The database table used by the model.