@fleetbase/fleetops-engine 0.6.70 → 0.6.71

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 (144) hide show
  1. package/RELEASE.md +21 -47
  2. package/addon/components/order/details/activity.hbs +8 -6
  3. package/addon/components/order/details/activity.js +9 -0
  4. package/addon/components/order/details.hbs +29 -0
  5. package/addon/components/order/details.js +26 -0
  6. package/addon/components/order/form/details.hbs +30 -4
  7. package/addon/components/order/form/details.js +46 -0
  8. package/addon/components/order/form/route.hbs +4 -2
  9. package/addon/components/order/form/route.js +11 -0
  10. package/addon/components/order/form.hbs +12 -0
  11. package/addon/components/order/form.js +21 -1
  12. package/addon/components/order/kanban.js +72 -0
  13. package/addon/components/order-config-manager/activity-flow.js +45 -10
  14. package/addon/components/order-config-manager/lifecycle.hbs +250 -0
  15. package/addon/components/order-config-manager/lifecycle.js +358 -0
  16. package/addon/components/order-config-manager.js +5 -1
  17. package/addon/controllers/analytics/reports/index.js +10 -1
  18. package/addon/controllers/connectivity/devices/index/details.js +1 -0
  19. package/addon/controllers/connectivity/devices/index.js +8 -2
  20. package/addon/controllers/connectivity/events/index.js +31 -1
  21. package/addon/controllers/connectivity/sensors/index/details.js +1 -1
  22. package/addon/controllers/connectivity/sensors/index.js +37 -1
  23. package/addon/controllers/maintenance/equipment/index/details.js +2 -2
  24. package/addon/controllers/maintenance/equipment/index.js +28 -9
  25. package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
  26. package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
  27. package/addon/controllers/maintenance/inspection-submissions/index.js +49 -9
  28. package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
  29. package/addon/controllers/maintenance/maintenances/index.js +20 -1
  30. package/addon/controllers/maintenance/parts/index/details.js +2 -2
  31. package/addon/controllers/maintenance/parts/index.js +28 -8
  32. package/addon/controllers/maintenance/schedules/index.js +33 -4
  33. package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
  34. package/addon/controllers/maintenance/work-orders/index.js +39 -8
  35. package/addon/controllers/management/contacts/customers.js +13 -0
  36. package/addon/controllers/management/contacts/index.js +43 -1
  37. package/addon/controllers/management/drivers/index/details.js +1 -0
  38. package/addon/controllers/management/drivers/index.js +32 -2
  39. package/addon/controllers/management/fleets/index/details.js +1 -0
  40. package/addon/controllers/management/fleets/index.js +39 -1
  41. package/addon/controllers/management/fuel-reports/index.js +22 -2
  42. package/addon/controllers/management/fuel-transactions/index.js +43 -7
  43. package/addon/controllers/management/issues/index/details.js +1 -0
  44. package/addon/controllers/management/issues/index.js +22 -2
  45. package/addon/controllers/management/places/index.js +36 -1
  46. package/addon/controllers/management/trailers/index/details.js +1 -0
  47. package/addon/controllers/management/trailers/index.js +50 -2
  48. package/addon/controllers/management/vehicles/index/details.js +1 -0
  49. package/addon/controllers/management/vehicles/index.js +33 -2
  50. package/addon/controllers/management/vendors/index.js +26 -2
  51. package/addon/controllers/operations/orders/index/details.js +31 -4
  52. package/addon/controllers/operations/orders/index.js +59 -4
  53. package/addon/controllers/operations/service-rates/index.js +15 -1
  54. package/addon/extension.js +32 -0
  55. package/addon/routes/operations/orders/index/details.js +1 -0
  56. package/addon/services/contact-actions.js +1 -0
  57. package/addon/services/customer-actions.js +2 -1
  58. package/addon/services/device-actions.js +1 -0
  59. package/addon/services/device-event-actions.js +1 -0
  60. package/addon/services/driver-actions.js +4 -13
  61. package/addon/services/equipment-actions.js +1 -0
  62. package/addon/services/fleet-actions.js +1 -0
  63. package/addon/services/fuel-report-actions.js +1 -0
  64. package/addon/services/inspection-form-actions.js +1 -0
  65. package/addon/services/inspection-submission-actions.js +1 -0
  66. package/addon/services/integrated-vendor-actions.js +1 -0
  67. package/addon/services/issue-actions.js +1 -0
  68. package/addon/services/maintenance-actions.js +1 -0
  69. package/addon/services/maintenance-schedule-actions.js +2 -1
  70. package/addon/services/order-actions.js +7 -0
  71. package/addon/services/order-presentation.js +129 -0
  72. package/addon/services/part-actions.js +1 -0
  73. package/addon/services/place-actions.js +1 -0
  74. package/addon/services/sensor-actions.js +1 -0
  75. package/addon/services/service-area-actions.js +1 -0
  76. package/addon/services/service-rate-actions.js +1 -0
  77. package/addon/services/telematic-actions.js +1 -0
  78. package/addon/services/trailer-actions.js +4 -14
  79. package/addon/services/vehicle-actions.js +4 -13
  80. package/addon/services/vendor-actions.js +1 -0
  81. package/addon/services/work-order-actions.js +1 -0
  82. package/addon/services/zone-actions.js +1 -0
  83. package/addon/templates/analytics/reports/index/details.hbs +1 -0
  84. package/addon/templates/analytics/reports/index.hbs +1 -0
  85. package/addon/templates/connectivity/devices/index/details.hbs +1 -0
  86. package/addon/templates/connectivity/devices/index.hbs +1 -0
  87. package/addon/templates/connectivity/events/index.hbs +1 -0
  88. package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
  89. package/addon/templates/connectivity/sensors/index.hbs +1 -0
  90. package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
  91. package/addon/templates/maintenance/equipment/index.hbs +2 -0
  92. package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
  93. package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
  94. package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
  95. package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
  96. package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
  97. package/addon/templates/maintenance/maintenances/index.hbs +1 -0
  98. package/addon/templates/maintenance/parts/index/details.hbs +1 -0
  99. package/addon/templates/maintenance/parts/index.hbs +2 -0
  100. package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
  101. package/addon/templates/maintenance/schedules/index.hbs +2 -0
  102. package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
  103. package/addon/templates/maintenance/work-orders/index.hbs +1 -0
  104. package/addon/templates/management/contacts/customers/details.hbs +1 -0
  105. package/addon/templates/management/contacts/customers.hbs +1 -0
  106. package/addon/templates/management/contacts/index/details.hbs +1 -0
  107. package/addon/templates/management/contacts/index.hbs +1 -0
  108. package/addon/templates/management/contacts.hbs +1 -0
  109. package/addon/templates/management/drivers/index/details.hbs +1 -0
  110. package/addon/templates/management/drivers/index.hbs +2 -0
  111. package/addon/templates/management/fleets/index/details.hbs +1 -0
  112. package/addon/templates/management/fleets/index.hbs +1 -0
  113. package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
  114. package/addon/templates/management/fuel-reports/index.hbs +1 -0
  115. package/addon/templates/management/fuel-transactions/index.hbs +1 -0
  116. package/addon/templates/management/issues/index/details.hbs +1 -0
  117. package/addon/templates/management/issues/index.hbs +1 -0
  118. package/addon/templates/management/places/index/details.hbs +1 -0
  119. package/addon/templates/management/places/index.hbs +1 -0
  120. package/addon/templates/management/trailers/index/details.hbs +1 -0
  121. package/addon/templates/management/trailers/index.hbs +2 -0
  122. package/addon/templates/management/vehicles/index/details.hbs +1 -0
  123. package/addon/templates/management/vehicles/index.hbs +2 -0
  124. package/addon/templates/management/vendors/index/details.hbs +1 -0
  125. package/addon/templates/management/vendors/index.hbs +2 -0
  126. package/addon/templates/management/vendors/integrated.hbs +1 -0
  127. package/addon/templates/operations/orders/index/details.hbs +1 -0
  128. package/addon/templates/operations/orders/index.hbs +1 -0
  129. package/addon/templates/operations/service-rates/index/details.hbs +1 -0
  130. package/addon/templates/operations/service-rates/index.hbs +1 -0
  131. package/addon/utils/normalize-order-config-flow.js +6 -1
  132. package/app/services/order-presentation.js +1 -0
  133. package/composer.json +1 -1
  134. package/extension.json +1 -1
  135. package/package.json +3 -3
  136. package/server/src/Http/Controllers/Internal/v1/OrderController.php +34 -4
  137. package/server/src/Models/Order.php +35 -3
  138. package/server/src/Models/OrderConfig.php +129 -0
  139. package/server/src/Models/TrackingNumber.php +8 -4
  140. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
  141. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
  142. package/server/tests/OrderConfigLifecycleTest.php +120 -0
  143. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
  144. package/translations/en-us.yaml +60 -0
