@fleetbase/fleetops-engine 0.6.44 → 0.6.45

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.
Files changed (42) hide show
  1. package/addon/components/entity/form.hbs +24 -0
  2. package/addon/components/entity/form.js +30 -0
  3. package/addon/components/leaflet-draw-control.js +1 -1
  4. package/addon/components/map/drawer/geofence-event-listing.js +5 -2
  5. package/addon/components/order/form/service-rate.hbs +1 -1
  6. package/addon/components/service-rate/details.hbs +6 -6
  7. package/addon/components/service-rate/form.hbs +28 -15
  8. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  9. package/addon/controllers/operations/orders/index/details.js +27 -2
  10. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  11. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  12. package/addon/helpers/f-to-int.js +6 -0
  13. package/addon/routes/application.js +3 -3
  14. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  15. package/addon/routes/operations/orders/index/details.js +19 -3
  16. package/addon/services/equipment-actions.js +4 -6
  17. package/addon/services/leaflet-map-manager.js +4 -0
  18. package/addon/services/maintenance-actions.js +4 -6
  19. package/addon/services/part-actions.js +4 -6
  20. package/addon/services/service-rate-actions.js +8 -3
  21. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  22. package/addon/utils/fleet-ops-options.js +33 -0
  23. package/addon/utils/serialize-payload.js +1 -1
  24. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  25. package/app/helpers/f-to-int.js +1 -0
  26. package/composer.json +1 -1
  27. package/extension.json +1 -1
  28. package/package.json +2 -2
  29. package/server/src/Http/Controllers/Api/v1/OrderController.php +5 -5
  30. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  31. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +4 -0
  32. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  33. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  34. package/server/src/Http/Resources/v1/Order.php +0 -1
  35. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  36. package/server/src/Models/Entity.php +7 -0
  37. package/server/src/Models/Order.php +39 -7
  38. package/server/src/Models/ServiceRate.php +122 -30
  39. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  40. package/server/src/Rules/ComputableAlgo.php +1 -3
  41. package/server/src/Support/Algo.php +87 -1
  42. package/server/src/Support/Utils.php +35 -0
@@ -23,6 +23,30 @@
23
23
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
24
24
  <InputGroup @name={{t "common.name"}} @value={{@resource.name}} @helpText={{t "modals.entity-form.name-text"}} />
25
25
  <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>
26
50
  <InputGroup @name={{t "modals.entity-form.sku"}} @value={{@resource.sku}} @helpText={{t "modals.entity-form.sku-text"}} />
27
51
  <div></div>
28
52
  <InputGroup @name={{t "modals.entity-form.description"}} @helpText={{t "modals.entity-form.description-text"}} @wrapperClass="col-span-2">
@@ -1,11 +1,41 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { inject as service } from '@ember/service';
3
+ import { action } from '@ember/object';
4
+ import { tracked } from '@glimmer/tracking';
3
5
  import { task } from 'ember-concurrency';
6
+ import fleetOpsOptions from '../../utils/fleet-ops-options';
4
7
 
5
8
  export default class EntityFormComponent extends Component {
6
9
  @service fetch;
7
10
  @service currentUser;
8
11
  @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
+ }
9
39
 
