@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.
- package/addon/components/layout/fleet-ops-sidebar.hbs +25 -0
- package/addon/templates/virtual.hbs +3 -3
- package/composer.json +3 -2
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/migrations/2025_08_11_170800_add_company_index_to_routes_table.php +21 -0
- package/server/migrations/2025_08_28_054920_create_warranties_table.php +56 -0
- package/server/migrations/2025_08_28_054921_create_telematics_table.php +60 -0
- package/server/migrations/2025_08_28_054922_create_assets_table.php +94 -0
- package/server/migrations/2025_08_28_054925_create_devices_table.php +190 -0
- package/server/migrations/2025_08_28_054926_create_device_events_table.php +99 -0
- package/server/migrations/2025_08_28_054926_create_sensors_table.php +62 -0
- package/server/migrations/2025_08_28_054927_create_parts_table.php +73 -0
- package/server/migrations/2025_08_28_054929_create_equipments_table.php +58 -0
- package/server/migrations/2025_08_28_054930_create_work_orders_table.php +85 -0
- package/server/migrations/2025_08_28_054931_create_maintenances_table.php +79 -0
- package/server/migrations/2025_08_28_082002_update_vehicles_table_telematics.php +60 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +19 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +31 -8
- package/server/src/Http/Resources/v1/Order.php +111 -60
- package/server/src/Models/Asset.php +548 -0
- package/server/src/Models/Contact.php +2 -0
- package/server/src/Models/Device.php +435 -0
- package/server/src/Models/DeviceEvent.php +501 -0
- package/server/src/Models/Driver.php +2 -0
- package/server/src/Models/Entity.php +27 -50
- package/server/src/Models/Equipment.php +483 -0
- package/server/src/Models/Fleet.php +2 -0
- package/server/src/Models/FuelReport.php +2 -0
- package/server/src/Models/Issue.php +2 -0
- package/server/src/Models/Maintenance.php +549 -0
- package/server/src/Models/Order.php +32 -112
- package/server/src/Models/OrderConfig.php +8 -0
- package/server/src/Models/Part.php +502 -0
- package/server/src/Models/Payload.php +101 -20
- package/server/src/Models/Place.php +10 -4
- package/server/src/Models/Sensor.php +510 -0
- package/server/src/Models/ServiceArea.php +1 -1
- package/server/src/Models/Telematic.php +336 -0
- package/server/src/Models/Vehicle.php +45 -1
- package/server/src/Models/VehicleDevice.php +1 -1
- package/server/src/Models/Vendor.php +2 -0
- package/server/src/Models/Warranty.php +413 -0
- package/server/src/Models/Waypoint.php +2 -0
- package/server/src/Models/WorkOrder.php +532 -0
- package/server/src/Support/Utils.php +5 -0
- package/server/src/Traits/HasTrackingNumber.php +64 -10
- package/server/src/Traits/Maintainable.php +307 -0
- package/server/src/Traits/PayloadAccessors.php +126 -0
|
@@ -0,0 +1,532 @@
|
|
|
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
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Class WorkOrder.
|
|
24
|
+
*
|
|
25
|
+
* Represents an operational task wrapper that coordinates who, when, and what
|
|
26
|
+
* for maintenance or other jobs in the fleet management system.
|
|
27
|
+
*/
|
|
28
|
+
class WorkOrder extends Model
|
|
29
|
+
{
|
|
30
|
+
use HasUuid;
|
|
31
|
+
use HasPublicId;
|
|
32
|
+
use TracksApiCredential;
|
|
33
|
+
use HasApiModelBehavior;
|
|
34
|
+
use LogsActivity;
|
|
35
|
+
use HasMetaAttributes;
|
|
36
|
+
use Searchable;
|
|
37
|
+
use HasCustomFields;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The database table used by the model.
|
|
41
|
+
*
|
|
42
|
+
* @var string
|
|
43
|
+
*/
|
|
44
|
+
protected $table = 'work_orders';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The type of public Id to generate.
|
|
48
|
+
*
|
|
49
|
+
* @var string
|
|
50
|
+
*/
|
|
51
|
+
protected $publicIdType = 'work_order';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The attributes that can be queried.
|
|
55
|
+
*
|
|
56
|
+
* @var array
|
|
57
|
+
*/
|
|
58
|
+
protected $searchableColumns = ['code', 'subject', 'instructions', 'public_id'];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The attributes that can be used for filtering.
|
|
62
|
+
*
|
|
63
|
+
* @var array
|
|
64
|
+
*/
|
|
65
|
+
protected $filterParams = ['status', 'priority', 'target_type', 'assignee_type'];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The attributes that are mass assignable.
|
|
69
|
+
*
|
|
70
|
+
* @var array
|
|
71
|
+
*/
|
|
72
|
+
protected $fillable = [
|
|
73
|
+
'company_uuid',
|
|
74
|
+
'code',
|
|
75
|
+
'subject',
|
|
76
|
+
'status',
|
|
77
|
+
'priority',
|
|
78
|
+
'target_type',
|
|
79
|
+
'target_uuid',
|
|
80
|
+
'assignee_type',
|
|
81
|
+
'assignee_uuid',
|
|
82
|
+
'opened_at',
|
|
83
|
+
'due_at',
|
|
84
|
+
'closed_at',
|
|
85
|
+
'instructions',
|
|
86
|
+
'checklist',
|
|
87
|
+
'meta',
|
|
88
|
+
'slug',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Dynamic attributes that are appended to object.
|
|
93
|
+
*
|
|
94
|
+
* @var array
|
|
95
|
+
*/
|
|
96
|
+
protected $appends = [
|
|
97
|
+
'target_name',
|
|
98
|
+
'assignee_name',
|
|
99
|
+
'is_overdue',
|
|
100
|
+
'days_until_due',
|
|
101
|
+
'completion_percentage',
|
|
102
|
+
'estimated_duration',
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The attributes excluded from the model's JSON form.
|
|
107
|
+
*
|
|
108
|
+
* @var array
|
|
109
|
+
*/
|
|
110
|
+
protected $hidden = ['target', 'assignee'];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The attributes that should be cast to native types.
|
|
114
|
+
*
|
|
115
|
+
* @var array
|
|
116
|
+
*/
|
|
117
|
+
protected $casts = [
|
|
118
|
+
'opened_at' => 'datetime',
|
|
119
|
+
'due_at' => 'datetime',
|
|
120
|
+
'closed_at' => 'datetime',
|
|
121
|
+
'checklist' => Json::class,
|
|
122
|
+
'meta' => Json::class,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Properties which activity needs to be logged.
|
|
127
|
+
*
|
|
128
|
+
* @var array
|
|
129
|
+
*/
|
|
130
|
+
protected static $logAttributes = '*';
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Do not log empty changed.
|
|
134
|
+
*
|
|
135
|
+
* @var bool
|
|
136
|
+
*/
|
|
137
|
+
protected static $submitEmptyLogs = false;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The name of the subject to log.
|
|
141
|
+
*
|
|
142
|
+
* @var string
|
|
143
|
+
*/
|
|
144
|
+
protected static $logName = 'work_order';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the activity log options for the model.
|
|
148
|
+
*/
|
|
149
|
+
public function getActivitylogOptions(): LogOptions
|
|
150
|
+
{
|
|
151
|
+
return LogOptions::defaults()->logAll();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public function createdBy(): BelongsTo
|
|
155
|
+
{
|
|
156
|
+
return $this->belongsTo(User::class, 'created_by_uuid', 'uuid');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public function updatedBy(): BelongsTo
|
|
160
|
+
{
|
|
161
|
+
return $this->belongsTo(User::class, 'updated_by_uuid', 'uuid');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public function target(): MorphTo
|
|
165
|
+
{
|
|
166
|
+
return $this->morphTo();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public function assignee(): MorphTo
|
|
170
|
+
{
|
|
171
|
+
return $this->morphTo();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public function maintenances(): HasMany
|
|
175
|
+
{
|
|
176
|
+
return $this->hasMany(Maintenance::class, 'work_order_uuid', 'uuid');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public function documents(): HasMany
|
|
180
|
+
{
|
|
181
|
+
return $this->hasMany(File::class, 'subject_uuid', 'uuid');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the target name.
|
|
186
|
+
*/
|
|
187
|
+
public function getTargetNameAttribute(): ?string
|
|
188
|
+
{
|
|
189
|
+
if ($this->target) {
|
|
190
|
+
return $this->target->name ?? $this->target->display_name ?? null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get the assignee name.
|
|
198
|
+
*/
|
|
199
|
+
public function getAssigneeNameAttribute(): ?string
|
|
200
|
+
{
|
|
201
|
+
if ($this->assignee) {
|
|
202
|
+
return $this->assignee->name ?? $this->assignee->display_name ?? null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if the work order is overdue.
|
|
210
|
+
*/
|
|
211
|
+
public function getIsOverdueAttribute(): bool
|
|
212
|
+
{
|
|
213
|
+
if (!$this->due_at || $this->status === 'closed') {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return now()->gt($this->due_at);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get the number of days until due.
|
|
222
|
+
*/
|
|
223
|
+
public function getDaysUntilDueAttribute(): ?int
|
|
224
|
+
{
|
|
225
|
+
if (!$this->due_at || $this->status === 'closed') {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
$days = now()->diffInDays($this->due_at, false);
|
|
230
|
+
|
|
231
|
+
return $days;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get the completion percentage based on checklist.
|
|
236
|
+
*/
|
|
237
|
+
public function getCompletionPercentageAttribute(): float
|
|
238
|
+
{
|
|
239
|
+
$checklist = $this->checklist ?? [];
|
|
240
|
+
|
|
241
|
+
if (empty($checklist)) {
|
|
242
|
+
return $this->status === 'closed' ? 100.0 : 0.0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
$totalItems = count($checklist);
|
|
246
|
+
$completedItems = 0;
|
|
247
|
+
|
|
248
|
+
foreach ($checklist as $item) {
|
|
249
|
+
if (isset($item['completed']) && $item['completed']) {
|
|
250
|
+
$completedItems++;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return $totalItems > 0 ? ($completedItems / $totalItems) * 100 : 0.0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get the estimated duration in hours.
|
|
259
|
+
*/
|
|
260
|
+
public function getEstimatedDurationAttribute(): ?float
|
|
261
|
+
{
|
|
262
|
+
$meta = $this->meta ?? [];
|
|
263
|
+
|
|
264
|
+
return $meta['estimated_duration_hours'] ?? null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Scope to get work orders by status.
|
|
269
|
+
*
|
|
270
|
+
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
271
|
+
*
|
|
272
|
+
* @return \Illuminate\Database\Eloquent\Builder
|
|
273
|
+
*/
|
|
274
|
+
public function scopeByStatus($query, string $status)
|
|
275
|
+
{
|
|
276
|
+
return $query->where('status', $status);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Scope to get open work orders.
|
|
281
|
+
*
|
|
282
|
+
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
283
|
+
*
|
|
284
|
+
* @return \Illuminate\Database\Eloquent\Builder
|
|
285
|
+
*/
|
|
286
|
+
public function scopeOpen($query)
|
|
287
|
+
{
|
|
288
|
+
return $query->whereIn('status', ['open', 'in_progress']);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Scope to get overdue work orders.
|
|
293
|
+
*
|
|
294
|
+
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
295
|
+
*
|
|
296
|
+
* @return \Illuminate\Database\Eloquent\Builder
|
|
297
|
+
*/
|
|
298
|
+
public function scopeOverdue($query)
|
|
299
|
+
{
|
|
300
|
+
return $query->where('due_at', '<', now())
|
|
301
|
+
->whereNotIn('status', ['closed', 'canceled']);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Scope to get work orders by priority.
|
|
306
|
+
*
|
|
307
|
+
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
308
|
+
*
|
|
309
|
+
* @return \Illuminate\Database\Eloquent\Builder
|
|
310
|
+
*/
|
|
311
|
+
public function scopeByPriority($query, string $priority)
|
|
312
|
+
{
|
|
313
|
+
return $query->where('priority', $priority);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Scope to get work orders assigned to a specific entity.
|
|
318
|
+
*
|
|
319
|
+
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
320
|
+
*
|
|
321
|
+
* @return \Illuminate\Database\Eloquent\Builder
|
|
322
|
+
*/
|
|
323
|
+
public function scopeAssignedTo($query, string $type, string $uuid)
|
|
324
|
+
{
|
|
325
|
+
return $query->where('assignee_type', $type)
|
|
326
|
+
->where('assignee_uuid', $uuid);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Assign the work order to an entity.
|
|
331
|
+
*/
|
|
332
|
+
public function assignTo(Model $assignee): bool
|
|
333
|
+
{
|
|
334
|
+
$updated = $this->update([
|
|
335
|
+
'assignee_type' => get_class($assignee),
|
|
336
|
+
'assignee_uuid' => $assignee->uuid,
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
if ($updated) {
|
|
340
|
+
activity('work_order_assigned')
|
|
341
|
+
->performedOn($this)
|
|
342
|
+
->withProperties([
|
|
343
|
+
'assigned_to_type' => get_class($assignee),
|
|
344
|
+
'assigned_to_uuid' => $assignee->uuid,
|
|
345
|
+
'assigned_to_name' => $assignee->name ?? $assignee->display_name ?? null,
|
|
346
|
+
])
|
|
347
|
+
->log('Work order assigned');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return $updated;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Start the work order.
|
|
355
|
+
*/
|
|
356
|
+
public function start(): bool
|
|
357
|
+
{
|
|
358
|
+
if ($this->status !== 'open') {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
$updated = $this->update([
|
|
363
|
+
'status' => 'in_progress',
|
|
364
|
+
'opened_at' => $this->opened_at ?? now(),
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
if ($updated) {
|
|
368
|
+
activity('work_order_started')
|
|
369
|
+
->performedOn($this)
|
|
370
|
+
->log('Work order started');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return $updated;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Complete the work order.
|
|
378
|
+
*/
|
|
379
|
+
public function complete(array $completionData = []): bool
|
|
380
|
+
{
|
|
381
|
+
if (!in_array($this->status, ['open', 'in_progress'])) {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
$updateData = [
|
|
386
|
+
'status' => 'closed',
|
|
387
|
+
'closed_at' => now(),
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
// Update meta with completion data
|
|
391
|
+
if (!empty($completionData)) {
|
|
392
|
+
$meta = $this->meta ?? [];
|
|
393
|
+
$meta['completion_data'] = $completionData;
|
|
394
|
+
$updateData['meta'] = $meta;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
$updated = $this->update($updateData);
|
|
398
|
+
|
|
399
|
+
if ($updated) {
|
|
400
|
+
activity('work_order_completed')
|
|
401
|
+
->performedOn($this)
|
|
402
|
+
->withProperties($completionData)
|
|
403
|
+
->log('Work order completed');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return $updated;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Cancel the work order.
|
|
411
|
+
*/
|
|
412
|
+
public function cancel(?string $reason = null): bool
|
|
413
|
+
{
|
|
414
|
+
if ($this->status === 'closed') {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
$updateData = [
|
|
419
|
+
'status' => 'canceled',
|
|
420
|
+
'closed_at' => now(),
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
if ($reason) {
|
|
424
|
+
$meta = $this->meta ?? [];
|
|
425
|
+
$meta['cancellation_reason'] = $reason;
|
|
426
|
+
$updateData['meta'] = $meta;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
$updated = $this->update($updateData);
|
|
430
|
+
|
|
431
|
+
if ($updated) {
|
|
432
|
+
activity('work_order_canceled')
|
|
433
|
+
->performedOn($this)
|
|
434
|
+
->withProperties(['reason' => $reason])
|
|
435
|
+
->log('Work order canceled');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return $updated;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Update a checklist item.
|
|
443
|
+
*/
|
|
444
|
+
public function updateChecklistItem(int $itemIndex, array $itemData): bool
|
|
445
|
+
{
|
|
446
|
+
$checklist = $this->checklist ?? [];
|
|
447
|
+
|
|
448
|
+
if (!isset($checklist[$itemIndex])) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
$checklist[$itemIndex] = array_merge($checklist[$itemIndex], $itemData);
|
|
453
|
+
|
|
454
|
+
return $this->update(['checklist' => $checklist]);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Mark a checklist item as completed.
|
|
459
|
+
*/
|
|
460
|
+
public function completeChecklistItem(int $itemIndex, ?string $completedBy = null): bool
|
|
461
|
+
{
|
|
462
|
+
return $this->updateChecklistItem($itemIndex, [
|
|
463
|
+
'completed' => true,
|
|
464
|
+
'completed_at' => now(),
|
|
465
|
+
'completed_by' => $completedBy ?? auth()->id(),
|
|
466
|
+
]);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Add a new checklist item.
|
|
471
|
+
*/
|
|
472
|
+
public function addChecklistItem(array $item): bool
|
|
473
|
+
{
|
|
474
|
+
$checklist = $this->checklist ?? [];
|
|
475
|
+
$checklist[] = array_merge($item, [
|
|
476
|
+
'completed' => false,
|
|
477
|
+
'created_at' => now(),
|
|
478
|
+
]);
|
|
479
|
+
|
|
480
|
+
return $this->update(['checklist' => $checklist]);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get the actual duration of the work order.
|
|
485
|
+
*
|
|
486
|
+
* @return float|null Hours
|
|
487
|
+
*/
|
|
488
|
+
public function getActualDuration(): ?float
|
|
489
|
+
{
|
|
490
|
+
if (!$this->opened_at || !$this->closed_at) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return $this->opened_at->diffInHours($this->closed_at);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Check if the work order is on schedule.
|
|
499
|
+
*/
|
|
500
|
+
public function isOnSchedule(): ?bool
|
|
501
|
+
{
|
|
502
|
+
if (!$this->due_at) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if ($this->status === 'closed') {
|
|
507
|
+
return $this->closed_at->lte($this->due_at);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// For open work orders, check if we're still within the due date
|
|
511
|
+
return now()->lte($this->due_at);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Get the priority level as a numeric value for sorting.
|
|
516
|
+
*/
|
|
517
|
+
public function getPriorityLevel(): int
|
|
518
|
+
{
|
|
519
|
+
switch ($this->priority) {
|
|
520
|
+
case 'critical':
|
|
521
|
+
return 5;
|
|
522
|
+
case 'high':
|
|
523
|
+
return 4;
|
|
524
|
+
case 'medium':
|
|
525
|
+
return 3;
|
|
526
|
+
case 'low':
|
|
527
|
+
return 2;
|
|
528
|
+
default:
|
|
529
|
+
return 1;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
@@ -200,6 +200,11 @@ class Utils extends FleetbaseUtils
|
|
|
200
200
|
$coordinates = $coordinates->location;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
// any model with spatial location point
|
|
204
|
+
if ($coordinates instanceof \Illuminate\Database\Eloquent\Model && $coordinates->location) {
|
|
205
|
+
$coordinates = $coordinates->location;
|
|
206
|
+
}
|
|
207
|
+
|
|
203
208
|
if ($coordinates instanceof \Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialExpression) {
|
|
204
209
|
$coordinates = $coordinates->getSpatialValue();
|
|
205
210
|
}
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Traits;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Flow\Activity;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
7
|
use Fleetbase\FleetOps\Models\Proof;
|
|
7
8
|
use Fleetbase\FleetOps\Models\TrackingNumber;
|
|
8
9
|
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
9
10
|
use Fleetbase\FleetOps\Support\Utils;
|
|
10
11
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
12
|
+
use Fleetbase\Support\TemplateString;
|
|
11
13
|
use Illuminate\Database\Eloquent\Model;
|
|
12
14
|
use Illuminate\Support\Facades\DB;
|
|
15
|
+
use Illuminate\Support\Facades\Log;
|
|
13
16
|
|
|
14
17
|
trait HasTrackingNumber
|
|
15
18
|
{
|
|
@@ -55,11 +58,11 @@ trait HasTrackingNumber
|
|
|
55
58
|
*/
|
|
56
59
|
public function createActivity(Activity $activity, $location = [], $proof = null): TrackingStatus
|
|
57
60
|
{
|
|
58
|
-
$status
|
|
59
|
-
$details
|
|
60
|
-
$code
|
|
61
|
-
$proof
|
|
62
|
-
$activity
|
|
61
|
+
$status = $this->resolveActivityTemplateString($activity->get('status', ''));
|
|
62
|
+
$details = $this->resolveActivityTemplateString($activity->get('details', ''));
|
|
63
|
+
$code = $activity->get('code');
|
|
64
|
+
$proof = static::resolveProof($proof);
|
|
65
|
+
$activity = TrackingStatus::create([
|
|
63
66
|
'company_uuid' => data_get($this, 'company_uuid', session('company')),
|
|
64
67
|
'tracking_number_uuid' => $this->tracking_number_uuid,
|
|
65
68
|
'proof_uuid' => data_get($proof, 'uuid'),
|
|
@@ -89,11 +92,11 @@ trait HasTrackingNumber
|
|
|
89
92
|
*/
|
|
90
93
|
public function insertActivity(Activity $activity, $location = [], $proof = null): string
|
|
91
94
|
{
|
|
92
|
-
$status
|
|
93
|
-
$details
|
|
94
|
-
$code
|
|
95
|
-
$proof
|
|
96
|
-
$activityId
|
|
95
|
+
$status = $this->resolveActivityTemplateString($activity->get('status', ''));
|
|
96
|
+
$details = $this->resolveActivityTemplateString($activity->get('details', ''));
|
|
97
|
+
$code = $activity->get('code');
|
|
98
|
+
$proof = static::resolveProof($proof);
|
|
99
|
+
$activityId = TrackingStatus::insertGetUuid([
|
|
97
100
|
'company_uuid' => data_get($this, 'company_uuid', session('company')),
|
|
98
101
|
'tracking_number_uuid' => $this->tracking_number_uuid,
|
|
99
102
|
'proof_uuid' => data_get($proof, 'uuid'),
|
|
@@ -184,4 +187,55 @@ trait HasTrackingNumber
|
|
|
184
187
|
|
|
185
188
|
return null;
|
|
186
189
|
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resolve {placeholders} inside an activity template string.
|
|
193
|
+
*
|
|
194
|
+
* Behavior:
|
|
195
|
+
* - If called on an Order, resolves placeholders against that Order.
|
|
196
|
+
* - Otherwise, if the model exposes getOrder(), resolves against the returned Order.
|
|
197
|
+
* - If no suitable target model can be determined, returns the original template unchanged.
|
|
198
|
+
* - Uses App\Support\TemplateString::resolve() to process placeholders and modifiers
|
|
199
|
+
* (e.g., {waypoint.type}, {capitalize waypoint.type}, {order.number | snake | uppercase}).
|
|
200
|
+
*
|
|
201
|
+
* Robustness:
|
|
202
|
+
* - Fast path: if there are no braces, returns early.
|
|
203
|
+
* - Catches unexpected resolver failures and logs a warning rather than throwing inside UI flows.
|
|
204
|
+
*
|
|
205
|
+
* @param string $template the template containing {placeholders}
|
|
206
|
+
*
|
|
207
|
+
* @return string the resolved template string, or the original on fallback
|
|
208
|
+
*/
|
|
209
|
+
private function resolveActivityTemplateString(string $template): string
|
|
210
|
+
{
|
|
211
|
+
// Fast path: no placeholders to resolve.
|
|
212
|
+
if ($template === '' || strpos($template, '{') === false) {
|
|
213
|
+
return $template;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Determine which model should resolve dynamic properties.
|
|
217
|
+
// Prefer $this when it's already an Order; otherwise try a getOrder() accessor.
|
|
218
|
+
$target = $this instanceof Order
|
|
219
|
+
? $this
|
|
220
|
+
: (method_exists($this, 'getOrder') ? $this->getOrder() : null);
|
|
221
|
+
|
|
222
|
+
// If we couldn't locate a suitable Eloquent model, leave template unchanged.
|
|
223
|
+
if (!$target instanceof Model) {
|
|
224
|
+
return $template;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
// Uses default resolver name 'resolveDynamicProperty' on the target model.
|
|
229
|
+
return TemplateString::resolve($template, $target, 'resolveDynamicProperty');
|
|
230
|
+
} catch (\Throwable $e) {
|
|
231
|
+
// Don't break user flows on template issues; log and return original.
|
|
232
|
+
Log::warning('Activity template resolution failed.', [
|
|
233
|
+
'template' => $template,
|
|
234
|
+
'target' => get_class($target),
|
|
235
|
+
'message' => $e->getMessage(),
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
return $template;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
187
241
|
}
|