@fleetbase/fleetops-engine 0.6.46 → 0.6.48

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 (95) hide show
  1. package/addon/components/admin/routing-settings.hbs +71 -0
  2. package/addon/components/admin/routing-settings.js +124 -0
  3. package/addon/components/customer/orders.js +1 -1
  4. package/addon/components/display-place.hbs +1 -1
  5. package/addon/components/map/order-list-overlay/order.hbs +18 -3
  6. package/addon/components/map/toolbar.hbs +2 -2
  7. package/addon/components/map/toolbar.js +10 -2
  8. package/addon/components/orchestrator/phase-builder.hbs +28 -5
  9. package/addon/components/orchestrator/phase-builder.js +21 -1
  10. package/addon/components/orchestrator/resource-panel.hbs +19 -1
  11. package/addon/components/orchestrator/resource-panel.js +4 -0
  12. package/addon/components/orchestrator-workbench.js +3 -1
  13. package/addon/components/order/details/purchase-rate.hbs +19 -22
  14. package/addon/components/order/details/route.hbs +26 -1
  15. package/addon/components/order/details/route.js +50 -0
  16. package/addon/components/order/details/tracking.hbs +171 -27
  17. package/addon/components/order/details/tracking.js +457 -0
  18. package/addon/components/order/form/details.hbs +1 -2
  19. package/addon/components/order/form/details.js +6 -0
  20. package/addon/components/order/form/route.js +9 -1
  21. package/addon/components/order/kanban-card.hbs +18 -3
  22. package/addon/components/order-list-overlay/order.hbs +18 -3
  23. package/addon/components/order-progress-card.hbs +21 -6
  24. package/addon/components/order-tracking-lookup.hbs +7 -7
  25. package/addon/components/order-tracking-lookup.js +1 -1
  26. package/addon/components/route-list.hbs +78 -8
  27. package/addon/components/route-list.js +48 -6
  28. package/addon/components/tracking-stop-progress.hbs +45 -0
  29. package/addon/components/tracking-stop-progress.js +79 -0
  30. package/addon/controllers/settings/payments/index.js +1 -1
  31. package/addon/controllers/settings/routing.js +98 -1
  32. package/addon/helpers/format-duration.js +38 -0
  33. package/addon/routes/application.js +6 -0
  34. package/addon/services/order-list-overlay.js +2 -2
  35. package/addon/styles/fleetops-engine.css +733 -0
  36. package/addon/templates/settings/routing.hbs +60 -2
  37. package/app/components/tracking-stop-progress.js +1 -0
  38. package/app/helpers/format-duration.js +1 -0
  39. package/composer.json +1 -1
  40. package/extension.json +1 -1
  41. package/package.json +3 -3
  42. package/pnpm-workspace.yaml +7 -0
  43. package/server/config/fleetops.php +32 -11
  44. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
  45. package/server/seeders/VroomOrchestrationSeeder.php +402 -0
  46. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
  47. package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
  48. package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
  49. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
  50. package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
  51. package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
  52. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
  53. package/server/src/Http/Controllers/Internal/v1/OrderController.php +69 -15
  54. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  55. package/server/src/Http/Controllers/Internal/v1/SettingController.php +119 -12
  56. package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
  57. package/server/src/Models/Contact.php +122 -13
  58. package/server/src/Models/Order.php +1 -1
  59. package/server/src/Models/Payload.php +8 -8
  60. package/server/src/Models/Place.php +0 -3
  61. package/server/src/Models/ServiceRate.php +1 -1
  62. package/server/src/Observers/ContactObserver.php +8 -0
  63. package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
  64. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +330 -38
  65. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
  66. package/server/src/Providers/FleetOpsServiceProvider.php +27 -0
  67. package/server/src/Support/LiveOrderQuery.php +75 -0
  68. package/server/src/Support/Metrics.php +15 -0
  69. package/server/src/Support/OSRM.php +11 -6
  70. package/server/src/Support/OrderTracker.php +7 -582
  71. package/server/src/Support/Utils.php +1 -1
  72. package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
  73. package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
  74. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
  75. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
  76. package/server/src/Tracking/README.md +25 -0
  77. package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
  78. package/server/src/Tracking/TrackingContext.php +63 -0
  79. package/server/src/Tracking/TrackingContextBuilder.php +204 -0
  80. package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
  81. package/server/src/Tracking/TrackingOptions.php +49 -0
  82. package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
  83. package/server/src/Tracking/TrackingProviderManager.php +89 -0
  84. package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
  85. package/server/src/Tracking/TrackingProviderResult.php +20 -0
  86. package/server/src/Tracking/TrackingStop.php +59 -0
  87. package/server/src/routes.php +11 -0
  88. package/server/tests/CapacityAllocationEngineTest.php +108 -0
  89. package/server/tests/LiveOrderQueryTest.php +34 -0
  90. package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
  91. package/server/tests/OrchestratorConsumableApiTest.php +74 -0
  92. package/server/tests/PingDriverEndpointTest.php +19 -0
  93. package/server/tests/TrackingIntelligenceTest.php +176 -0
  94. package/server/tests/VroomOrchestrationEngineTest.php +159 -0
  95. package/translations/en-us.yaml +4 -0
