@fleetbase/fleetops-engine 0.6.43 → 0.6.45

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 (82) hide show
  1. package/addon/components/entity/form.hbs +24 -0
  2. package/addon/components/entity/form.js +30 -0
  3. package/addon/components/leaflet-draw-control.js +1 -1
  4. package/addon/components/map/drawer/geofence-event-listing.hbs +123 -0
  5. package/addon/components/map/drawer/geofence-event-listing.js +103 -0
  6. package/addon/components/map/drawer.js +1 -0
  7. package/addon/components/map/leaflet-live-map.js +83 -0
  8. package/addon/components/modals/orchestrator-import.js +2 -78
  9. package/addon/components/order/form/service-rate.hbs +1 -1
  10. package/addon/components/service-area/form.hbs +59 -0
  11. package/addon/components/service-rate/details.hbs +6 -6
  12. package/addon/components/service-rate/form.hbs +28 -15
  13. package/addon/components/zone/form.hbs +60 -1
  14. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  15. package/addon/controllers/operations/orders/index/details.js +27 -2
  16. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  17. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  18. package/addon/helpers/f-to-int.js +6 -0
  19. package/addon/routes/application.js +3 -3
  20. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  21. package/addon/routes/operations/orders/index/details.js +19 -3
  22. package/addon/services/equipment-actions.js +4 -6
  23. package/addon/services/geofence-event-bus.js +223 -0
  24. package/addon/services/leaflet-map-manager.js +4 -0
  25. package/addon/services/maintenance-actions.js +4 -6
  26. package/addon/services/part-actions.js +4 -6
  27. package/addon/services/service-rate-actions.js +8 -3
  28. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  29. package/addon/utils/fleet-ops-options.js +33 -0
  30. package/addon/utils/serialize-payload.js +1 -1
  31. package/app/components/map/drawer/geofence-event-listing.js +1 -0
  32. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  33. package/app/helpers/f-to-int.js +1 -0
  34. package/app/services/geofence-event-bus.js +1 -0
  35. package/composer.json +1 -1
  36. package/extension.json +1 -1
  37. package/package.json +2 -2
  38. package/server/migrations/2026_04_05_000001_add_geofence_config_to_zones_table.php +48 -0
  39. package/server/migrations/2026_04_05_000002_add_geofence_config_to_service_areas_table.php +38 -0
  40. package/server/migrations/2026_04_05_000003_create_driver_geofence_states_table.php +62 -0
  41. package/server/migrations/2026_04_05_000004_create_geofence_events_log_table.php +66 -0
  42. package/server/migrations/2026_04_22_000001_add_vehicle_support_to_geofence_events_log.php +31 -0
  43. package/server/migrations/2026_04_22_000002_create_vehicle_geofence_states_table.php +39 -0
  44. package/server/src/Console/Commands/SimulateGeofenceEvents.php +278 -0
  45. package/server/src/Events/GeofenceDwelled.php +208 -0
  46. package/server/src/Events/GeofenceEntered.php +207 -0
  47. package/server/src/Events/GeofenceExited.php +213 -0
  48. package/server/src/Http/Controllers/Api/v1/DriverController.php +100 -0
  49. package/server/src/Http/Controllers/Api/v1/GeofenceController.php +263 -0
  50. package/server/src/Http/Controllers/Api/v1/OrderController.php +13 -8
  51. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  52. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  53. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +4 -0
  54. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  55. package/server/src/Http/Controllers/Api/v1/VehicleController.php +88 -0
  56. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -2
  57. package/server/src/Http/Controllers/Internal/v1/GeofenceController.php +7 -0
  58. package/server/src/Http/Requests/CreateServiceAreaRequest.php +8 -4
  59. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  60. package/server/src/Http/Requests/CreateZoneRequest.php +8 -4
  61. package/server/src/Http/Resources/v1/Order.php +0 -1
  62. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  63. package/server/src/Jobs/CheckGeofenceDwell.php +139 -0
  64. package/server/src/Listeners/HandleGeofenceDwelled.php +56 -0
  65. package/server/src/Listeners/HandleGeofenceEntered.php +187 -0
  66. package/server/src/Listeners/HandleGeofenceExited.php +56 -0
  67. package/server/src/Models/Entity.php +7 -0
  68. package/server/src/Models/GeofenceEventLog.php +155 -0
  69. package/server/src/Models/Order.php +39 -7
  70. package/server/src/Models/ServiceArea.php +21 -2
  71. package/server/src/Models/ServiceQuote.php +3 -1
  72. package/server/src/Models/ServiceRate.php +122 -30
  73. package/server/src/Models/Zone.php +20 -2
  74. package/server/src/Notifications/DriverArrivedAtGeofence.php +86 -0
  75. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  76. package/server/src/Providers/EventServiceProvider.php +19 -0
  77. package/server/src/Providers/FleetOpsServiceProvider.php +10 -0
  78. package/server/src/Rules/ComputableAlgo.php +1 -3
  79. package/server/src/Support/Algo.php +87 -1
  80. package/server/src/Support/GeofenceIntersectionService.php +206 -0
  81. package/server/src/Support/Utils.php +35 -0
  82. package/server/src/routes.php +16 -0
