@fleetbase/fleetops-engine 0.6.49 → 0.6.50
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/create-order-form.hbs +2 -2
- package/addon/components/customer/form.hbs +2 -2
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +2 -2
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +184 -20
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +45 -3
- package/addon/components/modals/update-order-activity.js +143 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +11 -0
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -17
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +41 -0
- package/addon/components/service-rate/form.hbs +118 -0
- package/addon/components/service-rate/form.js +55 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/operations/orders/index/details.js +4 -4
- package/addon/controllers/operations/orders/index.js +14 -0
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +91 -1
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +429 -26
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Models/Contact.php +92 -15
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Order.php +29 -4
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +1 -1
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +43 -9
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/en-us.yaml +1 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -41,23 +41,44 @@ export default class OrderDetailsRouteComponent extends Component {
|
|
|
41
41
|
isLoading: this.optimizeRoute.isRunning,
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
icon: 'ellipsis-h',
|
|
45
|
+
prefix: 'fas',
|
|
46
|
+
triggerClass: 'fleetops-btn-xxs',
|
|
47
|
+
items: [
|
|
48
|
+
{
|
|
49
|
+
text: 'Edit route',
|
|
50
|
+
icon: 'pencil',
|
|
51
|
+
disabled: !this.args.resource.hasActiveStatus,
|
|
52
|
+
onClick: () => {
|
|
53
|
+
this.orderActions.editRoute(this.args.resource);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
this.isMultipleWaypointOrder
|
|
57
|
+
? {
|
|
58
|
+
text: 'Change destination',
|
|
59
|
+
icon: 'location-dot',
|
|
60
|
+
disabled: !this.args.resource.hasActiveStatus,
|
|
61
|
+
onClick: () => {
|
|
62
|
+
this.changeDestination();
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
: null,
|
|
66
|
+
].filter(Boolean),
|
|
53
67
|
},
|
|
54
|
-
];
|
|
68
|
+
].filter(Boolean);
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
get canOptimizeRoute() {
|
|
58
72
|
return canOptimizeIntermediateWaypoints(this.args.resource?.payload);
|
|
59
73
|
}
|
|
60
74
|
|
|
75
|
+
get isMultipleWaypointOrder() {
|
|
76
|
+
const waypoints = this.args.resource?.payload?.waypoints;
|
|
77
|
+
const waypointCount = typeof waypoints?.toArray === 'function' ? waypoints.toArray().length : Array.isArray(waypoints) ? waypoints.length : (waypoints?.length ?? 0);
|
|
78
|
+
|
|
79
|
+
return Boolean(this.args.resource?.hasIntermediateWaypoints || waypointCount > 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
get showOptimizationSelect() {
|
|
62
83
|
return this.routeOptimization.availableEngines.length > 1;
|
|
63
84
|
}
|
|
@@ -66,6 +87,17 @@ export default class OrderDetailsRouteComponent extends Component {
|
|
|
66
87
|
return this.args.resource?.tracker_data;
|
|
67
88
|
}
|
|
68
89
|
|
|
90
|
+
get showRouteEtaData() {
|
|
91
|
+
if (this.trackerData?.lifecycle?.show_live_eta !== undefined) {
|
|
92
|
+
return Boolean(this.trackerData.lifecycle.show_live_eta);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const status = String(this.args.resource?.status ?? 'created').toLowerCase();
|
|
96
|
+
const hasStarted = Boolean(this.args.resource?.started ?? this.args.resource?.started_at ?? status === 'started');
|
|
97
|
+
|
|
98
|
+
return hasStarted && !['completed', 'canceled'].includes(status);
|
|
99
|
+
}
|
|
100
|
+
|
|
69
101
|
get hasTrackingRouteSummary() {
|
|
70
102
|
return Boolean(this.trackerData?.route || this.trackerData?.eta);
|
|
71
103
|
}
|
|
@@ -75,7 +107,7 @@ export default class OrderDetailsRouteComponent extends Component {
|
|
|
75
107
|
}
|
|
76
108
|
|
|
77
109
|
get hasCompletionEta() {
|
|
78
|
-
return Boolean(this.trackerData?.eta?.completion_at);
|
|
110
|
+
return this.showRouteEtaData && Boolean(this.trackerData?.eta?.completion_at);
|
|
79
111
|
}
|
|
80
112
|
|
|
81
113
|
get routeStopsCount() {
|
|
@@ -83,10 +115,14 @@ export default class OrderDetailsRouteComponent extends Component {
|
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
get hasTrackingDuration() {
|
|
86
|
-
return this.trackerData?.route?.duration_in_traffic_s !== null && this.trackerData?.route?.duration_in_traffic_s !== undefined;
|
|
118
|
+
return this.showRouteEtaData && this.trackerData?.route?.duration_in_traffic_s !== null && this.trackerData?.route?.duration_in_traffic_s !== undefined;
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
get trackingDurationSeconds() {
|
|
122
|
+
if (!this.showRouteEtaData) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
90
126
|
return this.trackerData?.route?.duration_in_traffic_s ?? this.trackerData?.route?.duration_s;
|
|
91
127
|
}
|
|
92
128
|
|
|
@@ -150,7 +186,48 @@ export default class OrderDetailsRouteComponent extends Component {
|
|
|
150
186
|
yield this.hostRouter.refresh();
|
|
151
187
|
}
|
|
152
188
|
|
|
153
|
-
@action
|
|
189
|
+
@action changeDestination() {
|
|
190
|
+
this.modalsManager.show('modals/order-change-destination', {
|
|
191
|
+
title: 'Change destination',
|
|
192
|
+
order: this.args.resource,
|
|
193
|
+
acceptButtonText: 'Change destination',
|
|
194
|
+
declineButtonText: 'Close',
|
|
195
|
+
onChange: async () => {
|
|
196
|
+
if (typeof this.args.resource?.reload === 'function') {
|
|
197
|
+
await this.args.resource.reload();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof this.args.resource?.set === 'function') {
|
|
201
|
+
this.args.resource.set('tracker_data', null);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (typeof this.args.resource?.loadTrackerData === 'function') {
|
|
205
|
+
await this.args.resource.loadTrackerData();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await this.hostRouter.refresh();
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@action viewStopActivity(stop) {
|
|
214
|
+
this.modalsManager.show('modals/stop-activity', {
|
|
215
|
+
title: 'Stop activity',
|
|
216
|
+
stop,
|
|
217
|
+
order: this.args.resource,
|
|
218
|
+
hideAcceptButton: true,
|
|
219
|
+
declineButtonText: 'Close',
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@action async viewWaypointLabel(stop) {
|
|
224
|
+
const waypoint = stop?.place ?? stop;
|
|
225
|
+
const waypointId = waypoint?.waypoint_public_id ?? stop?.waypoint_public_id;
|
|
226
|
+
if (!waypointId) {
|
|
227
|
+
this.notifications.warning('Labels are only available for waypoint stops.');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
154
231
|
this.modalsManager.show(`modals/order-label`, {
|
|
155
232
|
title: 'Waypoint Label',
|
|
156
233
|
modalClass: 'modal-xl',
|
|
@@ -161,7 +238,7 @@ export default class OrderDetailsRouteComponent extends Component {
|
|
|
161
238
|
try {
|
|
162
239
|
// eslint-disable-next-line no-undef
|
|
163
240
|
const fileReader = new FileReader();
|
|
164
|
-
const { data: pdfStream } = await this.fetch.get(`orders/label/${
|
|
241
|
+
const { data: pdfStream } = await this.fetch.get(`orders/label/${waypointId}?format=base64`);
|
|
165
242
|
// eslint-disable-next-line no-undef
|
|
166
243
|
const base64 = await fetch(`data:application/pdf;base64,${pdfStream}`);
|
|
167
244
|
const blob = await base64.blob();
|
|
@@ -45,93 +45,122 @@
|
|
|
45
45
|
</div>
|
|
46
46
|
{{/if}}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
<div class="tracking-intelligence-
|
|
50
|
-
<div class="tracking-intelligence-
|
|
51
|
-
|
|
52
|
-
<
|
|
48
|
+
{{#if this.lifecycleMessage}}
|
|
49
|
+
<div class="tracking-intelligence-alert">
|
|
50
|
+
<div class="tracking-intelligence-alert__icon"><FaIcon @icon={{this.lifecycleMessageIcon}} @size="xs" /></div>
|
|
51
|
+
<div class="min-w-0">
|
|
52
|
+
<div class="tracking-intelligence-alert__title">{{this.lifecycleMessageTitle}}</div>
|
|
53
|
+
<div class="tracking-intelligence-alert__body">{{this.lifecycleMessage}}</div>
|
|
53
54
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
</div>
|
|
56
|
+
{{/if}}
|
|
57
|
+
|
|
58
|
+
{{#if this.showStartEta}}
|
|
59
|
+
<div class="tracking-intelligence__eta-grid">
|
|
60
|
+
<div class="tracking-intelligence-cell tracking-intelligence-cell--accent">
|
|
61
|
+
<div class="tracking-intelligence-cell__label">
|
|
62
|
+
Estimated start
|
|
63
|
+
<span class="tracking-intelligence-tag">Pre-start</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="tracking-intelligence-cell__value">
|
|
66
|
+
{{#if this.hasStartEta}}
|
|
67
|
+
{{format-duration this.startEtaSeconds}}
|
|
68
|
+
{{else}}
|
|
69
|
+
Pending start
|
|
70
|
+
{{/if}}
|
|
71
|
+
</div>
|
|
72
|
+
<div class="tracking-intelligence-cell__sub">Based on the assigned driver route to the first stop</div>
|
|
60
73
|
</div>
|
|
61
|
-
<div class="tracking-intelligence-cell__sub">Based on provider route and driver signal</div>
|
|
62
74
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<div class="tracking-intelligence-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
{{else if this.showLiveEta}}
|
|
76
|
+
<div class="tracking-intelligence__eta-grid">
|
|
77
|
+
<div class="tracking-intelligence-cell tracking-intelligence-cell--accent">
|
|
78
|
+
<div class="tracking-intelligence-cell__label">
|
|
79
|
+
Smart adjusted ETA
|
|
80
|
+
<span class="tracking-intelligence-tag">Live</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="tracking-intelligence-cell__value">
|
|
83
|
+
{{#if this.hasSmartAdjustedEta}}
|
|
84
|
+
{{format-duration this.smartAdjustedEtaSeconds}}
|
|
85
|
+
{{else}}
|
|
86
|
+
{{this.smartAdjustedEtaUnavailableLabel}}
|
|
87
|
+
{{/if}}
|
|
88
|
+
</div>
|
|
89
|
+
<div class="tracking-intelligence-cell__sub">Based on provider route and driver signal</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="tracking-intelligence-cell">
|
|
92
|
+
<div class="tracking-intelligence-cell__label">Reported ETA</div>
|
|
93
|
+
<div class="tracking-intelligence-cell__value {{if this.isReportedEtaUntrusted 'tracking-intelligence-cell__value--muted'}}">
|
|
94
|
+
{{#if this.hasDisplayedReportedEta}}
|
|
95
|
+
{{format-duration this.displayedReportedEtaSeconds}}
|
|
96
|
+
{{else}}
|
|
97
|
+
{{this.etaUnavailableLabel}}
|
|
98
|
+
{{/if}}
|
|
99
|
+
</div>
|
|
100
|
+
<div class="tracking-intelligence-cell__sub {{if this.isReportedEtaUntrusted 'tracking-intelligence-cell__sub--warn'}}">{{this.reportedEtaWarning}}</div>
|
|
71
101
|
</div>
|
|
72
|
-
<div class="tracking-intelligence-cell__sub {{if this.isReportedEtaUntrusted 'tracking-intelligence-cell__sub--warn'}}">{{this.reportedEtaWarning}}</div>
|
|
73
102
|
</div>
|
|
74
|
-
</div>
|
|
75
103
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
<div class="tracking-intelligence-confidence">
|
|
105
|
+
<div class="tracking-intelligence-confidence__label">ETA confidence</div>
|
|
106
|
+
<div class="tracking-intelligence-confidence__segments {{this.confidenceToneClass}}">
|
|
107
|
+
{{#each this.confidenceSegments as |segment|}}
|
|
108
|
+
<span class="{{if segment.lit 'is-lit'}}"></span>
|
|
109
|
+
{{/each}}
|
|
110
|
+
</div>
|
|
111
|
+
<div class="tracking-intelligence-confidence__value">{{this.confidencePercent}}%</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="tracking-intelligence-destination">
|
|
115
|
+
<div class="tracking-intelligence-destination__marker">{{this.activeStopMarkerLabel}}</div>
|
|
116
|
+
<div class="min-w-0 flex-1">
|
|
117
|
+
<div class="tracking-intelligence-destination__label">{{this.activeStopLabel}}</div>
|
|
118
|
+
<div class="tracking-intelligence-destination__address">{{n-a @resource.tracker_data.active_stop.address}}</div>
|
|
119
|
+
<div class="tracking-intelligence-destination__eta">
|
|
120
|
+
ETA:
|
|
121
|
+
<strong>
|
|
122
|
+
{{#if this.hasActiveEta}}
|
|
123
|
+
{{format-duration this.activeEtaSeconds}}
|
|
124
|
+
{{else}}
|
|
125
|
+
{{this.etaUnavailableLabel}}
|
|
126
|
+
{{/if}}
|
|
127
|
+
</strong>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
82
130
|
</div>
|
|
83
|
-
<div class="tracking-intelligence-confidence__value">{{this.confidencePercent}}%</div>
|
|
84
|
-
</div>
|
|
85
131
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<div class="tracking-intelligence-destination__label">{{this.activeStopLabel}}</div>
|
|
90
|
-
<div class="tracking-intelligence-destination__address">{{n-a @resource.tracker_data.active_stop.address}}</div>
|
|
91
|
-
<div class="tracking-intelligence-destination__eta">
|
|
92
|
-
ETA:
|
|
132
|
+
<div class="tracking-intelligence-distance">
|
|
133
|
+
<div class="tracking-intelligence-distance__row">
|
|
134
|
+
<span>Reported total</span>
|
|
93
135
|
<strong>
|
|
94
|
-
{{#if this.
|
|
95
|
-
{{format-
|
|
136
|
+
{{#if this.hasRemainingDistance}}
|
|
137
|
+
{{format-meters @resource.tracker_data.route.distance_m}}
|
|
96
138
|
{{else}}
|
|
97
|
-
|
|
139
|
+
-
|
|
98
140
|
{{/if}}
|
|
99
141
|
</strong>
|
|
142
|
+
<div class="tracking-intelligence-distance__bar">
|
|
143
|
+
<span style={{this.totalProgressStyle}}></span>
|
|
144
|
+
</div>
|
|
100
145
|
</div>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
</strong>
|
|
114
|
-
<div class="tracking-intelligence-distance__bar">
|
|
115
|
-
<span style={{this.totalProgressStyle}}></span>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
<div class="tracking-intelligence-distance__row">
|
|
119
|
-
<span>Between stops</span>
|
|
120
|
-
<strong>
|
|
121
|
-
{{#if this.hasCurrentLegDistance}}
|
|
122
|
-
{{format-meters this.currentLeg.distance_m}}
|
|
123
|
-
{{else}}
|
|
124
|
-
-
|
|
125
|
-
{{/if}}
|
|
126
|
-
</strong>
|
|
127
|
-
<div class="tracking-intelligence-distance__bar tracking-intelligence-distance__bar--secondary">
|
|
128
|
-
<span style={{this.currentLegProgressStyle}}></span>
|
|
146
|
+
<div class="tracking-intelligence-distance__row">
|
|
147
|
+
<span>Between stops</span>
|
|
148
|
+
<strong>
|
|
149
|
+
{{#if this.hasCurrentLegDistance}}
|
|
150
|
+
{{format-meters this.currentLeg.distance_m}}
|
|
151
|
+
{{else}}
|
|
152
|
+
-
|
|
153
|
+
{{/if}}
|
|
154
|
+
</strong>
|
|
155
|
+
<div class="tracking-intelligence-distance__bar tracking-intelligence-distance__bar--secondary">
|
|
156
|
+
<span style={{this.currentLegProgressStyle}}></span>
|
|
157
|
+
</div>
|
|
129
158
|
</div>
|
|
159
|
+
<div class="tracking-intelligence-distance__foot">Provider context:
|
|
160
|
+
{{this.providerLabel}}
|
|
161
|
+
route</div>
|
|
130
162
|
</div>
|
|
131
|
-
|
|
132
|
-
{{this.providerLabel}}
|
|
133
|
-
route</div>
|
|
134
|
-
</div>
|
|
163
|
+
{{/if}}
|
|
135
164
|
|
|
136
165
|
<details class="tracking-intelligence-diagnostics">
|
|
137
166
|
<summary>
|
|
@@ -34,15 +34,150 @@ export default class OrderDetailsTrackingComponent extends Component {
|
|
|
34
34
|
return Boolean(this.trackerData);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
get lifecycle() {
|
|
38
|
+
return this.trackerData?.lifecycle ?? {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get lifecycleMode() {
|
|
42
|
+
if (this.lifecycle.mode) {
|
|
43
|
+
return this.lifecycle.mode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const status = this.statusCode;
|
|
47
|
+
|
|
48
|
+
if (['completed', 'canceled'].includes(status)) {
|
|
49
|
+
return status;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!this.hasAssignedDriver) {
|
|
53
|
+
return 'unassigned';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!this.hasOrderStarted && status === 'dispatched') {
|
|
57
|
+
return 'dispatched';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!this.hasOrderStarted) {
|
|
61
|
+
return 'pre_start';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return 'active';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get statusCode() {
|
|
68
|
+
return String(this.lifecycle.status ?? this.args.resource?.status ?? 'created').toLowerCase();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get hasOrderStarted() {
|
|
72
|
+
return Boolean(this.lifecycle.has_started ?? this.args.resource?.started ?? this.args.resource?.started_at ?? this.statusCode === 'started');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get showLiveEta() {
|
|
76
|
+
if (this.lifecycle.show_live_eta !== undefined) {
|
|
77
|
+
return Boolean(this.lifecycle.show_live_eta);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return this.hasOrderStarted && !this.isTerminalLifecycle;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get showStartEta() {
|
|
84
|
+
if (this.lifecycle.show_start_eta !== undefined) {
|
|
85
|
+
return Boolean(this.lifecycle.show_start_eta);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return this.lifecycleMode === 'dispatched' && this.hasStartEta;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get isTerminalLifecycle() {
|
|
92
|
+
return Boolean(this.lifecycle.is_terminal ?? ['completed', 'canceled'].includes(this.statusCode));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get isPreStartLifecycle() {
|
|
96
|
+
return ['created', 'pre_start'].includes(this.lifecycleMode);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get isDispatchedLifecycle() {
|
|
100
|
+
return this.lifecycleMode === 'dispatched';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get lifecycleMessage() {
|
|
104
|
+
if (this.lifecycleMode === 'unassigned') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return this.lifecycle.message ?? this.defaultLifecycleMessage;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get defaultLifecycleMessage() {
|
|
112
|
+
if (this.statusCode === 'completed') {
|
|
113
|
+
return 'Order has been completed.';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (this.statusCode === 'canceled') {
|
|
117
|
+
return 'Order has been canceled.';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.isDispatchedLifecycle) {
|
|
121
|
+
return 'Order has been dispatched. Estimated start is based on the assigned driver route to the first stop.';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this.isPreStartLifecycle) {
|
|
125
|
+
return 'Live ETA will begin once the order is started.';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get lifecycleMessageTitle() {
|
|
132
|
+
switch (this.lifecycleMode) {
|
|
133
|
+
case 'completed':
|
|
134
|
+
return 'Order completed';
|
|
135
|
+
case 'canceled':
|
|
136
|
+
return 'Order canceled';
|
|
137
|
+
case 'dispatched':
|
|
138
|
+
return 'Order dispatched';
|
|
139
|
+
case 'pre_start':
|
|
140
|
+
case 'created':
|
|
141
|
+
return 'Tracking pending start';
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get lifecycleMessageIcon() {
|
|
148
|
+
switch (this.lifecycleMode) {
|
|
149
|
+
case 'completed':
|
|
150
|
+
return 'circle-check';
|
|
151
|
+
case 'canceled':
|
|
152
|
+
return 'ban';
|
|
153
|
+
case 'dispatched':
|
|
154
|
+
return 'route';
|
|
155
|
+
default:
|
|
156
|
+
return 'clock';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get startEtaSeconds() {
|
|
161
|
+
return this.trackerData?.eta?.start_seconds;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get hasStartEta() {
|
|
165
|
+
return this.startEtaSeconds !== null && this.startEtaSeconds !== undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
37
168
|
get activeEtaSeconds() {
|
|
38
169
|
return this.trackerData?.eta?.active_stop_seconds;
|
|
39
170
|
}
|
|
40
171
|
|
|
41
172
|
get hasActiveEta() {
|
|
42
|
-
return this.activeEtaSeconds !== null && this.activeEtaSeconds !== undefined;
|
|
173
|
+
return this.showLiveEta && this.activeEtaSeconds !== null && this.activeEtaSeconds !== undefined;
|
|
43
174
|
}
|
|
44
175
|
|
|
45
176
|
get smartAdjustedEtaSeconds() {
|
|
177
|
+
if (!this.showLiveEta) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
46
181
|
return (
|
|
47
182
|
this.firstPositiveNumber(this.activeEtaSeconds) ??
|
|
48
183
|
this.firstPositiveNumber(this.trackerData?.route?.duration_in_traffic_s) ??
|
|
@@ -69,7 +204,7 @@ export default class OrderDetailsTrackingComponent extends Component {
|
|
|
69
204
|
}
|
|
70
205
|
|
|
71
206
|
get hasCompletionEta() {
|
|
72
|
-
return Boolean(this.trackerData?.eta?.completion_at);
|
|
207
|
+
return this.showLiveEta && Boolean(this.trackerData?.eta?.completion_at);
|
|
73
208
|
}
|
|
74
209
|
|
|
75
210
|
get hasRemainingDistance() {
|
|
@@ -249,6 +384,10 @@ export default class OrderDetailsTrackingComponent extends Component {
|
|
|
249
384
|
}
|
|
250
385
|
|
|
251
386
|
get displayedReportedEtaSeconds() {
|
|
387
|
+
if (!this.showLiveEta) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
252
391
|
return (
|
|
253
392
|
this.firstPositiveNumber(this.reportedEtaSeconds) ??
|
|
254
393
|
this.firstPositiveNumber(this.activeEtaSeconds) ??
|
|
@@ -269,6 +408,10 @@ export default class OrderDetailsTrackingComponent extends Component {
|
|
|
269
408
|
return false;
|
|
270
409
|
}
|
|
271
410
|
|
|
411
|
+
if (!this.showLiveEta) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
|
|
272
415
|
return !trackerData?.driver?.location || trackerData?.insights?.is_location_stale || trackerData.fallback_provider || (trackerData.confidence && trackerData.confidence !== 'high');
|
|
273
416
|
}
|
|
274
417
|
|
|
@@ -402,6 +545,10 @@ export default class OrderDetailsTrackingComponent extends Component {
|
|
|
402
545
|
return null;
|
|
403
546
|
}
|
|
404
547
|
|
|
548
|
+
if (this.isTerminalLifecycle || this.isPreStartLifecycle || this.isDispatchedLifecycle) {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
|
|
405
552
|
if (!this.hasAssignedDriver) {
|
|
406
553
|
return 'Assign a driver to start live tracking and improve ETA accuracy.';
|
|
407
554
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<ContentPanel @title="Details" @open={{true}} @wrapperClass="bordered-top">
|
|
2
2
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
|
|
3
|
-
<InputGroup @name={{t "order.fields.order-type"}} @helpText={{t "order.fields.order-type-help-text"}}>
|
|
3
|
+
<InputGroup @name={{t "order.fields.order-type"}} @helpText={{t "order.fields.order-type-help-text"}} @required={{true}}>
|
|
4
4
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
5
5
|
<PowerSelect
|
|
6
6
|
@options={{this.orderConfigActions.allOrderConfigs}}
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
</InputGroup>
|
|
161
161
|
|
|
162
162
|
{{#if (and @resource.pod_required (not @resource.facilitator.isIntegratedVendor))}}
|
|
163
|
-
<InputGroup @name={{t "order.fields.proof-delivery"}} @helpText={{t "order.fields.proof-delivery-help-text"}}>
|
|
163
|
+
<InputGroup @name={{t "order.fields.proof-delivery"}} @helpText={{t "order.fields.proof-delivery-help-text"}} @required={{true}}>
|
|
164
164
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
165
165
|
<PowerSelect
|
|
166
166
|
@options={{get-fleet-ops-options "podOptions"}}
|
|
@@ -279,4 +279,4 @@
|
|
|
279
279
|
</div>
|
|
280
280
|
</ContentPanel>
|
|
281
281
|
|
|
282
|
-
<Order::Form::CustomFields @resource={{@resource}} @customFields={{this.customFields}} />
|
|
282
|
+
<Order::Form::CustomFields @resource={{@resource}} @customFields={{this.customFields}} />
|
|
@@ -35,12 +35,19 @@ export default class OrderFormDocumentsComponent extends Component {
|
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
this.uploadQueue.pushObject(file);
|
|
38
|
+
const uploadOptions = {
|
|
39
|
+
path: 'uploads/fleet-ops/order-files',
|
|
40
|
+
type: 'order_file',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (!this.args.resource.isNew) {
|
|
44
|
+
uploadOptions.subject_uuid = this.args.resource.id;
|
|
45
|
+
uploadOptions.subject_type = 'fleet-ops:order';
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
yield this.fetch.uploadFile.perform(
|
|
39
49
|
file,
|
|
40
|
-
|
|
41
|
-
path: 'uploads/fleet-ops/order-files',
|
|
42
|
-
type: 'order_file',
|
|
43
|
-
},
|
|
50
|
+
uploadOptions,
|
|
44
51
|
(uploadedFile) => {
|
|
45
52
|
this.args.resource.files.pushObject(uploadedFile);
|
|
46
53
|
this.uploadQueue.removeObject(file);
|