@@ -10,7 +10,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
10
10
  @service intl;
11
11
 
12
12
  /** query params */
13
- @tracked queryParams = ['page', 'query', 'limit', 'sort', 'zone', 'service_area'];
13
+ @tracked queryParams = this.serviceRateActions.queryParamsFor(['page', 'query', 'limit', 'sort', 'zone', 'service_area']);
14
14
  @tracked page = 1;
15
15
  @tracked limit;
16
16
  @tracked query;
@@ -20,11 +20,13 @@ export default class OperationsServiceRatesIndexController extends Controller {
20
20
  get actionButtons() {
21
21
  return [
22
22
  {
23
+ id: 'refresh',
23
24
  icon: 'refresh',
24
25
  onClick: this.serviceRateActions.refresh,
25
26
  helpText: this.intl.t('common.refresh'),
26
27
  },
27
28
  {
29
+ id: 'create',
28
30
  text: this.intl.t('common.new'),
29
31
  type: 'primary',
30
32
  icon: 'plus',
@@ -32,6 +34,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
32
34
  permission: 'fleet-ops create service-rate',
33
35
  },
34
36
  {
37
+ id: 'export',
35
38
  text: this.intl.t('common.export'),
36
39
  icon: 'long-arrow-up',
37
40
  iconClass: 'rotate-icon-45',
@@ -46,6 +49,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
46
49
  get bulkActions() {
47
50
  return [
48
51
  {
52
+ id: 'bulk-delete',
49
53
  label: 'Delete selected...',
50
54
  class: 'text-red-500',
51
55
  fn: this.serviceRateActions.bulkDelete,
@@ -58,6 +62,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
58
62
  get columns() {
59
63
  return [
60
64
  {
65
+ id: 'public-id',
61
66
  sticky: true,
62
67
  label: this.intl.t('column.id'),
63
68
  valuePath: 'public_id',
@@ -70,6 +75,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
70
75
  filterComponent: 'filter/string',
71
76
  },
72
77
  {
78
+ id: 'service-name',
73
79
  label: this.intl.t('column.service'),
74
80
  valuePath: 'service_name',
75
81
  cellComponent: 'table/cell/base',
@@ -78,6 +84,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
78
84
  filterable: false,
79
85
  },
80
86
  {
87
+ id: 'service-area-name',
81
88
  label: this.intl.t('column.service-area'),
82
89
  valuePath: 'service_area.name',
83
90
  cellComponent: 'cell/service-area-identity',
@@ -93,6 +100,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
93
100
  model: 'service-area',
94
101
  },
95
102
  {
103
+ id: 'zone-name',
96
104
  label: this.intl.t('column.zone'),
97
105
  valuePath: 'zone.name',
98
106
  cellComponent: 'cell/zone-identity',
@@ -107,6 +115,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
107
115
  model: 'zone',
108
116
  },
109
117
  {
118
+ id: 'created-at',
110
119
  label: this.intl.t('column.created-at'),
111
120
  valuePath: 'createdAt',
112
121
  sortParam: 'created_at',
@@ -116,6 +125,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
116
125
  filterComponent: 'filter/date',
117
126
  },
118
127
  {
128
+ id: 'updated-at',
119
129
  label: this.intl.t('column.updated-at'),
120
130
  valuePath: 'updatedAt',
121
131
  sortParam: 'updated_at',
@@ -126,6 +136,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
126
136
  filterComponent: 'filter/date',
127
137
  },
128
138
  {
139
+ id: 'row-actions',
129
140
  label: '',
130
141
  cellComponent: 'table/cell/dropdown',
131
142
  ddButtonText: false,
@@ -137,18 +148,21 @@ export default class OperationsServiceRatesIndexController extends Controller {
137
148
  width: 60,
138
149
  actions: [
139
150
  {
151
+ id: 'view',
140
152
  label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.service-rate') }),
141
153
  icon: 'eye',
142
154
  fn: this.serviceRateActions.transition.view,
143
155
  permission: 'fleet-ops view service-rate',
144
156
  },
145
157
  {
158
+ id: 'edit',
146
159
  label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.service-rate') }),
147
160
  icon: 'pencil',
148
161
  fn: this.serviceRateActions.transition.edit,
149
162
  permission: 'fleet-ops view service-rate',
150
163
  },
151
164
  {
165
+ id: 'delete',
152
166
  label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.service-rate') }),
153
167
  icon: 'trash',
154
168
  fn: this.serviceRateActions.delete,
@@ -162,6 +162,38 @@ export default {
162
162
 
163
163
  // Create registries
164
164
  this.createRegistries(registryService);
165
+
166
+ // Declare the registries extensions can add table columns, actions and
167
+ // buttons to, e.g. `fleet-ops:driver:table:columns` or `fleet-ops:order:details:menu`.
168
+ universe
169
+ .getService('universe/resource-view-service')
170
+ ?.declare('fleet-ops', [
171
+ 'contact',
172
+ 'customer',
173
+ 'device',
174
+ 'device-event',
175
+ 'driver',
176
+ 'equipment',
177
+ 'fleet',
178
+ 'fuel-report',
179
+ 'fuel-transaction',
180
+ 'inspection-form',
181
+ 'inspection-submission',
182
+ 'integrated-vendor',
183
+ 'issue',
184
+ 'maintenance',
185
+ 'order',
186
+ 'part',
187
+ 'place',
188
+ 'report',
189
+ 'schedule',
190
+ 'sensor',
191
+ 'service-rate',
192
+ 'trailer',
193
+ 'vehicle',
194
+ 'vendor',
195
+ 'work-order',
196
+ ]);
165
197
  registryService.registerRenderableComponent('ai:action-preview:fleet-ops.create_order', new ExtensionComponent('@fleetbase/fleetops-engine', 'ai/create-order-preview'));
166
198
 
167
199
  // // Register console home guidance
@@ -60,6 +60,7 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
60
60
  with: [
61
61
  'payload',
62
62
  'driverAssigned',
63
+ 'vehicleAssigned',
63
64
  'orderConfig',
64
65
  'customer',
65
66
  'facilitator',
@@ -48,6 +48,7 @@ export default class ContactActionsService extends ResourceActionService {
48
48
  },
49
49
  view: (contact, options = {}) => {
50
50
  return this.resourceContextPanel.open({
51
+ registry: this.detailsRegistry,
51
52
  contact,
52
53
  title: contact?.name,
53
54
  header: 'contact/panel-header',
@@ -4,7 +4,7 @@ import { PANEL_DEFAULTS, registeredPanelTabs } from '../utils/context-panel';
4
4
  export default class CustomerActionsService extends ContactActionsService {
5
5
  constructor() {
6
6
  super(...arguments);
7
- this.initialize('contact', { defaultAttributes: { type: 'customer', status: 'active' } });
7
+ this.initialize('contact', { defaultAttributes: { type: 'customer', status: 'active' }, registryResource: 'customer' });
8
8
  }
9
9
 
10
10
  transition = {
@@ -38,6 +38,7 @@ export default class CustomerActionsService extends ContactActionsService {
38
38
  },
39
39
  view: (customer, options = {}) => {
40
40
  return this.resourceContextPanel.open({
41
+ registry: this.detailsRegistry,
41
42
  customer,
42
43
  title: customer?.name,
43
44
  header: 'contact/panel-header',
@@ -48,6 +48,7 @@ export default class DeviceActionsService extends ResourceActionService {
48
48
  const service = this;
49
49
 
50
50
  return this.resourceContextPanel.open({
51
+ registry: this.detailsRegistry,
51
52
  device,
52
53
  header: 'device/panel-header',
53
54
  title: device.name ?? device.serial_number,
@@ -17,6 +17,7 @@ export default class DeviceEventActionsService extends ResourceActionService {
17
17
  panel = {
18
18
  view: (deviceEvent) => {
19
19
  return this.resourceContextPanel.open({
20
+ registry: this.detailsRegistry,
20
21
  deviceEvent,
21
22
  tabs: [
22
23
  {
@@ -2,9 +2,7 @@ import ResourceActionService, { inject as service } from '@fleetbase/ember-core/
2
2
  import leafletIcon from '@fleetbase/ember-core/utils/leaflet-icon';
3
3
  import config from 'ember-get-config';
4
4
  import { action } from '@ember/object';
5
- import { isArray } from '@ember/array';
6
- import { dasherize } from '@ember/string';
7
- import { PANEL_DEFAULTS, closePanelsThen } from '../utils/context-panel';
5
+ import { PANEL_DEFAULTS, closePanelsThen, registeredPanelTabs } from '../utils/context-panel';
8
6
  import { confirmLoginAction, hasInactiveLogin, hasManagedLogin } from '../utils/profile-login-actions';
9
7
 
10
8
  export default class DriverActionsService extends ResourceActionService {
@@ -13,16 +11,8 @@ export default class DriverActionsService extends ResourceActionService {
13
11
  @service issueActions;
14
12
 
15
13
  get registeredTabs() {
16
- const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:driver:details');
17
- return (isArray(registeredTabs) ? registeredTabs : [])
18
- .map((tab) => {
19
- delete tab.route;
20
- if (!tab.key) {
21
- tab.key = tab.id ?? dasherize(tab.label ?? tab.title);
22
- }
23
- return tab;
24
- })
25
- .filter((tab) => !tab.component);
14
+ // Copies, so the shared registry items keep the \`route\` their routed details view needs.
15
+ return registeredPanelTabs(this.menuService, 'fleet-ops:component:driver:details');
26
16
  }
27
17
 
28
18
  get panelTabs() {
@@ -125,6 +115,7 @@ export default class DriverActionsService extends ResourceActionService {
125
115
  const service = this;
126
116
 
127
117
  return this.resourceContextPanel.open({
118
+ registry: this.detailsRegistry,
128
119
  driver,
129
120
  header: 'driver/panel-header',
130
121
  actionButtons: [
@@ -47,6 +47,7 @@ export default class EquipmentActionsService extends ResourceActionService {
47
47
  },
48
48
  view: (equipment, options = {}) => {
49
49
  return this.resourceContextPanel.open({
50
+ registry: this.detailsRegistry,
50
51
  equipment,
51
52
  title: equipment?.name,
52
53
  header: 'equipment/panel-header',
@@ -42,6 +42,7 @@ export default class FleetActionsService extends ResourceActionService {
42
42
  },
43
43
  view: (fleet, options = {}) => {
44
44
  return this.resourceContextPanel.open({
45
+ registry: this.detailsRegistry,
45
46
  fleet,
46
47
  title: fleet?.name,
47
48
  actionButtons: [
@@ -43,6 +43,7 @@ export default class FuelReportActionsService extends ResourceActionService {
43
43
  },
44
44
  view: (fuelReport, options = {}) => {
45
45
  return this.resourceContextPanel.open({
46
+ registry: this.detailsRegistry,
46
47
  fuelReport,
47
48
  title: fuelReport?.name ?? `Fuel reported on ${fuelReport?.createdAt ?? ''}`.trim(),
48
49
  actionButtons: [{ icon: 'pencil', permission: 'fleet-ops update fuel-report', fn: () => closePanelsThen(this.resourceContextPanel, () => this.panel.edit(fuelReport)) }],
@@ -59,6 +59,7 @@ export default class InspectionFormActionsService extends ResourceActionService
59
59
  const service = this;
60
60
 
61
61
  return this.resourceContextPanel.open({
62
+ registry: this.detailsRegistry,
62
63
  form,
63
64
  title: form?.name,
64
65
  get actionButtons() {
@@ -32,6 +32,7 @@ export default class InspectionSubmissionActionsService extends ResourceActionSe
32
32
  const service = this;
33
33
 
34
34
  return this.resourceContextPanel.open({
35
+ registry: this.detailsRegistry,
35
36
  submission,
36
37
  title: this.panelTitle(submission),
37
38
  actionButtons: [
@@ -37,6 +37,7 @@ export default class IntegratedVendorActionsService extends ResourceActionServic
37
37
  },
38
38
  view: (integratedVendor, options = {}) => {
39
39
  return this.resourceContextPanel.open({
40
+ registry: this.detailsRegistry,
40
41
  integratedVendor,
41
42
  tabs: [
42
43
  {
@@ -50,6 +50,7 @@ export default class IssueActionsService extends ResourceActionService {
50
50
  },
51
51
  view: (issue, options = {}) => {
52
52
  return this.resourceContextPanel.open({
53
+ registry: this.detailsRegistry,
53
54
  issue,
54
55
  header: 'issue/panel-header',
55
56
  width: '800px',
@@ -44,6 +44,7 @@ export default class MaintenanceActionsService extends ResourceActionService {
44
44
  },
45
45
  view: (maintenance, options = {}) => {
46
46
  return this.resourceContextPanel.open({
47
+ registry: this.detailsRegistry,
47
48
  maintenance,
48
49
  title: maintenance?.summary,
49
50
  header: 'maintenance/panel-header',
@@ -11,7 +11,7 @@ export default class MaintenanceScheduleActionsService extends ResourceActionSer
11
11
 
12
12
  constructor() {
13
13
  super(...arguments);
14
- this.initialize('maintenance-schedule');
14
+ this.initialize('maintenance-schedule', { registryResource: 'schedule' });
15
15
  }
16
16
 
17
17
  transition = {
@@ -43,6 +43,7 @@ export default class MaintenanceScheduleActionsService extends ResourceActionSer
43
43
  },
44
44
  view: (schedule, options = {}) => {
45
45
  return this.resourceContextPanel.open({
46
+ registry: this.detailsRegistry,
46
47
  schedule,
47
48
  title: schedule?.name,
48
49
  actionButtons: this.panelActionButtons(schedule, { onDeleted: () => this.resourceContextPanel.closeAll() }),
@@ -6,6 +6,7 @@ import { task } from 'ember-concurrency';
6
6
 
7
7
  export default class OrderActionsService extends ResourceActionService {
8
8
  @service store;
9
+ @service orderPresentation;
9
10
  modelNamePath = 'tracking';
10
11
 
11
12
  constructor() {
@@ -54,6 +55,7 @@ export default class OrderActionsService extends ResourceActionService {
54
55
  },
55
56
  view: (order, options = {}) => {
56
57
  return this.resourceContextPanel.open({
58
+ registry: this.detailsRegistry,
57
59
  order,
58
60
  tabs: [
59
61
  {
@@ -313,6 +315,11 @@ export default class OrderActionsService extends ResourceActionService {
313
315
  @action editOrderDetails(order, options = {}) {
314
316
  options = options === null ? {} : options;
315
317
 
318
+ // Orders with a presentation profile are edited in the profiled order form, not the fixed modal.
319
+ if (this.orderPresentation.profileFor(order)) {
320
+ return this.panel.edit(order, options);
321
+ }
322
+
316
323
  this.modalsManager.show('modals/order-form', {
317
324
  title: this.intl.t('order.actions.edit-order-details'),
318
325
  acceptButtonText: this.intl.t('common.save-changes'),
@@ -0,0 +1,129 @@
1
+ import Service, { inject as service } from '@ember/service';
2
+ import { isArray } from '@ember/array';
3
+
4
+ /**
5
+ * Resolves an extension-owned presentation profile for an order.
6
+ *
7
+ * A profile lets an extension compose the order form, details and actions for orders
8
+ * whose Order Config opts in via `order_config.meta.presentation_profile`. When no
9
+ * profile applies every consumer renders the standard Fleet-Ops presentation.
10
+ *
11
+ * Extensions register profiles through the registry service so registration does not
12
+ * depend on the Fleet-Ops engine having loaded:
13
+ *
14
+ * registryService.register('fleet-ops:order-presentation', 'profiles', 'my-profile', {
15
+ * id: 'my-profile',
16
+ * isEnabled() { return true; },
17
+ * form: { sections: ['details', 'route', new ExtensionComponent('@org/engine', 'my-section'), 'notes'] },
18
+ * details: { sections: [...] },
19
+ * hidden: { fields: ['driver', 'dispatch'], actions: ['dispatch', 'assign-driver'] },
20
+ * prepare(order) {},
21
+ * });
22
+ *
23
+ * Native section names — form: details, route, payload, service-rate, notes, documents,
24
+ * orchestrator-constraints, metadata, custom-fields. Details: activity, detail, custom-fields,
25
+ * purchase-rate, tracking, proof, notes, integrated-vendor-details, route, payload,
26
+ * documents, comments, metadata.
27
+ *
28
+ * `hidden.actions` takes built-in action ids, the same ids the order table and details use in
29
+ * the resource view registries: dispatch, assign-driver, unassign-driver, edit-order-details,
30
+ * update-activity, view-label, listen-to-socket-channel, view-metadata, cancel, delete.
31
+ */
32
+ export const ORDER_PRESENTATION_REGISTRY = 'fleet-ops:order-presentation';
33
+
34
+ export default class OrderPresentationService extends Service {
35
+ @service('universe/registry-service') registryService;
36
+
37
+ get profiles() {
38
+ return this.registryService.getRegistry(ORDER_PRESENTATION_REGISTRY, 'profiles') ?? [];
39
+ }
40
+
41
+ /**
42
+ * Returns the presentation profile applying to an order or order config, or null.
43
+ *
44
+ * @param {Object} orderOrConfig an order (with `order_config`) or an order-config
45
+ * @returns {Object|null}
46
+ */
47
+ profileFor(orderOrConfig) {
48
+ const orderConfig = this.#resolveOrderConfig(orderOrConfig);
49
+ const profileId = orderConfig?.meta?.presentation_profile;
50
+ if (!profileId) {
51
+ return null;
52
+ }
53
+
54
+ const profile = this.profiles.find((candidate) => candidate?.id === profileId);
55
+ if (!profile) {
56
+ return null;
57
+ }
58
+
59
+ if (typeof profile.isEnabled === 'function' && profile.isEnabled(orderConfig) !== true) {
60
+ return null;
61
+ }
62
+
63
+ return profile;
64
+ }
65
+
66
+ /**
67
+ * Returns the ordered sections of a profile surface ("form" or "details").
68
+ */
69
+ sectionsFor(profile, surface) {
70
+ const sections = profile?.[surface]?.sections;
71
+ return isArray(sections) ? sections : [];
72
+ }
73
+
74
+ /**
75
+ * Returns renderable section descriptors for a surface ("form" or "details").
76
+ * Native sections resolve to `order/<surface>/<name>`; anything else is an ExtensionComponent.
77
+ */
78
+ renderableSectionsFor(profile, surface) {
79
+ return this.sectionsFor(profile, surface).map((section, index) => {
80
+ if (typeof section === 'string') {
81
+ return { key: `native:${section}`, native: true, name: section, componentName: `order/${surface}/${section}` };
82
+ }
83
+
84
+ return { key: `extension:${section?.path ?? section?.name ?? index}`, native: false, component: section };
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Determines if a field or action is hidden by the profile applying to the subject.
90
+ *
91
+ * @param {Object} orderOrConfig
92
+ * @param {String} group "fields" or "actions"
93
+ * @param {String} key a field key, or a built-in action id
94
+ */
95
+ isHidden(orderOrConfig, group, key) {
96
+ const profile = this.profileFor(orderOrConfig);
97
+ const hidden = profile?.hidden?.[group];
98
+ return isArray(hidden) && hidden.includes(key);
99
+ }
100
+
101
+ hiddenFieldsFor(orderOrConfig) {
102
+ const hidden = this.profileFor(orderOrConfig)?.hidden?.fields;
103
+ return isArray(hidden) ? hidden : [];
104
+ }
105
+
106
+ /**
107
+ * Lets the profile normalize an order when it starts to apply (e.g. clear dispatch-only values).
108
+ */
109
+ prepare(order) {
110
+ const profile = this.profileFor(order);
111
+ if (profile && typeof profile.prepare === 'function') {
112
+ profile.prepare(order);
113
+ }
114
+ return profile;
115
+ }
116
+
117
+ #resolveOrderConfig(orderOrConfig) {
118
+ if (!orderOrConfig) {
119
+ return null;
120
+ }
121
+
122
+ // An order-config has a flow; an order references its config.
123
+ if (orderOrConfig.flow !== undefined && orderOrConfig.order_config === undefined) {
124
+ return orderOrConfig;
125
+ }
126
+
127
+ return orderOrConfig.order_config ?? null;
128
+ }
129
+ }
@@ -46,6 +46,7 @@ export default class PartActionsService extends ResourceActionService {
46
46
  },
47
47
  view: (part, options = {}) => {
48
48
  return this.resourceContextPanel.open({
49
+ registry: this.detailsRegistry,
49
50
  part,
50
51
  title: part?.name,
51
52
  header: 'part/panel-header',
@@ -52,6 +52,7 @@ export default class PlaceActionsService extends ResourceActionService {
52
52
  }
53
53
 
54
54
  return this.resourceContextPanel.open({
55
+ registry: this.detailsRegistry,
55
56
  place,
56
57
  title: place?.name ?? place?.street1,
57
58
  actionButtons: [{ icon: 'pencil', permission: 'fleet-ops update place', fn: () => closePanelsThen(this.resourceContextPanel, () => this.panel.edit(place)) }],
@@ -35,6 +35,7 @@ export default class SensorActionsService extends ResourceActionService {
35
35
  },
36
36
  view: (sensor) => {
37
37
  return this.resourceContextPanel.open({
38
+ registry: this.detailsRegistry,
38
39
  sensor,
39
40
  header: 'sensor/panel-header',
40
41
  tabs: [
@@ -41,6 +41,7 @@ export default class ServiceAreaActionsService extends ResourceActionService {
41
41
  },
42
42
  view: (serviceArea, options = {}) => {
43
43
  return this.resourceContextPanel.open({
44
+ registry: this.detailsRegistry,
44
45
  serviceArea,
45
46
  title: serviceArea?.name,
46
47
  actionButtons: [
@@ -51,6 +51,7 @@ export default class ServiceRateActionsService extends ResourceActionService {
51
51
  },
52
52
  view: (serviceRate) => {
53
53
  return this.resourceContextPanel.open({
54
+ registry: this.detailsRegistry,
54
55
  serviceRate,
55
56
  tabs: [
56
57
  {
@@ -41,6 +41,7 @@ export default class TelematicActionsService extends ResourceActionService {
41
41
  },
42
42
  view: (telematic) => {
43
43
  return this.resourceContextPanel.open({
44
+ registry: this.detailsRegistry,
44
45
  telematic,
45
46
  tabs: [
46
47
  {
@@ -2,9 +2,7 @@ import ResourceActionService, { inject as service } from '@fleetbase/ember-core/
2
2
  import leafletIcon from '@fleetbase/ember-core/utils/leaflet-icon';
3
3
  import config from 'ember-get-config';
4
4
  import { action } from '@ember/object';
5
- import { isArray } from '@ember/array';
6
- import { dasherize } from '@ember/string';
7
- import { PANEL_DEFAULTS, closePanelsThen } from '../utils/context-panel';
5
+ import { PANEL_DEFAULTS, closePanelsThen, registeredPanelTabs } from '../utils/context-panel';
8
6
 
9
7
  /**
10
8
  * Trailer resource actions.
@@ -32,17 +30,8 @@ export default class TrailerActionsService extends ResourceActionService {
32
30
  }
33
31
 
34
32
  get registeredTabs() {
35
- const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:trailer:details');
36
-
37
- return (isArray(registeredTabs) ? registeredTabs : [])
38
- .map((tab) => {
39
- delete tab.route;
40
- if (!tab.key) {
41
- tab.key = tab.id ?? dasherize(tab.label ?? tab.title);
42
- }
43
- return tab;
44
- })
45
- .filter((tab) => !tab.component);
33
+ // Copies, so the shared registry items keep the \`route\` their routed details view needs.
34
+ return registeredPanelTabs(this.menuService, 'fleet-ops:component:trailer:details');
46
35
  }
47
36
 
48
37
  get panelTabs() {
@@ -132,6 +121,7 @@ export default class TrailerActionsService extends ResourceActionService {
132
121
  const service = this;
133
122
 
134
123
  return this.resourceContextPanel.open({
124
+ registry: this.detailsRegistry,
135
125
  trailer,
136
126
  header: 'trailer/panel-header',
137
127
  actionButtons: [
@@ -2,9 +2,7 @@ import ResourceActionService, { inject as service } from '@fleetbase/ember-core/
2
2
  import leafletIcon from '@fleetbase/ember-core/utils/leaflet-icon';
3
3
  import config from 'ember-get-config';
4
4
  import { action } from '@ember/object';
5
- import { isArray } from '@ember/array';
6
- import { dasherize } from '@ember/string';
7
- import { PANEL_DEFAULTS, closePanelsThen } from '../utils/context-panel';
5
+ import { PANEL_DEFAULTS, closePanelsThen, registeredPanelTabs } from '../utils/context-panel';
8
6
 
9
7
  export default class VehicleActionsService extends ResourceActionService {
10
8
  @service('universe/menu-service') menuService;
@@ -15,16 +13,8 @@ export default class VehicleActionsService extends ResourceActionService {
15
13
  @service issueActions;
16
14
 
17
15
  get registeredTabs() {
18
- const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:vehicle:details');
19
- return (isArray(registeredTabs) ? registeredTabs : [])
20
- .map((tab) => {
21
- delete tab.route;
22
- if (!tab.key) {
23
- tab.key = tab.id ?? dasherize(tab.label ?? tab.title);
24
- }
25
- return tab;
26
- })
27
- .filter((tab) => !tab.component);
16
+ // Copies, so the shared registry items keep the \`route\` their routed details view needs.
17
+ return registeredPanelTabs(this.menuService, 'fleet-ops:component:vehicle:details');
28
18
  }
29
19
 
30
20
  get panelTabs() {
@@ -148,6 +138,7 @@ export default class VehicleActionsService extends ResourceActionService {
148
138
  const service = this;
149
139
 
150
140
  return this.resourceContextPanel.open({
141
+ registry: this.detailsRegistry,
151
142
  vehicle,
152
143
  header: 'vehicle/panel-header',
153
144
  actionButtons: [
@@ -43,6 +43,7 @@ export default class VendorActionsService extends ResourceActionService {
43
43
  },
44
44
  view: (vendor, options = {}) => {
45
45
  return this.resourceContextPanel.open({
46
+ registry: this.detailsRegistry,
46
47
  vendor,
47
48
  title: vendor?.name,
48
49
  header: 'vendor/panel-header',
@@ -41,6 +41,7 @@ export default class WorkOrderActionsService extends ResourceActionService {
41
41
  },
42
42
  view: (workOrder, options = {}) => {
43
43
  return this.resourceContextPanel.open({
44
+ registry: this.detailsRegistry,
44
45
  workOrder,
45
46
  title: workOrder?.code ?? workOrder?.subject,
46
47
  header: 'work-order/panel-header',
@@ -36,6 +36,7 @@ export default class ZoneActionsService extends ResourceActionService {
36
36
  },
37
37
  view: (zone, options = {}) => {
38
38
  return this.resourceContextPanel.open({
39
+ registry: this.detailsRegistry,
39
40
  zone,
40
41
  title: zone?.name,
41
42
  actionButtons: [