@@ -0,0 +1,187 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Listeners;
4
+
5
+ use Fleetbase\FleetOps\Events\GeofenceEntered;
6
+ use Fleetbase\FleetOps\Models\GeofenceEventLog;
7
+ use Fleetbase\FleetOps\Models\Order;
8
+ use Fleetbase\FleetOps\Notifications\DriverArrivedAtGeofence;
9
+ use Illuminate\Contracts\Queue\ShouldQueue;
10
+ use Illuminate\Queue\InteractsWithQueue;
11
+ use Illuminate\Support\Facades\Log;
12
+ use Illuminate\Support\Str;
13
+
14
+ /**
15
+ * HandleGeofenceEntered.
16
+ *
17
+ * Handles the business logic triggered when a driver enters a geofence:
18
+ * 1. Writes a record to the geofence_events_log table.
19
+ * 2. Checks if the entered geofence corresponds to the driver's current
20
+ * order destination and auto-transitions the order to "arrived" status.
21
+ * 3. Sends a customer notification if the order destination matches.
22
+ */
23
+ class HandleGeofenceEntered implements ShouldQueue
24
+ {
25
+ use InteractsWithQueue;
26
+
27
+ /**
28
+ * The number of times the job may be attempted.
29
+ */
30
+ public int $tries = 3;
31
+
32
+ /**
33
+ * Handle the event.
34
+ */
35
+ public function handle(GeofenceEntered $event): void
36
+ {
37
+ $driver = $event->driver;
38
+ $vehicle = $event->vehicle;
39
+ $geofence = $event->geofence;
40
+
41
+ // Set company session context for any subsequent queries
42
+ session(['company' => $event->getCompanyUuid()]);
43
+
44
+ // ----------------------------------------------------------------
45
+ // 1. Write to the geofence event log
46
+ // ----------------------------------------------------------------
47
+ $order = $driver?->getCurrentOrder();
48
+ $subject = $event->subjectType === 'vehicle' ? $vehicle : $driver;
49
+
50
+ GeofenceEventLog::create([
51
+ 'uuid' => Str::uuid()->toString(),
52
+ 'company_uuid' => $event->getCompanyUuid(),
53
+ 'driver_uuid' => $driver?->uuid,
54
+ 'vehicle_uuid' => $vehicle?->uuid,
55
+ 'order_uuid' => $order?->uuid,
56
+ 'subject_uuid' => $subject?->uuid,
57
+ 'subject_type' => $event->subjectType,
58
+ 'subject_name' => $event->subjectType === 'vehicle' ? ($vehicle?->display_name ?? $vehicle?->plate_number) : $driver?->name,
59
+ 'geofence_uuid' => $geofence->uuid,
60
+ 'geofence_type' => $event->geofenceType,
61
+ 'geofence_name' => $geofence->name,
62
+ 'event_type' => 'entered',
63
+ 'latitude' => $event->location->getLat(),
64
+ 'longitude' => $event->location->getLng(),
65
+ 'occurred_at' => $event->timestamp,
66
+ ]);
67
+
68
+ // ----------------------------------------------------------------
69
+ // 2. Order status automation
70
+ //
71
+ // If the driver has an active order and the geofence is in
72
+ // proximity to the current destination waypoint, auto-transition
73
+ // the order to "arrived" status and notify the customer.
74
+ // ----------------------------------------------------------------
75
+ if ($driver && $order) {
76
+ $this->handleOrderArrival($driver, $geofence, $order, $event);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Attempt to auto-transition an order to "arrived" status when the
82
+ * driver enters a geofence near the order's current destination.
83
+ */
84
+ private function handleOrderArrival($driver, $geofence, Order $order, GeofenceEntered $event): void
85
+ {
86
+ // Do not re-trigger if the order is already arrived or completed
87
+ if (in_array($order->status, ['arrived', 'completed', 'canceled'])) {
88
+ return;
89
+ }
90
+
91
+ // Get the current destination waypoint
92
+ $destination = null;
93
+ try {
94
+ $destination = $order->payload?->getPickupOrCurrentWaypoint();
95
+ } catch (\Throwable $e) {
96
+ Log::warning('GeofenceEntered: Could not resolve order destination', [
97
+ 'order_uuid' => $order->uuid,
98
+ 'driver_uuid' => $driver->uuid,
99
+ 'error' => $e->getMessage(),
100
+ ]);
101
+
102
+ return;
103
+ }
104
+
105
+ if (!$destination) {
106
+ return;
107
+ }
108
+
109
+ // Resolve the destination place
110
+ $place = null;
111
+ try {
112
+ $place = $destination->place ?? $destination->getPlace();
113
+ } catch (\Throwable $e) {
114
+ return;
115
+ }
116
+
117
+ if (!$place || !$place->location) {
118
+ return;
119
+ }
120
+
121
+ // Check proximity: is the geofence centroid within 500m of the destination place?
122
+ try {
123
+ $geofenceLat = $geofence->getLatitudeAttribute();
124
+ $geofenceLng = $geofence->getLongitudeAttribute();
125
+ } catch (\Throwable $e) {
126
+ return;
127
+ }
128
+
129
+ $placeLat = $place->location->getLat();
130
+ $placeLng = $place->location->getLng();
131
+
132
+ $distanceMeters = $this->haversineDistance($geofenceLat, $geofenceLng, $placeLat, $placeLng);
133
+
134
+ // Only trigger if the geofence is within 500m of the destination
135
+ if ($distanceMeters > 500) {
136
+ return;
137
+ }
138
+
139
+ // Auto-transition order to "arrived"
140
+ try {
141
+ $order->setStatus('arrived');
142
+ $order->createActivity(
143
+ [
144
+ 'status' => 'arrived',
145
+ 'details' => sprintf('Driver entered destination geofence "%s".', $geofence->name),
146
+ ],
147
+ $event->location
148
+ );
149
+ } catch (\Throwable $e) {
150
+ Log::error('GeofenceEntered: Failed to set order status to arrived', [
151
+ 'order_uuid' => $order->uuid,
152
+ 'error' => $e->getMessage(),
153
+ ]);
154
+
155
+ return;
156
+ }
157
+
158
+ // Notify the customer
159
+ if ($order->customer) {
160
+ try {
161
+ $order->customer->notify(new DriverArrivedAtGeofence($order, $geofence));
162
+ } catch (\Throwable $e) {
163
+ // Notification failure must not interrupt the geofence pipeline
164
+ Log::warning('GeofenceEntered: Failed to notify customer', [
165
+ 'order_uuid' => $order->uuid,
166
+ 'error' => $e->getMessage(),
167
+ ]);
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Calculate the haversine distance in metres between two lat/lng points.
174
+ *
175
+ * @return float Distance in metres
176
+ */
177
+ private function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float
178
+ {
179
+ $earthRadius = 6371000; // metres
180
+ $dLat = deg2rad($lat2 - $lat1);
181
+ $dLng = deg2rad($lng2 - $lng1);
182
+ $a = sin($dLat / 2) ** 2
183
+ + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
184
+
185
+ return $earthRadius * 2 * atan2(sqrt($a), sqrt(1 - $a));
186
+ }
187
+ }
@@ -0,0 +1,56 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Listeners;
4
+
5
+ use Fleetbase\FleetOps\Events\GeofenceExited;
6
+ use Fleetbase\FleetOps\Models\GeofenceEventLog;
7
+ use Illuminate\Contracts\Queue\ShouldQueue;
8
+ use Illuminate\Queue\InteractsWithQueue;
9
+ use Illuminate\Support\Str;
10
+
11
+ /**
12
+ * HandleGeofenceExited.
13
+ *
14
+ * Handles the business logic triggered when a driver exits a geofence:
15
+ * 1. Writes a record to the geofence_events_log table, including the
16
+ * dwell duration calculated from the entry timestamp.
17
+ */
18
+ class HandleGeofenceExited implements ShouldQueue
19
+ {
20
+ use InteractsWithQueue;
21
+
22
+ /**
23
+ * The number of times the job may be attempted.
24
+ */
25
+ public int $tries = 3;
26
+
27
+ /**
28
+ * Handle the event.
29
+ */
30
+ public function handle(GeofenceExited $event): void
31
+ {
32
+ $driver = $event->driver;
33
+ $vehicle = $event->vehicle;
34
+ $order = $driver?->getCurrentOrder();
35
+ $subject = $event->subjectType === 'vehicle' ? $vehicle : $driver;
36
+
37
+ GeofenceEventLog::create([
38
+ 'uuid' => Str::uuid()->toString(),
39
+ 'company_uuid' => $event->getCompanyUuid(),
40
+ 'driver_uuid' => $driver?->uuid,
41
+ 'vehicle_uuid' => $vehicle?->uuid,
42
+ 'order_uuid' => $order?->uuid,
43
+ 'subject_uuid' => $subject?->uuid,
44
+ 'subject_type' => $event->subjectType,
45
+ 'subject_name' => $event->subjectType === 'vehicle' ? ($vehicle?->display_name ?? $vehicle?->plate_number) : $driver?->name,
46
+ 'geofence_uuid' => $event->geofence->uuid,
47
+ 'geofence_type' => $event->geofenceType,
48
+ 'geofence_name' => $event->geofence->name,
49
+ 'event_type' => 'exited',
50
+ 'latitude' => $event->location->getLat(),
51
+ 'longitude' => $event->location->getLng(),
52
+ 'dwell_duration_minutes' => $event->dwellDurationMinutes,
53
+ 'occurred_at' => $event->timestamp,
54
+ ]);
55
+ }
56
+ }
@@ -168,6 +168,13 @@ class Entity extends Model
168
168
  ])->render();
169
169
  }
170
170
 
171
+ public function setTypeAttribute(?string $type = null): void
172
+ {
173
+ $type = is_string($type) ? trim($type) : $type;
174
+
175
+ $this->attributes['type'] = filled($type) ? Str::snake($type) : null;
176
+ }
177
+
171
178
  /**
172
179
  * @var BelongsTo
173
180
  */
@@ -0,0 +1,155 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Models;
4
+
5
+ use Fleetbase\Traits\HasUuid;
6
+ use Illuminate\Database\Eloquent\Model;
7
+ use Illuminate\Database\Eloquent\Relations\BelongsTo;
8
+
9
+ /**
10
+ * GeofenceEventLog.
11
+ *
12
+ * Persistent audit log of all geofence events (entered, exited, dwelled).
13
+ * Powers the reporting dashboard, dwell time analytics, and geofence
14
+ * activity history views.
15
+ *
16
+ * @property string $uuid
17
+ * @property string $company_uuid
18
+ * @property string $driver_uuid
19
+ * @property string|null $vehicle_uuid
20
+ * @property string|null $order_uuid
21
+ * @property string $geofence_uuid
22
+ * @property string $geofence_type
23
+ * @property string|null $geofence_name
24
+ * @property string $event_type
25
+ * @property float|null $latitude
26
+ * @property float|null $longitude
27
+ * @property float|null $speed_kmh
28
+ * @property int|null $dwell_duration_minutes
29
+ * @property \Carbon\Carbon $occurred_at
30
+ */
31
+ class GeofenceEventLog extends Model
32
+ {
33
+ use HasUuid;
34
+
35
+ /**
36
+ * The database table used by the model.
37
+ *
38
+ * @var string
39
+ */
40
+ protected $table = 'geofence_events_log';
41
+
42
+ /**
43
+ * The primary key for the model.
44
+ *
45
+ * @var string
46
+ */
47
+ protected $primaryKey = 'uuid';
48
+
49
+ /**
50
+ * The "type" of the primary key ID.
51
+ *
52
+ * @var string
53
+ */
54
+ protected $keyType = 'string';
55
+
56
+ /**
57
+ * Indicates if the IDs are auto-incrementing.
58
+ *
59
+ * @var bool
60
+ */
61
+ public $incrementing = false;
62
+
63
+ /**
64
+ * The attributes that are mass assignable.
65
+ *
66
+ * @var array
67
+ */
68
+ protected $fillable = [
69
+ 'uuid',
70
+ 'company_uuid',
71
+ 'driver_uuid',
72
+ 'vehicle_uuid',
73
+ 'order_uuid',
74
+ 'subject_uuid',
75
+ 'subject_type',
76
+ 'subject_name',
77
+ 'geofence_uuid',
78
+ 'geofence_type',
79
+ 'geofence_name',
80
+ 'event_type',
81
+ 'latitude',
82
+ 'longitude',
83
+ 'speed_kmh',
84
+ 'dwell_duration_minutes',
85
+ 'occurred_at',
86
+ ];
87
+
88
+ /**
89
+ * The attributes that should be cast to native types.
90
+ *
91
+ * @var array
92
+ */
93
+ protected $casts = [
94
+ 'occurred_at' => 'datetime',
95
+ 'latitude' => 'float',
96
+ 'longitude' => 'float',
97
+ 'speed_kmh' => 'float',
98
+ 'dwell_duration_minutes' => 'integer',
99
+ ];
100
+
101
+ /**
102
+ * The attributes excluded from the model's JSON form.
103
+ *
104
+ * @var array
105
+ */
106
+ protected $hidden = [];
107
+
108
+ /**
109
+ * Get the driver associated with this event.
110
+ */
111
+ public function driver(): BelongsTo
112
+ {
113
+ return $this->belongsTo(Driver::class, 'driver_uuid', 'uuid');
114
+ }
115
+
116
+ /**
117
+ * Get the order associated with this event (if any).
118
+ */
119
+ public function order(): BelongsTo
120
+ {
121
+ return $this->belongsTo(Order::class, 'order_uuid', 'uuid');
122
+ }
123
+
124
+ /**
125
+ * Get the vehicle associated with this event (if any).
126
+ */
127
+ public function vehicle(): BelongsTo
128
+ {
129
+ return $this->belongsTo(Vehicle::class, 'vehicle_uuid', 'uuid');
130
+ }
131
+
132
+ /**
133
+ * Scope to filter by company.
134
+ *
135
+ * @param \Illuminate\Database\Eloquent\Builder $query
136
+ *
137
+ * @return \Illuminate\Database\Eloquent\Builder
138
+ */
139
+ public function scopeForCompany($query, string $companyUuid)
140
+ {
141
+ return $query->where('company_uuid', $companyUuid);
142
+ }
143
+
144
+ /**
145
+ * Scope to filter by event type.
146
+ *
147
+ * @param \Illuminate\Database\Eloquent\Builder $query
148
+ *
149
+ * @return \Illuminate\Database\Eloquent\Builder
150
+ */
151
+ public function scopeOfType($query, string $type)
152
+ {
153
+ return $query->where('event_type', $type);
154
+ }
155
+ }
@@ -1055,9 +1055,7 @@ class Order extends Model
1055
1055
  'meta' => $meta,
1056
1056
  ]);
