@fleetbase/fleetops-engine 0.6.11 → 0.6.13
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/customer/orders.js +6 -8
- package/addon/components/layout/fleet-ops-sidebar.js +8 -0
- package/addon/components/leaflet-tracking-marker.js +19 -0
- package/addon/components/live-map.hbs +5 -0
- package/addon/components/live-map.js +6 -8
- package/addon/components/order-config/fields-editor.js +1 -1
- package/addon/components/route-optimization-engine-select-button.hbs +25 -0
- package/addon/components/route-optimization-engine-select-button.js +13 -0
- package/addon/controllers/operations/orders/index/new.js +78 -171
- package/addon/controllers/operations/orders/index/view.js +30 -29
- package/addon/controllers/settings/routing.js +47 -0
- package/addon/engine.js +27 -0
- package/addon/routes/application.js +8 -0
- package/addon/routes/operations/orders/index/view.js +12 -3
- package/addon/routes/settings/routing.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/leaflet-router-control.js +64 -0
- package/addon/services/osrm.js +46 -0
- package/addon/services/route-optimization-interface.js +9 -0
- package/addon/services/route-optimization.js +67 -0
- package/addon/templates/operations/orders/index/new.hbs +21 -16
- package/addon/templates/settings/routing.hbs +32 -0
- package/app/components/route-optimization-engine-select-button.js +1 -0
- package/app/controllers/settings/routing.js +1 -0
- package/app/routes/settings/routing.js +1 -0
- package/app/services/leaflet-router-control.js +1 -0
- package/app/services/osrm.js +1 -0
- package/app/services/route-optimization-interface.js +1 -0
- package/app/services/route-optimization.js +1 -0
- package/app/templates/settings/routing.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +2 -2
- package/server/config/api.php +10 -1
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Console/Commands/DebugOrderTracker.php +5 -5
- package/server/src/Events/EntityActivityChanged.php +118 -0
- package/server/src/Events/EntityCompleted.php +118 -0
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +3 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +16 -13
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +31 -1
- package/server/src/Http/Resources/v1/Waypoint.php +1 -1
- package/server/src/Models/Order.php +44 -8
- package/server/src/Models/Payload.php +29 -10
- package/server/src/Models/Place.php +49 -13
- package/server/src/Support/Geocoding.php +0 -2
- package/server/src/routes.php +2 -0
- package/translations/en-us.yaml +5 -0
|
@@ -6,8 +6,7 @@ import { action, computed } from '@ember/object';
|
|
|
6
6
|
import { later } from '@ember/runloop';
|
|
7
7
|
import { not, notEmpty, alias } from '@ember/object/computed';
|
|
8
8
|
import { task } from 'ember-concurrency';
|
|
9
|
-
import {
|
|
10
|
-
import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
|
|
9
|
+
import { Control as RoutingControl } from '@fleetbase/leaflet-routing-machine';
|
|
11
10
|
|
|
12
11
|
export default class OperationsOrdersIndexViewController extends BaseController {
|
|
13
12
|
@controller('operations.orders.index') ordersController;
|
|
@@ -24,6 +23,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
24
23
|
@service socket;
|
|
25
24
|
@service universe;
|
|
26
25
|
@service contextPanel;
|
|
26
|
+
@service leafletRouterControl;
|
|
27
27
|
|
|
28
28
|
@tracked isLoadingAdditionalData = false;
|
|
29
29
|
@tracked isWaypointsCollapsed;
|
|
@@ -243,39 +243,42 @@ 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
|
+
this.routeControl = null;
|
|
280
|
+
resolve(true);
|
|
281
|
+
});
|
|
279
282
|
}
|
|
280
283
|
|
|
281
284
|
@action forceRemoveRoutePreview() {
|
|
@@ -370,9 +373,8 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
370
373
|
const leafletMap = this.leafletMap;
|
|
371
374
|
const payload = this.model.payload;
|
|
372
375
|
const waypoints = this.getPayloadCoordinates(payload);
|
|
373
|
-
const routingHost = getRoutingHost(payload, this.getPayloadWaypointsAsArray());
|
|
374
376
|
|
|
375
|
-
if (!waypoints || waypoints.length < 2 || !leafletMap) {
|
|
377
|
+
if (!waypoints || waypoints.length < 2 || !leafletMap || this.routeControl) {
|
|
376
378
|
return;
|
|
377
379
|
}
|
|
378
380
|
|
|
@@ -380,12 +382,12 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
380
382
|
leafletMap.stop();
|
|
381
383
|
leafletMap.flyTo(waypoints.firstObject);
|
|
382
384
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
profile: 'driving',
|
|
386
|
-
});
|
|
385
|
+
const routingService = this.currentUser.getOption('routing', { router: 'osrm' }).router;
|
|
386
|
+
const { router, formatter } = this.leafletRouterControl.get(routingService);
|
|
387
387
|
|
|
388
388
|
this.routeControl = new RoutingControl({
|
|
389
|
+
router,
|
|
390
|
+
formatter,
|
|
389
391
|
waypoints,
|
|
390
392
|
markerOptions: {
|
|
391
393
|
icon: L.icon({
|
|
@@ -398,7 +400,6 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
398
400
|
},
|
|
399
401
|
alternativeClassName: 'hidden',
|
|
400
402
|
addWaypoints: false,
|
|
401
|
-
router,
|
|
402
403
|
}).addTo(leafletMap);
|
|
403
404
|
|
|
404
405
|
this.routeControl.on('routesfound', (event) => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { task } from 'ember-concurrency';
|
|
5
|
+
|
|
6
|
+
export default class SettingsRoutingController extends Controller {
|
|
7
|
+
@service fetch;
|
|
8
|
+
@service notifications;
|
|
9
|
+
@service currentUser;
|
|
10
|
+
@service leafletRouterControl;
|
|
11
|
+
@tracked routerService = 'osrm';
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.getSettings.perform();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Save routing settings.
|
|
20
|
+
*
|
|
21
|
+
* @memberof SettingsRoutingController
|
|
22
|
+
*/
|
|
23
|
+
@task *saveSettings() {
|
|
24
|
+
try {
|
|
25
|
+
yield this.fetch.post('fleet-ops/settings/routing-settings', { router: this.routerService });
|
|
26
|
+
// Save in local memory too
|
|
27
|
+
this.currentUser.setOption('routing', { router: this.routerService });
|
|
28
|
+
this.notifications.success('Routing setting saved.');
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.notifications.serverError(error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get routing settings.
|
|
36
|
+
*
|
|
37
|
+
* @memberof SettingsRoutingController
|
|
38
|
+
*/
|
|
39
|
+
@task *getSettings() {
|
|
40
|
+
try {
|
|
41
|
+
const { router } = yield this.fetch.get('fleet-ops/settings/routing-settings');
|
|
42
|
+
this.routerService = router;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.notifications.serverError(error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/addon/engine.js
CHANGED
|
@@ -9,6 +9,9 @@ import AdminAvatarManagementComponent from './components/admin/avatar-management
|
|
|
9
9
|
import CustomerOrdersComponent from './components/customer/orders';
|
|
10
10
|
import CustomerAdminSettingsComponent from './components/customer/admin-settings';
|
|
11
11
|
import OrderTrackingLookupComponent from './components/order-tracking-lookup';
|
|
12
|
+
import { RouterControl } from './services/leaflet-router-control';
|
|
13
|
+
import { OSRMv1 } from '@fleetbase/leaflet-routing-machine';
|
|
14
|
+
import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
|
|
12
15
|
|
|
13
16
|
const { modulePrefix } = config;
|
|
14
17
|
const externalRoutes = ['console', 'extensions'];
|
|
@@ -57,6 +60,29 @@ export default class FleetOpsEngine extends Engine {
|
|
|
57
60
|
},
|
|
58
61
|
});
|
|
59
62
|
|
|
63
|
+
// Register OSRM as route optimization service
|
|
64
|
+
const routeOptimization = app.lookup('service:route-optimization');
|
|
65
|
+
const osrm = app.lookup('service:osrm');
|
|
66
|
+
if (routeOptimization && osrm) {
|
|
67
|
+
routeOptimization.register('osrm', osrm);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Register OSRM as Routing Controler
|
|
71
|
+
const leafletRouterControl = app.lookup('service:leaflet-router-control');
|
|
72
|
+
if (leafletRouterControl) {
|
|
73
|
+
const routingHost = getRoutingHost();
|
|
74
|
+
leafletRouterControl.register(
|
|
75
|
+
'osrm',
|
|
76
|
+
new RouterControl({
|
|
77
|
+
name: 'OSRM',
|
|
78
|
+
router: new OSRMv1({
|
|
79
|
+
serviceUrl: `${routingHost}/route/v1`,
|
|
80
|
+
profile: 'driving',
|
|
81
|
+
}),
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
60
86
|
// widgets for registry
|
|
61
87
|
const KeyMetricsWidgetDefinition = {
|
|
62
88
|
widgetId: 'fleet-ops-key-metrics-widget',
|
|
@@ -106,6 +132,7 @@ export default class FleetOpsEngine extends Engine {
|
|
|
106
132
|
'fleet-ops:template:operations:orders:view',
|
|
107
133
|
'fleet-ops:template:operations:orders:new',
|
|
108
134
|
'fleet-ops:template:operations:orders:new:entities-input',
|
|
135
|
+
'fleet-ops:template:operations:orders:new:entities-input:entity',
|
|
109
136
|
]);
|
|
110
137
|
|
|
111
138
|
universe.afterBoot(function (universe) {
|
|
@@ -10,6 +10,8 @@ export default class ApplicationRoute extends Route {
|
|
|
10
10
|
@service abilities;
|
|
11
11
|
@service hostRouter;
|
|
12
12
|
@service notifications;
|
|
13
|
+
@service fetch;
|
|
14
|
+
@service currentUser;
|
|
13
15
|
|
|
14
16
|
@action loading(transition) {
|
|
15
17
|
const resourceName = getResourceNameFromTransition(transition, { humanize: true });
|
|
@@ -25,5 +27,11 @@ export default class ApplicationRoute extends Route {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
this.location.getUserLocation();
|
|
30
|
+
this.#loadRoutingSettings();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async #loadRoutingSettings() {
|
|
34
|
+
const routingSetting = await this.fetch.get('fleet-ops/settings/routing-settings');
|
|
35
|
+
this.currentUser.setOption('routing', routingSetting);
|
|
28
36
|
}
|
|
29
37
|
}
|
|
@@ -12,10 +12,18 @@ export default class OperationsOrdersIndexViewRoute extends Route {
|
|
|
12
12
|
@service abilities;
|
|
13
13
|
@service intl;
|
|
14
14
|
|
|
15
|
-
@action willTransition() {
|
|
16
|
-
if
|
|
15
|
+
@action willTransition(transition) {
|
|
16
|
+
// if we're coming _from_ this route and going _to_ a different one, then reset
|
|
17
|
+
let fromName = transition.from?.name;
|
|
18
|
+
let toName = transition.to.name;
|
|
19
|
+
|
|
20
|
+
// transition.from is null on initial page load, so guard that
|
|
21
|
+
if (fromName && fromName !== toName) {
|
|
17
22
|
this.controller.resetView();
|
|
18
23
|
}
|
|
24
|
+
|
|
25
|
+
// bubble the event on up so Ember can finish the transition
|
|
26
|
+
return true;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
@action error(error) {
|
|
@@ -75,13 +83,14 @@ export default class OperationsOrdersIndexViewRoute extends Route {
|
|
|
75
83
|
// Only reload if the order has a status change stemming from an updated event OR
|
|
76
84
|
// if a waypoint has been completed which will trigger `order.completed`
|
|
77
85
|
const statusChanged = event === 'order.updated' && data.status !== model.status;
|
|
78
|
-
const shouldReload = ['order.completed', 'waypoint.activity', 'order.created'].includes(event);
|
|
86
|
+
const shouldReload = ['order.completed', 'waypoint.activity', 'entity.activity', 'order.created'].includes(event);
|
|
79
87
|
if (statusChanged || shouldReload) {
|
|
80
88
|
this.refresh();
|
|
81
89
|
|
|
82
90
|
// reload the controller stuff as well
|
|
83
91
|
if (this.controller) {
|
|
84
92
|
this.controller.loadOrderRelations.perform(model);
|
|
93
|
+
this.controller.displayOrderRoute();
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
|
package/addon/routes.js
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Service, { inject as service } from '@ember/service';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { underscore } from '@ember/string';
|
|
4
|
+
|
|
5
|
+
export class RouterControlRegistry {
|
|
6
|
+
@tracked routers = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class RouterControl {
|
|
10
|
+
@tracked name;
|
|
11
|
+
@tracked router;
|
|
12
|
+
@tracked formatter;
|
|
13
|
+
|
|
14
|
+
constructor(init) {
|
|
15
|
+
const { name, router, formatter } = typeof init === 'function' ? init() : init;
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.router = router;
|
|
18
|
+
this.formatter = formatter;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default class LeafletRouterControlService extends Service {
|
|
23
|
+
@service universe;
|
|
24
|
+
registry = this.#initializeRegistry();
|
|
25
|
+
|
|
26
|
+
get availableEngines() {
|
|
27
|
+
return Object.keys(this.registry.routers).map(underscore);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get availableServices() {
|
|
31
|
+
return Object.entries(this.registry.routers).map(([key, control]) => ({
|
|
32
|
+
key,
|
|
33
|
+
name: control.name ?? key,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
register(name, getter) {
|
|
38
|
+
this.registry.routers = {
|
|
39
|
+
...this.registry.routers,
|
|
40
|
+
[underscore(name)]: getter,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* eslint-disable no-unused-vars */
|
|
45
|
+
unregister(name) {
|
|
46
|
+
let key = underscore(name);
|
|
47
|
+
let { [key]: _, ...rest } = this.registry.routers;
|
|
48
|
+
this.registry.routers = rest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get(name) {
|
|
52
|
+
return this.registry.routers[underscore(name)];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#initializeRegistry() {
|
|
56
|
+
const registry = 'registry:router-controls';
|
|
57
|
+
const application = this.universe.getApplicationInstance();
|
|
58
|
+
if (!application.hasRegistration(registry)) {
|
|
59
|
+
application.register(registry, new RouterControlRegistry(), { instantiate: false });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return application.resolveRegistration(registry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import RouteOptimizationInterfaceService from './route-optimization-interface';
|
|
2
|
+
import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
|
|
3
|
+
import polyline from '@fleetbase/ember-core/utils/polyline';
|
|
4
|
+
import { debug } from '@ember/debug';
|
|
5
|
+
|
|
6
|
+
export default class OsrmService extends RouteOptimizationInterfaceService {
|
|
7
|
+
name = 'OSRM';
|
|
8
|
+
|
|
9
|
+
async optimize({ order, payload, waypoints, coordinates: originalCoords }, options = {}) {
|
|
10
|
+
const driverAssigned = order.driver_assigned;
|
|
11
|
+
const driverPosition = driverAssigned?.location?.coordinates; // [lon,lat] | undefined
|
|
12
|
+
const coordinates = driverPosition ? [driverPosition, ...originalCoords] : [...originalCoords];
|
|
13
|
+
const hasDriverStart = Boolean(driverPosition);
|
|
14
|
+
const source = 'first';
|
|
15
|
+
const destination = 'any';
|
|
16
|
+
const roundtrip = false; // don’t loop back
|
|
17
|
+
const routingHost = getRoutingHost(payload, waypoints);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const result = await this.fetch.routing(coordinates, { source, destination, roundtrip, annotations: true }, { host: routingHost, ...options });
|
|
21
|
+
|
|
22
|
+
// Pair each OSRM waypoint with its Waypoint model
|
|
23
|
+
const modelsByInputIndex = hasDriverStart ? [null, ...waypoints] : waypoints;
|
|
24
|
+
const pairs = result.waypoints.map((wp, idx) => ({
|
|
25
|
+
model: modelsByInputIndex[idx], // Ember model or null (driver)
|
|
26
|
+
wp,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Drop the driver start if present
|
|
30
|
+
const payloadPairs = hasDriverStart ? pairs.slice(1) : pairs;
|
|
31
|
+
|
|
32
|
+
// Sort by the optimised order
|
|
33
|
+
payloadPairs.sort((a, b) => a.wp.waypoint_index - b.wp.waypoint_index);
|
|
34
|
+
|
|
35
|
+
// Extract the Ember models (null-safe)
|
|
36
|
+
const sortedWaypoints = payloadPairs.map((p) => p.model).filter(Boolean);
|
|
37
|
+
const trip = result.trips?.[0];
|
|
38
|
+
const route = polyline.decode(trip.geometry);
|
|
39
|
+
|
|
40
|
+
return { sortedWaypoints, trip, route, result };
|
|
41
|
+
} catch (err) {
|
|
42
|
+
debug(`[OSRM] Error routing trip : ${err.message}`);
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Service, { inject as service } from '@ember/service';
|
|
2
|
+
|
|
3
|
+
export default class RouteOptimizationInterfaceService extends Service {
|
|
4
|
+
@service fetch;
|
|
5
|
+
|
|
6
|
+
optimize() {
|
|
7
|
+
throw new Error(`${this.constructor.name} must implement optimize(params, options)`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Service, { inject as service } from '@ember/service';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { underscore } from '@ember/string';
|
|
4
|
+
|
|
5
|
+
export class RouteOptimizationRegistry {
|
|
6
|
+
@tracked engines = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default class RouteOptimizationService extends Service {
|
|
10
|
+
@service universe;
|
|
11
|
+
registry = this.#initializeRegistry();
|
|
12
|
+
|
|
13
|
+
get availableEngines() {
|
|
14
|
+
return Object.keys(this.registry.engines).map(underscore);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get availableServices() {
|
|
18
|
+
return Object.entries(this.registry.engines).map(([key, engine]) => ({
|
|
19
|
+
key,
|
|
20
|
+
name: engine.name ?? key,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
register(name, engine) {
|
|
25
|
+
if (typeof engine.optimize !== 'function') {
|
|
26
|
+
throw new Error(`Cannot register "${name}": missing optimize()`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.registry.engines = {
|
|
30
|
+
...this.registry.engines,
|
|
31
|
+
[underscore(name)]: engine,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* eslint-disable no-unused-vars */
|
|
36
|
+
unregister(name) {
|
|
37
|
+
let key = underscore(name);
|
|
38
|
+
let { [key]: _, ...rest } = this.registry.engines;
|
|
39
|
+
this.registry.engines = rest;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
optimize(name, params = {}, options = {}) {
|
|
43
|
+
let engine = this.registry.engines[underscore(name)];
|
|
44
|
+
if (!engine) {
|
|
45
|
+
return Promise.reject(new Error(`No route optimization engine registered as "${name}"`));
|
|
46
|
+
}
|
|
47
|
+
return engine.optimize(params, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
handler(name, context, data) {
|
|
51
|
+
let engine = this.registry.engines[underscore(name)];
|
|
52
|
+
if (!engine) {
|
|
53
|
+
throw new Error(`No route optimization engine registered as "${name}"`);
|
|
54
|
+
}
|
|
55
|
+
return engine.handler(context, data);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#initializeRegistry() {
|
|
59
|
+
const registry = 'registry:route-optimization-engines';
|
|
60
|
+
const application = this.universe.getApplicationInstance();
|
|
61
|
+
if (!application.hasRegistration(registry)) {
|
|
62
|
+
application.register(registry, new RouteOptimizationRegistry(), { instantiate: false });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return application.resolveRegistration(registry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -315,17 +315,21 @@
|
|
|
315
315
|
</div>
|
|
316
316
|
<div class="flex flex-1 justify-end space-x-2">
|
|
317
317
|
{{#if this.isMultipleDropoffOrder}}
|
|
318
|
-
|
|
319
|
-
@
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
318
|
+
{{#if this.hasRouteOptimizationEngines}}
|
|
319
|
+
<RouteOptimizationEngineSelectButton @onClick={{perform this.optimizeRouteWithService}} @isLoading={{this.optimizeRouteWithService.isRunning}} />
|
|
320
|
+
{{else}}
|
|
321
|
+
<Button
|
|
322
|
+
@type="magic"
|
|
323
|
+
@icon="magic"
|
|
324
|
+
@text={{t "fleet-ops.operations.orders.index.new.optimize-route"}}
|
|
325
|
+
@size="sm"
|
|
326
|
+
@onClick={{perform this.optimizeRoute}}
|
|
327
|
+
@permission="fleet-ops optimize order"
|
|
328
|
+
@helpText={{t "fleet-ops.operations.orders.index.new.optimize-route-help-text"}}
|
|
329
|
+
@disabled={{lt this.waypoints.length 3}}
|
|
330
|
+
@isLoading={{this.optimizeRoute.isRunning}}
|
|
331
|
+
/>
|
|
332
|
+
{{/if}}
|
|
329
333
|
<Button
|
|
330
334
|
@icon="map-marked-alt"
|
|
331
335
|
@text={{t "fleet-ops.operations.orders.index.new.add-waypoint"}}
|
|
@@ -630,11 +634,9 @@
|
|
|
630
634
|
</div>
|
|
631
635
|
</DropdownButton>
|
|
632
636
|
{{/if}}
|
|
633
|
-
|
|
634
|
-
{{
|
|
635
|
-
|
|
636
|
-
{{/each}}
|
|
637
|
-
{{/if}}
|
|
637
|
+
<RegistryYield @registry="fleet-ops:template:operations:orders:new:entities-input" as |RegistryComponent|>
|
|
638
|
+
<RegistryComponent @order={{this.order}} @controller={{this}} />
|
|
639
|
+
</RegistryYield>
|
|
638
640
|
</div>
|
|
639
641
|
{{/if}}
|
|
640
642
|
{{#if this.isCsvImportedOrder}}
|
|
@@ -709,6 +711,9 @@
|
|
|
709
711
|
<div>
|
|
710
712
|
<Input @value={{entity.sku}} @type="text" class="w-full form-input form-input-sm" placeholder={{t "fleet-ops.operations.orders.index.new.sku"}} />
|
|
711
713
|
</div>
|
|
714
|
+
<RegistryYield @registry="fleet-ops:template:operations:orders:new:entities-input:entity" as |RegistryComponent|>
|
|
715
|
+
<RegistryComponent @entity={{entity}} @order={{this.order}} @controller={{this}} />
|
|
716
|
+
</RegistryYield>
|
|
712
717
|
{{#if this.waypoints.length}}
|
|
713
718
|
<Select
|
|
714
719
|
@value={{entity.destination_uuid}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<Layout::Section::Header @title={{t "fleet-ops.settings.routing.fleet-ops-routing-settings"}}>
|
|
2
|
+
<Button
|
|
3
|
+
@type="primary"
|
|
4
|
+
@size="sm"
|
|
5
|
+
@icon="save"
|
|
6
|
+
@text={{t "common.save-button-text"}}
|
|
7
|
+
@onClick={{perform this.saveSettings}}
|
|
8
|
+
@disabled={{this.saveSettings.isRunning}}
|
|
9
|
+
@isLoading={{or this.saveSettings.isRunning this.getSettings.isRunning}}
|
|
10
|
+
/>
|
|
11
|
+
</Layout::Section::Header>
|
|
12
|
+
|
|
13
|
+
<Layout::Section::Body class="overflow-y-scroll h-full">
|
|
14
|
+
<div class="container mx-auto h-screen">
|
|
15
|
+
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
|
16
|
+
<ContentPanel @title={{t "fleet-ops.settings.routing.configure-routing"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
|
17
|
+
<InputGroup @name="Routing Service" @helpText="Select the service which is responsible for calculating and plotting routes on the map.">
|
|
18
|
+
<Select
|
|
19
|
+
@value={{this.routerService}}
|
|
20
|
+
@options={{this.leafletRouterControl.availableServices}}
|
|
21
|
+
@optionLabel="name"
|
|
22
|
+
@optionValue="key"
|
|
23
|
+
@onSelect={{fn (mut this.routerService)}}
|
|
24
|
+
@placeholder="Select routing service..."
|
|
25
|
+
class="w-full"
|
|
26
|
+
/>
|
|
27
|
+
</InputGroup>
|
|
28
|
+
</ContentPanel>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<Spacer @height="600px" />
|
|
32
|
+
</Layout::Section::Body>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/components/route-optimization-engine-select-button';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/controllers/settings/routing';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/routes/settings/routing';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/services/leaflet-router-control';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/services/osrm';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/services/route-optimization-interface';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/services/route-optimization';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/fleetops-engine/templates/settings/routing';
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/fleetops-engine",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
4
4
|
"description": "Fleet & Transport Management Extension for Fleetbase",
|
|
5
5
|
"fleetbase": {
|
|
6
6
|
"route": "fleet-ops"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@fleetbase/ember-core": "latest",
|
|
46
46
|
"@fleetbase/ember-ui": "latest",
|
|
47
47
|
"@fleetbase/fleetops-data": "latest",
|
|
48
|
-
"@fleetbase/leaflet-routing-machine": "^3.2.
|
|
48
|
+
"@fleetbase/leaflet-routing-machine": "^3.2.17",
|
|
49
49
|
"@fortawesome/ember-fontawesome": "^2.0.0",
|
|
50
50
|
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
|
51
51
|
"@fortawesome/free-brands-svg-icons": "6.4.0",
|