@fleetbase/fleetops-engine 0.6.44 → 0.6.46

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 (172) hide show
  1. package/addon/components/admin/map-settings.hbs +26 -0
  2. package/addon/components/admin/map-settings.js +46 -0
  3. package/addon/components/admin/routing-settings.hbs +11 -0
  4. package/addon/components/cell/order-route-type.hbs +46 -0
  5. package/addon/components/cell/order-route-type.js +90 -0
  6. package/addon/components/cell/place-address.hbs +1 -0
  7. package/addon/components/display-place.hbs +45 -56
  8. package/addon/components/display-place.js +21 -0
  9. package/addon/components/entity/form.hbs +24 -0
  10. package/addon/components/entity/form.js +30 -0
  11. package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  12. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  13. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  14. package/addon/components/leaflet-draw-control.js +1 -1
  15. package/addon/components/maintenance-schedule/form.js +2 -1
  16. package/addon/components/map/drawer/driver-listing.js +5 -5
  17. package/addon/components/map/drawer/geofence-event-listing.js +5 -2
  18. package/addon/components/map/drawer/place-listing.js +5 -5
  19. package/addon/components/map/drawer/position-listing.js +102 -21
  20. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  21. package/addon/components/map/google-live-map.hbs +9 -0
  22. package/addon/components/map/google-live-map.js +445 -0
  23. package/addon/components/map/leaflet-live-map.hbs +186 -142
  24. package/addon/components/map/leaflet-live-map.js +121 -41
  25. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  26. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  27. package/addon/components/map/toolbar.hbs +3 -3
  28. package/addon/components/map/toolbar.js +9 -1
  29. package/addon/components/modals/map-layer-form.hbs +132 -63
  30. package/addon/components/modals/orchestrator-import.hbs +4 -11
  31. package/addon/components/modals/orchestrator-import.js +17 -1
  32. package/addon/components/modals/update-order-activity.hbs +1 -1
  33. package/addon/components/orchestrator/order-pool.js +3 -2
  34. package/addon/components/orchestrator-workbench.js +29 -41
  35. package/addon/components/order/details/detail.js +2 -2
  36. package/addon/components/order/details/payload.hbs +5 -5
  37. package/addon/components/order/details/payload.js +18 -1
  38. package/addon/components/order/details/route.hbs +2 -2
  39. package/addon/components/order/details/route.js +76 -3
  40. package/addon/components/order/form/payload.hbs +33 -0
  41. package/addon/components/order/form/payload.js +1 -0
  42. package/addon/components/order/form/route.hbs +8 -8
  43. package/addon/components/order/form/route.js +78 -18
  44. package/addon/components/order/form/service-rate.hbs +1 -1
  45. package/addon/components/order/form.js +2 -2
  46. package/addon/components/order/route-editor.hbs +209 -147
  47. package/addon/components/order/route-editor.js +143 -102
  48. package/addon/components/place/address.hbs +1 -0
  49. package/addon/components/place/address.js +12 -0
  50. package/addon/components/route-list.hbs +43 -55
  51. package/addon/components/route-list.js +47 -0
  52. package/addon/components/route-optimization-wizard-panel.js +3 -4
  53. package/addon/components/service-rate/details.hbs +6 -6
  54. package/addon/components/service-rate/form.hbs +28 -15
  55. package/addon/components/vehicle/card.hbs +4 -4
  56. package/addon/controllers/maintenance/schedules/index.js +2 -1
  57. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  58. package/addon/controllers/operations/orders/index/details.js +113 -15
  59. package/addon/controllers/operations/orders/index/new.js +1 -1
  60. package/addon/controllers/operations/orders/index.js +8 -1
  61. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  62. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  63. package/addon/controllers/settings/map.js +97 -0
  64. package/addon/controllers/settings/routing.js +47 -12
  65. package/addon/extension.js +11 -0
  66. package/addon/helpers/f-to-int.js +6 -0
  67. package/addon/helpers/place-address-html.js +6 -0
  68. package/addon/instance-initializers/register-google-routes.js +17 -0
  69. package/addon/instance-initializers/register-osrm.js +5 -0
  70. package/addon/routes/application.js +9 -3
  71. package/addon/routes/operations/orchestrator.js +13 -0
  72. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  73. package/addon/routes/operations/orders/index/details.js +47 -24
  74. package/addon/routes/operations/orders/index/new.js +9 -0
  75. package/addon/routes/operations/orders/index.js +0 -1
  76. package/addon/routes/settings/map.js +3 -0
  77. package/addon/routes.js +1 -0
  78. package/addon/services/entity-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +4 -6
  80. package/addon/services/geofence-event-bus.js +2 -1
  81. package/addon/services/geofence.js +204 -168
  82. package/addon/services/google-routes.js +300 -0
  83. package/addon/services/leaflet-draw-restriction.js +2 -1
  84. package/addon/services/leaflet-map-manager.js +21 -0
  85. package/addon/services/maintenance-actions.js +4 -6
  86. package/addon/services/map-adapter/google.js +2205 -0
  87. package/addon/services/map-adapter/leaflet.js +1042 -0
  88. package/addon/services/map-adapter-interface.js +551 -0
  89. package/addon/services/map-manager.js +768 -0
  90. package/addon/services/map-settings.js +75 -0
  91. package/addon/services/movement-tracker.js +83 -32
  92. package/addon/services/order-actions.js +11 -1
  93. package/addon/services/osrm.js +79 -6
  94. package/addon/services/part-actions.js +4 -6
  95. package/addon/services/position-playback.js +135 -62
  96. package/addon/services/route-engine.js +83 -0
  97. package/addon/services/service-areas.js +8 -1
  98. package/addon/services/service-rate-actions.js +8 -3
  99. package/addon/styles/fleetops-engine.css +555 -0
  100. package/addon/templates/management/vehicles/index.hbs +0 -1
  101. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  102. package/addon/templates/operations/orders/index/details.hbs +0 -1
  103. package/addon/templates/settings/map.hbs +53 -0
  104. package/addon/templates/settings/routing.hbs +15 -4
  105. package/addon/utils/fleet-ops-options.js +34 -0
  106. package/addon/utils/has-valid-coordinates.js +6 -0
  107. package/addon/utils/map-provider-options.js +6 -0
  108. package/addon/utils/order-route-editing.js +86 -0
  109. package/addon/utils/order-route-summary.js +20 -0
  110. package/addon/utils/place-address-html.js +41 -0
  111. package/addon/utils/route-colors.js +43 -12
  112. package/addon/utils/route-visualization.js +93 -0
  113. package/addon/utils/serialize-payload.js +1 -1
  114. package/addon/utils/vendor-integration.js +2 -1
  115. package/app/components/admin/map-settings.js +1 -0
  116. package/app/components/admin/routing-settings.js +1 -0
  117. package/app/components/cell/order-route-type.js +1 -0
  118. package/app/components/cell/place-address.js +1 -0
  119. package/app/components/map/google-live-map.js +1 -0
  120. package/app/components/place/address.js +1 -0
  121. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  122. package/app/controllers/settings/map.js +1 -0
  123. package/app/helpers/f-to-int.js +1 -0
  124. package/app/helpers/place-address-html.js +1 -0
  125. package/app/routes/settings/map.js +1 -0
  126. package/app/services/map-adapter/google.js +1 -0
  127. package/app/services/map-adapter/leaflet.js +1 -0
  128. package/app/services/map-adapter-interface.js +1 -0
  129. package/app/services/map-manager.js +1 -0
  130. package/app/services/map-settings.js +1 -0
  131. package/app/templates/settings/map.js +1 -0
  132. package/app/utils/has-valid-coordinates.js +1 -0
  133. package/app/utils/order-route-editing.js +2 -0
  134. package/app/utils/order-route-summary.js +1 -0
  135. package/app/utils/place-address-html.js +1 -0
  136. package/app/utils/route-colors.js +1 -1
  137. package/app/utils/route-visualization.js +1 -0
  138. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  139. package/composer.json +1 -1
  140. package/extension.json +1 -1
  141. package/package.json +3 -3
  142. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  143. package/server/src/Http/Controllers/Api/v1/OrderController.php +36 -8
  144. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  145. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  146. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  147. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +7 -2
  148. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  149. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  150. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  151. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  152. package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
  153. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  154. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  155. package/server/src/Http/Filter/OrderFilter.php +2 -1
  156. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  157. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  158. package/server/src/Http/Resources/v1/Order.php +20 -2
  159. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  160. package/server/src/Models/Entity.php +7 -0
  161. package/server/src/Models/Order.php +81 -8
  162. package/server/src/Models/OrderConfig.php +15 -0
  163. package/server/src/Models/Payload.php +131 -27
  164. package/server/src/Models/Place.php +182 -48
  165. package/server/src/Models/ServiceRate.php +125 -32
  166. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  168. package/server/src/Rules/ComputableAlgo.php +1 -3
  169. package/server/src/Support/Algo.php +87 -1
  170. package/server/src/Support/Utils.php +69 -0
  171. package/server/src/routes.php +4 -0
  172. package/translations/en-us.yaml +1937 -1916