1057
1057
 
1058
- $this->purchase_rate_uuid = $purchasedRate->uuid;
1059
-
1060
- return $this->save();
1058
+ return $this->attachPurchaseRate($purchasedRate);
1061
1059
  }
1062
1060
 
1063
1061
  /**
@@ -1096,14 +1094,48 @@ class Order extends Model
1096
1094
  'meta' => $meta,
1097
1095
  ]);
1098
1096
 
1099
- return $this->updateQuietly([
1100
- 'purchase_rate_uuid' => $purchasedRate->uuid,
1101
- ]);
1097
+ return $this->attachPurchaseRate($purchasedRate);
1102
1098
  }
1103
1099
 
1104
1100
  return false;
1105
1101
  }
1106
1102
 
1103
+ /**
1104
+ * Attach a purchase rate to the order and void any superseded transaction.
1105
+ */
1106
+ public function attachPurchaseRate(PurchaseRate $purchaseRate): bool
1107
+ {
1108
+ $oldTransaction = $this->transaction_uuid
1109
+ ? Transaction::where('uuid', $this->transaction_uuid)->first()
1110
+ : null;
1111
+
1112
+ $attached = (bool) $this->updateQuietly([
1113
+ 'purchase_rate_uuid' => $purchaseRate->uuid,
1114
+ 'transaction_uuid' => $purchaseRate->transaction_uuid,
1115
+ ]);
1116
+
1117
+ if ($attached) {
1118
+ $this->setRelation('purchaseRate', $purchaseRate);
1119
+
1120
+ if ($purchaseRate->transaction_uuid) {
1121
+ $this->setRelation('transaction', Transaction::where('uuid', $purchaseRate->transaction_uuid)->first());
1122
+ }
1123
+
1124
+ if (
1125
+ $oldTransaction instanceof Transaction
1126
+ && $oldTransaction->uuid !== $purchaseRate->transaction_uuid
1127
+ && !$oldTransaction->trashed()
1128
+ ) {
1129
+ $oldTransaction->update([
1130
+ 'status' => Transaction::STATUS_VOIDED,
1131
+ 'voided_at' => now(),
1132
+ ]);
1133
+ }
1134
+ }
1135
+
1136
+ return $attached;
1137
+ }
1138
+
1107
1139
  /**
1108
1140
  * Creates a transaction for the order without a service quote.
1109
1141
  * This method is used when an order is made without selecting a specific service quote.
@@ -1122,7 +1154,7 @@ class Order extends Model
1122
1154
  'gateway_transaction_id' => Transaction::generateNumber(),
1123
1155
  'gateway' => 'internal',
1124
1156
  'amount' => 0,
1125
- 'currency' => data_get($this->company, 'country') ? Utils::getCurrenyFromCountryCode(data_get($this->company, 'country')) : 'SGD',
1157
+ 'currency' => Utils::getCompanyTransactionCurrency($this->company ?? $this->company_uuid),
1126
1158
  'description' => 'Dispatch order',
1127
1159
  'type' => 'dispatch',
1128
1160
  'status' => 'success',
@@ -60,7 +60,22 @@ class ServiceArea extends Model
60
60
  *
61
61
  * @var array
62
62
  */
