@fleetbase/fleetops-engine 0.4.3 → 0.4.5
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/contact-form-panel.hbs +1 -0
- package/addon/components/driver-panel.hbs +1 -0
- package/addon/components/edit-order-route-panel.hbs +183 -0
- package/addon/components/edit-order-route-panel.js +265 -0
- package/addon/components/fleet-form-panel.hbs +1 -0
- package/addon/components/fleet-panel.hbs +1 -0
- package/addon/components/fleet-panel.js +3 -3
- package/addon/components/fuel-report-form-panel.hbs +1 -0
- package/addon/components/fuel-report-panel.hbs +1 -0
- package/addon/components/issue-form-panel.hbs +1 -0
- package/addon/components/issue-panel.hbs +1 -0
- package/addon/components/map-container/toolbar.hbs +2 -2
- package/addon/components/modals/order-route-form.hbs +1 -1
- package/addon/components/place-form-panel.hbs +1 -0
- package/addon/components/place-panel.hbs +1 -0
- package/addon/components/vehicle-form-panel.hbs +1 -0
- package/addon/components/vehicle-panel.hbs +1 -0
- package/addon/components/vendor-form-panel.hbs +1 -0
- package/addon/components/vendor-panel.hbs +1 -0
- package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -0
- package/addon/components/widget/fleet-ops-key-metrics.js +85 -0
- package/addon/controllers/operations/orders/index/new.js +66 -22
- package/addon/controllers/operations/orders/index/view.js +131 -126
- package/addon/controllers/operations/orders/index.js +1 -1
- package/addon/engine.js +18 -12
- package/addon/routes/operations/orders/index/view.js +1 -1
- package/addon/services/context-panel.js +6 -0
- package/addon/styles/fleetops-engine.css +7 -2
- 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/app/components/widget/{issues.js → fleet-ops-key-metrics.js} +1 -1
- package/composer.json +6 -6
- package/extension.json +1 -1
- package/package.json +5 -6
- package/server/src/Casts/MultiPolygon.php +3 -3
- package/server/src/Casts/Point.php +2 -2
- package/server/src/Casts/Polygon.php +3 -3
- package/server/src/Console/Commands/DispatchAdhocOrders.php +1 -1
- package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
- package/server/src/Flow/Activity.php +12 -0
- package/server/src/Flow/Config.php +7 -0
- package/server/src/Flow/Logic.php +7 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +61 -21
- package/server/src/Http/Controllers/Api/v1/OrderController.php +5 -3
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +17 -4
- package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +6 -50
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +13 -2
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +1 -1
- package/server/src/Http/Filter/PlaceFilter.php +1 -1
- package/server/src/Http/Resources/v1/Driver.php +1 -1
- package/server/src/Http/Resources/v1/FuelReport.php +1 -1
- package/server/src/Http/Resources/v1/Issue.php +1 -1
- package/server/src/Http/Resources/v1/Order.php +3 -0
- package/server/src/Http/Resources/v1/Payload.php +1 -0
- package/server/src/Http/Resources/v1/Place.php +1 -1
- 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/Http/Resources/v1/TrackingStatus.php +1 -1
- package/server/src/Http/Resources/v1/Vehicle.php +1 -1
- package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +1 -1
- package/server/src/Http/Resources/v1/Waypoint.php +2 -4
- package/server/src/Integrations/Lalamove/LalamoveDeliveryStop.php +2 -2
- package/server/src/Jobs/SimulateWaypointReached.php +2 -2
- package/server/src/Models/Contact.php +6 -18
- package/server/src/Models/Driver.php +8 -20
- package/server/src/Models/Fleet.php +6 -25
- package/server/src/Models/FuelReport.php +1 -1
- package/server/src/Models/Issue.php +1 -1
- package/server/src/Models/Order.php +31 -19
- package/server/src/Models/Payload.php +7 -7
- package/server/src/Models/Place.php +16 -16
- package/server/src/Models/Position.php +1 -1
- package/server/src/Models/ServiceArea.php +14 -14
- package/server/src/Models/TrackingNumber.php +1 -1
- package/server/src/Models/TrackingStatus.php +1 -1
- package/server/src/Models/Vehicle.php +7 -19
- package/server/src/Models/VehicleDeviceEvent.php +1 -1
- package/server/src/Models/Vendor.php +8 -25
- package/server/src/Models/Waypoint.php +6 -6
- package/server/src/Models/Zone.php +8 -8
- package/server/src/Observers/DriverObserver.php +7 -2
- package/server/src/Observers/TrackingNumberObserver.php +1 -1
- package/server/src/Rules/ResolvablePoint.php +1 -1
- package/server/src/Support/Encoding/Polyline.php +1 -1
- package/server/src/Support/OSRM.php +1 -1
- package/server/src/Support/Utils.php +60 -60
- package/server/src/Traits/HasTrackingNumber.php +3 -3
- package/server/src/routes.php +2 -4
- package/translations/en-us.yaml +10 -2
- package/addon/components/widget/fleet-ops-quickstart.hbs +0 -40
- package/addon/components/widget/fleet-ops-quickstart.js +0 -59
- package/addon/components/widget/issues.hbs +0 -1
- package/addon/components/widget/key-metrics.hbs +0 -121
- package/addon/components/widget/key-metrics.js +0 -46
- package/addon/components/widget/live-order-map.hbs +0 -59
- package/addon/components/widget/live-order-map.js +0 -11
- package/addon/components/widget/recent-orders.hbs +0 -35
- package/addon/components/widget/recent-orders.js +0 -24
- package/addon/components/widget/transaction-earnings.hbs +0 -1
- package/addon/components/widget/transactions.hbs +0 -36
- package/addon/components/widget/transactions.js +0 -29
- package/addon/components/widget/weekly-orders.hbs +0 -1
- package/addon/components/widget/weekly-orders.js +0 -3
- package/app/components/widget/key-metrics.js +0 -1
- package/app/components/widget/live-order-map.js +0 -1
- package/app/components/widget/recent-orders.js +0 -1
- package/app/components/widget/transaction-earnings.js +0 -1
- package/app/components/widget/transactions.js +0 -1
- package/app/components/widget/weekly-orders.js +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { task } from 'ember-concurrency-decorators';
|
|
5
|
+
|
|
6
|
+
export default class WidgetFleetOpsKeyMetricsComponent extends Component {
|
|
7
|
+
/**
|
|
8
|
+
* Inject the fetch service.
|
|
9
|
+
*
|
|
10
|
+
* @memberof WidgetKeyMetricsComponent
|
|
11
|
+
*/
|
|
12
|
+
@service fetch;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Property for loading metrics to.
|
|
16
|
+
*
|
|
17
|
+
* @memberof WidgetKeyMetricsComponent
|
|
18
|
+
*/
|
|
19
|
+
@tracked metrics = {};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates an instance of WidgetKeyMetricsComponent.
|
|
23
|
+
* @memberof WidgetKeyMetricsComponent
|
|
24
|
+
*/
|
|
25
|
+
constructor() {
|
|
26
|
+
super(...arguments);
|
|
27
|
+
this.getDashboardMetrics.perform();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Task which fetches key metrics.
|
|
32
|
+
*
|
|
33
|
+
* @memberof WidgetKeyMetricsComponent
|
|
34
|
+
*/
|
|
35
|
+
@task *getDashboardMetrics() {
|
|
36
|
+
this.metrics = yield this.fetch.get('fleet-ops/metrics').then((response) => {
|
|
37
|
+
return this.createMetricsMapFromResponse(response);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a map of metrics from the response data. This method organizes the metrics data into a more usable format.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} metrics - The metrics object fetched from the server.
|
|
45
|
+
* @returns {Object} A map of metrics where each key is a metric name and its value is an object of metric options.
|
|
46
|
+
* @memberof WidgetKeyMetricsComponent
|
|
47
|
+
*/
|
|
48
|
+
createMetricsMapFromResponse(metrics = {}) {
|
|
49
|
+
const keys = Object.keys(metrics);
|
|
50
|
+
const map = {};
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < keys.length; i++) {
|
|
53
|
+
const key = keys[i];
|
|
54
|
+
map[key] = this.createMetricOptionsHash(key, metrics[key]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return map;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a hash of options for a given metric. Depending on the metric key, it assigns a specific format.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} key - The key representing the specific metric.
|
|
64
|
+
* @param {number} value - The value of the metric.
|
|
65
|
+
* @returns {Object} An object containing the metric value and its format.
|
|
66
|
+
* @memberof WidgetKeyMetricsComponent
|
|
67
|
+
*/
|
|
68
|
+
createMetricOptionsHash(key, value) {
|
|
69
|
+
const options = { value };
|
|
70
|
+
|
|
71
|
+
if (key === 'earnings') {
|
|
72
|
+
options.format = 'money';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (key === 'fuel_costs') {
|
|
76
|
+
options.format = 'money';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (key === 'total_distance_traveled') {
|
|
80
|
+
options.format = 'meters';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return options;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -161,6 +161,28 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
161
161
|
onClick: this.editMetaData,
|
|
162
162
|
},
|
|
163
163
|
];
|
|
164
|
+
@tracked uploadQueue = [];
|
|
165
|
+
acceptedFileTypes = [
|
|
166
|
+
'application/vnd.ms-excel',
|
|
167
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
168
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
169
|
+
'application/msword',
|
|
170
|
+
'application/pdf',
|
|
171
|
+
'application/x-pdf',
|
|
172
|
+
'image/jpeg',
|
|
173
|
+
'image/png',
|
|
174
|
+
'image/gif',
|
|
175
|
+
'image/webp',
|
|
176
|
+
'video/mp4',
|
|
177
|
+
'video/quicktime',
|
|
178
|
+
'video/x-msvideo',
|
|
179
|
+
'video/x-flv',
|
|
180
|
+
'video/x-ms-wmv',
|
|
181
|
+
'audio/mpeg',
|
|
182
|
+
'video/x-msvideo',
|
|
183
|
+
'application/zip',
|
|
184
|
+
'application/x-tar',
|
|
185
|
+
];
|
|
164
186
|
|
|
165
187
|
@not('isServicable') isNotServicable;
|
|
166
188
|
@alias('currentUser.latitude') userLatitude;
|
|
@@ -211,27 +233,6 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
211
233
|
this.payloadCoordinates = coordinates;
|
|
212
234
|
}
|
|
213
235
|
|
|
214
|
-
// @computed('payload.{dropoff,pickup,waypoints}', 'waypoints.[]')
|
|
215
|
-
// get payloadCoordinates() {
|
|
216
|
-
// let waypoints = [];
|
|
217
|
-
// let coordinates = [];
|
|
218
|
-
|
|
219
|
-
// console.log('[this.waypoints]', this.waypoints);
|
|
220
|
-
|
|
221
|
-
// waypoints.pushObjects([this.payload.pickup, ...this.waypoints.map((waypoint) => waypoint.place), this.payload.dropoff]);
|
|
222
|
-
// waypoints.forEach((place) => {
|
|
223
|
-
// if (place && place.get('longitude') && place.get('latitude')) {
|
|
224
|
-
// if (place.hasInvalidCoordinates) {
|
|
225
|
-
// return;
|
|
226
|
-
// }
|
|
227
|
-
|
|
228
|
-
// coordinates.pushObject([place.get('longitude'), place.get('latitude')]);
|
|
229
|
-
// }
|
|
230
|
-
// });
|
|
231
|
-
|
|
232
|
-
// return coordinates;
|
|
233
|
-
// }
|
|
234
|
-
|
|
235
236
|
@computed('payloadCoordinates.length', 'waypoints.[]') get isServicable() {
|
|
236
237
|
return this.payloadCoordinates.length >= 2;
|
|
237
238
|
}
|
|
@@ -597,7 +598,17 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
597
598
|
}
|
|
598
599
|
|
|
599
600
|
_seriailizeModel(model) {
|
|
600
|
-
|
|
601
|
+
if (isModel(model)) {
|
|
602
|
+
if (typeof model.toJSON === 'function') {
|
|
603
|
+
return model.toJSON();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (typeof model.serialize === 'function') {
|
|
607
|
+
return model.serialize();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return model;
|
|
601
612
|
}
|
|
602
613
|
|
|
603
614
|
_serializeArray(array) {
|
|
@@ -1441,4 +1452,37 @@ export default class OperationsOrdersIndexNewController extends BaseController {
|
|
|
1441
1452
|
}
|
|
1442
1453
|
}
|
|
1443
1454
|
}
|
|
1455
|
+
|
|
1456
|
+
@action queueFile(file) {
|
|
1457
|
+
// since we have dropzone and upload button within dropzone validate the file state first
|
|
1458
|
+
// as this method can be called twice from both functions
|
|
1459
|
+
if (['queued', 'failed', 'timed_out', 'aborted'].indexOf(file.state) === -1) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Queue and upload immediatley
|
|
1464
|
+
this.uploadQueue.pushObject(file);
|
|
1465
|
+
this.fetch.uploadFile.perform(
|
|
1466
|
+
file,
|
|
1467
|
+
{
|
|
1468
|
+
path: 'uploads/fleet-ops/order-files',
|
|
1469
|
+
type: 'order_file',
|
|
1470
|
+
},
|
|
1471
|
+
(uploadedFile) => {
|
|
1472
|
+
this.order.files.pushObject(uploadedFile);
|
|
1473
|
+
this.uploadQueue.removeObject(file);
|
|
1474
|
+
},
|
|
1475
|
+
() => {
|
|
1476
|
+
this.uploadQueue.removeObject(file);
|
|
1477
|
+
// remove file from queue
|
|
1478
|
+
if (file.queue && typeof file.queue.remove === 'function') {
|
|
1479
|
+
file.queue.remove(file);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
@action removeFile(file) {
|
|
1486
|
+
return file.destroyRecord();
|
|
1487
|
+
}
|
|
1444
1488
|
}
|
|
@@ -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
|
}
|
|
@@ -949,7 +949,7 @@ export default class OperationsOrdersIndexController extends BaseController {
|
|
|
949
949
|
* @memberof OperationsOrdersIndexController
|
|
950
950
|
*/
|
|
951
951
|
@action fetchActiveOrdersCount() {
|
|
952
|
-
this.fetch.get('fleet-ops/metrics
|
|
952
|
+
this.fetch.get('fleet-ops/metrics', { discover: ['orders_in_progress'] }).then((response) => {
|
|
953
953
|
this.activeOrdersCount = response.orders_in_progress;
|
|
954
954
|
});
|
|
955
955
|
}
|
package/addon/engine.js
CHANGED
|
@@ -5,6 +5,7 @@ import config from './config/environment';
|
|
|
5
5
|
import services from '@fleetbase/ember-core/exports/services';
|
|
6
6
|
import AdminVisibilityControlsComponent from './components/admin/visibility-controls';
|
|
7
7
|
import NavigatorAppConfigComponent from './components/admin/navigator-app';
|
|
8
|
+
import FleetOpsKeyMetricsWidget from './components/widget/fleet-ops-key-metrics';
|
|
8
9
|
|
|
9
10
|
const { modulePrefix } = config;
|
|
10
11
|
const externalRoutes = ['console', 'extensions'];
|
|
@@ -40,20 +41,25 @@ export default class FleetOpsEngine extends Engine {
|
|
|
40
41
|
}
|
|
41
42
|
);
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
-
|
|
44
|
+
// widgets for registry
|
|
45
|
+
const KeyMetricsWidgetDefinition = {
|
|
46
|
+
did: 'fleetops-metrics',
|
|
47
|
+
name: 'Fleet-Ops Metrics',
|
|
48
|
+
description: 'Key metrics from Fleet-Ops.',
|
|
49
|
+
icon: 'truck',
|
|
50
|
+
component: FleetOpsKeyMetricsWidget,
|
|
51
|
+
grid_options: { w: 12, h: 12, minW: 8, minH: 12 },
|
|
52
|
+
options: {
|
|
53
|
+
title: 'Fleet-Ops Metrics',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
45
56
|
|
|
46
|
-
// register
|
|
47
|
-
universe.
|
|
57
|
+
// register widgets
|
|
58
|
+
universe.registerDefaultDashboardWidgets([KeyMetricsWidgetDefinition]);
|
|
59
|
+
universe.registerDashboardWidgets([KeyMetricsWidgetDefinition]);
|
|
48
60
|
|
|
49
|
-
//
|
|
50
|
-
universe.
|
|
51
|
-
|
|
52
|
-
// register vehicle context menu
|
|
53
|
-
universe.createRegistry('contextmenu:vehicle');
|
|
54
|
-
|
|
55
|
-
// register driver context menu
|
|
56
|
-
universe.createRegistry('contextmenu:driver');
|
|
61
|
+
// create all registries necessary
|
|
62
|
+
universe.createRegistries(['engine:fleet-ops', 'component:vehicle-panel', 'component:driver-panel', 'contextmenu:vehicle', 'contextmenu:driver']);
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -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
|
/**
|
|
@@ -51,12 +51,12 @@ body[data-theme='dark'] .scheduler-left-sidebar {
|
|
|
51
51
|
--fleet-summary-nav-item-base-padding: 1.5rem;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-toggle,
|
|
54
|
+
.next-sidebar .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-toggle,
|
|
55
55
|
.next-sidebar .next-sidebar-panel-container.next-fleet-summary > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-toggle {
|
|
56
56
|
padding-left: 2rem;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
.next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-content-panel-body .next-nav-item,
|
|
59
|
+
.next-sidebar .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-content-panel-body .next-nav-item,
|
|
60
60
|
.next-sidebar .next-sidebar-panel-container.next-driver-listing > .next-sidebar-panel > .next-content-panel > .next-sidebar-panel-body .next-nav-item.driver-nav-item {
|
|
61
61
|
padding-left: 3.5rem;
|
|
62
62
|
}
|
|
@@ -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
|
+
}
|