@fleetbase/fleetops-engine 0.6.10 → 0.6.12
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/leaflet-tracking-marker.js +19 -0
- package/addon/components/live-map.hbs +5 -0
- package/addon/controllers/operations/orders/index/new.js +27 -22
- package/addon/controllers/operations/orders/index/view.js +22 -20
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/src/Console/Commands/DebugOrderTracker.php +5 -5
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +3 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +15 -12
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +1 -1
- package/server/src/Http/Resources/v1/Waypoint.php +1 -1
- package/server/src/Models/Order.php +38 -8
- package/server/src/Models/Payload.php +10 -2
- package/server/src/Support/Geocoding.php +0 -2
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import MarkerLayer from 'ember-leaflet/components/marker-layer';
|
|
2
2
|
import { isArray } from '@ember/array';
|
|
3
3
|
|
|
4
|
+
const __draggingHotfix = (layer) => {
|
|
5
|
+
if (!layer.dragging) {
|
|
6
|
+
layer.dragging = {
|
|
7
|
+
enabled: () => false,
|
|
8
|
+
enable: () => (layer.options.draggable = true),
|
|
9
|
+
disable: () => (layer.options.draggable = false),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
4
14
|
const arrayFromLatLng = (latlng) => {
|
|
5
15
|
if (isArray(latlng)) {
|
|
6
16
|
return latlng;
|
|
@@ -52,6 +62,7 @@ L.TrackingMarker = L.Marker.extend({
|
|
|
52
62
|
this._slideKeepAtCenter = false;
|
|
53
63
|
this._slideDraggingWasAllowed = false;
|
|
54
64
|
this._slideFrame = 0;
|
|
65
|
+
__draggingHotfix(this);
|
|
55
66
|
},
|
|
56
67
|
|
|
57
68
|
slideTo: function (latlng, options = {}) {
|
|
@@ -69,6 +80,7 @@ L.TrackingMarker = L.Marker.extend({
|
|
|
69
80
|
this._nextPosition = arrayFromLatLng(latlng);
|
|
70
81
|
this._slideKeepAtCenter = !!options.keepAtCenter;
|
|
71
82
|
this._slideDraggingWasAllowed = this._slideDraggingWasAllowed !== undefined ? this._slideDraggingWasAllowed : this._map.dragging.enabled();
|
|
83
|
+
__draggingHotfix(this);
|
|
72
84
|
|
|
73
85
|
if (this._slideKeepAtCenter) {
|
|
74
86
|
this._map.dragging.disable();
|
|
@@ -245,6 +257,13 @@ export default class LeafletTrackingMarkerComponent extends MarkerLayer {
|
|
|
245
257
|
*/
|
|
246
258
|
rotationAngle = 0;
|
|
247
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Default value for the draggable prop.
|
|
262
|
+
*
|
|
263
|
+
* @memberof LeafletTrackingMarkerComponent
|
|
264
|
+
*/
|
|
265
|
+
draggable = false;
|
|
266
|
+
|
|
248
267
|
getOption(key, defaultValue = null) {
|
|
249
268
|
const value = this.options[key] ?? this[key];
|
|
250
269
|
if (value === undefined) {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
@icon={{icon iconUrl=driver.vehicle_avatar iconSize=(array 24 24)}}
|
|
39
39
|
@onAdd={{fn this.triggerAction "onDriverAdded" driver}}
|
|
40
40
|
@onClick={{fn this.triggerAction "onDriverClicked" driver}}
|
|
41
|
+
@draggable={{false}}
|
|
41
42
|
as |marker|
|
|
42
43
|
>
|
|
43
44
|
<marker.popup @maxWidth="500" @minWidth="225">
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
@icon={{icon iconUrl=driver.vehicle_avatar iconSize=(array 24 24)}}
|
|
78
79
|
@onAdd={{fn this.triggerAction "onDriverAdded" driver}}
|
|
79
80
|
@onClick={{fn this.triggerAction "onDriverClicked" driver}}
|
|
81
|
+
@draggable={{false}}
|
|
80
82
|
as |marker|
|
|
81
83
|
>
|
|
82
84
|
<marker.popup @maxWidth="500" @minWidth="225">
|
|
@@ -114,6 +116,7 @@
|
|
|
114
116
|
@icon={{icon iconUrl=vehicle.avatar_url iconSize=(array 24 24)}}
|
|
115
117
|
@onAdd={{fn this.triggerAction "onVehicleAdded" vehicle}}
|
|
116
118
|
@onClick={{fn this.triggerAction "onVehicleClicked" vehicle}}
|
|
119
|
+
@draggable={{false}}
|
|
117
120
|
as |marker|
|
|
118
121
|
>
|
|
119
122
|
<marker.popup @permanent={{false}} @sticky={{true}}>
|
|
@@ -150,6 +153,7 @@
|
|
|
150
153
|
@icon={{icon iconUrl=vehicle.avatar_url iconSize=(array 24 24)}}
|
|
151
154
|
@onAdd={{fn this.triggerAction "onVehicleAdded" vehicle}}
|
|
152
155
|
@onClick={{fn this.triggerAction "onVehicleClicked" vehicle}}
|
|
156
|
+
@draggable={{false}}
|
|
153
157
|
as |marker|
|
|
154
158
|
>
|
|
155
159
|
<marker.popup @permanent={{false}} @sticky={{true}}>
|
|
@@ -189,6 +193,7 @@
|
|
|
189
193
|
@alt={{place.address}}
|
|
190
194
|
@onAdd={{fn this.triggerAction "onPlaceAdded" place}}
|
|
191
195
|
@onClick={{fn this.triggerAction "onPlaceClicked" place}}
|
|
196
|
+
@draggable={{false}}
|
|
192
197
|
as |marker|
|
|
193
198
|
>
|
|
194
199
|
<marker.popup>
|
|
@@ -677,7 +677,7 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
677
677
|
|
|
678
678
|
@action resetInterface() {
|
|
679
679
|
if (this.leafletMap && this.leafletMap.liveMap) {
|
|
680
|
-
this.leafletMap.liveMap.
|
|
680
|
+
this.leafletMap.liveMap.showAll();
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
683
|
|
|
@@ -709,31 +709,35 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
709
709
|
}
|
|
710
710
|
|
|
711
711
|
@action removeRoutingControlPreview() {
|
|
712
|
-
|
|
713
|
-
|
|
712
|
+
return new Promise((resolve) => {
|
|
713
|
+
const leafletMap = this.leafletMap;
|
|
714
|
+
const previewRouteControl = this.previewRouteControl;
|
|
714
715
|
|
|
715
|
-
|
|
716
|
+
let removed = false;
|
|
716
717
|
|
|
717
|
-
|
|
718
|
-
try {
|
|
719
|
-
previewRouteControl.remove();
|
|
720
|
-
removed = true;
|
|
721
|
-
} catch (e) {
|
|
722
|
-
// silent
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
if (!removed) {
|
|
718
|
+
if (leafletMap && previewRouteControl instanceof RoutingControl) {
|
|
726
719
|
try {
|
|
727
|
-
|
|
720
|
+
previewRouteControl.remove();
|
|
721
|
+
removed = true;
|
|
728
722
|
} catch (e) {
|
|
729
723
|
// silent
|
|
730
724
|
}
|
|
725
|
+
|
|
726
|
+
if (!removed) {
|
|
727
|
+
try {
|
|
728
|
+
leafletMap.removeControl(previewRouteControl);
|
|
729
|
+
} catch (e) {
|
|
730
|
+
// silent
|
|
731
|
+
}
|
|
732
|
+
}
|
|
731
733
|
}
|
|
732
|
-
}
|
|
733
734
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
735
|
+
if (!removed) {
|
|
736
|
+
this.forceRemoveRoutePreview();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
resolve(true);
|
|
740
|
+
});
|
|
737
741
|
}
|
|
738
742
|
|
|
739
743
|
@action forceRemoveRoutePreview() {
|
|
@@ -944,7 +948,7 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
944
948
|
const originalCoords = this.getCoordinatesFromPayload(); // [[lon,lat], …]
|
|
945
949
|
const coordinates = driverPosition ? [driverPosition, ...originalCoords] : [...originalCoords];
|
|
946
950
|
const hasDriverStart = Boolean(driverPosition);
|
|
947
|
-
const source =
|
|
951
|
+
const source = 'first';
|
|
948
952
|
const destination = 'any';
|
|
949
953
|
const roundtrip = false; // don’t loop back
|
|
950
954
|
const routingHost = getRoutingHost(this.payload, this.waypoints);
|
|
@@ -1060,7 +1064,7 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
1060
1064
|
}
|
|
1061
1065
|
}
|
|
1062
1066
|
|
|
1063
|
-
@action resetForm() {
|
|
1067
|
+
@action async resetForm() {
|
|
1064
1068
|
const order = this.store.createRecord('order', { meta: [] });
|
|
1065
1069
|
const payload = this.store.createRecord('payload');
|
|
1066
1070
|
const driversQuery = {};
|
|
@@ -1080,8 +1084,6 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
1080
1084
|
const customFields = [];
|
|
1081
1085
|
const customFieldValues = {};
|
|
1082
1086
|
|
|
1083
|
-
this.removeRoutingControlPreview();
|
|
1084
|
-
this.removeOptimizedRoute();
|
|
1085
1087
|
this.setProperties({
|
|
1086
1088
|
order,
|
|
1087
1089
|
payload,
|
|
@@ -1102,6 +1104,9 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
1102
1104
|
customFields,
|
|
1103
1105
|
customFieldValues,
|
|
1104
1106
|
});
|
|
1107
|
+
|
|
1108
|
+
await this.removeRoutingControlPreview();
|
|
1109
|
+
this.removeOptimizedRoute();
|
|
1105
1110
|
this.resetInterface();
|
|
1106
1111
|
}
|
|
1107
1112
|
|
|
@@ -243,39 +243,41 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
243
243
|
this.customFieldGroups = customFieldGroups;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
@action resetView() {
|
|
247
|
-
this.removeRoutingControlPreview();
|
|
246
|
+
@action async resetView() {
|
|
247
|
+
await this.removeRoutingControlPreview();
|
|
248
248
|
this.resetInterface();
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
@action resetInterface() {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
liveMap.reload();
|
|
255
|
-
liveMap.showAll();
|
|
252
|
+
if (this.leafletMap && this.leafletMap.liveMap) {
|
|
253
|
+
this.leafletMap.liveMap.showAll();
|
|
254
|
+
this.leafletMap.liveMap.reload();
|
|
256
255
|
}
|
|
257
256
|
}
|
|
258
257
|
|
|
259
258
|
@action removeRoutingControlPreview() {
|
|
260
|
-
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
const { leafletMap, routeControl } = this;
|
|
261
261
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
if (routeControl instanceof RoutingControl) {
|
|
263
|
+
try {
|
|
264
|
+
routeControl.remove();
|
|
265
|
+
} catch (e) {
|
|
266
|
+
// silent
|
|
267
|
+
}
|
|
267
268
|
}
|
|
268
|
-
}
|
|
269
269
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
270
|
+
if (leafletMap instanceof L.Map) {
|
|
271
|
+
try {
|
|
272
|
+
leafletMap.removeControl(routeControl);
|
|
273
|
+
} catch (e) {
|
|
274
|
+
// silent
|
|
275
|
+
}
|
|
275
276
|
}
|
|
276
|
-
}
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
this.forceRemoveRoutePreview();
|
|
279
|
+
resolve(true);
|
|
280
|
+
});
|
|
279
281
|
}
|
|
280
282
|
|
|
281
283
|
@action forceRemoveRoutePreview() {
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -29,11 +29,11 @@ class DebugOrderTracker extends Command
|
|
|
29
29
|
*/
|
|
30
30
|
public function handle()
|
|
31
31
|
{
|
|
32
|
-
$order = Order::where('public_id', 'order_n227274')->first();
|
|
33
|
-
if ($order) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
32
|
+
// $order = Order::where('public_id', 'order_n227274')->first();
|
|
33
|
+
// if ($order) {
|
|
34
|
+
// $tracker = new OrderTracker($order);
|
|
35
|
+
// dd($tracker->getOrderProgressPercentage());
|
|
36
|
+
// }
|
|
37
37
|
|
|
38
38
|
return Command::SUCCESS;
|
|
39
39
|
}
|
|
@@ -66,7 +66,9 @@ class ServiceAreaController extends Controller
|
|
|
66
66
|
try {
|
|
67
67
|
$serviceArea = ServiceArea::create($input);
|
|
68
68
|
} catch (\Throwable $e) {
|
|
69
|
-
|
|
69
|
+
logger()->error('Unable to create service area.', ['error' => $e->getMessage()]);
|
|
70
|
+
|
|
71
|
+
return response()->apiError('Failed to create service area.');
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
// response the driver resource
|
|
@@ -167,25 +167,28 @@ class OrderController extends FleetOpsController
|
|
|
167
167
|
);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
171
|
-
$order
|
|
170
|
+
// Run background processes on queue
|
|
171
|
+
dispatch(function () use ($order, $serviceQuote): void {
|
|
172
|
+
// notify driver if assigned
|
|
173
|
+
$order->notifyDriverAssigned();
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
// set driving distance and time
|
|
176
|
+
$order->setPreliminaryDistanceAndTime();
|
|
175
177
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
// if service quote attached purchase
|
|
179
|
+
$order->purchaseServiceQuote($serviceQuote);
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
// dispatch if flagged true
|
|
182
|
+
$order->firstDispatchWithActivity();
|
|
181
183
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
// Trigger order created event
|
|
185
|
+
event(new OrderReady($order));
|
|
186
|
+
})->afterCommit();
|
|
184
187
|
}
|
|
185
188
|
);
|
|
186
189
|
|
|
187
|
-
//
|
|
188
|
-
|
|
190
|
+
// Reload payload and tracking number
|
|
191
|
+
$record->load(['payload', 'trackingNumber']);
|
|
189
192
|
|
|
190
193
|
return ['order' => new $this->resource($record)];
|
|
191
194
|
} catch (\Exception $e) {
|
|
@@ -150,7 +150,7 @@ class SettingController extends Controller
|
|
|
150
150
|
if (!is_array($notificationSettings)) {
|
|
151
151
|
throw new \Exception('Invalid notification settings data.');
|
|
152
152
|
}
|
|
153
|
-
$currentNotificationSettings = Setting::lookupCompany('notification_settings');
|
|
153
|
+
$currentNotificationSettings = Setting::lookupCompany('notification_settings', []);
|
|
154
154
|
Setting::configureCompany('notification_settings', array_merge($currentNotificationSettings, $notificationSettings));
|
|
155
155
|
|
|
156
156
|
return response()->json([
|
|
@@ -58,7 +58,7 @@ class Waypoint extends FleetbaseResource
|
|
|
58
58
|
'owner' => $this->when(!Http::isInternalRequest(), Resolve::resourceForMorph($this->owner_type, $this->owner_uuid)),
|
|
59
59
|
'tracking_number' => $this->whenLoaded('trackingNumber', $waypoint->trackingNumber),
|
|
60
60
|
'customer' => $this->setCustomerType(Resolve::resourceForMorph($waypoint->customer_type, $waypoint->customer_uuid)),
|
|
61
|
-
'type' => $this->type,
|
|
61
|
+
'type' => $waypoint->type ?? $this->type,
|
|
62
62
|
'meta' => data_get($this, 'meta', Utils::createObject()),
|
|
63
63
|
'eta' => $this->eta,
|
|
64
64
|
'updated_at' => $this->updated_at,
|
|
@@ -1395,8 +1395,6 @@ class Order extends Model
|
|
|
1395
1395
|
$activity = $flow->firstWhere('code', $code);
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
|
-
// dd($activity);
|
|
1399
|
-
|
|
1400
1398
|
if (!Utils::isActivity($activity)) {
|
|
1401
1399
|
return false;
|
|
1402
1400
|
}
|
|
@@ -1760,23 +1758,55 @@ class Order extends Model
|
|
|
1760
1758
|
*/
|
|
1761
1759
|
public function resolveDynamicProperty(string $property)
|
|
1762
1760
|
{
|
|
1763
|
-
|
|
1761
|
+
// Special payload shortcuts
|
|
1762
|
+
$root = Str::before($property, '.'); // e.g. "pickup" in "pickup.address"
|
|
1763
|
+
$payloadMap = [
|
|
1764
|
+
'pickup' => 'payload.pickup',
|
|
1765
|
+
'dropoff' => 'payload.dropoff',
|
|
1766
|
+
'waypoint' => 'payload.currentWaypointMarker',
|
|
1767
|
+
'currentWaypoint' => 'payload.currentWaypointMarker',
|
|
1768
|
+
'currentWaypointMarker' => 'payload.currentWaypointMarker',
|
|
1769
|
+
];
|
|
1770
|
+
|
|
1771
|
+
if (isset($payloadMap[$root])) {
|
|
1772
|
+
$this->loadMissing($payloadMap[$root]);
|
|
1773
|
+
$target = data_get($this, $payloadMap[$root]);
|
|
1774
|
+
|
|
1775
|
+
// “pickup”, “dropoff”, or “currentWaypoint” on their own
|
|
1776
|
+
if ($property === $root) {
|
|
1777
|
+
return $target;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// e.g. "address.city" part of "pickup.address.city"
|
|
1781
|
+
$subKey = Str::after($property, $root . '.');
|
|
1782
|
+
|
|
1783
|
+
// if waypoint we can do "waypoint.place.address" or "waypoint.address" for resolution
|
|
1784
|
+
$isWaypointMarker = Str::startsWith($root, 'currentWaypoint') || $root === 'waypoint';
|
|
1785
|
+
if ($isWaypointMarker && $target instanceof Waypoint) {
|
|
1786
|
+
$target->loadMissing('place');
|
|
1787
|
+
|
|
1788
|
+
return data_get($target, $subKey) ?? data_get($target->place, $subKey);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
return data_get($target, $subKey);
|
|
1792
|
+
}
|
|
1764
1793
|
|
|
1765
|
-
//
|
|
1766
|
-
|
|
1767
|
-
|
|
1794
|
+
// Direct attribute on the model
|
|
1795
|
+
$snake = Str::snake($property);
|
|
1796
|
+
if (array_key_exists($snake, $this->getAttributes())) {
|
|
1797
|
+
return $this->getAttribute($snake);
|
|
1768
1798
|
}
|
|
1769
1799
|
|
|
1770
|
-
//
|
|
1800
|
+
// Custom-field / meta look-ups
|
|
1771
1801
|
if ($this->isCustomField($property)) {
|
|
1772
1802
|
return $this->getCustomFieldValueByKey($property);
|
|
1773
1803
|
}
|
|
1774
1804
|
|
|
1775
|
-
// Check if meta attribute
|
|
1776
1805
|
if ($this->hasMeta($property)) {
|
|
1777
1806
|
return $this->getMeta($property);
|
|
1778
1807
|
}
|
|
1779
1808
|
|
|
1809
|
+
// Fallback deep-path access
|
|
1780
1810
|
return data_get($this, $property);
|
|
1781
1811
|
}
|
|
1782
1812
|
|
|
@@ -185,6 +185,14 @@ class Payload extends Model
|
|
|
185
185
|
return $this->belongsTo(Place::class, 'current_waypoint_uuid')->withoutGlobalScopes();
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* The current waypoint of the payload in progress.
|
|
190
|
+
*/
|
|
191
|
+
public function currentWaypointMarker()
|
|
192
|
+
{
|
|
193
|
+
return $this->belongsTo(Waypoint::class, 'current_waypoint_uuid', 'place_uuid')->withoutGlobalScopes();
|
|
194
|
+
}
|
|
195
|
+
|
|
188
196
|
/**
|
|
189
197
|
* Waypoints between start and end.
|
|
190
198
|
*
|
|
@@ -305,7 +313,7 @@ class Payload extends Model
|
|
|
305
313
|
}
|
|
306
314
|
|
|
307
315
|
foreach ($waypoints as $index => $attributes) {
|
|
308
|
-
$waypoint = ['payload_uuid' => $this->payload_uuid];
|
|
316
|
+
$waypoint = ['payload_uuid' => $this->payload_uuid, 'type' => data_get($attributes, 'type', 'dropoff')];
|
|
309
317
|
|
|
310
318
|
if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) {
|
|
311
319
|
$attributes = Utils::get($attributes, 'place');
|
|
@@ -355,7 +363,7 @@ class Payload extends Model
|
|
|
355
363
|
}
|
|
356
364
|
|
|
357
365
|
foreach ($waypoints as $index => $attributes) {
|
|
358
|
-
$waypoint = ['payload_uuid' => $this->uuid, 'order' => $index];
|
|
366
|
+
$waypoint = ['payload_uuid' => $this->uuid, 'order' => $index, 'type' => data_get($attributes, 'type', 'dropoff')];
|
|
359
367
|
|
|
360
368
|
if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) {
|
|
361
369
|
$placeAttributes = Utils::get($attributes, 'place');
|