@fleetbase/fleetops-engine 0.4.3 → 0.4.4
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/edit-order-route-panel.hbs +182 -0
- package/addon/components/edit-order-route-panel.js +265 -0
- package/addon/components/fleet-panel.js +3 -3
- package/addon/components/map-container/toolbar.hbs +2 -2
- package/addon/components/modals/order-route-form.hbs +1 -1
- package/addon/controllers/operations/orders/index/new.js +66 -22
- package/addon/controllers/operations/orders/index/view.js +131 -126
- package/addon/routes/operations/orders/index/view.js +1 -1
- package/addon/services/context-panel.js +6 -0
- package/addon/styles/fleetops-engine.css +5 -0
- package/addon/templates/operations/orders/index/new.hbs +390 -58
- package/addon/templates/operations/orders/index/view.hbs +135 -105
- package/addon/templates/operations/service-rates/index/new.hbs +3 -1
- package/composer.json +2 -2
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/src/Http/Controllers/Api/v1/DriverController.php +59 -20
- package/server/src/Http/Controllers/Api/v1/OrderController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +16 -3
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +11 -0
- package/server/src/Http/Resources/v1/Order.php +3 -0
- package/server/src/Http/Resources/v1/PurchaseRate.php +2 -1
- package/server/src/Http/Resources/v1/ServiceQuote.php +1 -0
- package/server/src/Http/Resources/v1/ServiceQuoteItem.php +29 -0
- package/server/src/Models/Driver.php +1 -1
- package/server/src/Models/Order.php +16 -0
- package/server/src/Models/Place.php +2 -2
- package/server/src/Observers/DriverObserver.php +6 -1
- package/server/src/routes.php +1 -0
- package/translations/en-us.yaml +9 -2
|
@@ -3,13 +3,11 @@ import { inject as controller } from '@ember/controller';
|
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { tracked } from '@glimmer/tracking';
|
|
5
5
|
import { action, computed } from '@ember/object';
|
|
6
|
-
import { isArray } from '@ember/array';
|
|
7
6
|
import { later } from '@ember/runloop';
|
|
8
7
|
import { not, notEmpty, alias } from '@ember/object/computed';
|
|
9
8
|
import { task } from 'ember-concurrency-decorators';
|
|
10
9
|
import { OSRMv1, Control as RoutingControl } from '@fleetbase/leaflet-routing-machine';
|
|
11
10
|
import groupBy from '@fleetbase/ember-core/utils/macros/group-by';
|
|
12
|
-
import findClosestWaypoint from '@fleetbase/ember-core/utils/find-closest-waypoint';
|
|
13
11
|
import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
|
|
14
12
|
|
|
15
13
|
export default class OperationsOrdersIndexViewController extends BaseController {
|
|
@@ -115,6 +113,29 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
115
113
|
@tracked isWaypointsCollapsed;
|
|
116
114
|
@tracked leafletRoute;
|
|
117
115
|
@tracked routeControl;
|
|
116
|
+
@tracked commentInput = '';
|
|
117
|
+
@tracked uploadQueue = [];
|
|
118
|
+
acceptedFileTypes = [
|
|
119
|
+
'application/vnd.ms-excel',
|
|
120
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
121
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
122
|
+
'application/msword',
|
|
123
|
+
'application/pdf',
|
|
124
|
+
'application/x-pdf',
|
|
125
|
+
'image/jpeg',
|
|
126
|
+
'image/png',
|
|
127
|
+
'image/gif',
|
|
128
|
+
'image/webp',
|
|
129
|
+
'video/mp4',
|
|
130
|
+
'video/quicktime',
|
|
131
|
+
'video/x-msvideo',
|
|
132
|
+
'video/x-flv',
|
|
133
|
+
'video/x-ms-wmv',
|
|
134
|
+
'audio/mpeg',
|
|
135
|
+
'video/x-msvideo',
|
|
136
|
+
'application/zip',
|
|
137
|
+
'application/x-tar',
|
|
138
|
+
];
|
|
118
139
|
|
|
119
140
|
@alias('currentUser.latitude') userLatitude;
|
|
120
141
|
@alias('currentUser.longitude') userLongitude;
|
|
@@ -133,16 +154,16 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
133
154
|
];
|
|
134
155
|
|
|
135
156
|
@tracked routePanelButtons = [
|
|
136
|
-
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
157
|
+
// {
|
|
158
|
+
// type: 'default',
|
|
159
|
+
// text: 'Edit',
|
|
160
|
+
// icon: 'pencil',
|
|
161
|
+
// iconPrefix: 'fas',
|
|
162
|
+
// onClick: () => {
|
|
163
|
+
// const order = this.model;
|
|
164
|
+
// this.editOrderRoute(order);
|
|
165
|
+
// },
|
|
166
|
+
// },
|
|
146
167
|
];
|
|
147
168
|
|
|
148
169
|
@not('isWaypointsCollapsed') waypointsIsNotCollapsed;
|
|
@@ -179,6 +200,25 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
179
200
|
return groups;
|
|
180
201
|
}
|
|
181
202
|
|
|
203
|
+
@computed('model.payload.{dropoff,pickup,waypoints}') get routeWaypoints() {
|
|
204
|
+
const { payload } = this.model;
|
|
205
|
+
let waypoints = [];
|
|
206
|
+
let coordinates = [];
|
|
207
|
+
|
|
208
|
+
waypoints.pushObjects([payload.pickup, ...payload.waypoints.toArray(), payload.dropoff]);
|
|
209
|
+
waypoints.forEach((place) => {
|
|
210
|
+
if (place && place.get('longitude') && place.get('latitude')) {
|
|
211
|
+
if (place.hasInvalidCoordinates) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
coordinates.pushObject([place.get('latitude'), place.get('longitude')]);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return coordinates;
|
|
220
|
+
}
|
|
221
|
+
|
|
182
222
|
@task *loadOrderRelations(order) {
|
|
183
223
|
yield order.loadPayload();
|
|
184
224
|
yield order.loadDriver();
|
|
@@ -186,6 +226,8 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
186
226
|
yield order.loadCustomer();
|
|
187
227
|
yield order.loadTrackingActivity();
|
|
188
228
|
yield order.loadOrderConfig();
|
|
229
|
+
yield order.loadPurchaseRate();
|
|
230
|
+
yield order.loadFiles();
|
|
189
231
|
}
|
|
190
232
|
|
|
191
233
|
@action resetView() {
|
|
@@ -280,8 +322,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
280
322
|
setup();
|
|
281
323
|
}
|
|
282
324
|
|
|
283
|
-
|
|
284
|
-
const { payload } = this.model;
|
|
325
|
+
getPayloadCoordinates(payload) {
|
|
285
326
|
let waypoints = [];
|
|
286
327
|
let coordinates = [];
|
|
287
328
|
|
|
@@ -299,11 +340,19 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
299
340
|
return coordinates;
|
|
300
341
|
}
|
|
301
342
|
|
|
343
|
+
getPayloadWaypointsAsArray() {
|
|
344
|
+
if (this.model.payload && this.model.payload.waypoints) {
|
|
345
|
+
return this.model.payload.waypoints.toArray();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
|
|
302
351
|
@action displayOrderRoute() {
|
|
303
|
-
const payload = this.model.payload;
|
|
304
|
-
const waypoints = this.routeWaypoints;
|
|
305
352
|
const leafletMap = this.leafletMap;
|
|
306
|
-
const
|
|
353
|
+
const payload = this.model.payload;
|
|
354
|
+
const waypoints = this.getPayloadCoordinates(payload);
|
|
355
|
+
const routingHost = getRoutingHost(payload, this.getPayloadWaypointsAsArray());
|
|
307
356
|
|
|
308
357
|
if (!waypoints || waypoints.length < 2 || !leafletMap) {
|
|
309
358
|
return;
|
|
@@ -334,7 +383,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
334
383
|
router,
|
|
335
384
|
}).addTo(leafletMap);
|
|
336
385
|
|
|
337
|
-
this.routeControl
|
|
386
|
+
this.routeControl.on('routesfound', (event) => {
|
|
338
387
|
const { routes } = event;
|
|
339
388
|
|
|
340
389
|
this.leafletRoute = routes.firstObject;
|
|
@@ -343,7 +392,7 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
343
392
|
later(
|
|
344
393
|
this,
|
|
345
394
|
() => {
|
|
346
|
-
leafletMap
|
|
395
|
+
leafletMap.flyToBounds(waypoints, {
|
|
347
396
|
paddingBottomRight: [200, 0],
|
|
348
397
|
maxZoom: 14,
|
|
349
398
|
animate: true,
|
|
@@ -412,9 +461,14 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
412
461
|
order,
|
|
413
462
|
confirm: (modal) => {
|
|
414
463
|
modal.startLoading();
|
|
415
|
-
return order
|
|
416
|
-
|
|
417
|
-
|
|
464
|
+
return order
|
|
465
|
+
.save()
|
|
466
|
+
.then(() => {
|
|
467
|
+
this.notifications.success(options.successNotification || this.intl.t('fleet-ops.operations.orders.index.view.update-success', { orderId: order.public_id }));
|
|
468
|
+
})
|
|
469
|
+
.catch((error) => {
|
|
470
|
+
this.notifications.serverError(error);
|
|
471
|
+
});
|
|
418
472
|
},
|
|
419
473
|
decline: () => {
|
|
420
474
|
order.payload.rollbackAttributes();
|
|
@@ -475,114 +529,30 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
475
529
|
* @void
|
|
476
530
|
*/
|
|
477
531
|
@action async editOrderRoute(order, options = {}) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
userLatitude: this.userLatitude,
|
|
488
|
-
userLongitude: this.userLongitude,
|
|
489
|
-
isOptimizingRoute: false,
|
|
490
|
-
editPlace: async (place) => {
|
|
491
|
-
await this.modalsManager.done();
|
|
492
|
-
|
|
493
|
-
this.contextPanel.focus(place, 'editing', {
|
|
494
|
-
args: {
|
|
495
|
-
onClose: () => {
|
|
496
|
-
this.editOrderRoute(order);
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
});
|
|
500
|
-
},
|
|
501
|
-
toggleMultiDropOrder: () => {
|
|
502
|
-
if (order.payload.isMultiDrop) {
|
|
503
|
-
order.payload.waypoints.clear();
|
|
504
|
-
order.payload.setProperties({
|
|
505
|
-
pickup_uuid: '#',
|
|
506
|
-
dropoff_uuid: '#',
|
|
507
|
-
});
|
|
508
|
-
} else {
|
|
509
|
-
const waypoint = this.store.createRecord('waypoint');
|
|
510
|
-
order.payload.waypoints.pushObject(waypoint);
|
|
511
|
-
|
|
512
|
-
order.payload.setProperties({
|
|
513
|
-
pickup_uuid: null,
|
|
514
|
-
dropoff_uuid: null,
|
|
515
|
-
pickup: null,
|
|
516
|
-
dropoff: null,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
},
|
|
520
|
-
removeWaypoint: (waypoint) => {
|
|
521
|
-
order.payload.waypoints.removeObject(waypoint);
|
|
522
|
-
},
|
|
523
|
-
addWaypoint: () => {
|
|
524
|
-
const waypoint = this.store.createRecord('waypoint');
|
|
525
|
-
order.payload.waypoints.pushObject(waypoint);
|
|
526
|
-
},
|
|
527
|
-
setWaypointPlace: (index, place) => {
|
|
528
|
-
if (!order.payload.waypoints?.objectAt(index)) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
order.payload.waypoints.objectAt(index).setProperties({
|
|
533
|
-
name: place.name,
|
|
534
|
-
place_uuid: place.id,
|
|
535
|
-
place,
|
|
536
|
-
});
|
|
537
|
-
},
|
|
538
|
-
setPayloadPlace: (prop, place) => {
|
|
539
|
-
order.payload.set(prop, place);
|
|
540
|
-
},
|
|
541
|
-
optimizeRoute: async () => {
|
|
542
|
-
this.modalsManager.setOption('isOptimizingRoute', true);
|
|
543
|
-
|
|
544
|
-
const coordinates = order.payload.payloadCoordinates;
|
|
545
|
-
const routingHost = getRoutingHost(order.payload, order.payload.waypoints);
|
|
546
|
-
|
|
547
|
-
const response = await this.fetch.routing(coordinates, { source: 'any', destination: 'any', annotations: true }, { host: routingHost }).catch(() => {
|
|
548
|
-
this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.view.route-error'));
|
|
549
|
-
this.modalsManager.setOption('isOptimizingRoute', false);
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
if (response && response.code === 'Ok') {
|
|
553
|
-
if (response.waypoints && isArray(response.waypoints)) {
|
|
554
|
-
const responseWaypoints = response.waypoints.sortBy('waypoint_index');
|
|
555
|
-
const sortedWaypoints = [];
|
|
556
|
-
|
|
557
|
-
for (let i = 0; i < responseWaypoints.length; i++) {
|
|
558
|
-
const optimizedWaypoint = responseWaypoints.objectAt(i);
|
|
559
|
-
const optimizedWaypointLongitude = optimizedWaypoint.location.firstObject;
|
|
560
|
-
const optimizedWaypointLatitude = optimizedWaypoint.location.lastObject;
|
|
561
|
-
const waypointModel = findClosestWaypoint(optimizedWaypointLatitude, optimizedWaypointLongitude, order.payload.waypoints);
|
|
562
|
-
|
|
563
|
-
sortedWaypoints.pushObject(waypointModel);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
order.payload.waypoints = sortedWaypoints;
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
this.notifications.error(this.intl.t('fleet-ops.operations.orders.index.view.route-error'));
|
|
570
|
-
}
|
|
532
|
+
const updateRouteDisplay = () => {
|
|
533
|
+
later(
|
|
534
|
+
this,
|
|
535
|
+
() => {
|
|
536
|
+
this.displayOrderRoute();
|
|
537
|
+
},
|
|
538
|
+
100
|
|
539
|
+
);
|
|
540
|
+
};
|
|
571
541
|
|
|
572
|
-
|
|
542
|
+
this.contextPanel.focus(order, 'editingRoute', {
|
|
543
|
+
args: {
|
|
544
|
+
isResizable: true,
|
|
545
|
+
latitude: this.userLatitude,
|
|
546
|
+
longitude: this.userLongitude,
|
|
547
|
+
...options,
|
|
573
548
|
},
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
return order.payload.save().then(() => {
|
|
578
|
-
this.notifications.success(options.successNotification ?? this.intl.t('fleet-ops.operations.orders.index.view.route-update-success', { orderId: order.public_id }));
|
|
579
|
-
});
|
|
549
|
+
onRouteChanged: () => {
|
|
550
|
+
updateRouteDisplay();
|
|
580
551
|
},
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
552
|
+
onAfterSave: () => {
|
|
553
|
+
this.contextPanel.clear();
|
|
554
|
+
updateRouteDisplay();
|
|
584
555
|
},
|
|
585
|
-
...options,
|
|
586
556
|
});
|
|
587
557
|
}
|
|
588
558
|
|
|
@@ -911,4 +881,39 @@ export default class OperationsOrdersIndexViewController extends BaseController
|
|
|
911
881
|
},
|
|
912
882
|
});
|
|
913
883
|
}
|
|
884
|
+
|
|
885
|
+
@action queueFile(file) {
|
|
886
|
+
// since we have dropzone and upload button within dropzone validate the file state first
|
|
887
|
+
// as this method can be called twice from both functions
|
|
888
|
+
if (['queued', 'failed', 'timed_out', 'aborted'].indexOf(file.state) === -1) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Queue and upload immediatley
|
|
893
|
+
this.uploadQueue.pushObject(file);
|
|
894
|
+
this.fetch.uploadFile.perform(
|
|
895
|
+
file,
|
|
896
|
+
{
|
|
897
|
+
path: 'uploads/fleet-ops/order-files',
|
|
898
|
+
subject_uuid: this.model.id,
|
|
899
|
+
subject_type: 'fleet-ops:order',
|
|
900
|
+
type: 'order_file',
|
|
901
|
+
},
|
|
902
|
+
(uploadedFile) => {
|
|
903
|
+
this.model.files.pushObject(uploadedFile);
|
|
904
|
+
this.uploadQueue.removeObject(file);
|
|
905
|
+
},
|
|
906
|
+
() => {
|
|
907
|
+
this.uploadQueue.removeObject(file);
|
|
908
|
+
// remove file from queue
|
|
909
|
+
if (file.queue && typeof file.queue.remove === 'function') {
|
|
910
|
+
file.queue.remove(file);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
@action removeFile(file) {
|
|
917
|
+
return file.destroyRecord();
|
|
918
|
+
}
|
|
914
919
|
}
|
|
@@ -29,7 +29,7 @@ export default class OperationsOrdersIndexViewRoute extends Route {
|
|
|
29
29
|
return this.store.queryRecord('order', {
|
|
30
30
|
public_id,
|
|
31
31
|
single: true,
|
|
32
|
-
with: ['payload', 'driverAssigned', 'customer', 'facilitator', 'trackingStatuses', 'trackingNumber'],
|
|
32
|
+
with: ['payload', 'driverAssigned', 'customer', 'facilitator', 'trackingStatuses', 'trackingNumber', 'purchaseRate', 'comments', 'files'],
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -97,6 +97,12 @@ export default class ContextPanelService extends Service {
|
|
|
97
97
|
componentArguments: [{ isResizable: true }, { width: '500px' }],
|
|
98
98
|
},
|
|
99
99
|
},
|
|
100
|
+
order: {
|
|
101
|
+
editingRoute: {
|
|
102
|
+
component: 'edit-order-route-panel',
|
|
103
|
+
componentArguments: [{ isResizable: true }, { width: '500px' }],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
100
106
|
};
|
|
101
107
|
|
|
102
108
|
/**
|
|
@@ -66,3 +66,8 @@ body[data-theme='dark'] .scheduler-left-sidebar {
|
|
|
66
66
|
border: none;
|
|
67
67
|
box-shadow: none;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
body[data-theme='dark'] .next-table-wrapper.order-view-purchase-rate-table-wrapper > table#order-view-purchase-rate-table tbody tr td {
|
|
71
|
+
background-color: rgb(31, 41, 55);
|
|
72
|
+
color: rgb(249, 250, 251);
|
|
73
|
+
}
|