@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.
- package/addon/components/entity/form.hbs +24 -0
- package/addon/components/entity/form.js +30 -0
- package/addon/components/leaflet-draw-control.js +1 -1
- package/addon/components/map/drawer/geofence-event-listing.hbs +123 -0
- package/addon/components/map/drawer/geofence-event-listing.js +103 -0
- package/addon/components/map/drawer.js +1 -0
- package/addon/components/map/leaflet-live-map.js +83 -0
- package/addon/components/modals/orchestrator-import.js +2 -78
- package/addon/components/order/form/service-rate.hbs +1 -1
- package/addon/components/service-area/form.hbs +59 -0
- package/addon/components/service-rate/details.hbs +6 -6
- package/addon/components/service-rate/form.hbs +28 -15
- package/addon/components/zone/form.hbs +60 -1
- package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
- package/addon/controllers/operations/orders/index/details.js +27 -2
- package/addon/controllers/operations/service-rates/index/edit.js +1 -0
- package/addon/controllers/operations/service-rates/index/new.js +1 -0
- package/addon/helpers/f-to-int.js +6 -0
- package/addon/routes/application.js +3 -3
- package/addon/routes/operations/orders/index/details/virtual.js +1 -0
- package/addon/routes/operations/orders/index/details.js +19 -3
- package/addon/services/equipment-actions.js +4 -6
- package/addon/services/geofence-event-bus.js +223 -0
- package/addon/services/leaflet-map-manager.js +4 -0
- package/addon/services/maintenance-actions.js +4 -6
- package/addon/services/part-actions.js +4 -6
- package/addon/services/service-rate-actions.js +8 -3
- package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
- package/addon/utils/fleet-ops-options.js +33 -0
- package/addon/utils/serialize-payload.js +1 -1
- package/app/components/map/drawer/geofence-event-listing.js +1 -0
- package/app/controllers/operations/orders/index/details/virtual.js +1 -0
- package/app/helpers/f-to-int.js +1 -0
- package/app/services/geofence-event-bus.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +2 -2
- package/server/migrations/2026_04_05_000001_add_geofence_config_to_zones_table.php +48 -0
- package/server/migrations/2026_04_05_000002_add_geofence_config_to_service_areas_table.php +38 -0
- package/server/migrations/2026_04_05_000003_create_driver_geofence_states_table.php +62 -0
- package/server/migrations/2026_04_05_000004_create_geofence_events_log_table.php +66 -0
- package/server/migrations/2026_04_22_000001_add_vehicle_support_to_geofence_events_log.php +31 -0
- package/server/migrations/2026_04_22_000002_create_vehicle_geofence_states_table.php +39 -0
- package/server/src/Console/Commands/SimulateGeofenceEvents.php +278 -0
- package/server/src/Events/GeofenceDwelled.php +208 -0
- package/server/src/Events/GeofenceEntered.php +207 -0
- package/server/src/Events/GeofenceExited.php +213 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +100 -0
- package/server/src/Http/Controllers/Api/v1/GeofenceController.php +263 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +13 -8
- package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +4 -0
- package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +88 -0
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/GeofenceController.php +7 -0
- package/server/src/Http/Requests/CreateServiceAreaRequest.php +8 -4
- package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
- package/server/src/Http/Requests/CreateZoneRequest.php +8 -4
- package/server/src/Http/Resources/v1/Order.php +0 -1
- package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
- package/server/src/Jobs/CheckGeofenceDwell.php +139 -0
- package/server/src/Listeners/HandleGeofenceDwelled.php +56 -0
- package/server/src/Listeners/HandleGeofenceEntered.php +187 -0
- package/server/src/Listeners/HandleGeofenceExited.php +56 -0
- package/server/src/Models/Entity.php +7 -0
- package/server/src/Models/GeofenceEventLog.php +155 -0
- package/server/src/Models/Order.php +39 -7
- package/server/src/Models/ServiceArea.php +21 -2
- package/server/src/Models/ServiceQuote.php +3 -1
- package/server/src/Models/ServiceRate.php +122 -30
- package/server/src/Models/Zone.php +20 -2
- package/server/src/Notifications/DriverArrivedAtGeofence.php +86 -0
- package/server/src/Observers/PurchaseRateObserver.php +3 -5
- package/server/src/Providers/EventServiceProvider.php +19 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +10 -0
- package/server/src/Rules/ComputableAlgo.php +1 -3
- package/server/src/Support/Algo.php +87 -1
- package/server/src/Support/GeofenceIntersectionService.php +206 -0
- package/server/src/Support/Utils.php +35 -0
- 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->
|
|
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->
|
|
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' =>
|
|
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 = [
|
|
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'
|
|
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
|
|