Skip to content

Commit 35263ce

Browse files
authored
Merge pull request #233 from fleetbase/feature/fleetops-repricing-service-rate-fixes
Implement repricing flow and service rate fixes
2 parents 1b9a792 + d8ffbfa commit 35263ce

47 files changed

Lines changed: 675 additions & 129 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

addon/components/entity/form.hbs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,30 @@
2323
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
2424
<InputGroup @name={{t "common.name"}} @value={{@resource.name}} @helpText={{t "modals.entity-form.name-text"}} />
2525
<InputGroup @name={{t "common.internal-id"}} @value={{@resource.internal_id}} @helpText={{t "modals.entity-form.id-text"}} />
26+
<div class="space-y-2">
27+
<InputGroup @name={{t "common.type"}} @helpText="Choose a common entity type or switch to a custom value.">
28+
{{#if this.useCustomType}}
29+
<Input @value={{@resource.type}} @type="text" class="w-full form-input" placeholder="Enter custom entity type" />
30+
{{else}}
31+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
32+
<PowerSelect
33+
@options={{this.entityTypes}}
34+
@selected={{this.selectedEntityType}}
35+
@onChange={{this.selectEntityType}}
36+
@placeholder="Select entity type"
37+
@triggerClass="form-select"
38+
as |type|
39+
>
40+
<div class="text-sm">
41+
<div class="font-semibold normalize-in-trigger">{{type.label}}</div>
42+
<div class="hide-from-trigger">{{type.description}}</div>
43+
</div>
44+
</PowerSelect>
45+
</div>
46+
{{/if}}
47+
</InputGroup>
48+
<Checkbox @value={{this.useCustomType}} @label="Use custom type" @onToggle={{this.toggleCustomType}} @alignItems="center" @labelClass="mb-0i" />
49+
</div>
2650
<InputGroup @name={{t "modals.entity-form.sku"}} @value={{@resource.sku}} @helpText={{t "modals.entity-form.sku-text"}} />
2751
<div></div>
2852
<InputGroup @name={{t "modals.entity-form.description"}} @helpText={{t "modals.entity-form.description-text"}} @wrapperClass="col-span-2">

addon/components/entity/form.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
11
import Component from '@glimmer/component';
22
import { inject as service } from '@ember/service';
3+
import { action } from '@ember/object';
4+
import { tracked } from '@glimmer/tracking';
35
import { task } from 'ember-concurrency';
6+
import fleetOpsOptions from '../../utils/fleet-ops-options';
47

58
export default class EntityFormComponent extends Component {
69
@service fetch;
710
@service currentUser;
811
@service notifications;
12+
@tracked useCustomType = false;
13+
14+
constructor() {
15+
super(...arguments);
16+
this.useCustomType = this.selectedEntityType === undefined && Boolean(this.args.resource?.type);
17+
}
18+
19+
get entityTypes() {
20+
return fleetOpsOptions('entityTypes');
21+
}
22+
23+
get selectedEntityType() {
24+
return this.entityTypes.find((option) => option.value === this.args.resource?.type);
25+
}
26+
27+
@action selectEntityType(option) {
28+
this.useCustomType = false;
29+
this.args.resource.type = option?.value ?? null;
30+
}
31+
32+
@action toggleCustomType(value) {
33+
this.useCustomType = value;
34+
35+
if (!value && !this.selectedEntityType) {
36+
this.args.resource.type = null;
37+
}
38+
}
939

1040
@task *handlePhotoUpload(file) {
1141
try {

addon/components/leaflet-draw-control.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default class LeafletDrawControl extends BaseLayer {
2727
leafletOptions = ['draw', 'edit', 'remove', 'poly', 'position'];
2828

2929
@computed('leafletEvents.[]', 'args') get usedLeafletEvents() {
30-
const leafletEvents = [...this.leafletEvents, ...Object.values(L.Draw.Event)];
30+
const leafletEvents = [...this.leafletEvents, ...Object.values(L.Draw?.Event ?? {})];
3131
return leafletEvents.filter((eventName) => {
3232
eventName = camelize(eventName.replace(':', ' '));
3333
let methodName = `_${eventName}`;

addon/components/map/drawer/geofence-event-listing.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
22
import { tracked } from '@glimmer/tracking';
33
import { inject as service } from '@ember/service';
44
import { action } from '@ember/object';
5+
import { next } from '@ember/runloop';
56

67
export default class MapDrawerGeofenceEventListingComponent extends Component {
78
@service fetch;
@@ -12,8 +13,10 @@ export default class MapDrawerGeofenceEventListingComponent extends Component {
1213

1314
constructor() {
1415
super(...arguments);
15-
this.geofenceEventBus.subscribe(this.currentUser.companyId);
16-
this.loadRecentEvents();
16+
next(() => {
17+
this.geofenceEventBus.subscribe(this.currentUser.companyId);
18+
this.loadRecentEvents();
19+
});
1720
}
1821

1922
get events() {

addon/components/order/form/service-rate.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
@name="serviceQuote"
5959
@changed={{fn (mut @resource.service_quote_uuid)}}
6060
/>
61-
<label for={{serviceQuote.uuid}} class="ml-3 flex-1">{{serviceQuote.public_id}}</label>
61+
<label for={{serviceQuote.uuid}} class="ml-3 flex-1 text-sm">{{serviceQuote.public_id}} ({{serviceQuote.service_rate_name}})</label>
6262
<Badge @hideStatusDot={{true}} @status="info">
6363
{{serviceQuote.request_id}}
6464
</Badge>

addon/components/service-rate/details.hbs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@
106106
</tr>
107107
</thead>
108108
<tbody>
109-
{{#each @resource.rate_fees as |rateFee|}}
109+
{{#each @resource.rateFees as |rateFee|}}
110110
<tr>
111111
<td>{{n-a rateFee.min}}</td>
112112
<td>{{n-a rateFee.max}}</td>
113-
<td>{{format-currency rateFee.fee @resource.currency}}</td>
113+
<td>{{format-currency (f-to-int rateFee.fee) @resource.currency}}</td>
114114
</tr>
115115
{{else}}
116116
<tr>
@@ -158,7 +158,7 @@
158158
<div class="field-info-container">
159159
<div class="field-name">Algorithm Code</div>
160160
<div class="field-value">
161-
<pre class="bg-gray-100 dark:bg-gray-800 p-3 rounded text-xs overflow-x-auto"><code>{{n-a @resource.algorithm}}</code></pre>
161+
<pre class="bg-gray-100 dark:bg-gray-900 font-mono p-3 rounded text-xs overflow-x-auto"><code class="font-mono text-xs">{{n-a @resource.algorithm}}</code></pre>
162162
</div>
163163
</div>
164164
</ContentPanel>
@@ -167,7 +167,7 @@
167167
{{#if @resource.isParcelService}}
168168
<ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
169169
<div class="space-y-2">
170-
{{#each @resource.parcel_fees as |parcelFee|}}
170+
{{#each @resource.parcelFees as |parcelFee|}}
171171
<div class="dark:text-gray-100">
172172
<div class="grid grid-cols-7">
173173
<div class="flex flex-col items-start justify-start pt-3">
@@ -242,7 +242,7 @@
242242
{{t "service-rate.fields.additional-fee"}}
243243
</div>
244244
<div class="field-value">
245-
{{n-a (format-currency parcelFee.fee @resource.currency)}}
245+
{{n-a (format-currency (f-to-int parcelFee.fee) @resource.currency)}}
246246
</div>
247247
</div>
248248
</div>
@@ -323,4 +323,4 @@
323323
</ContentPanel>
324324

325325
<CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
326-
</div>
326+
</div>

addon/components/service-rate/form.hbs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
</tr>
177177
</thead>
178178
<tbody>
179-
{{#each @resource.rate_fees as |rateFee|}}
179+
{{#each @resource.rateFees as |rateFee|}}
180180
<tr>
181181
<td>
182182
<Input
@@ -252,25 +252,38 @@
252252
{{else if @resource.isAlgorithm}}
253253
<ContentPanel @title={{t "service-rate.fields.custom-algorithm-title"}} @open={{true}} @wrapperClass="bordered-top">
254254
<InfoBlock class="mb-5">
255-
{{t "service-rate.fields.custom-algorithm-info-message"}}
256-
{{t "service-rate.fields.custom-algorithm-info-second-message"}}
255+
Define a custom formula for this service rate using variables wrapped in a single pair of curly braces.
257256

258257
<div class="block my-4 break-text">
259258
<ul class="list-disc space-y-2 pl-16">
260-
<li class="leading-5"><i>{{t "service-rate.fields.distance-message"}}</i>
261-
{{t "service-rate.fields.distance-continue-message"}}</li>
262-
<li class="leading-5"><i>{{t "service-rate.fields.time-message"}}</i>
263-
{{t "service-rate.fields.time-continue-message"}}</li>
259+
<li class="leading-5"><code>&#123;distance&#125;</code>, <code>&#123;distance_m&#125;</code>, <code>&#123;distance_km&#125;</code>, <code>&#123;distance_mi&#125;</code>: route distance in meters, kilometers, or miles.</li>
260+
<li class="leading-5"><code>&#123;time&#125;</code>, <code>&#123;time_s&#125;</code>, <code>&#123;time_min&#125;</code>: route time in seconds or minutes.</li>
261+
<li class="leading-5"><code>&#123;stops&#125;</code>: total service stops including pickup, dropoff, and intermediate waypoints.</li>
262+
<li class="leading-5"><code>&#123;waypoints&#125;</code>: intermediate waypoint count only.</li>
263+
<li class="leading-5"><code>&#123;parcels&#125;</code>, <code>&#123;entities&#125;</code>: parcel count and total payload entity count.</li>
264+
<li class="leading-5"><code>&#123;base_fee&#125;</code>: the configured base fee for this service rate.</li>
265+
<li class="leading-5">Supported functions: <code>max(a,b)</code>, <code>min(a,b)</code>, <code>ceil(x)</code>, <code>floor(x)</code>, <code>round(x)</code>.</li>
264266
</ul>
265267
</div>
266268

267-
<div>
268-
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}}</h4>
269-
<div class="mb-3 text-inherit">
270-
{{t "service-rate.fields.example-message"}}
271-
{{t "service-rate.fields.example-second-message"}}
269+
<div class="space-y-3">
270+
<div>
271+
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 1</h4>
272+
<div class="mb-2 text-inherit">Charge by distance in miles after the first 15 miles, plus the base fee.</div>
273+
<code class="text-xs font-mono">max(&#123;distance_mi&#125; - 15, 0) * 5 + &#123;base_fee&#125;</code>
274+
</div>
275+
276+
<div>
277+
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 2</h4>
278+
<div class="mb-2 text-inherit">Charge extra for stops after the first 2 service stops.</div>
279+
<code class="text-xs font-mono">max(&#123;stops&#125; - 2, 0) * 10 + &#123;base_fee&#125;</code>
280+
</div>
281+
282+
<div>
283+
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 3</h4>
284+
<div class="mb-2 text-inherit">Combine waypoint, parcel, and distance surcharges in one formula.</div>
285+
<code class="text-xs font-mono">max(&#123;waypoints&#125;, 0) * 15 + max(&#123;parcels&#125; - 3, 0) * 2 + max(&#123;distance_km&#125; - 25, 0) * 0.5 + &#123;base_fee&#125;</code>
272286
</div>
273-
<code class="text-xs font-mono">(( {distance} / 50 ) * .05 ) + (( {time} / 60 ) * .01)</code>
274287
</div>
275288
</InfoBlock>
276289

@@ -287,7 +300,7 @@
287300
{{else if @resource.isParcelService}}
288301
<ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
289302
<div class="space-y-2">
290-
{{#each @resource.parcel_fees as |parcelFee|}}
303+
{{#each @resource.parcelFees as |parcelFee|}}
291304
<div class="dark:text-gray-100">
292305
<div class="grid grid-cols-7">
293306
<div class="flex flex-col items-start justify-start pt-3">
@@ -541,4 +554,4 @@
541554
</InputGroup>
542555
{{/if}}
543556
</ContentPanel>
544-
</div>
557+
</div>

addon/controllers/operations/orders/index/details.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
1616
@service universe;
1717
@service sidebar;
1818
@tracked routingControl;
19+
@tracked routingCompleted = false;
1920

2021
get tabs() {
2122
const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:order:details');
@@ -109,7 +110,19 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
109110
@action async setup() {
110111
// Change to map layout and display order route
111112
this.index.changeLayout('map');
112-
this.routingControl = await this.leafletMapManager.addRoutingControl(this.model.routeWaypoints);
113+
this.routingCompleted = false;
114+
this.routingControl = await this.leafletMapManager.addRoutingControl(this.model.routeWaypoints, {
115+
onRouteFound: () => {
116+
this.routingCompleted = true;
117+
},
118+
onRoutingError: () => {
119+
this.routingCompleted = true;
120+
},
121+
});
122+
123+
if (!this.routingControl) {
124+
this.routingCompleted = true;
125+
}
113126

114127
// Hide sidebar
115128
this.sidebar.hideNow();
@@ -125,7 +138,19 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
125138
async (_msg, { reloadable }) => {
126139
if (reloadable) {
127140
await this.hostRouter.refresh();
128-
this.leafletMapManager.replaceRoutingControl(this.model.routeWaypoints, this.routingControl);
141+
this.routingCompleted = false;
142+
this.routingControl = await this.leafletMapManager.replaceRoutingControl(this.model.routeWaypoints, this.routingControl, {
143+
onRouteFound: () => {
144+
this.routingCompleted = true;
145+
},
146+
onRoutingError: () => {
147+
this.routingCompleted = true;
148+
},
149+
});
150+
151+
if (!this.routingControl) {
152+
this.routingCompleted = true;
153+
}
129154
}
130155
},
131156
{ debounceMs: 250 }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Controller, { inject as controller } from '@ember/controller';
2+
3+
export default class OperationsOrdersIndexDetailsVirtualController extends Controller {
4+
@controller('operations.orders.index.details') parent;
5+
}

addon/controllers/operations/service-rates/index/edit.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default class OperationsServiceRatesIndexEditController extends Controlle
2222
@task *save(serviceRate) {
2323
try {
2424
yield serviceRate.save();
25+
yield serviceRate.reload();
2526
this.events.trackResourceUpdated(serviceRate);
2627
this.overlay?.close();
2728

0 commit comments

Comments
 (0)