@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
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
11
11
|
<div class="mb-2 flex flex-wrap items-center gap-1.5">
|
|
12
|
-
{{#if this.firstStop.
|
|
13
|
-
<Badge @status={{this.firstStop.
|
|
12
|
+
{{#if this.firstStop.statusCode}}
|
|
13
|
+
<Badge @status={{this.firstStop.statusCode}} />
|
|
14
14
|
{{/if}}
|
|
15
|
-
{{#if this.firstStop.
|
|
15
|
+
{{#if this.firstStop.showCompletedFlagBadge}}
|
|
16
16
|
<Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
|
|
17
17
|
{{else if this.firstStop.active}}
|
|
18
18
|
<Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns this.firstStop.etaAt "d MMM HH:mm"}}</Badge>
|
|
25
25
|
{{/if}}
|
|
26
26
|
</div>
|
|
27
|
-
<DisplayPlace @place={{this.firstStop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
|
|
27
|
+
<DisplayPlace @place={{this.firstStop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{this.firstStop}} @hideBadges={{true}} />
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
{{/if}}
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
</div>
|
|
48
48
|
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
49
49
|
<div class="mb-2 flex flex-wrap items-center gap-1.5">
|
|
50
|
-
{{#if stop.
|
|
51
|
-
<Badge @status={{stop.
|
|
50
|
+
{{#if stop.statusCode}}
|
|
51
|
+
<Badge @status={{stop.statusCode}} />
|
|
52
52
|
{{/if}}
|
|
53
|
-
{{#if stop.
|
|
53
|
+
{{#if stop.showCompletedFlagBadge}}
|
|
54
54
|
<Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
|
|
55
55
|
{{else if stop.active}}
|
|
56
56
|
<Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns stop.etaAt "d MMM HH:mm"}}</Badge>
|
|
63
63
|
{{/if}}
|
|
64
64
|
</div>
|
|
65
|
-
<DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
|
|
65
|
+
<DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{stop}} @hideBadges={{true}} />
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
{{/each}}
|
|
@@ -75,10 +75,10 @@
|
|
|
75
75
|
</div>
|
|
76
76
|
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
77
77
|
<div class="mb-2 flex flex-wrap items-center gap-1.5">
|
|
78
|
-
{{#if stop.
|
|
79
|
-
<Badge @status={{stop.
|
|
78
|
+
{{#if stop.statusCode}}
|
|
79
|
+
<Badge @status={{stop.statusCode}} />
|
|
80
80
|
{{/if}}
|
|
81
|
-
{{#if stop.
|
|
81
|
+
{{#if stop.showCompletedFlagBadge}}
|
|
82
82
|
<Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
|
|
83
83
|
{{else if stop.active}}
|
|
84
84
|
<Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
<Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns stop.etaAt "d MMM HH:mm"}}</Badge>
|
|
91
91
|
{{/if}}
|
|
92
92
|
</div>
|
|
93
|
-
<DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
|
|
93
|
+
<DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{stop}} @hideBadges={{true}} />
|
|
94
94
|
</div>
|
|
95
95
|
</div>
|
|
96
96
|
{{/each}}
|
|
@@ -106,10 +106,10 @@
|
|
|
106
106
|
</div>
|
|
107
107
|
<div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
|
|
108
108
|
<div class="mb-2 flex flex-wrap items-center gap-1.5">
|
|
109
|
-
{{#if this.lastStop.
|
|
110
|
-
<Badge @status={{this.lastStop.
|
|
109
|
+
{{#if this.lastStop.statusCode}}
|
|
110
|
+
<Badge @status={{this.lastStop.statusCode}} />
|
|
111
111
|
{{/if}}
|
|
112
|
-
{{#if this.lastStop.
|
|
112
|
+
{{#if this.lastStop.showCompletedFlagBadge}}
|
|
113
113
|
<Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
|
|
114
114
|
{{else if this.lastStop.active}}
|
|
115
115
|
<Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
|
|
@@ -121,8 +121,8 @@
|
|
|
121
121
|
<Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns this.lastStop.etaAt "d MMM HH:mm"}}</Badge>
|
|
122
122
|
{{/if}}
|
|
123
123
|
</div>
|
|
124
|
-
<DisplayPlace @place={{this.lastStop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
|
|
124
|
+
<DisplayPlace @place={{this.lastStop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{this.lastStop}} @hideBadges={{true}} />
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
127
127
|
{{/if}}
|
|
128
|
-
</div>
|
|
128
|
+
</div>
|
|
@@ -17,20 +17,29 @@ export default class RouteListComponent extends Component {
|
|
|
17
17
|
const routePoints = buildRoutePointsFromPayload(this.args.order?.payload);
|
|
18
18
|
|
|
19
19
|
return routePoints
|
|
20
|
-
.map((routePoint) =>
|
|
21
|
-
routePoint
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
.map((routePoint) => {
|
|
21
|
+
const trackingStop = this.trackingStopFor(routePoint.place);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
routePoint,
|
|
25
|
+
place: routePoint.place,
|
|
26
|
+
badgeStyle: this.badgeStyleForStop(routePoint),
|
|
27
|
+
trackingStop,
|
|
28
|
+
routeLeg: this.routeLegFor(routePoint.place),
|
|
29
|
+
trackingNumberUuid: routePoint.trackingNumberUuid ?? trackingStop?.tracking_number_uuid,
|
|
30
|
+
...describeRoutePoint(routePoint, this.routeColor),
|
|
31
|
+
};
|
|
32
|
+
})
|
|
28
33
|
.map((stop) => ({
|
|
29
34
|
...stop,
|
|
30
|
-
etaSeconds:
|
|
31
|
-
|
|
35
|
+
etaSeconds: this.shouldShowEtaForStop(stop)
|
|
36
|
+
? (stop.routeLeg?.eta_seconds ?? stop.routeLeg?.duration_in_traffic_s ?? stop.routeLeg?.duration_s ?? this.legacyEtaFor(stop.place))
|
|
37
|
+
: null,
|
|
38
|
+
etaAt: this.shouldShowEtaForStop(stop) ? stop.routeLeg?.eta_at : null,
|
|
32
39
|
completed: Boolean(stop.trackingStop?.completed),
|
|
33
40
|
active: this.matchesStop(stop.trackingStop, this.args.order?.tracker_data?.active_stop),
|
|
41
|
+
statusCode: stop.place?.status_code ?? stop.trackingStop?.status,
|
|
42
|
+
showCompletedFlagBadge: this.shouldShowCompletedFlagBadge(stop),
|
|
34
43
|
}));
|
|
35
44
|
}
|
|
36
45
|
|
|
@@ -54,6 +63,10 @@ export default class RouteListComponent extends Component {
|
|
|
54
63
|
return this.args.isCollapsible !== false && this.hasExtraStops;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
get showEta() {
|
|
67
|
+
return this.args.showEta !== false;
|
|
68
|
+
}
|
|
69
|
+
|
|
57
70
|
badgeStyleForStop(routePoint) {
|
|
58
71
|
const { markerColor } = describeRoutePoint(routePoint, this.routeColor);
|
|
59
72
|
const isYellow = markerColor?.toLowerCase?.() === '#facc15' || markerColor?.toLowerCase?.() === '#ca8a04';
|
|
@@ -94,6 +107,25 @@ export default class RouteListComponent extends Component {
|
|
|
94
107
|
return stop.uuid === activeStop.uuid || stop.public_id === activeStop.public_id;
|
|
95
108
|
}
|
|
96
109
|
|
|
110
|
+
shouldShowCompletedFlagBadge(stop) {
|
|
111
|
+
if (!stop.trackingStop?.completed) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const visibleStatus = stop.statusCode ?? stop.place?.status_code ?? stop.trackingStop?.status_code ?? stop.trackingStop?.status ?? stop.place?.status;
|
|
116
|
+
if (typeof visibleStatus !== 'string') {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return visibleStatus.trim().toLowerCase() !== 'completed';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
shouldShowEtaForStop(stop) {
|
|
124
|
+
const status = this.args.order?.status;
|
|
125
|
+
|
|
126
|
+
return this.showEta && !['completed', 'canceled'].includes(status) && !stop.trackingStop?.completed;
|
|
127
|
+
}
|
|
128
|
+
|
|
97
129
|
@action toggleWaypointsCollapse() {
|
|
98
130
|
this.isWaypointsCollapsed = !this.isWaypointsCollapsed;
|
|
99
131
|
}
|
|
@@ -121,6 +121,47 @@
|
|
|
121
121
|
</table>
|
|
122
122
|
</div>
|
|
123
123
|
</ContentPanel>
|
|
124
|
+
{{else if @resource.isMultiZoneDistance}}
|
|
125
|
+
<ContentPanel @title="Multi-zone Distance" @open={{true}} @wrapperClass="bordered-top" @panelBodyWrapperClass="p-0i">
|
|
126
|
+
<div class="next-table-wrapper h-auto table-fluid rounded-none overflow-x-auto">
|
|
127
|
+
<table class="table table-fixed w-full">
|
|
128
|
+
<thead>
|
|
129
|
+
<tr>
|
|
130
|
+
<th>Label</th>
|
|
131
|
+
<th>Geography Type</th>
|
|
132
|
+
<th>Geography</th>
|
|
133
|
+
<th>Priority</th>
|
|
134
|
+
<th>Rate</th>
|
|
135
|
+
<th>Unit</th>
|
|
136
|
+
</tr>
|
|
137
|
+
</thead>
|
|
138
|
+
<tbody>
|
|
139
|
+
{{#each @resource.rateFees as |rateFee|}}
|
|
140
|
+
<tr>
|
|
141
|
+
<td>{{n-a rateFee.label}}</td>
|
|
142
|
+
<td>{{smart-humanize rateFee.geography_type}}</td>
|
|
143
|
+
<td>
|
|
144
|
+
{{#if (eq rateFee.geography_type "zone")}}
|
|
145
|
+
{{n-a rateFee.zone.name}}
|
|
146
|
+
{{else if (eq rateFee.geography_type "fallback")}}
|
|
147
|
+
Unmatched route distance
|
|
148
|
+
{{else}}
|
|
149
|
+
{{n-a rateFee.service_area.name}}
|
|
150
|
+
{{/if}}
|
|
151
|
+
</td>
|
|
152
|
+
<td>{{n-a rateFee.priority}}</td>
|
|
153
|
+
<td>{{format-currency (f-to-int rateFee.fee) @resource.currency}}</td>
|
|
154
|
+
<td>{{n-a rateFee.distance_unit}}</td>
|
|
155
|
+
</tr>
|
|
156
|
+
{{else}}
|
|
157
|
+
<tr>
|
|
158
|
+
<td colspan="6" class="text-center text-gray-500 italic">No geographic pricing rules defined</td>
|
|
159
|
+
</tr>
|
|
160
|
+
{{/each}}
|
|
161
|
+
</tbody>
|
|
162
|
+
</table>
|
|
163
|
+
</div>
|
|
164
|
+
</ContentPanel>
|
|
124
165
|
{{else if @resource.isPerMeter}}
|
|
125
166
|
<ContentPanel @title={{t "service-rate.fields.per-meter"}} @open={{true}} @wrapperClass="bordered-top">
|
|
126
167
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
|
|
@@ -209,6 +209,124 @@
|
|
|
209
209
|
</div>
|
|
210
210
|
</div>
|
|
211
211
|
</ContentPanel>
|
|
212
|
+
{{else if @resource.isMultiZoneDistance}}
|
|
213
|
+
<ContentPanel @title="Multi-zone Distance" @open={{true}} @wrapperClass="bordered-top" @panelBodyWrapperClass="px-0i">
|
|
214
|
+
<div class="px-2 mb-3">
|
|
215
|
+
<InfoBlock
|
|
216
|
+
class="mb-3"
|
|
217
|
+
@text="Price route distance across multiple service areas or zones. Priority controls overlap: if a route segment matches more than one rule, the highest-priority rule is used. Unmatched distance can use a fallback rule."
|
|
218
|
+
@exampleText="Main City: 2.50 per km, Remote Area: 3.75 per km, Fallback: 3.75 per km."
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
<div class="flex flex-row items-center space-x-2">
|
|
222
|
+
<Button @text="Add Rule" @icon="plus" @iconPrefix="fas" @onClick={{@resource.addMultiZoneDistanceRule}} />
|
|
223
|
+
<Button @text="Add Fallback" @icon="route" @iconPrefix="fas" @onClick={{@resource.addMultiZoneDistanceFallbackRule}} />
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="overflow-x-auto">
|
|
228
|
+
<div class="next-table-wrapper h-auto table-fluid rounded-none">
|
|
229
|
+
<table class="table table-fixed">
|
|
230
|
+
<thead>
|
|
231
|
+
<tr>
|
|
232
|
+
<th class="w-1/6">Label</th>
|
|
233
|
+
<th class="w-1/6">Geography Type</th>
|
|
234
|
+
<th class="w-1/5">Geography</th>
|
|
235
|
+
<th class="w-1/12">Priority</th>
|
|
236
|
+
<th class="w-1/6">Rate</th>
|
|
237
|
+
<th class="w-1/6">Unit</th>
|
|
238
|
+
<th class="w-12"></th>
|
|
239
|
+
</tr>
|
|
240
|
+
</thead>
|
|
241
|
+
<tbody>
|
|
242
|
+
{{#each @resource.rateFees as |rateFee|}}
|
|
243
|
+
<tr>
|
|
244
|
+
<td>
|
|
245
|
+
<Input @value={{rateFee.label}} placeholder="Rule label" disabled={{cannot-write @resource}} class="form-input w-full" />
|
|
246
|
+
</td>
|
|
247
|
+
<td>
|
|
248
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
249
|
+
<PowerSelect
|
|
250
|
+
@options={{this.geographyTypes}}
|
|
251
|
+
@selected={{find-by "value" rateFee.geography_type this.geographyTypes}}
|
|
252
|
+
@onChange={{fn this.selectRuleGeographyType rateFee}}
|
|
253
|
+
@placeholder="Geography type"
|
|
254
|
+
@triggerClass="form-select form-input"
|
|
255
|
+
@disabled={{cannot-write @resource}}
|
|
256
|
+
as |type|
|
|
257
|
+
>
|
|
258
|
+
{{type.label}}
|
|
259
|
+
</PowerSelect>
|
|
260
|
+
</div>
|
|
261
|
+
</td>
|
|
262
|
+
<td>
|
|
263
|
+
{{#if (eq rateFee.geography_type "zone")}}
|
|
264
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
265
|
+
<PowerSelect
|
|
266
|
+
@options={{this.zones}}
|
|
267
|
+
@selected={{rateFee.zone}}
|
|
268
|
+
@onChange={{fn this.selectRuleZone rateFee}}
|
|
269
|
+
@placeholder="Select zone"
|
|
270
|
+
@triggerClass="form-select form-input"
|
|
271
|
+
@disabled={{cannot-write @resource}}
|
|
272
|
+
as |zone|
|
|
273
|
+
>
|
|
274
|
+
{{zone.name}}
|
|
275
|
+
</PowerSelect>
|
|
276
|
+
</div>
|
|
277
|
+
{{else if (eq rateFee.geography_type "fallback")}}
|
|
278
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">Unmatched route distance</div>
|
|
279
|
+
{{else}}
|
|
280
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
281
|
+
<PowerSelect
|
|
282
|
+
@options={{this.serviceAreas}}
|
|
283
|
+
@selected={{rateFee.service_area}}
|
|
284
|
+
@onChange={{fn this.selectRuleServiceArea rateFee}}
|
|
285
|
+
@placeholder="Select service area"
|
|
286
|
+
@triggerClass="form-select form-input"
|
|
287
|
+
@disabled={{cannot-write @resource}}
|
|
288
|
+
as |serviceArea|
|
|
289
|
+
>
|
|
290
|
+
{{serviceArea.name}}
|
|
291
|
+
</PowerSelect>
|
|
292
|
+
</div>
|
|
293
|
+
{{/if}}
|
|
294
|
+
</td>
|
|
295
|
+
<td>
|
|
296
|
+
<Input @type="number" @value={{rateFee.priority}} placeholder="0" disabled={{cannot-write @resource}} class="form-input w-full" />
|
|
297
|
+
</td>
|
|
298
|
+
<td>
|
|
299
|
+
<MoneyInput class="w-full" @currency={{@resource.currency}} @value={{rateFee.fee}} disabled={{cannot-write @resource}} />
|
|
300
|
+
</td>
|
|
301
|
+
<td>
|
|
302
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
303
|
+
<PowerSelect
|
|
304
|
+
@options={{get-fleet-ops-options "distanceUnits"}}
|
|
305
|
+
@selected={{find-by "value" rateFee.distance_unit (get-fleet-ops-options "distanceUnits")}}
|
|
306
|
+
@onChange={{set-model-attr rateFee "distance_unit"}}
|
|
307
|
+
@placeholder="Distance unit"
|
|
308
|
+
@triggerClass="form-select form-input"
|
|
309
|
+
@disabled={{cannot-write @resource}}
|
|
310
|
+
as |unit|
|
|
311
|
+
>
|
|
312
|
+
{{unit.label}}
|
|
313
|
+
</PowerSelect>
|
|
314
|
+
</div>
|
|
315
|
+
</td>
|
|
316
|
+
<td>
|
|
317
|
+
<Button @type="danger" @icon="trash" @onClick={{fn @resource.removeMultiZoneDistanceRule rateFee}} />
|
|
318
|
+
</td>
|
|
319
|
+
</tr>
|
|
320
|
+
{{else}}
|
|
321
|
+
<tr>
|
|
322
|
+
<td colspan="7" class="text-center text-gray-500 italic">No geographic pricing rules defined</td>
|
|
323
|
+
</tr>
|
|
324
|
+
{{/each}}
|
|
325
|
+
</tbody>
|
|
326
|
+
</table>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
</ContentPanel>
|
|
212
330
|
{{else if @resource.isPerMeter}}
|
|
213
331
|
<ContentPanel @title={{t "service-rate.fields.per-meter"}} @open={{true}} @wrapperClass="bordered-top">
|
|
214
332
|
<InfoBlock class="mb-5" @text={{t "service-rate.fields.per-meter-text"}} @exampleText={{t "service-rate.fields.per-meter-example-text"}} />
|
|
@@ -4,11 +4,29 @@ import { action } from '@ember/object';
|
|
|
4
4
|
|
|
5
5
|
export default class ServiceRateFormComponent extends Component {
|
|
6
6
|
@service orderConfigActions;
|
|
7
|
+
@service serviceAreaActions;
|
|
7
8
|
@service serviceRateActions;
|
|
8
9
|
|
|
9
10
|
constructor() {
|
|
10
11
|
super(...arguments);
|
|
11
12
|
this.orderConfigActions.loadAll.perform();
|
|
13
|
+
this.serviceAreaActions.loadAll.perform();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get serviceAreas() {
|
|
17
|
+
return this.serviceAreaActions.serviceAreas ?? [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get zones() {
|
|
21
|
+
return Array.from(this.serviceAreas).flatMap((serviceArea) => serviceArea.zones?.toArray?.() ?? serviceArea.zones ?? []);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get geographyTypes() {
|
|
25
|
+
return [
|
|
26
|
+
{ label: 'Service Area', value: 'service_area' },
|
|
27
|
+
{ label: 'Zone', value: 'zone' },
|
|
28
|
+
{ label: 'Fallback', value: 'fallback' },
|
|
29
|
+
];
|
|
12
30
|
}
|
|
13
31
|
|
|
14
32
|
@action selectOrderConfig(orderConfig) {
|
|
@@ -24,10 +42,47 @@ export default class ServiceRateFormComponent extends Component {
|
|
|
24
42
|
this.args.resource.resetPerDropFees();
|
|
25
43
|
} else if (rateCalculationMethod === 'fixed_meter' || rateCalculationMethod === 'fixed_rate') {
|
|
26
44
|
this.serviceRateActions.generateFixedRateFees(this.args.resource);
|
|
45
|
+
} else if (rateCalculationMethod === 'multi_zone_distance' && !this.args.resource.rateFees.length) {
|
|
46
|
+
this.args.resource.addMultiZoneDistanceRule();
|
|
47
|
+
this.args.resource.addMultiZoneDistanceFallbackRule();
|
|
27
48
|
}
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
@action onMaxDistanceChange() {
|
|
31
52
|
this.serviceRateActions.generateFixedRateFees(this.args.resource);
|
|
32
53
|
}
|
|
54
|
+
|
|
55
|
+
@action selectRuleGeographyType(rule, { value }) {
|
|
56
|
+
rule.set('zone', null);
|
|
57
|
+
rule.set('zone_uuid', null);
|
|
58
|
+
rule.set('service_area', null);
|
|
59
|
+
rule.set('service_area_uuid', null);
|
|
60
|
+
rule.set('is_fallback', value === 'fallback');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@action selectRuleServiceArea(rule, serviceArea) {
|
|
64
|
+
rule.set('service_area', serviceArea);
|
|
65
|
+
rule.set('service_area_uuid', serviceArea?.id);
|
|
66
|
+
rule.set('zone', null);
|
|
67
|
+
rule.set('zone_uuid', null);
|
|
68
|
+
rule.set('is_fallback', false);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@action selectRuleZone(rule, zone) {
|
|
72
|
+
rule.set('zone', zone);
|
|
73
|
+
rule.set('zone_uuid', zone?.id);
|
|
74
|
+
rule.set('service_area', null);
|
|
75
|
+
rule.set('service_area_uuid', null);
|
|
76
|
+
rule.set('is_fallback', false);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@action setRuleFallback(rule, isFallback) {
|
|
80
|
+
rule.set('is_fallback', Boolean(isFallback));
|
|
81
|
+
if (isFallback) {
|
|
82
|
+
rule.set('zone', null);
|
|
83
|
+
rule.set('zone_uuid', null);
|
|
84
|
+
rule.set('service_area', null);
|
|
85
|
+
rule.set('service_area_uuid', null);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
33
88
|
}
|
|
@@ -10,7 +10,7 @@ export default class VehicleFormComponent extends Component {
|
|
|
10
10
|
@service currentUser;
|
|
11
11
|
@service notifications;
|
|
12
12
|
@service modalsManager;
|
|
13
|
-
@tracked statusOptions = ['
|
|
13
|
+
@tracked statusOptions = ['available', 'pending'];
|
|
14
14
|
|
|
15
15
|
@action updateAvatarUrl(option) {
|
|
16
16
|
this.args.resource.avatar_url = option.key === 'custom_avatar' ? option.value : [option.value];
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<:tooltip>
|
|
21
21
|
<div>
|
|
22
22
|
<div class="text-xs font-semibold">{{this.resource.name this.resource.yearMakeModel}}</div>
|
|
23
|
-
<div class="text-xs">{{t "
|
|
23
|
+
<div class="text-xs">{{t "resource.driver"}}: {{n-a this.resource.driver_name}}</div>
|
|
24
24
|
<div class="text-xs">
|
|
25
25
|
<span>{{t "common.status"}}:</span>
|
|
26
26
|
<span class="{{if this.resource.online 'text-green-500' 'text-red-400'}}">
|
|
@@ -4,7 +4,7 @@ import { inject as service } from '@ember/service';
|
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
5
|
import { task } from 'ember-concurrency';
|
|
6
6
|
|
|
7
|
-
const DEFAULT_PROPERTIES = { status: '
|
|
7
|
+
const DEFAULT_PROPERTIES = { status: 'available' };
|
|
8
8
|
|
|
9
9
|
export default class ManagementVehiclesIndexNewController extends Controller {
|
|
10
10
|
@service store;
|
|
@@ -103,10 +103,10 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
103
103
|
}),
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
|
-
text: 'Unassign driver',
|
|
107
|
-
icon: 'user-xmark',
|
|
108
|
-
disabled: this.model.
|
|
109
|
-
fn: () => this.orderActions.unassignDriver(this.model),
|
|
106
|
+
text: this.model.has_driver_assigned ? 'Unassign driver' : 'Assign driver',
|
|
107
|
+
icon: this.model.has_driver_assigned ? 'user-xmark' : 'edit',
|
|
108
|
+
disabled: this.model.has_driver_assigned ? !this.model.hasActiveStatus || !this.model.driver_assigned : !this.model.hasActiveStatus,
|
|
109
|
+
fn: () => (this.model.has_driver_assigned ? this.orderActions.unassignDriver(this.model) : this.orderActions.assignDriver(this.model)),
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
text: 'View order label',
|
|
@@ -385,6 +385,20 @@ export default class OperationsOrdersIndexController extends Controller {
|
|
|
385
385
|
permission: 'fleet-ops dispatch order',
|
|
386
386
|
isVisible: (order) => order.canBeDispatched,
|
|
387
387
|
},
|
|
388
|
+
{
|
|
389
|
+
label: this.intl.t('common.assign-driver'),
|
|
390
|
+
icon: 'edit',
|
|
391
|
+
fn: this.orderActions.assignDriver,
|
|
392
|
+
permission: 'fleet-ops update order',
|
|
393
|
+
isVisible: (order) => !order.has_driver_assigned,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
label: this.intl.t('common.unassign-driver'),
|
|
397
|
+
icon: 'edit',
|
|
398
|
+
fn: this.orderActions.unassignDriver,
|
|
399
|
+
permission: 'fleet-ops update order',
|
|
400
|
+
isVisible: (order) => order.has_driver_assigned,
|
|
401
|
+
},
|
|
388
402
|
{
|
|
389
403
|
label: this.intl.t('common.cancel-resource', { resource: this.intl.t('resource.order') }),
|
|
390
404
|
icon: 'ban',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { helper } from '@ember/component/helper';
|
|
2
2
|
import placeAddressHtml from '../utils/place-address-html';
|
|
3
3
|
|
|
4
|
-
export default helper(function placeAddressHtmlHelper([place]) {
|
|
5
|
-
return placeAddressHtml(place);
|
|
4
|
+
export default helper(function placeAddressHtmlHelper([place], hash = {}) {
|
|
5
|
+
return placeAddressHtml(place, hash);
|
|
6
6
|
});
|
|
@@ -27,7 +27,7 @@ export default class OperationsServiceRatesIndexRoute extends Route {
|
|
|
27
27
|
model(params) {
|
|
28
28
|
return this.store.query('service-rate', {
|
|
29
29
|
...params,
|
|
30
|
-
with: ['parcelFees', 'rateFees', 'zone', 'serviceArea'],
|
|
30
|
+
with: ['parcelFees', 'rateFees.zone', 'rateFees.serviceArea', 'zone', 'serviceArea'],
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
}
|