@fleetbase/fleetops-engine 0.6.52 → 0.6.54

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 (151) hide show
  1. package/addon/components/device/details.hbs +106 -135
  2. package/addon/components/device/details.js +174 -1
  3. package/addon/components/device/panel-header.hbs +2 -2
  4. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  5. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
  6. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  7. package/addon/components/layout/fleet-ops-sidebar.js +364 -395
  8. package/addon/components/maintenance-schedule/form.js +1 -0
  9. package/addon/components/order/form/details.hbs +1 -43
  10. package/addon/components/order/form/details.js +18 -53
  11. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  12. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  13. package/addon/components/order/form/payload.js +50 -2
  14. package/addon/components/order/form/route.js +15 -0
  15. package/addon/components/order/form/service-rate.hbs +62 -11
  16. package/addon/components/order/form/service-rate.js +124 -2
  17. package/addon/components/order/form.hbs +2 -0
  18. package/addon/components/telematic/details.hbs +7 -1
  19. package/addon/components/telematic/form.js +16 -8
  20. package/addon/components/work-order/details.hbs +5 -1
  21. package/addon/components/work-order/form.hbs +26 -4
  22. package/addon/components/work-order/form.js +5 -1
  23. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  24. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  25. package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
  26. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -1
  28. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  29. package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
  30. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  31. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  32. package/addon/controllers/maintenance/index.js +100 -0
  33. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  34. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  35. package/addon/controllers/management/index.js +88 -0
  36. package/addon/controllers/settings/index.js +142 -0
  37. package/addon/extension.js +21 -0
  38. package/addon/routes/analytics/index.js +3 -0
  39. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  40. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  41. package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
  42. package/addon/routes/connectivity/devices/index.js +3 -0
  43. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  44. package/addon/routes/connectivity/telematics/details/devices.js +4 -0
  45. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  46. package/addon/routes/connectivity/telematics/details.js +1 -1
  47. package/addon/routes/connectivity/telematics/edit.js +1 -1
  48. package/addon/routes/maintenance/index.js +3 -0
  49. package/addon/routes/maintenance/work-orders/index.js +3 -0
  50. package/addon/routes/settings/index.js +3 -0
  51. package/addon/routes.js +2 -0
  52. package/addon/services/order-creation.js +54 -1
  53. package/addon/services/order-validation.js +3 -2
  54. package/addon/services/service-rate-actions.js +1 -0
  55. package/addon/styles/fleetops-engine.css +1821 -6
  56. package/addon/templates/analytics/index.hbs +14 -0
  57. package/addon/templates/analytics/reports/index.hbs +4 -4
  58. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  59. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  60. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
  61. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  62. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  63. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  64. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  65. package/addon/templates/maintenance/index.hbs +113 -0
  66. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  67. package/addon/templates/management/index.hbs +109 -73
  68. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  69. package/addon/templates/operations/orders/index.hbs +1 -1
  70. package/addon/templates/operations/service-rates/index.hbs +1 -1
  71. package/addon/templates/settings/index.hbs +83 -0
  72. package/addon/templates/settings/routing.hbs +2 -2
  73. package/addon/utils/fleet-ops-options.js +38 -0
  74. package/app/components/order/form/orchestrator-constraints.js +1 -0
  75. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  76. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  77. package/app/controllers/maintenance/index.js +1 -0
  78. package/app/controllers/management/index.js +1 -0
  79. package/app/controllers/settings/index.js +1 -0
  80. package/app/routes/analytics/index.js +1 -0
  81. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  82. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  83. package/app/routes/maintenance/index.js +1 -0
  84. package/app/routes/settings/index.js +1 -0
  85. package/app/templates/analytics/index.js +1 -0
  86. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  87. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  88. package/app/templates/maintenance/index.js +1 -0
  89. package/app/templates/management/index.js +1 -0
  90. package/app/templates/settings/index.js +1 -0
  91. package/composer.json +1 -1
  92. package/extension.json +1 -1
  93. package/package.json +3 -3
  94. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  95. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  96. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  97. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  98. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  99. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  100. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  101. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  102. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  103. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
  104. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
  105. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  106. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  107. package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
  108. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  109. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  112. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  113. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  114. package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
  115. package/server/src/Http/Filter/DeviceFilter.php +131 -0
  116. package/server/src/Http/Filter/DriverFilter.php +6 -0
  117. package/server/src/Http/Filter/SensorFilter.php +125 -0
  118. package/server/src/Http/Filter/VehicleFilter.php +19 -0
  119. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  120. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  121. package/server/src/Http/Resources/v1/Driver.php +11 -2
  122. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  123. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  124. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  125. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  126. package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
  127. package/server/src/Models/Device.php +15 -1
  128. package/server/src/Models/DeviceEvent.php +1 -1
  129. package/server/src/Models/Driver.php +17 -1
  130. package/server/src/Models/Sensor.php +15 -2
  131. package/server/src/Models/Telematic.php +1 -1
  132. package/server/src/Models/Vehicle.php +5 -1
  133. package/server/src/Models/WorkOrder.php +5 -2
  134. package/server/src/Observers/DriverObserver.php +3 -3
  135. package/server/src/Observers/FleetObserver.php +23 -0
  136. package/server/src/Observers/VehicleObserver.php +3 -3
  137. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  138. package/server/src/Support/LiveCacheService.php +1 -1
  139. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
  140. package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
  141. package/server/src/Support/Telematics/TelematicService.php +330 -30
  142. package/server/src/routes.php +10 -0
  143. package/server/tests/DeviceFilterTest.php +36 -0
  144. package/server/tests/HubRoutesTest.php +62 -0
  145. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  146. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  147. package/server/tests/TelematicsHardeningTest.php +455 -3
  148. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  149. package/translations/ar-ae.yml +1 -1
  150. package/translations/en-us.yaml +1 -0
  151. package/translations/vi-vn.yaml +1 -1