10
40
  @task *handlePhotoUpload(file) {
11
41
  try {
@@ -27,7 +27,7 @@ export default class LeafletDrawControl extends BaseLayer {
27
27
  leafletOptions = ['draw', 'edit', 'remove', 'poly', 'position'];
28
28
 
29
29
  @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 ?? {})];
31
31
  return leafletEvents.filter((eventName) => {
32
32
  eventName = camelize(eventName.replace(':', ' '));
33
33
  let methodName = `_${eventName}`;
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
+ import { next } from '@ember/runloop';
5
6
 
6
7
  export default class MapDrawerGeofenceEventListingComponent extends Component {
7
8
  @service fetch;
@@ -12,8 +13,10 @@ export default class MapDrawerGeofenceEventListingComponent extends Component {
12
13
 
13
14
  constructor() {
14
15
  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
+ });
17
20
  }
18
21
 
19
22
  get events() {
@@ -58,7 +58,7 @@
58
58
  @name="serviceQuote"
59
59
  @changed={{fn (mut @resource.service_quote_uuid)}}
60
60
  />
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>
62
62
  <Badge @hideStatusDot={{true}} @status="info">
63
63
  {{serviceQuote.request_id}}
64
64
  </Badge>
@@ -106,11 +106,11 @@
106
106
  </tr>
107
107
  </thead>
108
108
  <tbody>
109
- {{#each @resource.rate_fees as |rateFee|}}
109
+ {{#each @resource.rateFees as |rateFee|}}
110
110
  <tr>
111
111
  <td>{{n-a rateFee.min}}</td>
112
112
  <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>
114
114
  </tr>
115
115
  {{else}}
116
116
  <tr>
@@ -158,7 +158,7 @@
158
158
  <div class="field-info-container">
159
159
  <div class="field-name">Algorithm Code</div>
160
160
  <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>
162
162
  </div>
163
163
  </div>
164
164
  </ContentPanel>
@@ -167,7 +167,7 @@
167
167
  {{#if @resource.isParcelService}}
168
168
  <ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
169
169
  <div class="space-y-2">
170
- {{#each @resource.parcel_fees as |parcelFee|}}
170
+ {{#each @resource.parcelFees as |parcelFee|}}
171
171
  <div class="dark:text-gray-100">
172
172
  <div class="grid grid-cols-7">
173
173
  <div class="flex flex-col items-start justify-start pt-3">
@@ -242,7 +242,7 @@
242
242
  {{t "service-rate.fields.additional-fee"}}
243
243
  </div>
244
244
  <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)}}
246
246
  </div>
247
247
  </div>
248
248
  </div>
@@ -323,4 +323,4 @@
323
323
  </ContentPanel>
324
324
 
325
325
  <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
326
- </div>
326
+ </div>
@@ -176,7 +176,7 @@
176
176
  </tr>
177
177
  </thead>
178
178
  <tbody>
179
- {{#each @resource.rate_fees as |rateFee|}}
179
+ {{#each @resource.rateFees as |rateFee|}}
180
180
  <tr>
181
181
  <td>
182
182
  <Input
@@ -252,25 +252,38 @@
252
252
  {{else if @resource.isAlgorithm}}
253
253
  <ContentPanel @title={{t "service-rate.fields.custom-algorithm-title"}} @open={{true}} @wrapperClass="bordered-top">
254
254
  <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.
257
256
 
258
257
  <div class="block my-4 break-text">
259
258
  <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>
264
266
  </ul>
265
267
  </div>
266
268
 
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>
272
286
  </div>
273
- <code class="text-xs font-mono">(( {distance} / 50 ) * .05 ) + (( {time} / 60 ) * .01)</code>
274
287
  </div>
275
288
  </InfoBlock>
276
289
 
@@ -287,7 +300,7 @@
287
300
  {{else if @resource.isParcelService}}
288
301
  <ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
289
302
  <div class="space-y-2">
290
- {{#each @resource.parcel_fees as |parcelFee|}}
303
+ {{#each @resource.parcelFees as |parcelFee|}}
291
304
  <div class="dark:text-gray-100">
292
305
  <div class="grid grid-cols-7">
293
306
  <div class="flex flex-col items-start justify-start pt-3">
@@ -541,4 +554,4 @@
541
554
  </InputGroup>
542
555
  {{/if}}
543
556
  </ContentPanel>
544
- </div>
557
+ </div>
@@ -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
+ }
@@ -16,6 +16,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
16
16
  @service universe;
17
17
  @service sidebar;
18
18
  @tracked routingControl;
19
+ @tracked routingCompleted = false;
19
20
 
20
21
  get tabs() {
21
22
  const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:order:details');
@@ -109,7 +110,19 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
109
110
  @action async setup() {
110
111
  // Change to map layout and display order route
111
112
  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
+ }
113
126
 
114
127
  // Hide sidebar
115
128
  this.sidebar.hideNow();
@@ -125,7 +138,19 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
125
138
  async (_msg, { reloadable }) => {
126
139
  if (reloadable) {
127
140
  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
+ }
129
154
  }
130
155
  },
131
156
  { debounceMs: 250 }
@@ -22,6 +22,7 @@ export default class OperationsServiceRatesIndexEditController extends Controlle
22
22
  @task *save(serviceRate) {
23
23
  try {
24
24
  yield serviceRate.save();
25
+ yield serviceRate.reload();
25
26
  this.events.trackResourceUpdated(serviceRate);
26
27
  this.overlay?.close();
27
28
 
@@ -16,6 +16,7 @@ export default class OperationsServiceRatesIndexNewController extends Controller
16
16
  @task *save(serviceRate) {
17
17
  try {
18
18
  yield serviceRate.save();
19
+ yield serviceRate.reload();
19
20
  this.events.trackResourceCreated(serviceRate);
20
21
  this.overlay?.close();
21
22
 
@@ -0,0 +1,6 @@
1
+ import { helper } from '@ember/component/helper';
2
+ import numbersOnly from '@fleetbase/ember-core/utils/numbers-only';
3
+
4
+ export default helper(function fToInt([value]) {
5
+ return parseInt(numbersOnly(value));
6
+ });
@@ -20,14 +20,14 @@ export default class ApplicationRoute extends Route {
20
20
  });
21
21
  }
22
22
 
23
- beforeModel() {
23
+ async beforeModel() {
24
24
  if (this.abilities.cannot('fleet-ops see extension')) {
25
25
  this.notifications.warning(this.intl.t('common.unauthorized-access'));
26
26
  return this.hostRouter.transitionTo('console');
27
27
  }
28
28
 
29
- this.location.getUserLocation();
30
- this.#loadRoutingSettings();
29
+ await this.location.getUserLocation();
30
+ await this.#loadRoutingSettings();
31
31
  }
32
32
 
33
33
  async #loadRoutingSettings() {
@@ -19,5 +19,6 @@ export default class OperationsOrdersIndexDetailsVirtualRoute extends Route {
19
19
  setupController(controller) {
20
20
  super.setupController(...arguments);
21
21
  controller.resource = this.modelFor('operations.orders.index.details');
22
+ controller.detailsController = this.controllerFor('operations.orders.index.details');
22
23
  }
23
24
  }
@@ -15,9 +15,12 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
15
15
  @action willTransition(transition) {
16
16
  const fromName = transition.from?.name;
17
17
  const toName = transition.to?.name;
18
+ const orderDetailsRoute = 'console.fleet-ops.operations.orders.index.details';
18
19
 
19
- // only cleanup when actually leaving this route (not intra-route changes)
20
- if (fromName && fromName !== toName) {
20
+ // only cleanup when leaving the order details route tree entirely
21
+ const isLeavingOrderDetails = fromName?.startsWith(orderDetailsRoute) && !toName?.startsWith(orderDetailsRoute);
22
+
23
+ if (isLeavingOrderDetails) {
21
24
  const controller = this.controllerFor('operations.orders.index.details');
22
25
  const rc = controller.routingControl;
23
26
 
@@ -55,7 +58,20 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
55
58
  return this.store.queryRecord('order', {
56
59
  public_id,
57
60
  single: true,
58
- with: ['payload', 'driverAssigned', 'orderConfig', 'customer', 'facilitator', 'trackingStatuses', 'trackingNumber', 'purchaseRate', 'comments', 'files'],
61
+ with: [
62
+ 'payload',
63
+ 'driverAssigned',
64
+ 'orderConfig',
65
+ 'customer',
66
+ 'facilitator',
67
+ 'trackingStatuses',
68
+ 'trackingNumber',
69
+ 'purchaseRate',
70
+ 'purchaseRate.serviceQuote',
71
+ 'purchaseRate.serviceQuote.items',
72
+ 'comments',
73
+ 'files',
74
+ ],
59
75
  });
60
76
  }
61
77
 
@@ -1,7 +1,9 @@
1
- import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
1
+ import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
2
2
 
3
3
  export default class EquipmentActionsService extends ResourceActionService {
4
- @service currentUser;
4
+ get defaultCurrency() {
5
+ return this.currentUser?.company?.currency || this.currentUser.currency || 'USD';
6
+ }
5
7
 
6
8
  constructor() {
7
9
  super(...arguments);
@@ -12,10 +14,6 @@ export default class EquipmentActionsService extends ResourceActionService {
12
14
  });
13
15
  }
14
16
 
15
- get defaultCurrency() {
16
- return this.currentUser?.company?.currency || 'USD';
17
- }
18
-
19
17
  transition = {
20
18
  view: (equipment) => this.transitionTo('maintenance.equipment.index.details', equipment),
21
19
  edit: (equipment) => this.transitionTo('maintenance.equipment.index.edit', equipment),
@@ -270,6 +270,10 @@ export default class LeafletMapManagerService extends Service {
270
270
  this.route = routes[0];
271
271
  });
272
272
 
273
+ routingControl.on('routingerror', (event) => {
274
+ options?.onRoutingError?.(event);
275
+ });
276
+
273
277
  this.positionWaypoints(waypoints);
274
278
 
275
279
  return routingControl;
@@ -1,7 +1,9 @@
1
- import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
1
+ import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
2
2
 
3
3
  export default class MaintenanceActionsService extends ResourceActionService {
4
- @service currentUser;
4
+ get defaultCurrency() {
5
+ return this.currentUser?.company?.currency || this.currentUser.currency || 'USD';
6
+ }
5
7
 
6
8
  constructor() {
7
9
  super(...arguments);
@@ -12,10 +14,6 @@ export default class MaintenanceActionsService extends ResourceActionService {
12
14
  });
13
15
  }
14
16
 
15
- get defaultCurrency() {
16
- return this.currentUser?.company?.currency || 'USD';
17
- }
18
-
19
17
  transition = {
20
18
  view: (maintenance) => this.transitionTo('maintenance.maintenances.index.details', maintenance),
21
19
  edit: (maintenance) => this.transitionTo('maintenance.maintenances.index.edit', maintenance),
@@ -1,7 +1,9 @@
1
- import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
1
+ import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
2
2
 
3
3
  export default class PartActionsService extends ResourceActionService {
4
- @service currentUser;
4
+ get defaultCurrency() {
5
+ return this.currentUser?.company?.currency || this.currentUser.currency || 'USD';
6
+ }
5
7
 
6
8
  constructor() {
7
9
  super(...arguments);
@@ -12,10 +14,6 @@ export default class PartActionsService extends ResourceActionService {
12
14
  });
13
15
  }
14
16
 
15
- get defaultCurrency() {
16
- return this.currentUser?.company?.currency || 'USD';
17
- }
18
-
19
17
  transition = {
20
18
  view: (part) => this.transitionTo('maintenance.parts.index.details', part),
21
19
  edit: (part) => this.transitionTo('maintenance.parts.index.edit', part),
@@ -6,13 +6,17 @@ import serializePayload from '../utils/serialize-payload';
6
6
  export default class ServiceRateActionsService extends ResourceActionService {
7
7
  modelNamePath = 'service_name';
8
8
 
9
+ get defaultCurrency() {
10
+ return this.currentUser?.company?.currency || this.currentUser.currency || 'USD';
11
+ }
12
+
9
13
  constructor() {
10
14
  super(...arguments);
11
15
  this.initialize('service-rate', {
12
16
  defaultAttributes: {
13
17
  rate_calculation_method: 'per_meter',
14
18
  per_meter_unit: 'm',
15
- currency: this.currentUser.currency,
19
+ currency: this.defaultCurrency,
16
20
  parcel_fees: this.#getDefaultParcelFees(),
17
21
  },
18
22
  });
@@ -118,12 +122,13 @@ export default class ServiceRateActionsService extends ResourceActionService {
118
122
 
119
123
  const hasFacilitator = !isNone(order.facilitator);
120
124
  const facilitatorServiceType = order.facilitator?.get('service_types.firstObject.key') ?? order.type;
125
+ const routeSummary = order.route?.summary ?? order.route?.details?.summary ?? {};
121
126
 
122
127
  try {
123
128
  const serviceQuotes = yield this.fetch.post('service-quotes/preliminary', {
124
129
  payload: serializePayload(order.payload),
125
- distance: order.route.summary?.totalDistance,
126
- time: order.route.summary?.totalTime,
130
+ distance: routeSummary.totalDistance,
131
+ time: routeSummary.totalTime,
127
132
  service_type: hasFacilitator ? facilitatorServiceType : order.type,
128
133
  facilitator: order.facilitator?.public_id,
129
134
  scheduled_at: order.scheduled_at,
@@ -1 +1,7 @@
1
- {{component (lazy-engine-component @model.component) resource=this.resource order=this.resource params=@model.componentParams}}
1
+ {{#if this.parent.routingCompleted}}
2
+ {{component (lazy-engine-component @model.component) resource=this.resource order=this.resource params=@model.componentParams}}
3
+ {{else}}
4
+ <div class="flex items-center justify-center py-12">
5
+ <Spinner />
6
+ </div>
7
+ {{/if}}
@@ -123,6 +123,38 @@ export const contactTypes = [
123
123
  { label: 'Dispatcher', value: 'dispatcher', description: 'Scheduling and operations contact' },
124
124
  ];
125
125
 
126
+ export const entityTypes = [
127
+ { label: 'Parcel', value: 'parcel', description: 'Standard parcel or package used by parcel-based pricing.' },
128
+ { label: 'Package', value: 'package', description: 'Generic package or boxed item.' },
129
+ { label: 'Product', value: 'product', description: 'Sellable product or catalog item.' },
130
+ { label: 'Item', value: 'item', description: 'Generic inventory item when no stronger type applies.' },
131
+ { label: 'SKU / Inventory Unit', value: 'sku', description: 'Stock keeping unit or tracked inventory piece.' },
132
+ { label: 'Document', value: 'document', description: 'Envelopes, paperwork, and lightweight documents.' },
133
+ { label: 'Envelope', value: 'envelope', description: 'Flat document mailer or envelope.' },
134
+ { label: 'Box', value: 'box', description: 'Single boxed shipment unit.' },
135
+ { label: 'Carton', value: 'carton', description: 'Single carton, box, or case.' },
136
+ { label: 'Case', value: 'case', description: 'Case-packed goods or packaged case.' },
137
+ { label: 'Bundle', value: 'bundle', description: 'Bundled or strapped items shipped together.' },
138
+ { label: 'Bag', value: 'bag', description: 'Bagged goods, sacks, or pouches.' },
139
+ { label: 'Tote', value: 'tote', description: 'Reusable tote, bin, or container.' },
140
+ { label: 'Crate', value: 'crate', description: 'Rigid crate or caged shipment unit.' },
141
+ { label: 'Pallet', value: 'pallet', description: 'Palletized freight or grouped cargo.' },
142
+ { label: 'Container', value: 'container', description: 'Containerized shipment or storage container.' },
143
+ { label: 'Freight', value: 'freight', description: 'Loose freight or larger shipment unit.' },
144
+ { label: 'Cargo', value: 'cargo', description: 'General cargo not classified more specifically.' },
145
+ { label: 'Equipment', value: 'equipment', description: 'Tools, machines, or operational equipment.' },
146
+ { label: 'Machinery', value: 'machinery', description: 'Industrial machinery or large mechanical unit.' },
147
+ { label: 'Asset', value: 'asset', description: 'Tracked business asset or returnable item.' },
148
+ { label: 'Return', value: 'return', description: 'Returned goods or reverse-logistics item.' },
149
+ { label: 'Sample', value: 'sample', description: 'Sample item for trial, demo, or inspection.' },
150
+ { label: 'Spare Part', value: 'spare_part', description: 'Replacement component or service part.' },
151
+ { label: 'Medical Supply', value: 'medical_supply', description: 'Healthcare, lab, or medical delivery item.' },
152
+ { label: 'Food', value: 'food', description: 'Prepared food, meal, or grocery item.' },
153
+ { label: 'Beverage', value: 'beverage', description: 'Drink, liquid refreshment, or bottled shipment.' },
154
+ { label: 'Retail Goods', value: 'retail_goods', description: 'General consumer or retail merchandise.' },
155
+ { label: 'Passenger', value: 'passenger', description: 'Person transported as part of the order payload.' },
156
+ ];
157
+
126
158
  export const contactStatuses = [
127
159
  { label: 'Active', value: 'active', description: 'Contact is valid and in use' },
128
160
  { label: 'Inactive', value: 'inactive', description: 'Contact is no longer valid' },
@@ -821,6 +853,7 @@ export default function fleetOpsOptions(key) {
821
853
  fleetTypes,
822
854
  fleetStatuses,
823
855
  contactTypes,
856
+ entityTypes,
824
857
  contactStatuses,
825
858
  fuelReportTypes,
826
859
  fuelReportStatuses,
@@ -5,7 +5,7 @@ export default function serializePayload(payload) {
5
5
  const serialized = {
6
6
  pickup: serializeModel(payload.pickup),
7
7
  dropoff: serializeModel(payload.dropoff),
8
- entitities: serializeArray(payload.entities),
8
+ entities: serializeArray(payload.entities),
9
9
  waypoints: serializeArray(payload.waypoints),
10
10
  };
11
11
 
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/operations/orders/index/details/virtual';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/helpers/f-to-int';
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Fleet-Ops",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -44,7 +44,7 @@
44
44
  "@babel/core": "^7.23.2",
45
45
  "@fleetbase/ember-core": "^0.3.18",
46
46
  "@fleetbase/ember-ui": "^0.3.26",
47
- "@fleetbase/fleetops-data": "^0.1.29",
47
+ "@fleetbase/fleetops-data": "^0.1.30",
48
48
  "@fleetbase/leaflet-routing-machine": "^3.2.17",
49
49
  "@fortawesome/ember-fontawesome": "^2.0.0",
50
50
  "@fortawesome/fontawesome-svg-core": "6.4.0",