@fleetbase/fleetops-engine 0.6.44 → 0.6.46
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/admin/map-settings.hbs +26 -0
- package/addon/components/admin/map-settings.js +46 -0
- package/addon/components/admin/routing-settings.hbs +11 -0
- package/addon/components/cell/order-route-type.hbs +46 -0
- package/addon/components/cell/order-route-type.js +90 -0
- package/addon/components/cell/place-address.hbs +1 -0
- package/addon/components/display-place.hbs +45 -56
- package/addon/components/display-place.js +21 -0
- package/addon/components/entity/form.hbs +24 -0
- package/addon/components/entity/form.js +30 -0
- package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
- package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
- package/addon/components/layout/fleet-ops-sidebar.js +8 -0
- package/addon/components/leaflet-draw-control.js +1 -1
- package/addon/components/maintenance-schedule/form.js +2 -1
- package/addon/components/map/drawer/driver-listing.js +5 -5
- package/addon/components/map/drawer/geofence-event-listing.js +5 -2
- package/addon/components/map/drawer/place-listing.js +5 -5
- package/addon/components/map/drawer/position-listing.js +102 -21
- package/addon/components/map/drawer/vehicle-listing.js +5 -5
- package/addon/components/map/google-live-map.hbs +9 -0
- package/addon/components/map/google-live-map.js +445 -0
- package/addon/components/map/leaflet-live-map.hbs +186 -142
- package/addon/components/map/leaflet-live-map.js +121 -41
- package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
- package/addon/components/map/toolbar/zones-panel.hbs +9 -9
- package/addon/components/map/toolbar.hbs +3 -3
- package/addon/components/map/toolbar.js +9 -1
- package/addon/components/modals/map-layer-form.hbs +132 -63
- package/addon/components/modals/orchestrator-import.hbs +4 -11
- package/addon/components/modals/orchestrator-import.js +17 -1
- package/addon/components/modals/update-order-activity.hbs +1 -1
- package/addon/components/orchestrator/order-pool.js +3 -2
- package/addon/components/orchestrator-workbench.js +29 -41
- package/addon/components/order/details/detail.js +2 -2
- package/addon/components/order/details/payload.hbs +5 -5
- package/addon/components/order/details/payload.js +18 -1
- package/addon/components/order/details/route.hbs +2 -2
- package/addon/components/order/details/route.js +76 -3
- package/addon/components/order/form/payload.hbs +33 -0
- package/addon/components/order/form/payload.js +1 -0
- package/addon/components/order/form/route.hbs +8 -8
- package/addon/components/order/form/route.js +78 -18
- package/addon/components/order/form/service-rate.hbs +1 -1
- package/addon/components/order/form.js +2 -2
- package/addon/components/order/route-editor.hbs +209 -147
- package/addon/components/order/route-editor.js +143 -102
- package/addon/components/place/address.hbs +1 -0
- package/addon/components/place/address.js +12 -0
- package/addon/components/route-list.hbs +43 -55
- package/addon/components/route-list.js +47 -0
- package/addon/components/route-optimization-wizard-panel.js +3 -4
- package/addon/components/service-rate/details.hbs +6 -6
- package/addon/components/service-rate/form.hbs +28 -15
- package/addon/components/vehicle/card.hbs +4 -4
- package/addon/controllers/maintenance/schedules/index.js +2 -1
- package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
- package/addon/controllers/operations/orders/index/details.js +113 -15
- package/addon/controllers/operations/orders/index/new.js +1 -1
- package/addon/controllers/operations/orders/index.js +8 -1
- package/addon/controllers/operations/service-rates/index/edit.js +1 -0
- package/addon/controllers/operations/service-rates/index/new.js +1 -0
- package/addon/controllers/settings/map.js +97 -0
- package/addon/controllers/settings/routing.js +47 -12
- package/addon/extension.js +11 -0
- package/addon/helpers/f-to-int.js +6 -0
- package/addon/helpers/place-address-html.js +6 -0
- package/addon/instance-initializers/register-google-routes.js +17 -0
- package/addon/instance-initializers/register-osrm.js +5 -0
- package/addon/routes/application.js +9 -3
- package/addon/routes/operations/orchestrator.js +13 -0
- package/addon/routes/operations/orders/index/details/virtual.js +1 -0
- package/addon/routes/operations/orders/index/details.js +47 -24
- package/addon/routes/operations/orders/index/new.js +9 -0
- package/addon/routes/operations/orders/index.js +0 -1
- package/addon/routes/settings/map.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/entity-actions.js +1 -1
- package/addon/services/equipment-actions.js +4 -6
- package/addon/services/geofence-event-bus.js +2 -1
- package/addon/services/geofence.js +204 -168
- package/addon/services/google-routes.js +300 -0
- package/addon/services/leaflet-draw-restriction.js +2 -1
- package/addon/services/leaflet-map-manager.js +21 -0
- package/addon/services/maintenance-actions.js +4 -6
- package/addon/services/map-adapter/google.js +2205 -0
- package/addon/services/map-adapter/leaflet.js +1042 -0
- package/addon/services/map-adapter-interface.js +551 -0
- package/addon/services/map-manager.js +768 -0
- package/addon/services/map-settings.js +75 -0
- package/addon/services/movement-tracker.js +83 -32
- package/addon/services/order-actions.js +11 -1
- package/addon/services/osrm.js +79 -6
- package/addon/services/part-actions.js +4 -6
- package/addon/services/position-playback.js +135 -62
- package/addon/services/route-engine.js +83 -0
- package/addon/services/service-areas.js +8 -1
- package/addon/services/service-rate-actions.js +8 -3
- package/addon/styles/fleetops-engine.css +555 -0
- package/addon/templates/management/vehicles/index.hbs +0 -1
- package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
- package/addon/templates/operations/orders/index/details.hbs +0 -1
- package/addon/templates/settings/map.hbs +53 -0
- package/addon/templates/settings/routing.hbs +15 -4
- package/addon/utils/fleet-ops-options.js +34 -0
- package/addon/utils/has-valid-coordinates.js +6 -0
- package/addon/utils/map-provider-options.js +6 -0
- package/addon/utils/order-route-editing.js +86 -0
- package/addon/utils/order-route-summary.js +20 -0
- package/addon/utils/place-address-html.js +41 -0
- package/addon/utils/route-colors.js +43 -12
- package/addon/utils/route-visualization.js +93 -0
- package/addon/utils/serialize-payload.js +1 -1
- package/addon/utils/vendor-integration.js +2 -1
- package/app/components/admin/map-settings.js +1 -0
- package/app/components/admin/routing-settings.js +1 -0
- package/app/components/cell/order-route-type.js +1 -0
- package/app/components/cell/place-address.js +1 -0
- package/app/components/map/google-live-map.js +1 -0
- package/app/components/place/address.js +1 -0
- package/app/controllers/operations/orders/index/details/virtual.js +1 -0
- package/app/controllers/settings/map.js +1 -0
- package/app/helpers/f-to-int.js +1 -0
- package/app/helpers/place-address-html.js +1 -0
- package/app/routes/settings/map.js +1 -0
- package/app/services/map-adapter/google.js +1 -0
- package/app/services/map-adapter/leaflet.js +1 -0
- package/app/services/map-adapter-interface.js +1 -0
- package/app/services/map-manager.js +1 -0
- package/app/services/map-settings.js +1 -0
- package/app/templates/settings/map.js +1 -0
- package/app/utils/has-valid-coordinates.js +1 -0
- package/app/utils/order-route-editing.js +2 -0
- package/app/utils/order-route-summary.js +1 -0
- package/app/utils/place-address-html.js +1 -0
- package/app/utils/route-colors.js +1 -1
- package/app/utils/route-visualization.js +1 -0
- package/assets/images/drawing-controls-spritesheet.svg +156 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +36 -8
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
- package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +7 -2
- package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
- package/server/src/Http/Filter/OrderFilter.php +2 -1
- package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
- package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
- package/server/src/Http/Resources/v1/Order.php +20 -2
- package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
- package/server/src/Models/Entity.php +7 -0
- package/server/src/Models/Order.php +81 -8
- package/server/src/Models/OrderConfig.php +15 -0
- package/server/src/Models/Payload.php +131 -27
- package/server/src/Models/Place.php +182 -48
- package/server/src/Models/ServiceRate.php +125 -32
- package/server/src/Observers/PurchaseRateObserver.php +3 -5
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
- package/server/src/Rules/ComputableAlgo.php +1 -3
- package/server/src/Support/Algo.php +87 -1
- package/server/src/Support/Utils.php +69 -0
- package/server/src/routes.php +4 -0
- package/translations/en-us.yaml +1937 -1916
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
</tr>
|
|
177
177
|
</thead>
|
|
178
178
|
<tbody>
|
|
179
|
-
{{#each @resource.
|
|
179
|
+
{{#each @resource.rateFees as |rateFee|}}
|
|
180
180
|
<tr>
|
|
181
181
|
<td>
|
|
182
182
|
<Input
|
|
@@ -252,25 +252,38 @@
|
|
|
252
252
|
{{else if @resource.isAlgorithm}}
|
|
253
253
|
<ContentPanel @title={{t "service-rate.fields.custom-algorithm-title"}} @open={{true}} @wrapperClass="bordered-top">
|
|
254
254
|
<InfoBlock class="mb-5">
|
|
255
|
-
|
|
256
|
-
{{t "service-rate.fields.custom-algorithm-info-second-message"}}
|
|
255
|
+
Define a custom formula for this service rate using variables wrapped in a single pair of curly braces.
|
|
257
256
|
|
|
258
257
|
<div class="block my-4 break-text">
|
|
259
258
|
<ul class="list-disc space-y-2 pl-16">
|
|
260
|
-
<li class="leading-5"><
|
|
261
|
-
|
|
262
|
-
<li class="leading-5"><
|
|
263
|
-
|
|
259
|
+
<li class="leading-5"><code>{distance}</code>, <code>{distance_m}</code>, <code>{distance_km}</code>, <code>{distance_mi}</code>: route distance in meters, kilometers, or miles.</li>
|
|
260
|
+
<li class="leading-5"><code>{time}</code>, <code>{time_s}</code>, <code>{time_min}</code>: route time in seconds or minutes.</li>
|
|
261
|
+
<li class="leading-5"><code>{stops}</code>: total service stops including pickup, dropoff, and intermediate waypoints.</li>
|
|
262
|
+
<li class="leading-5"><code>{waypoints}</code>: intermediate waypoint count only.</li>
|
|
263
|
+
<li class="leading-5"><code>{parcels}</code>, <code>{entities}</code>: parcel count and total payload entity count.</li>
|
|
264
|
+
<li class="leading-5"><code>{base_fee}</code>: the configured base fee for this service rate.</li>
|
|
265
|
+
<li class="leading-5">Supported functions: <code>max(a,b)</code>, <code>min(a,b)</code>, <code>ceil(x)</code>, <code>floor(x)</code>, <code>round(x)</code>.</li>
|
|
264
266
|
</ul>
|
|
265
267
|
</div>
|
|
266
268
|
|
|
267
|
-
<div>
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
<div class="space-y-3">
|
|
270
|
+
<div>
|
|
271
|
+
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 1</h4>
|
|
272
|
+
<div class="mb-2 text-inherit">Charge by distance in miles after the first 15 miles, plus the base fee.</div>
|
|
273
|
+
<code class="text-xs font-mono">max({distance_mi} - 15, 0) * 5 + {base_fee}</code>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div>
|
|
277
|
+
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 2</h4>
|
|
278
|
+
<div class="mb-2 text-inherit">Charge extra for stops after the first 2 service stops.</div>
|
|
279
|
+
<code class="text-xs font-mono">max({stops} - 2, 0) * 10 + {base_fee}</code>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<div>
|
|
283
|
+
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 3</h4>
|
|
284
|
+
<div class="mb-2 text-inherit">Combine waypoint, parcel, and distance surcharges in one formula.</div>
|
|
285
|
+
<code class="text-xs font-mono">max({waypoints}, 0) * 15 + max({parcels} - 3, 0) * 2 + max({distance_km} - 25, 0) * 0.5 + {base_fee}</code>
|
|
272
286
|
</div>
|
|
273
|
-
<code class="text-xs font-mono">(( {distance} / 50 ) * .05 ) + (( {time} / 60 ) * .01)</code>
|
|
274
287
|
</div>
|
|
275
288
|
</InfoBlock>
|
|
276
289
|
|
|
@@ -287,7 +300,7 @@
|
|
|
287
300
|
{{else if @resource.isParcelService}}
|
|
288
301
|
<ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
|
|
289
302
|
<div class="space-y-2">
|
|
290
|
-
{{#each @resource.
|
|
303
|
+
{{#each @resource.parcelFees as |parcelFee|}}
|
|
291
304
|
<div class="dark:text-gray-100">
|
|
292
305
|
<div class="grid grid-cols-7">
|
|
293
306
|
<div class="flex flex-col items-start justify-start pt-3">
|
|
@@ -541,4 +554,4 @@
|
|
|
541
554
|
</InputGroup>
|
|
542
555
|
{{/if}}
|
|
543
556
|
</ContentPanel>
|
|
544
|
-
</div>
|
|
557
|
+
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<Layout::Resource::Card ...attributes as |Card|>
|
|
2
|
-
<Card.header class="{{@headerClass}}
|
|
3
|
-
<div class="font-semibold">{{or @resource.name @resource.yearMakeModel}}</div>
|
|
4
|
-
<div class="text-gray-300 dark:text-gray-500 text-sm">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
|
|
2
|
+
<Card.header class="{{@headerClass}}">
|
|
3
|
+
<div class="font-semibold w-full truncate">{{or @resource.name @resource.yearMakeModel}}</div>
|
|
4
|
+
<div class="text-gray-300 dark:text-gray-500 text-sm w-full truncate">{{or @resource.plate_number @resource.vin @resource.serial_number @resource.call_sign @resource.public_id}}</div>
|
|
5
5
|
{{#if (has-block "header")}}
|
|
6
6
|
{{yield to="header"}}
|
|
7
7
|
{{/if}}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
{{#if (has-block "footer")}}
|
|
22
22
|
{{yield to="footer"}}
|
|
23
23
|
{{/if}}
|
|
24
|
-
<div class="mt-1">
|
|
24
|
+
<div class="mt-1.5">
|
|
25
25
|
<div class="text-gray-300 dark:text-gray-500 text-xs">Last Modified: {{@resource.updatedAt}}</div>
|
|
26
26
|
</div>
|
|
27
27
|
</Card.footer>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
|
+
import { isArray } from '@ember/array';
|
|
2
3
|
import { inject as service } from '@ember/service';
|
|
3
4
|
import { tracked } from '@glimmer/tracking';
|
|
4
5
|
import { action } from '@ember/object';
|
|
@@ -75,7 +76,7 @@ export default class MaintenanceSchedulesIndexController extends Controller {
|
|
|
75
76
|
})
|
|
76
77
|
.then((response) => {
|
|
77
78
|
// The API returns { events: [...] } — unwrap the array.
|
|
78
|
-
const raw =
|
|
79
|
+
const raw = isArray(response) ? response : (response?.events ?? []);
|
|
79
80
|
const events = raw.map((event) => ({
|
|
80
81
|
id: event.id,
|
|
81
82
|
title: event.title,
|
|
@@ -1,21 +1,73 @@
|
|
|
1
1
|
import Controller, { inject as controller } from '@ember/controller';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
|
-
import { action } from '@ember/object';
|
|
5
4
|
import { isArray } from '@ember/array';
|
|
6
5
|
import { task } from 'ember-concurrency';
|
|
6
|
+
import { colorForId, routeColorForStatus, routeStyleForStatus } from '../../../../utils/route-colors';
|
|
7
|
+
import { buildRoutePointMarkerPresentation, buildRoutePointsFromPayload } from '../../../../utils/route-visualization';
|
|
7
8
|
|
|
8
9
|
export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
9
10
|
@controller('operations.orders.index') index;
|
|
10
11
|
@service('universe/menu-service') menuService;
|
|
11
12
|
@service orderActions;
|
|
12
13
|
@service orderSocketEvents;
|
|
13
|
-
@service
|
|
14
|
+
@service mapManager;
|
|
14
15
|
@service leafletLayerVisibilityManager;
|
|
15
16
|
@service hostRouter;
|
|
16
17
|
@service universe;
|
|
17
18
|
@service sidebar;
|
|
18
19
|
@tracked routingControl;
|
|
20
|
+
@tracked routingCompleted = false;
|
|
21
|
+
@tracked realtimeOrderPublicId = null;
|
|
22
|
+
|
|
23
|
+
get routePoints() {
|
|
24
|
+
return buildRoutePointsFromPayload(this.model?.payload);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get routeMarkerFactory() {
|
|
28
|
+
const routeColor = colorForId(this.model.public_id ?? this.model.id ?? 'order-route');
|
|
29
|
+
|
|
30
|
+
return (_waypoint, index) => {
|
|
31
|
+
return buildRoutePointMarkerPresentation(this.routePoints[index], routeColor);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get routeMarkerWaypoints() {
|
|
36
|
+
return this.routePoints.map(({ place }) => [place.latitude, place.longitude]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get routeStatus() {
|
|
40
|
+
return this.model.status ?? 'created';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get routePolylineOptions() {
|
|
44
|
+
const color = routeColorForStatus(this.routeStatus);
|
|
45
|
+
const styles = routeStyleForStatus(this.routeStatus, color);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
color,
|
|
49
|
+
weight: styles.at(-1)?.weight ?? 4,
|
|
50
|
+
opacity: styles.at(-1)?.opacity ?? 0.85,
|
|
51
|
+
styles,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get routeControlTag() {
|
|
56
|
+
return this.model?.public_id ? `order-details:${this.model.public_id}` : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get routingControlOptions() {
|
|
60
|
+
return {
|
|
61
|
+
tag: this.routeControlTag,
|
|
62
|
+
color: this.routePolylineOptions.color,
|
|
63
|
+
status: this.routeStatus,
|
|
64
|
+
markerWaypoints: this.routeMarkerWaypoints,
|
|
65
|
+
polylineOptions: this.routePolylineOptions,
|
|
66
|
+
createMarker: this.routeMarkerFactory,
|
|
67
|
+
onRouteFound: this.handleRoutingComplete,
|
|
68
|
+
onRoutingError: this.handleRoutingComplete,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
19
71
|
|
|
20
72
|
get tabs() {
|
|
21
73
|
const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:order:details');
|
|
@@ -103,32 +155,78 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
103
155
|
}
|
|
104
156
|
|
|
105
157
|
@task *refresh() {
|
|
106
|
-
|
|
158
|
+
try {
|
|
159
|
+
yield this.hostRouter.refresh();
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return error;
|
|
162
|
+
}
|
|
107
163
|
}
|
|
108
164
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.routingControl = await this.leafletMapManager.addRoutingControl(this.model.routeWaypoints);
|
|
165
|
+
handleRoutingComplete = () => {
|
|
166
|
+
this.routingCompleted = true;
|
|
167
|
+
};
|
|
113
168
|
|
|
114
|
-
|
|
115
|
-
this.
|
|
169
|
+
async setupRealtime() {
|
|
170
|
+
const currentOrderPublicId = this.model?.public_id;
|
|
171
|
+
if (!currentOrderPublicId) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
116
174
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
175
|
+
if (this.realtimeOrderPublicId && this.realtimeOrderPublicId !== currentOrderPublicId) {
|
|
176
|
+
this.orderSocketEvents.stop({ public_id: this.realtimeOrderPublicId });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.realtimeOrderPublicId = currentOrderPublicId;
|
|
121
180
|
|
|
122
|
-
// Listen for this order events
|
|
123
181
|
this.orderSocketEvents.start(
|
|
124
182
|
this.model,
|
|
125
183
|
async (_msg, { reloadable }) => {
|
|
126
184
|
if (reloadable) {
|
|
127
185
|
await this.hostRouter.refresh();
|
|
128
|
-
this.leafletMapManager.replaceRoutingControl(this.model.routeWaypoints, this.routingControl);
|
|
129
186
|
}
|
|
130
187
|
},
|
|
131
188
|
{ debounceMs: 250 }
|
|
132
189
|
);
|
|
133
190
|
}
|
|
191
|
+
|
|
192
|
+
async syncRoutingControl() {
|
|
193
|
+
this.routingCompleted = false;
|
|
194
|
+
this.teardownRoutingControls();
|
|
195
|
+
|
|
196
|
+
this.routingControl = await this.mapManager.addRoutingControl(this.model.routeWaypoints, this.routingControlOptions);
|
|
197
|
+
|
|
198
|
+
if (!this.routingControl) {
|
|
199
|
+
this.routingCompleted = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
syncMapContext() {
|
|
204
|
+
this.leafletLayerVisibilityManager.hideCategory('drivers');
|
|
205
|
+
this.leafletLayerVisibilityManager.showModelLayer(this.model.driver_assigned);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
teardownRoutingControls() {
|
|
209
|
+
if (this.routeControlTag) {
|
|
210
|
+
this.mapManager.clearRoutingControlsByTag(this.routeControlTag);
|
|
211
|
+
} else if (this.routingControl) {
|
|
212
|
+
this.mapManager.removeRoutingControl(this.routingControl);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.routingControl = undefined;
|
|
216
|
+
this.routingCompleted = false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
teardownRealtime() {
|
|
220
|
+
if (this.realtimeOrderPublicId) {
|
|
221
|
+
this.orderSocketEvents.stop({ public_id: this.realtimeOrderPublicId });
|
|
222
|
+
this.realtimeOrderPublicId = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async setupDetailsSession() {
|
|
227
|
+
this.index.changeLayout('map');
|
|
228
|
+
await this.setupRealtime();
|
|
229
|
+
await this.syncRoutingControl();
|
|
230
|
+
this.syncMapContext();
|
|
231
|
+
}
|
|
134
232
|
}
|
|
@@ -20,6 +20,7 @@ export default class OperationsOrdersIndexNewController extends Controller {
|
|
|
20
20
|
@service orderCreation;
|
|
21
21
|
@service orderValidation;
|
|
22
22
|
@service events;
|
|
23
|
+
@service sidebar;
|
|
23
24
|
@tracked order = this.orderCreation.newOrder();
|
|
24
25
|
@tracked overlay;
|
|
25
26
|
|
|
@@ -92,7 +93,6 @@ export default class OperationsOrdersIndexNewController extends Controller {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
@action setup() {
|
|
95
|
-
// Change to map layout
|
|
96
96
|
this.index.changeLayout('map');
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -142,6 +142,13 @@ export default class OperationsOrdersIndexController extends Controller {
|
|
|
142
142
|
filterable: true,
|
|
143
143
|
filterComponent: 'filter/string',
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
label: this.intl.t('column.route-type'),
|
|
147
|
+
valuePath: 'payload.waypoints.length',
|
|
148
|
+
cellComponent: 'cell/order-route-type',
|
|
149
|
+
cellClassNames: 'overflow-visible',
|
|
150
|
+
resizable: true,
|
|
151
|
+
},
|
|
145
152
|
{
|
|
146
153
|
label: this.intl.t('column.internal-id'),
|
|
147
154
|
valuePath: 'internal_id',
|
|
@@ -283,7 +290,7 @@ export default class OperationsOrdersIndexController extends Controller {
|
|
|
283
290
|
label: this.intl.t('column.type'),
|
|
284
291
|
valuePath: 'type',
|
|
285
292
|
resizable: true,
|
|
286
|
-
|
|
293
|
+
humanize: true,
|
|
287
294
|
sortable: true,
|
|
288
295
|
filterable: true,
|
|
289
296
|
filterComponent: 'filter/model',
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
import MAP_PROVIDER_OPTIONS from '../../utils/map-provider-options';
|
|
7
|
+
|
|
8
|
+
export default class SettingsMapController extends Controller {
|
|
9
|
+
@service fetch;
|
|
10
|
+
@service notifications;
|
|
11
|
+
@service intl;
|
|
12
|
+
@service mapManager;
|
|
13
|
+
@service mapSettings;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Available map provider options for the provider selector.
|
|
17
|
+
*
|
|
18
|
+
* @memberof SettingsMapController
|
|
19
|
+
* @var {Array}
|
|
20
|
+
*/
|
|
21
|
+
@tracked mapProviderOptions = MAP_PROVIDER_OPTIONS;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The currently-selected map provider key (e.g. 'leaflet' or 'google').
|
|
25
|
+
*
|
|
26
|
+
* @memberof SettingsMapController
|
|
27
|
+
* @var {String}
|
|
28
|
+
*/
|
|
29
|
+
@tracked mapProvider = 'leaflet';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether settings have been loaded from the server.
|
|
33
|
+
*
|
|
34
|
+
* @memberof SettingsMapController
|
|
35
|
+
* @var {Boolean}
|
|
36
|
+
*/
|
|
37
|
+
@tracked settingsLoaded = false;
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
super(...arguments);
|
|
41
|
+
this.getSettings.perform();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Computed getters ──────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* True when the user has selected Google Maps as the provider.
|
|
48
|
+
*
|
|
49
|
+
* @memberof SettingsMapController
|
|
50
|
+
* @return {Boolean}
|
|
51
|
+
*/
|
|
52
|
+
get isGoogleMapsSelected() {
|
|
53
|
+
return this.mapProvider === 'google';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Actions ───────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Called when the user picks a different map provider from the Select.
|
|
60
|
+
*
|
|
61
|
+
* @param {Object|String} option The selected option object or raw value.
|
|
62
|
+
* @memberof SettingsMapController
|
|
63
|
+
*/
|
|
64
|
+
@action selectMapProvider(option) {
|
|
65
|
+
this.mapProvider = option?.value ?? option ?? 'leaflet';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@task *saveSettings() {
|
|
69
|
+
const settings = {
|
|
70
|
+
mapProvider: this.mapProvider,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const response = yield this.fetch.post('fleet-ops/settings/map', { settings });
|
|
75
|
+
this.mapSettings.applySettings(response);
|
|
76
|
+
|
|
77
|
+
if (this.mapManager && typeof this.mapManager.setActiveProvider === 'function') {
|
|
78
|
+
this.mapManager.setActiveProvider(this.mapProvider);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.notifications.success(this.intl.t('settings.map.settings-saved'));
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.notifications.serverError(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@task *getSettings() {
|
|
88
|
+
try {
|
|
89
|
+
const response = yield this.mapSettings.load({ force: true });
|
|
90
|
+
|
|
91
|
+
this.mapProvider = response?.mapProvider ?? 'leaflet';
|
|
92
|
+
this.settingsLoaded = true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.notifications.serverError(error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -7,14 +7,20 @@ export default class SettingsRoutingController extends Controller {
|
|
|
7
7
|
@service fetch;
|
|
8
8
|
@service notifications;
|
|
9
9
|
@service currentUser;
|
|
10
|
-
@service
|
|
11
|
-
@
|
|
10
|
+
@service routeEngine;
|
|
11
|
+
@service routeOptimization;
|
|
12
|
+
@tracked displayEngine = 'osrm';
|
|
13
|
+
@tracked optimizationEngine = 'osrm';
|
|
12
14
|
@tracked routingUnit = 'km';
|
|
13
15
|
@tracked routingUnitOptions = [
|
|
14
16
|
{ label: 'Kilometers', value: 'km' },
|
|
15
17
|
{ label: 'Miles', value: 'mi' },
|
|
16
18
|
];
|
|
17
|
-
|
|
19
|
+
saveTasks = new Map();
|
|
20
|
+
|
|
21
|
+
get hasEngineOverrides() {
|
|
22
|
+
return ['vroom', 'valhalla'].includes(this.displayEngine) || ['vroom', 'valhalla'].includes(this.optimizationEngine);
|
|
23
|
+
}
|
|
18
24
|
|
|
19
25
|
constructor() {
|
|
20
26
|
super(...arguments);
|
|
@@ -28,10 +34,19 @@ export default class SettingsRoutingController extends Controller {
|
|
|
28
34
|
*/
|
|
29
35
|
@task *saveSettings() {
|
|
30
36
|
try {
|
|
31
|
-
yield this.fetch.post('fleet-ops/settings/routing-settings', {
|
|
37
|
+
yield this.fetch.post('fleet-ops/settings/routing-settings', {
|
|
38
|
+
display_engine: this.displayEngine,
|
|
39
|
+
optimization_engine: this.optimizationEngine,
|
|
40
|
+
unit: this.routingUnit,
|
|
41
|
+
});
|
|
32
42
|
yield this.performAdditionalSaveTasks();
|
|
33
43
|
// Save in local memory too
|
|
34
|
-
this.currentUser.setOption('routing', {
|
|
44
|
+
this.currentUser.setOption('routing', {
|
|
45
|
+
router: this.displayEngine,
|
|
46
|
+
routing_display_engine: this.displayEngine,
|
|
47
|
+
routing_optimization_engine: this.optimizationEngine,
|
|
48
|
+
unit: this.routingUnit,
|
|
49
|
+
});
|
|
35
50
|
this.notifications.success('Routing setting saved.');
|
|
36
51
|
} catch (error) {
|
|
37
52
|
this.notifications.serverError(error);
|
|
@@ -45,23 +60,43 @@ export default class SettingsRoutingController extends Controller {
|
|
|
45
60
|
*/
|
|
46
61
|
@task *getSettings() {
|
|
47
62
|
try {
|
|
48
|
-
const { router, unit } = yield this.fetch.get('fleet-ops/settings/routing-settings');
|
|
49
|
-
this.
|
|
63
|
+
const { router, display_engine, optimization_engine, unit } = yield this.fetch.get('fleet-ops/settings/routing-settings');
|
|
64
|
+
this.displayEngine = display_engine ?? router ?? 'osrm';
|
|
65
|
+
this.optimizationEngine = optimization_engine ?? display_engine ?? router ?? 'osrm';
|
|
50
66
|
this.routingUnit = unit;
|
|
51
67
|
} catch (error) {
|
|
52
68
|
this.notifications.serverError(error);
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
registerSaveTask(task) {
|
|
57
|
-
|
|
72
|
+
registerSaveTask(key, task) {
|
|
73
|
+
if (!key || typeof task?.perform !== 'function') {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.saveTasks.set(key, task);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
unregisterSaveTask(key) {
|
|
81
|
+
if (!key) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.saveTasks.delete(key);
|
|
58
86
|
}
|
|
59
87
|
|
|
60
88
|
async performAdditionalSaveTasks() {
|
|
61
|
-
for (
|
|
62
|
-
|
|
63
|
-
if (typeof task.perform === 'function') {
|
|
89
|
+
for (const task of this.saveTasks.values()) {
|
|
90
|
+
try {
|
|
64
91
|
await task.perform();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Ignore explicit ember-concurrency task cancellations from components
|
|
94
|
+
// that may have unmounted during routing-engine selection changes.
|
|
95
|
+
if (typeof error?.name === 'string' && error.name.includes('TaskCancelation')) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw error;
|
|
65
100
|
}
|
|
66
101
|
}
|
|
67
102
|
return true;
|
package/addon/extension.js
CHANGED
|
@@ -73,6 +73,16 @@ export default {
|
|
|
73
73
|
menuService.registerAdminMenuPanel(
|
|
74
74
|
'Fleet-Ops Config',
|
|
75
75
|
[
|
|
76
|
+
new MenuItem({
|
|
77
|
+
title: 'Routing',
|
|
78
|
+
icon: 'route',
|
|
79
|
+
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'admin/routing-settings'),
|
|
80
|
+
}),
|
|
81
|
+
new MenuItem({
|
|
82
|
+
title: 'Map',
|
|
83
|
+
icon: 'map',
|
|
84
|
+
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'admin/map-settings'),
|
|
85
|
+
}),
|
|
76
86
|
new MenuItem({
|
|
77
87
|
title: 'Navigator App',
|
|
78
88
|
icon: 'location-arrow',
|
|
@@ -179,6 +189,7 @@ export default {
|
|
|
179
189
|
'fleet-ops:component:order:form:payload:entity:form',
|
|
180
190
|
'fleet-ops:template:settings:routing',
|
|
181
191
|
'fleet-ops:template:settings:orchestrator',
|
|
192
|
+
'fleet-ops:component:admin:routing-settings',
|
|
182
193
|
]);
|
|
183
194
|
},
|
|
184
195
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function initialize(owner) {
|
|
2
|
+
const routeOptimization = owner.lookup('service:route-optimization');
|
|
3
|
+
const routeEngine = owner.lookup('service:route-engine');
|
|
4
|
+
const googleRoutes = owner.lookup('service:google-routes');
|
|
5
|
+
|
|
6
|
+
if (routeOptimization && googleRoutes) {
|
|
7
|
+
routeOptimization.register('google', googleRoutes);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (routeEngine && googleRoutes) {
|
|
11
|
+
routeEngine.register('google', googleRoutes, { display: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
initialize,
|
|
17
|
+
};
|
|
@@ -5,11 +5,16 @@ import { OSRMv1 } from '@fleetbase/leaflet-routing-machine';
|
|
|
5
5
|
export function initialize(owner) {
|
|
6
6
|
// Register OSRM as route optimization service
|
|
7
7
|
const routeOptimization = owner.lookup('service:route-optimization');
|
|
8
|
+
const routeEngine = owner.lookup('service:route-engine');
|
|
8
9
|
const osrm = owner.lookup('service:osrm');
|
|
9
10
|
if (routeOptimization && osrm) {
|
|
10
11
|
routeOptimization.register('osrm', osrm);
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
if (routeEngine && osrm) {
|
|
15
|
+
routeEngine.register('osrm', osrm, { display: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
// Register OSRM as Routing Controler
|
|
14
19
|
const leafletRoutingControl = owner.lookup('service:leaflet-routing-control');
|
|
15
20
|
if (leafletRoutingControl) {
|
|
@@ -12,6 +12,7 @@ export default class ApplicationRoute extends Route {
|
|
|
12
12
|
@service notifications;
|
|
13
13
|
@service fetch;
|
|
14
14
|
@service currentUser;
|
|
15
|
+
@service mapSettings;
|
|
15
16
|
|
|
16
17
|
@action loading(transition) {
|
|
17
18
|
const resource = getResourceNameFromTransition(transition, { humanize: true });
|
|
@@ -20,18 +21,23 @@ export default class ApplicationRoute extends Route {
|
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
beforeModel() {
|
|
24
|
+
async beforeModel() {
|
|
24
25
|
if (this.abilities.cannot('fleet-ops see extension')) {
|
|
25
26
|
this.notifications.warning(this.intl.t('common.unauthorized-access'));
|
|
26
27
|
return this.hostRouter.transitionTo('console');
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
this.location.getUserLocation();
|
|
30
|
-
this.#loadRoutingSettings();
|
|
30
|
+
await this.location.getUserLocation();
|
|
31
|
+
await this.#loadRoutingSettings();
|
|
32
|
+
await this.#loadMapSettings();
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
async #loadRoutingSettings() {
|
|
34
36
|
const routingSetting = await this.fetch.get('fleet-ops/settings/routing-settings');
|
|
35
37
|
this.currentUser.setOption('routing', routingSetting);
|
|
36
38
|
}
|
|
39
|
+
|
|
40
|
+
async #loadMapSettings() {
|
|
41
|
+
await this.mapSettings.load();
|
|
42
|
+
}
|
|
37
43
|
}
|