63
- protected $fillable = ['_key', 'company_uuid', 'name', 'type', 'parent_uuid', 'border', 'color', 'stroke_color', 'status', 'country'];
63
+ protected $fillable = [
64
+ '_key',
65
+ 'company_uuid',
66
+ 'name',
67
+ 'type',
68
+ 'parent_uuid',
69
+ 'border',
70
+ 'color',
71
+ 'stroke_color',
72
+ 'status',
73
+ 'country',
74
+ 'trigger_on_entry',
75
+ 'trigger_on_exit',
76
+ 'dwell_threshold_minutes',
77
+ 'speed_limit_kmh',
78
+ ];
64
79
 
65
80
  /**
66
81
  * The attributes that are spatial columns.
@@ -96,7 +111,11 @@ class ServiceArea extends Model
96
111
  * @var array
97
112
  */
98
113
  protected $casts = [
99
- 'border' => MultiPolygonCast::class,
114
+ 'border' => MultiPolygonCast::class,
115
+ 'trigger_on_entry' => 'boolean',
116
+ 'trigger_on_exit' => 'boolean',
117
+ 'dwell_threshold_minutes' => 'integer',
118
+ 'speed_limit_kmh' => 'integer',
100
119
  ];
101
120
 
102
121
  /**
@@ -3,6 +3,8 @@
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Http\Requests\CreateOrderRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdateOrderRequest;
6
8
  use Fleetbase\FleetOps\Integrations\Lalamove\Lalamove;
7
9
  use Fleetbase\FleetOps\Support\Payment;
8
10
  use Fleetbase\FleetOps\Support\Utils;
@@ -136,7 +138,7 @@ class ServiceQuote extends Model
136
138
  return Lalamove::serviceQuoteFromQuotation($quotation);
137
139
  }
138
140
 
139
- public static function resolveFromRequest(Request $request): ?ServiceQuote
141
+ public static function resolveFromRequest(Request|CreateOrderRequest|UpdateOrderRequest $request): ?ServiceQuote
140
142
  {
141
143
  $serviceQuote = $request->or(['order.service_quote_uuid', 'service_quote', 'service_quote_id', 'order.service_quote']);
142
144