@@ -0,0 +1,35 @@
1
+ import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
3
+
4
+ export default class ConnectivityDevicesIndexDetailsSensorsRoute extends Route {
5
+ @service store;
6
+
7
+ queryParams = {
8
+ sensors_page: { refreshModel: true },
9
+ sensors_limit: { refreshModel: true },
10
+ sensors_sort: { refreshModel: true },
11
+ sensors_query: { refreshModel: true },
12
+ sensors_status: { refreshModel: true },
13
+ sensors_type: { refreshModel: true },
14
+ sensors_last_reading_at: { refreshModel: true },
15
+ };
16
+
17
+ model(params) {
18
+ const device = this.modelFor('connectivity.devices.index.details');
19
+ return this.store.query('sensor', {
20
+ page: params.sensors_page,
21
+ limit: params.sensors_limit,
22
+ sort: params.sensors_sort,
23
+ query: params.sensors_query,
24
+ status: params.sensors_status,
25
+ type: params.sensors_type,
26
+ last_reading_at: params.sensors_last_reading_at,
27
+ device_uuid: device.id,
28
+ });
29
+ }
30
+
31
+ setupController(controller, model) {
32
+ super.setupController(controller, model);
33
+ controller.device = this.modelFor('connectivity.devices.index.details');
34
+ }
35
+ }
@@ -0,0 +1,7 @@
1
+ import Route from '@ember/routing/route';
2
+
3
+ export default class ConnectivityDevicesIndexDetailsVehicleRoute extends Route {
4
+ model() {
5
+ return this.modelFor('connectivity.devices.index.details');
6
+ }
7
+ }
@@ -11,6 +11,9 @@ export default class ConnectivityDevicesIndexRoute extends Route {
11
11
  query: { refreshModel: true },
12
12
  name: { refreshModel: true },
13
13
  public_id: { refreshModel: true },
14
+ status: { refreshModel: true },
15
+ attachment_state: { refreshModel: true },
16
+ telematic: { refreshModel: true },
14
17
  created_at: { refreshModel: true },
15
18
  updated_at: { refreshModel: true },
16
19
  };
@@ -1,28 +1,91 @@
1
1
  import Route from '@ember/routing/route';
2
2
  import { inject as service } from '@ember/service';
3
3
 