@@ -1,6 +1,7 @@
1
1
  import Controller from '@ember/controller';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
4
5
  import { task } from 'ember-concurrency';
5
6
 
6
7
  export default class SettingsRoutingController extends Controller {
@@ -12,6 +13,19 @@ export default class SettingsRoutingController extends Controller {
12
13
  @tracked displayEngine = 'osrm';
13
14
  @tracked optimizationEngine = 'osrm';
14
15
  @tracked routingUnit = 'km';
16
+ @tracked trackingProvider = 'google_routes';
17
+ @tracked trackingFallbacks = ['osrm', 'calculated'];
18
+ @tracked trackingTrafficEnabled = true;
19
+ @tracked trackingCacheTtlSeconds = 60;
20
+ @tracked trackingRouteCacheTtlSeconds = 600;
21
+ @tracked trackingStaleLocationThresholdSeconds = 300;
22
+ @tracked trackingDefaultVehicleSpeedKph = 35;
23
+ @tracked showTrackingAdvancedSettings = false;
24
+ @tracked trackingProviderOptions = [
25
+ { value: 'google_routes', label: 'Google Routes' },
26
+ { value: 'osrm', label: 'OSRM' },
27
+ { value: 'calculated', label: 'Calculated' },
28
+ ];
15
29
  @tracked routingUnitOptions = [
16
30
  { label: 'Kilometers', value: 'km' },
17
31
  { label: 'Miles', value: 'mi' },
@@ -39,6 +53,8 @@ export default class SettingsRoutingController extends Controller {
39
53
  optimization_engine: this.optimizationEngine,
40
54
  unit: this.routingUnit,
41
55
  });
56
+ const trackingSettings = this.trackingSettingsPayload;
57
+ yield this.fetch.post('fleet-ops/settings/tracking-settings', trackingSettings);
42
58
  yield this.performAdditionalSaveTasks();
43
59
  // Save in local memory too
44
60
  this.currentUser.setOption('routing', {
@@ -47,7 +63,8 @@ export default class SettingsRoutingController extends Controller {
47
63
  routing_optimization_engine: this.optimizationEngine,
48
64
  unit: this.routingUnit,
49
65
  });
50
- this.notifications.success('Routing setting saved.');
66
+ this.currentUser.setOption('tracking', trackingSettings);
67
+ this.notifications.success('Routing and tracking settings saved.');
51
68
  } catch (error) {
52
69
  this.notifications.serverError(error);
53
70
  }
@@ -64,11 +81,91 @@ export default class SettingsRoutingController extends Controller {
64
81
  this.displayEngine = display_engine ?? router ?? 'osrm';
65
82
  this.optimizationEngine = optimization_engine ?? display_engine ?? router ?? 'osrm';
66
83
  this.routingUnit = unit;
84
+ const trackingSettings = yield this.fetch.get('fleet-ops/settings/tracking-settings');
85
+ this.trackingProvider = trackingSettings.provider ?? 'google_routes';
86
+ this.trackingFallbacks = this.normalizeFallbacks(trackingSettings.fallbacks);
87
+ this.trackingTrafficEnabled = trackingSettings.traffic_enabled ?? true;
88
+ this.trackingCacheTtlSeconds = trackingSettings.cache_ttl_seconds ?? 60;
89
+ this.trackingRouteCacheTtlSeconds = trackingSettings.route_cache_ttl_seconds ?? 600;
90
+ this.trackingStaleLocationThresholdSeconds = trackingSettings.stale_location_threshold_seconds ?? 300;
91
+ this.trackingDefaultVehicleSpeedKph = trackingSettings.default_vehicle_speed_kph ?? 35;
92
+ this.trackingProviderOptions = this.normalizeProviderOptions(trackingSettings.providers ?? this.trackingProviderOptions);
67
93
  } catch (error) {
68
94
  this.notifications.serverError(error);
69
95
  }
70
96
  }
71
97
 
98
+ get selectedTrackingFallbackOptions() {
99
+ const selected = new Set(this.normalizeFallbacks(this.trackingFallbacks));
100
+
101
+ return this.trackingProviderOptions.filter((option) => selected.has(this.optionValue(option)));
102
+ }
103
+
104
+ get trackingSettingsPayload() {
105
+ return {
106
+ provider: this.trackingProvider,
107
+ fallbacks: this.normalizeFallbacks(this.trackingFallbacks),
108
+ traffic_enabled: this.trackingTrafficEnabled,
109
+ cache_ttl_seconds: Number(this.trackingCacheTtlSeconds) || 60,
110
+ route_cache_ttl_seconds: Number(this.trackingRouteCacheTtlSeconds) || 600,
111
+ stale_location_threshold_seconds: Number(this.trackingStaleLocationThresholdSeconds) || 300,
112
+ default_vehicle_speed_kph: Number(this.trackingDefaultVehicleSpeedKph) || 35,
113
+ };
114
+ }
115
+
116
+ normalizeFallbacks(fallbacks) {
117
+ if (Array.isArray(fallbacks)) {
118
+ return fallbacks
119
+ .map((fallback) => this.optionValue(fallback))
120
+ .map((fallback) => String(fallback).trim())
121
+ .filter(Boolean);
122
+ }
123
+
124
+ return String(fallbacks ?? '')
125
+ .split(',')
126
+ .map((fallback) => fallback.trim())
127
+ .filter(Boolean);
128
+ }
129
+
130
+ normalizeProviderOptions(options = []) {
131
+ return options.map((option) => {
132
+ const value = this.optionValue(option);
133
+ const label = option?.label ?? option?.name ?? this.providerLabel(value);
134
+
135
+ return {
136
+ ...option,
137
+ key: option?.key ?? value,
138
+ name: option?.name ?? label,
139
+ value,
140
+ label,
141
+ };
142
+ });
143
+ }
144
+
145
+ providerLabel(value) {
146
+ if (value === 'osrm') {
147
+ return 'OSRM';
148
+ }
149
+
150
+ return String(value ?? '')
151
+ .replace(/[_-]+/g, ' ')
152
+ .replace(/\s+/g, ' ')
153
+ .trim()
154
+ .replace(/\b\w/g, (character) => character.toUpperCase());
155
+ }
156
+
157
+ optionValue(option) {
158
+ return typeof option === 'object' && option !== null ? (option.value ?? option.key) : option;
159
+ }
160
+
161
+ @action setTrackingFallbacks(options) {
162
+ this.trackingFallbacks = this.normalizeFallbacks(options);
163
+ }
164
+
165
+ @action toggleTrackingAdvancedSettings() {
166
+ this.showTrackingAdvancedSettings = !this.showTrackingAdvancedSettings;
167
+ }
168
+
72
169
  registerSaveTask(key, task) {
73
170
  if (!key || typeof task?.perform !== 'function') {
74
171
  return;
@@ -0,0 +1,38 @@
1
+ import { helper } from '@ember/component/helper';
2
+
3
+ export function formatDurationValue(seconds) {
4
+ const value = Number(seconds);
5
+
6
+ if (!Number.isFinite(value)) {
7
+ return '0s';
8
+ }
9
+
10
+ const totalSeconds = Math.max(0, Math.ceil(value));
11
+ const days = Math.floor(totalSeconds / 86400);
12
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
13
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
14
+ const remainingSeconds = totalSeconds % 60;
15
+ const parts = [];
16
+
17
+ if (days) {
18
+ parts.push(`${days}d`);
19
+ }
20
+
21
+ if (hours) {
22
+ parts.push(`${hours}h`);
23
+ }
24
+
25
+ if (!days && minutes) {
26
+ parts.push(`${minutes}m`);
27
+ }
28
+
29
+ if (!days && !hours && parts.length < 2 && remainingSeconds) {
30
+ parts.push(`${remainingSeconds}s`);
31
+ }
32
+
33
+ return parts.length ? parts.join(' ') : '0s';
34
+ }
35
+
36
+ export default helper(function formatDuration([seconds]) {
37
+ return formatDurationValue(seconds);
38
+ });
@@ -29,6 +29,7 @@ export default class ApplicationRoute extends Route {
29
29
 
30
30
  await this.location.getUserLocation();
31
31
  await this.#loadRoutingSettings();
32
+ await this.#loadTrackingSettings();
32
33
  await this.#loadMapSettings();
33
34
  }
34
35
 
@@ -37,6 +38,11 @@ export default class ApplicationRoute extends Route {
37
38
  this.currentUser.setOption('routing', routingSetting);
38
39
  }
39
40
 
41
+ async #loadTrackingSettings() {
42
+ const trackingSettings = await this.fetch.get('fleet-ops/settings/tracking-settings');
43
+ this.currentUser.setOption('tracking', trackingSettings);
44
+ }
45
+
40
46
  async #loadMapSettings() {
41
47
  await this.mapSettings.load();
42
48
  }
@@ -185,12 +185,12 @@ export default class OrderListOverlayService extends Service {
185
185
 
186
186
  #peekActiveOrders() {
187
187
  // Consider an order "active" if it has an assigned driver and is not terminal
188
- const TERMINAL = ['created', 'completed', 'canceled', 'expired'];
188
+ const TERMINAL = ['created', 'completed', 'canceled', 'expired', 'order_canceled', 'pending'];
189
189
  return this.#peekOrders((order) => !!order.driver_assigned && !TERMINAL.includes(order.status));
190
190
  }
191
191
 
192
192
  #peekDriverActiveOrders(driver) {
193
- const TERMINAL = ['created', 'completed', 'canceled', 'expired'];
193
+ const TERMINAL = ['created', 'completed', 'canceled', 'expired', 'order_canceled', 'pending'];
194
194
  return this.#peekOrders((order) => !!order.driver_assigned && order.driver_assigned?.id === driver.id && !TERMINAL.includes(order.status));
195
195
  }
196
196