@@ -1,5 +1,15 @@
1
+ /**
2
+ * PositionPlaybackService
3
+ *
4
+ * Refactored to use the provider-agnostic MapManagerService for all
5
+ * marker operations. The Leaflet-specific `marker._map`, `marker.slideTo`,
6
+ * `marker.setRotationAngle`, and `map.panTo` calls are replaced with
7
+ * adapter calls. Backward-compat: if `this.marker` is still a raw Leaflet
8
+ * marker (set via the old `leafletLayer` option), the Leaflet path is used.
9
+ */
1
10
  import Service from '@ember/service';
2
11
  import { tracked } from '@glimmer/tracking';
12
+ import { inject as service } from '@ember/service';
3
13
  import { task, timeout } from 'ember-concurrency';
4
14
  import { debug } from '@ember/debug';
5
15
  import { isArray } from '@ember/array';
@@ -21,14 +31,18 @@ import { isArray } from '@ember/array';
21
31
  * - Real-time replay: respects actual time intervals between positions
22
32
  */
23
33
  export default class PositionPlaybackService extends Service {
34
+ @service mapManager;
35
+
24
36
  @tracked isPlaying = false;
25
37
  @tracked isPaused = false;
26
38
  @tracked currentIndex = 0;
27
39
  @tracked positions = [];
28
40
  @tracked speed = 1;
29
41
  @tracked marker = null;
42
+ @tracked markerId = null;
30
43
  @tracked map = null;
31
44
  @tracked callback = null;
45
+ @tracked lastLatLng = null;
32
46
 
33
47
  /**
34
48
  * Initialize replay session with positions and marker
@@ -43,12 +57,19 @@ export default class PositionPlaybackService extends Service {
43
57
  */
44
58
  initialize(options = {}) {
45
59
  const { subject, leafletLayer, positions = [], speed = 1, callback = null, map = null } = options;
46
-
47
- // Get marker from subject or manual layer
48
- this.marker = leafletLayer || subject?.leafletLayer || subject?._layer || subject?._marker;
49
-
50
- if (!this.marker) {
51
- debug('[PositionPlayback] Warning: No leaflet marker found. Marker must be provided or subject must have leafletLayer property.');
60
+ const subjectId = subject?.id ?? null;
61
+ const existingLeafletMarker = leafletLayer || subject?.leafletLayer || subject?._layer || subject?._marker;
62
+
63
+ if (subjectId && this.mapManager?.hasMarker(subjectId)) {
64
+ this.markerId = subjectId;
65
+ this.marker = null;
66
+ } else if (existingLeafletMarker) {
67
+ this.markerId = null;
68
+ this.marker = existingLeafletMarker;
69
+ } else {
70
+ this.markerId = null;
71
+ this.marker = null;
72
+ debug('[PositionPlayback] Warning: No marker found. Provide a subject with a registered marker or leafletLayer property.');
52
73
  }
53
74
 
54
75
  this.positions = positions;
@@ -58,8 +79,9 @@ export default class PositionPlaybackService extends Service {
58
79
  this.currentIndex = 0;
59
80
  this.isPlaying = false;
60
81
  this.isPaused = false;
82
+ this.lastLatLng = this.#resolveCurrentLatLng();
61
83
 
62
- debug(`[PositionPlayback] Initialized with ${positions.length} positions at ${speed}x speed`);
84
+ debug(`[PositionPlayback] Initialized with ${positions.length} positions at ${speed}x speed (markerId=${this.markerId ?? 'leaflet-compat'})`);
63
85
  }
64
86
 
65
87
  /**
@@ -173,35 +195,41 @@ export default class PositionPlaybackService extends Service {
173
195
  const position = this.positions[index];
174
196
  this.currentIndex = index;
175
197
 
176
- if (!this.marker || !position) {
198
+ const useAdapter = this.markerId && this.mapManager?.hasMarker(this.markerId);
199
+
200
+ if (!useAdapter && !this.marker) {
177
201
  return;
178
202
  }
179
203
 
180
- // Update marker position without animation
181
204
  const latLng = this.#getLatLngFromPosition(position);
182
205
  if (latLng) {
183
- // Update rotation if heading is available
184
- if (typeof this.marker.setRotationAngle === 'function' && Number.isFinite(position.heading) && position.heading !== -1) {
185
- this.marker.setRotationAngle(position.heading);
186
- }
187
-
188
- if (typeof this.marker.slideTo === 'function') {
189
- this.marker.slideTo(latLng, { duration: 100 });
206
+ if (useAdapter) {
207
+ // Provider-agnostic path
208
+ if (Number.isFinite(position.heading) && position.heading !== -1) {
209
+ this.mapManager.setMarkerRotation(this.markerId, position.heading);
210
+ }
211
+ this.mapManager.updateMarkerPosition(this.markerId, latLng[0], latLng[1], true, 100);
212
+ this.mapManager.panTo(latLng[0], latLng[1]);
190
213
  } else {
191
- this.marker.setLatLng(latLng);
192
- requestAnimationFrame(() => {
193
- if (typeof this.marker.setRotationAngle === 'function' && Number.isFinite(position.heading) && position.heading !== -1) {
194
- this.marker.setRotationAngle(position.heading);
195
- }
196
- });
197
- }
198
-
199
- // Pan map to position if map is provided
200
- if (this.map) {
201
- this.map.panTo(latLng, { animate: true });
214
+ // Leaflet backward-compat path
215
+ if (typeof this.marker.setRotationAngle === 'function' && Number.isFinite(position.heading) && position.heading !== -1) {
216
+ this.marker.setRotationAngle(position.heading);
217
+ }
218
+ if (typeof this.marker.slideTo === 'function') {
219
+ this.marker.slideTo(latLng, { duration: 100 });
220
+ } else {
221
+ this.marker.setLatLng(latLng);
222
+ requestAnimationFrame(() => {
223
+ if (typeof this.marker.setRotationAngle === 'function' && Number.isFinite(position.heading) && position.heading !== -1) {
224
+ this.marker.setRotationAngle(position.heading);
225
+ }
226
+ });
227
+ }
228
+ if (this.map) {
229
+ this.map.panTo(latLng, { animate: true });
230
+ }
202
231
  }
203
232
 
204
- // Trigger callback
205
233
  this.#triggerCallback(position, index, { animated: false });
206
234
  }
207
235
 
@@ -236,9 +264,11 @@ export default class PositionPlaybackService extends Service {
236
264
  this.stop();
237
265
  this.positions = [];
238
266
  this.marker = null;
267
+ this.markerId = null;
239
268
  this.map = null;
240
269
  this.callback = null;
241
270
  this.speed = 1;
271
+ this.lastLatLng = null;
242
272
 
243
273
  debug('[PositionPlayback] Reset complete');
244
274
  }
@@ -260,9 +290,11 @@ export default class PositionPlaybackService extends Service {
260
290
  continue;
261
291
  }
262
292
 
263
- // Get marker (it might have been updated)
264
- const marker = this.marker;
265
- if (!marker || !marker._map) {
293
+ // Resolve marker — prefer adapter, fall back to Leaflet
294
+ const useAdapter = this.markerId && this.mapManager?.hasMarker(this.markerId);
295
+ const marker = useAdapter ? null : this.marker;
296
+
297
+ if (!useAdapter && (!marker || !marker._map)) {
266
298
  debug('[PositionPlayback] Marker not available or not on map');
267
299
  this.currentIndex++;
268
300
  continue;
@@ -276,26 +308,35 @@ export default class PositionPlaybackService extends Service {
276
308
  continue;
277
309
  }
278
310
 
279
- // Calculate animation duration based on distance and speed
280
- const animationDuration = this.#calculateAnimationDuration(marker, latLng, position);
311
+ // Calculate animation duration
312
+ const animationDuration = useAdapter ? this.#calculateAnimationDurationFromAdapter(latLng, position) : this.#calculateAnimationDuration(marker, latLng, position);
281
313
 
282
314
  try {
283
- // Apply rotation if heading is valid
284
- if (typeof marker.setRotationAngle === 'function' && Number.isFinite(position.heading) && position.heading !== -1) {
285
- marker.setRotationAngle(position.heading);
286
- }
287
-
288
- // Move marker with animation
289
- if (typeof marker.slideTo === 'function') {
290
- marker.slideTo(latLng, { duration: animationDuration });
315
+ if (useAdapter) {
316
+ // ── Provider-agnostic path ─────────────────────────────
317
+ if (Number.isFinite(position.heading) && position.heading !== -1) {
318
+ this.mapManager.setMarkerRotation(this.markerId, position.heading);
319
+ }
320
+ this.mapManager.updateMarkerPosition(this.markerId, latLng[0], latLng[1], true, animationDuration);
321
+ if (this.map || this.mapManager.isReady) {
322
+ this.mapManager.panTo(latLng[0], latLng[1]);
323
+ }
324
+ this.lastLatLng = latLng;
291
325
  } else {
292
- marker.setLatLng(latLng);
293
- }
294
-
295
- // Pan map to follow marker if map is provided
296
- if (this.map) {
297
- const targetLatLng = marker._slideToLatLng ?? marker.getLatLng();
298
- this.map.panTo(targetLatLng, { animate: true });
326
+ // ── Leaflet backward-compat path ───────────────────────
327
+ if (typeof marker.setRotationAngle === 'function' && Number.isFinite(position.heading) && position.heading !== -1) {
328
+ marker.setRotationAngle(position.heading);
329
+ }
330
+ if (typeof marker.slideTo === 'function') {
331
+ marker.slideTo(latLng, { duration: animationDuration });
332
+ } else {
333
+ marker.setLatLng(latLng);
334
+ }
335
+ if (this.map) {
336
+ const targetLatLng = marker._slideToLatLng ?? marker.getLatLng();
337
+ this.map.panTo(targetLatLng, { animate: true });
338
+ }
339
+ this.lastLatLng = latLng;
299
340
  }
300
341
 
301
342
  // Trigger callback
@@ -444,23 +485,31 @@ export default class PositionPlaybackService extends Service {
444
485
  const prev = marker.getLatLng();
445
486
  const meters = map ? map.distance(prev, nextLatLng) : prev.distanceTo(nextLatLng);
446
487
 
447
- // Get speed from position data (assume m/s)
448
488
  let mps = Number.isFinite(position.speed) && position.speed > 0 ? position.speed : null;
489
+ if (mps && position.speed_unit === 'kmh') mps = mps / 3.6;
449
490
 
450
- // If speed is in km/h, convert to m/s
451
- if (mps && position.speed_unit === 'kmh') {
452
- mps = mps / 3.6;
453
- }
454
-
455
- // Calculate base duration for animation
456
- let baseDuration = mps ? (meters / mps) * 1000 : 500;
457
-
458
- // For animation, we want it relatively quick regardless of playback speed
459
- // The playback speed affects the delay between positions, not the animation speed
460
- // Clamp between 100ms and 1000ms for smooth animation
461
- const duration = Math.max(100, Math.min(baseDuration, 1000));
491
+ const baseDuration = mps ? (meters / mps) * 1000 : 500;
492
+ return Math.max(100, Math.min(baseDuration, 1000));
493
+ }
462
494
 
463
- return duration;
495
+ /**
496
+ * Calculate animation duration using the adapter (no direct Leaflet access).
497
+ *
498
+ * @private
499
+ * @param {Array} nextLatLng - [lat, lng]
500
+ * @param {Object} position
501
+ * @returns {number}
502
+ */
503
+ #calculateAnimationDurationFromAdapter(nextLatLng, position) {
504
+ const currentLatLng = this.lastLatLng ?? this.#resolveCurrentLatLng();
505
+ let meters = 0;
506
+ if (currentLatLng) {
507
+ meters = this.mapManager.distanceBetween(currentLatLng[0], currentLatLng[1], nextLatLng[0], nextLatLng[1]);
508
+ }
509
+ let mps = Number.isFinite(position.speed) && position.speed > 0 ? position.speed : null;
510
+ if (mps && position.speed_unit === 'kmh') mps = mps / 3.6;
511
+ const baseDuration = mps ? (meters / mps) * 1000 : 500;
512
+ return Math.max(100, Math.min(baseDuration, 1000));
464
513
  }
465
514
 
466
515
  /**
@@ -483,4 +532,28 @@ export default class PositionPlaybackService extends Service {
483
532
  });
484
533
  }
485
534
  }
535
+
536
+ #resolveCurrentLatLng() {
537
+ if (this.markerId && this.mapManager?.hasMarker?.(this.markerId)) {
538
+ const marker = this.mapManager.getMarker(this.markerId);
539
+ const position = marker?.position ?? marker?.getPosition?.();
540
+
541
+ if (position) {
542
+ const lat = typeof position.lat === 'function' ? position.lat() : position.lat;
543
+ const lng = typeof position.lng === 'function' ? position.lng() : position.lng;
544
+ if (Number.isFinite(lat) && Number.isFinite(lng)) {
545
+ return [lat, lng];
546
+ }
547
+ }
548
+ }
549
+
550
+ if (this.marker?.getLatLng) {
551
+ const latlng = this.marker.getLatLng();
552
+ if (Number.isFinite(latlng?.lat) && Number.isFinite(latlng?.lng)) {
553
+ return [latlng.lat, latlng.lng];
554
+ }
555
+ }
556
+
557
+ return null;
558
+ }
486
559
  }
@@ -0,0 +1,83 @@
1
+ import Service, { inject as service } from '@ember/service';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { underscore } from '@ember/string';
4
+
5
+ export class RouteEngineRegistry {
6
+ @tracked engines = {};
7
+ }
8
+
9
+ export default class RouteEngineService extends Service {
10
+ @service universe;
11
+ @service currentUser;
12
+ registry = this.#initializeRegistry();
13
+
14
+ get availableServices() {
15
+ return Object.entries(this.registry.engines)
16
+ .filter(([, registration]) => registration?.capabilities?.display === true)
17
+ .map(([key, registration]) => ({
18
+ key,
19
+ name: registration?.engine?.name ?? key,
20
+ }));
21
+ }
22
+
23
+ register(name, engine, capabilities = {}) {
24
+ const key = underscore(name);
25
+ const normalizedCapabilities = {
26
+ display: capabilities.display === true || typeof engine?.computeRoute === 'function',
27
+ };
28
+
29
+ if (!normalizedCapabilities.display) {
30
+ throw new Error(`Cannot register "${name}" as a route engine without computeRoute() support`);
31
+ }
32
+
33
+ this.registry.engines = {
34
+ ...this.registry.engines,
35
+ [key]: {
36
+ engine,
37
+ capabilities: normalizedCapabilities,
38
+ },
39
+ };
40
+ }
41
+
42
+ unregister(name) {
43
+ const key = underscore(name);
44
+ const rest = { ...this.registry.engines };
45
+ delete rest[key];
46
+ this.registry.engines = rest;
47
+ }
48
+
49
+ get(name) {
50
+ const key = underscore(name ?? this.getDisplayEngine());
51
+ const registration = this.registry.engines[key] ?? this.registry.engines.osrm;
52
+ return registration?.engine ?? null;
53
+ }
54
+
55
+ getDisplayEngine(fallback = 'osrm') {
56
+ const routingSettings = this.currentUser.getOption('routing', {});
57
+ return routingSettings.routing_display_engine ?? routingSettings.router ?? fallback;
58
+ }
59
+
60
+ getOptimizationEngine(fallback = null) {
61
+ const routingSettings = this.currentUser.getOption('routing', {});
62
+ return routingSettings.routing_optimization_engine ?? routingSettings.router ?? this.getDisplayEngine(fallback ?? 'osrm');
63
+ }
64
+
65
+ async compute(name, waypoints, options = {}) {
66
+ const engine = this.get(name);
67
+ if (!engine || typeof engine.computeRoute !== 'function') {
68
+ throw new Error(`No route engine registered as "${name}"`);
69
+ }
70
+
71
+ return engine.computeRoute(waypoints, options);
72
+ }
73
+
74
+ #initializeRegistry() {
75
+ const registry = 'registry:route-engines';
76
+ const application = this.universe.getApplicationInstance();
77
+ if (!application.hasRegistration(registry)) {
78
+ application.register(registry, new RouteEngineRegistry(), { instantiate: false });
79
+ }
80
+
81
+ return application.resolveRegistration(registry);
82
+ }
83
+ }
@@ -312,6 +312,7 @@ export default class ServiceAreasService extends Service {
312
312
 
313
313
  this.modalsManager.show('modals/map-layer-form', {
314
314
  title: 'Create new Layer',
315
+ modalClass: 'flb-resource-modal',
315
316
  acceptButtonText: 'Create',
316
317
  acceptButtonIcon: 'magic',
317
318
  declineButtonIcon: 'times',
@@ -319,7 +320,13 @@ export default class ServiceAreasService extends Service {
319
320
  layerTypes: ['Service Area', 'Zone'],
320
321
  selectedLayerType: 'Service Area',
321
322
  serviceAreaTypes: this.serviceAreaTypes,
322
- layerOptions: {},
323
+ layerOptions: {
324
+ trigger_on_entry: false,
325
+ trigger_on_exit: false,
326
+ dwell_threshold_minutes: null,
327
+ speed_limit_kmh: null,
328
+ description: null,
329
+ },
323
330
  confirm: (modal) => {
324
331
  modal.startLoading();
325
332
 
@@ -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,