4
+ const PAGE_LIMIT = 100;
5
+ const MAX_PAGES = 100;
6
+
4
7
  export default class ConnectivityTelematicsDetailsAttachmentsRoute extends Route {
5
8
  @service store;
6
9
 
7
10
  queryParams = {
8
- query: { refreshModel: true },
9
- status: { refreshModel: true },
10
- attachment_state: { refreshModel: true },
11
- vehicle: { refreshModel: true },
11
+ query: { refreshModel: false },
12
+ status: { refreshModel: false },
13
+ vehicle: { refreshModel: false },
12
14
  sort: { refreshModel: true },
13
15
  };
14
16
 
15
- model(params) {
17
+ async model(params) {
16
18
  const telematic = this.modelFor('connectivity.telematics.details');
19
+ const selectedVehicle = await this.loadSelectedVehicle(params.vehicle);
20
+ const devices = [];
21
+ let page = 1;
22
+ let total = null;
23
+ let lastPage = null;
24
+
25
+ try {
26
+ do {
27
+ const pageDevices = await this.store.query('device', {
28
+ telematic_uuid: telematic.id,
29
+ sort: params.sort,
30
+ page,
31
+ limit: PAGE_LIMIT,
32
+ });
33
+ const records = Array.from(pageDevices ?? []);
34
+ const meta = pageDevices?.meta ?? {};
35
+ const perPage = Number(meta.per_page ?? meta.limit ?? PAGE_LIMIT);
36
+ const currentPage = Number(meta.current_page ?? meta.currentPage ?? page);
37
+
38
+ total = Number(meta.total ?? total ?? records.length);
39
+ lastPage = Number(meta.last_page ?? meta.lastPage ?? (total && perPage ? Math.ceil(total / perPage) : null));
40
+
41
+ devices.push(...records);
42
+
43
+ if (records.length === 0 || (lastPage && currentPage >= lastPage) || (!lastPage && records.length < PAGE_LIMIT)) {
44
+ break;
45
+ }
46
+
47
+ page += 1;
48
+ } while (page <= MAX_PAGES);
49
+
50
+ return {
51
+ devices,
52
+ error: null,
53
+ selectedVehicle,
54
+ meta: {
55
+ total: total ?? devices.length,
56
+ loaded: devices.length,
57
+ pages: page,
58
+ },
59
+ };
60
+ } catch (error) {
61
+ return {
62
+ devices: [],
63
+ error,
64
+ selectedVehicle,
65
+ meta: {
66
+ total: 0,
67
+ loaded: 0,
68
+ pages: page,
69
+ },
70
+ };
71
+ }
72
+ }
73
+
74
+ async loadSelectedVehicle(vehicleId) {
75
+ if (!vehicleId) {
76
+ return null;
77
+ }
17
78
 
18
- return this.store.query('device', {
19
- telematic_uuid: telematic.id,
20
- sort: params.sort,
21
- });
79
+ try {
80
+ return await this.store.findRecord('vehicle', vehicleId);
81
+ } catch {
82
+ return null;
83
+ }
22
84
  }
23
85
 
24
86
  setupController(controller, model) {
25
87
  super.setupController(controller, model);
26
88
  controller.telematic = this.modelFor('connectivity.telematics.details');
89
+ controller.selectedVehicle = model?.selectedVehicle ?? null;
27
90
  }
28
91
  }
@@ -12,7 +12,11 @@ export default class ConnectivityTelematicsDetailsDevicesRoute extends Route {
12
12
  status: { refreshModel: true },
13
13
  provider: { refreshModel: true },
14
14
  attachment_state: { refreshModel: true },
15
+ vehicle: { refreshModel: true },
16
+ connection_status: { refreshModel: true },
15
17
  device_id: { refreshModel: true },
18
+ last_online_at: { refreshModel: true },
19
+ updated_at: { refreshModel: true },
16
20
  };
17
21
 
18
22
  model(params) {
@@ -12,6 +12,7 @@ export default class ConnectivityTelematicsDetailsSensorsRoute extends Route {
12
12
  status: { refreshModel: true },
13
13
  type: { refreshModel: true },
14
14
  device_uuid: { refreshModel: true },
15
+ last_reading_at: { refreshModel: true },
15
16
  };
16
17
 
17
18
  model(params) {
@@ -24,6 +24,6 @@ export default class ConnectivityTelematicsDetailsRoute extends Route {
24
24
  }
25
25
 
26
26
  model({ public_id }) {
27
- return this.store.findRecord('telematic', public_id);
27
+ return this.store.queryRecord('telematic', { public_id, single: true });
28
28
  }
29
29
  }
@@ -24,6 +24,6 @@ export default class ConnectivityTelematicsEditRoute extends Route {
24
24
  }
25
25
 
26
26
  model({ public_id }) {
27
- return this.store.findRecord('telematic', public_id);
27
+ return this.store.queryRecord('telematic', { public_id, single: true });
28
28
  }
29
29
  }
@@ -0,0 +1,3 @@
1
+ import Route from '@ember/routing/route';
2
+
3
+ export default class MaintenanceIndexRoute extends Route {}
@@ -11,6 +11,9 @@ export default class MaintenanceWorkOrdersIndexRoute extends Route {
11
11
  query: { refreshModel: true },
12
12
  name: { refreshModel: true },
13
13
  public_id: { refreshModel: true },
14
+ category: { refreshModel: true },
15
+ status: { refreshModel: true },
16
+ priority: { refreshModel: true },
14
17
  created_at: { refreshModel: true },
15
18
  updated_at: { refreshModel: true },
16
19
  };
@@ -0,0 +1,3 @@
1
+ import Route from '@ember/routing/route';
2
+
3
+ export default class SettingsIndexRoute extends Route {}
package/addon/routes.js CHANGED
@@ -184,6 +184,8 @@ export default buildRoutes(function () {
184
184
  this.route('edit', { path: '/edit/:public_id' });
185
185
  this.route('details', { path: '/:public_id' }, function () {
186
186
  this.route('index', { path: '/' });
187
+ this.route('vehicle');
188
+ this.route('sensors');
187
189
  this.route('events');
188
190
  this.route('virtual', { path: '/:slug' });
189
191
  });
@@ -1,12 +1,17 @@
1
1
  import Service, { inject as service } from '@ember/service';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { next } from '@ember/runloop';
4
+ import Evented from '@ember/object/evented';
4
5
 
5
- export default class OrderCreationService extends Service {
6
+ export const SERVICE_QUOTE_REFRESH_REQUESTED = 'service-quote-refresh-requested';
7
+
8
+ export default class OrderCreationService extends Service.extend(Evented) {
6
9
  @service orderActions;
7
10
  @tracked context;
8
11
  @tracked order;
9
12
  @tracked cfManager;
13
+ @tracked serviceQuoteOverrides = {};
14
+ orderValidationRules = {};
10
15
 
11
16
  newOrder(attrs = {}) {
12
17
  const order = this.orderActions.createNewInstance(attrs);
@@ -34,4 +39,52 @@ export default class OrderCreationService extends Service {
34
39
  removeContext(key) {
35
40
  delete this.context[key];
36
41
  }
42
+
43
+ requestServiceQuoteRefresh(reason, order = this.order) {
44
+ this.trigger(SERVICE_QUOTE_REFRESH_REQUESTED, {
45
+ reason,
46
+ order,
47
+ });
48
+ }
49
+
50
+ setServiceQuoteOverride(key, config = {}) {
51
+ this.serviceQuoteOverrides = {
52
+ ...this.serviceQuoteOverrides,
53
+ [key]: {
54
+ ...config,
55
+ key,
56
+ },
57
+ };
58
+ }
59
+
60
+ getServiceQuoteOverride() {
61
+ return Object.values(this.serviceQuoteOverrides ?? {}).find((override) => override?.mode === 'locked') ?? null;
62
+ }
63
+
64
+ clearServiceQuoteOverride(key) {
65
+ const overrides = { ...(this.serviceQuoteOverrides ?? {}) };
66
+ delete overrides[key];
67
+ this.serviceQuoteOverrides = overrides;
68
+ }
69
+
70
+ setOrderValidationRule(key, fn) {
71
+ if (typeof fn !== 'function') {
72
+ return;
73
+ }
74
+
75
+ this.orderValidationRules = {
76
+ ...(this.orderValidationRules ?? {}),
77
+ [key]: fn,
78
+ };
79
+ }
80
+
81
+ clearOrderValidationRule(key) {
82
+ const rules = { ...(this.orderValidationRules ?? {}) };
83
+ delete rules[key];
84
+ this.orderValidationRules = rules;
85
+ }
86
+
87
+ validateOrderRules(order, cfManager = null) {
88
+ return Object.values(this.orderValidationRules ?? {}).every((fn) => fn(order, cfManager) !== false);
89
+ }
37
90
  }
@@ -24,12 +24,13 @@ export default class OrderValidationService extends Service {
24
24
  const hasPickup = isNotEmpty(order.payload.pickup);
25
25
  const hasDropoff = isNotEmpty(order.payload.dropoff);
26
26
  const hasValidCustomFields = cfManager ? this.isCustomFieldsValid(cfManager) : true;
27
+ const hasValidExtensionRules = this.orderCreation.validateOrderRules(order, cfManager);
27
28
 
28
29
  if (hasWaypoints) {
29
- return hasOrderConfig && hasOrderType && hasValidCustomFields;
30
+ return hasOrderConfig && hasOrderType && hasValidCustomFields && hasValidExtensionRules;
30
31
  }
31
32
 
32
- return hasPickup && hasDropoff && hasOrderConfig && hasOrderType && hasValidCustomFields;
33
+ return hasPickup && hasDropoff && hasOrderConfig && hasOrderType && hasValidCustomFields && hasValidExtensionRules;
33
34
  }
34
35
 
35
36
  validationFails(order, cfManager) {
@@ -134,6 +134,7 @@ export default class ServiceRateActionsService extends ResourceActionService {
134
134
  scheduled_at: order.scheduled_at,
135
135
  is_route_optimized: order.optimized,
136
136
  service: serviceRate.id,
137
+ meta: order.meta,
137
138
  });
138
139
 
139
140
  return serviceQuotes;