@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.
- package/RELEASE.md +21 -47
- package/addon/components/order/details/activity.hbs +8 -6
- package/addon/components/order/details/activity.js +9 -0
- package/addon/components/order/details.hbs +29 -0
- package/addon/components/order/details.js +26 -0
- package/addon/components/order/form/details.hbs +30 -4
- package/addon/components/order/form/details.js +46 -0
- package/addon/components/order/form/route.hbs +4 -2
- package/addon/components/order/form/route.js +11 -0
- package/addon/components/order/form.hbs +12 -0
- package/addon/components/order/form.js +21 -1
- package/addon/components/order/kanban.js +72 -0
- package/addon/components/order-config-manager/activity-flow.js +45 -10
- package/addon/components/order-config-manager/lifecycle.hbs +250 -0
- package/addon/components/order-config-manager/lifecycle.js +358 -0
- package/addon/components/order-config-manager.js +5 -1
- package/addon/controllers/analytics/reports/index.js +10 -1
- package/addon/controllers/connectivity/devices/index/details.js +1 -0
- package/addon/controllers/connectivity/devices/index.js +8 -2
- package/addon/controllers/connectivity/events/index.js +31 -1
- package/addon/controllers/connectivity/sensors/index/details.js +1 -1
- package/addon/controllers/connectivity/sensors/index.js +37 -1
- package/addon/controllers/maintenance/equipment/index/details.js +2 -2
- package/addon/controllers/maintenance/equipment/index.js +28 -9
- package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
- package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
- package/addon/controllers/maintenance/inspection-submissions/index.js +49 -9
- package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
- package/addon/controllers/maintenance/maintenances/index.js +20 -1
- package/addon/controllers/maintenance/parts/index/details.js +2 -2
- package/addon/controllers/maintenance/parts/index.js +28 -8
- package/addon/controllers/maintenance/schedules/index.js +33 -4
- package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
- package/addon/controllers/maintenance/work-orders/index.js +39 -8
- package/addon/controllers/management/contacts/customers.js +13 -0
- package/addon/controllers/management/contacts/index.js +43 -1
- package/addon/controllers/management/drivers/index/details.js +1 -0
- package/addon/controllers/management/drivers/index.js +32 -2
- package/addon/controllers/management/fleets/index/details.js +1 -0
- package/addon/controllers/management/fleets/index.js +39 -1
- package/addon/controllers/management/fuel-reports/index.js +22 -2
- package/addon/controllers/management/fuel-transactions/index.js +43 -7
- package/addon/controllers/management/issues/index/details.js +1 -0
- package/addon/controllers/management/issues/index.js +22 -2
- package/addon/controllers/management/places/index.js +36 -1
- package/addon/controllers/management/trailers/index/details.js +1 -0
- package/addon/controllers/management/trailers/index.js +50 -2
- package/addon/controllers/management/vehicles/index/details.js +1 -0
- package/addon/controllers/management/vehicles/index.js +33 -2
- package/addon/controllers/management/vendors/index.js +26 -2
- package/addon/controllers/operations/orders/index/details.js +31 -4
- package/addon/controllers/operations/orders/index.js +59 -4
- package/addon/controllers/operations/service-rates/index.js +15 -1
- package/addon/extension.js +32 -0
- package/addon/routes/operations/orders/index/details.js +1 -0
- package/addon/services/contact-actions.js +1 -0
- package/addon/services/customer-actions.js +2 -1
- package/addon/services/device-actions.js +1 -0
- package/addon/services/device-event-actions.js +1 -0
- package/addon/services/driver-actions.js +4 -13
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/fleet-actions.js +1 -0
- package/addon/services/fuel-report-actions.js +1 -0
- package/addon/services/inspection-form-actions.js +1 -0
- package/addon/services/inspection-submission-actions.js +1 -0
- package/addon/services/integrated-vendor-actions.js +1 -0
- package/addon/services/issue-actions.js +1 -0
- package/addon/services/maintenance-actions.js +1 -0
- package/addon/services/maintenance-schedule-actions.js +2 -1
- package/addon/services/order-actions.js +7 -0
- package/addon/services/order-presentation.js +129 -0
- package/addon/services/part-actions.js +1 -0
- package/addon/services/place-actions.js +1 -0
- package/addon/services/sensor-actions.js +1 -0
- package/addon/services/service-area-actions.js +1 -0
- package/addon/services/service-rate-actions.js +1 -0
- package/addon/services/telematic-actions.js +1 -0
- package/addon/services/trailer-actions.js +4 -14
- package/addon/services/vehicle-actions.js +4 -13
- package/addon/services/vendor-actions.js +1 -0
- package/addon/services/work-order-actions.js +1 -0
- package/addon/services/zone-actions.js +1 -0
- package/addon/templates/analytics/reports/index/details.hbs +1 -0
- package/addon/templates/analytics/reports/index.hbs +1 -0
- package/addon/templates/connectivity/devices/index/details.hbs +1 -0
- package/addon/templates/connectivity/devices/index.hbs +1 -0
- package/addon/templates/connectivity/events/index.hbs +1 -0
- package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
- package/addon/templates/connectivity/sensors/index.hbs +1 -0
- package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
- package/addon/templates/maintenance/equipment/index.hbs +2 -0
- package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index.hbs +1 -0
- package/addon/templates/maintenance/parts/index/details.hbs +1 -0
- package/addon/templates/maintenance/parts/index.hbs +2 -0
- package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
- package/addon/templates/maintenance/schedules/index.hbs +2 -0
- package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
- package/addon/templates/maintenance/work-orders/index.hbs +1 -0
- package/addon/templates/management/contacts/customers/details.hbs +1 -0
- package/addon/templates/management/contacts/customers.hbs +1 -0
- package/addon/templates/management/contacts/index/details.hbs +1 -0
- package/addon/templates/management/contacts/index.hbs +1 -0
- package/addon/templates/management/contacts.hbs +1 -0
- package/addon/templates/management/drivers/index/details.hbs +1 -0
- package/addon/templates/management/drivers/index.hbs +2 -0
- package/addon/templates/management/fleets/index/details.hbs +1 -0
- package/addon/templates/management/fleets/index.hbs +1 -0
- package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
- package/addon/templates/management/fuel-reports/index.hbs +1 -0
- package/addon/templates/management/fuel-transactions/index.hbs +1 -0
- package/addon/templates/management/issues/index/details.hbs +1 -0
- package/addon/templates/management/issues/index.hbs +1 -0
- package/addon/templates/management/places/index/details.hbs +1 -0
- package/addon/templates/management/places/index.hbs +1 -0
- package/addon/templates/management/trailers/index/details.hbs +1 -0
- package/addon/templates/management/trailers/index.hbs +2 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -0
- package/addon/templates/management/vehicles/index.hbs +2 -0
- package/addon/templates/management/vendors/index/details.hbs +1 -0
- package/addon/templates/management/vendors/index.hbs +2 -0
- package/addon/templates/management/vendors/integrated.hbs +1 -0
- package/addon/templates/operations/orders/index/details.hbs +1 -0
- package/addon/templates/operations/orders/index.hbs +1 -0
- package/addon/templates/operations/service-rates/index/details.hbs +1 -0
- package/addon/templates/operations/service-rates/index.hbs +1 -0
- package/addon/utils/normalize-order-config-flow.js +6 -1
- package/app/services/order-presentation.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +34 -4
- package/server/src/Models/Order.php +35 -3
- package/server/src/Models/OrderConfig.php +129 -0
- package/server/src/Models/TrackingNumber.php +8 -4
- package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
- package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
- package/server/tests/OrderConfigLifecycleTest.php +120 -0
- package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
- package/translations/en-us.yaml +60 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { isArray } from '@ember/array';
|
|
6
|
+
import { task } from 'ember-concurrency';
|
|
7
|
+
import { underscore } from '@ember/string';
|
|
8
|
+
import generateUUID from '@fleetbase/ember-core/utils/generate-uuid';
|
|
9
|
+
import normalizeOrderConfigFlow, { getOrderConfigFlowRootCode } from '../../utils/normalize-order-config-flow';
|
|
10
|
+
|
|
11
|
+
/** Keys of `meta.lifecycle` understood by the server (see OrderConfig::lifecycle()). */
|
|
12
|
+
const DEFAULTS = { initial: null, completed: null, canceled: null, terminal: [], dispatch: true, strict_transitions: false };
|
|
13
|
+
|
|
14
|
+
const NEW_ACTIVITY = { status: '', code: '', details: '', color: '#1f2937' };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Manages an order config's configured lifecycle (`meta.lifecycle`): the activity new orders start
|
|
18
|
+
* at, the activities that complete, cancel and end an order, whether orders may be dispatched and
|
|
19
|
+
* whether transitions must follow the flow. Without a configured lifecycle the default dispatch
|
|
20
|
+
* lifecycle applies.
|
|
21
|
+
*
|
|
22
|
+
* The activities themselves (the config's `flow`) can be defined here too, so a lifecycle can be
|
|
23
|
+
* built from scratch without the graph editor: add or remove activities, set their labels and the
|
|
24
|
+
* activities each one can move to. Flow and lifecycle are saved together.
|
|
25
|
+
*
|
|
26
|
+
* @extends Component
|
|
27
|
+
*/
|
|
28
|
+
export default class OrderConfigManagerLifecycleComponent extends Component {
|
|
29
|
+
@service modalsManager;
|
|
30
|
+
@service notifications;
|
|
31
|
+
@service abilities;
|
|
32
|
+
@service intl;
|
|
33
|
+
|
|
34
|
+
/** The config being managed. */
|
|
35
|
+
@tracked config;
|
|
36
|
+
|
|
37
|
+
/** Working copy of `meta.lifecycle`, or null when the config has none. */
|
|
38
|
+
@tracked lifecycle = null;
|
|
39
|
+
|
|
40
|
+
/** Last saved lifecycle, to tell whether there is anything to save. */
|
|
41
|
+
@tracked saved = null;
|
|
42
|
+
|
|
43
|
+
/** Working copy of the config's flow, keyed by activity code, with next activities as codes. */
|
|
44
|
+
@tracked flow = {};
|
|
45
|
+
|
|
46
|
+
/** Flow as last saved, serialized, to tell whether there is anything to save. */
|
|
47
|
+
@tracked savedFlow = '{}';
|
|
48
|
+
|
|
49
|
+
/** The activity being added. */
|
|
50
|
+
@tracked newActivity = { ...NEW_ACTIVITY };
|
|
51
|
+
|
|
52
|
+
constructor(owner, { config, configManagerContext }) {
|
|
53
|
+
super(...arguments);
|
|
54
|
+
this.changeConfig(config);
|
|
55
|
+
|
|
56
|
+
configManagerContext.on('onConfigChanged', (newConfig) => {
|
|
57
|
+
this.changeConfig(newConfig);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get canEdit() {
|
|
62
|
+
return this.abilities.can('fleet-ops update order-config') && !this.config?.core_service;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get enabled() {
|
|
66
|
+
return this.lifecycle !== null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Activities of the flow as select options. */
|
|
70
|
+
get activities() {
|
|
71
|
+
return Object.values(this.flow).map((activity) => ({ code: activity.code, label: activity.status || activity.code }));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Activities with their next activities resolved, for the editor. */
|
|
75
|
+
get activityRows() {
|
|
76
|
+
return Object.values(this.flow).map((activity) => ({
|
|
77
|
+
...activity,
|
|
78
|
+
next: (activity.activities ?? []).map((code) => this.option(code)).filter(Boolean),
|
|
79
|
+
isInitial: this.lifecycle?.initial === activity.code,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get hasActivities() {
|
|
84
|
+
return Object.keys(this.flow).length > 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get newActivityCode() {
|
|
88
|
+
return underscore((this.newActivity.code || this.newActivity.status || '').trim());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get canAddActivity() {
|
|
92
|
+
const code = this.newActivityCode;
|
|
93
|
+
return this.canEdit && this.newActivity.status.trim() !== '' && code !== '' && !this.flow[code];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get newActivityCodeTaken() {
|
|
97
|
+
const code = this.newActivityCode;
|
|
98
|
+
return code !== '' && Boolean(this.flow[code]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get selectedInitial() {
|
|
102
|
+
return this.option(this.lifecycle?.initial);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get selectedCompleted() {
|
|
106
|
+
return this.option(this.lifecycle?.completed);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get selectedCanceled() {
|
|
110
|
+
return this.option(this.lifecycle?.canceled);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get selectedTerminal() {
|
|
114
|
+
return (this.lifecycle?.terminal ?? []).map((code) => this.option(code)).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get isDirty() {
|
|
118
|
+
return JSON.stringify(this.lifecycle) !== JSON.stringify(this.saved) || this.serializeFlow() !== this.savedFlow;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Problems that make the lifecycle unusable server-side. */
|
|
122
|
+
get problems() {
|
|
123
|
+
if (!this.lifecycle) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const problems = [];
|
|
128
|
+
const codes = this.activities.map((activity) => activity.code);
|
|
129
|
+
if (codes.length === 0) {
|
|
130
|
+
problems.push(this.intl.t('order-config-manager.lifecycle.problem-no-activities'));
|
|
131
|
+
}
|
|
132
|
+
if (!this.lifecycle.initial) {
|
|
133
|
+
problems.push(this.intl.t('order-config-manager.lifecycle.problem-initial-required'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const referenced = [this.lifecycle.initial, this.lifecycle.completed, this.lifecycle.canceled, ...(this.lifecycle.terminal ?? [])].filter(Boolean);
|
|
137
|
+
for (const code of new Set(referenced)) {
|
|
138
|
+
if (!codes.includes(code)) {
|
|
139
|
+
problems.push(this.intl.t('order-config-manager.lifecycle.problem-missing-activity', { code }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return problems;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get canSave() {
|
|
147
|
+
return this.canEdit && this.isDirty && this.problems.length === 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Arrow function so templates can call it as a helper: (this.option code). */
|
|
151
|
+
option = (code) => {
|
|
152
|
+
return code ? (this.activities.find((activity) => activity.code === code) ?? { code, label: code }) : null;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
changeConfig(config) {
|
|
156
|
+
this.config = config;
|
|
157
|
+
this.saved = this.normalize(config?.meta?.lifecycle);
|
|
158
|
+
this.lifecycle = this.normalize(config?.meta?.lifecycle);
|
|
159
|
+
this.flow = this.readFlow(config?.flow);
|
|
160
|
+
this.savedFlow = this.serializeFlow();
|
|
161
|
+
this.newActivity = { ...NEW_ACTIVITY };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The stored flow as plain activity objects with next activities as codes. */
|
|
165
|
+
readFlow(flow) {
|
|
166
|
+
const normalized = normalizeOrderConfigFlow(flow ?? {});
|
|
167
|
+
const result = {};
|
|
168
|
+
Object.values(normalized).forEach((activity, index) => {
|
|
169
|
+
if (!activity || typeof activity !== 'object') {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const code = activity.code ?? activity.key;
|
|
173
|
+
if (!code) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
result[code] = {
|
|
177
|
+
...activity,
|
|
178
|
+
code,
|
|
179
|
+
key: activity.key ?? code,
|
|
180
|
+
sequence: activity.sequence ?? index,
|
|
181
|
+
activities: (isArray(activity.activities) ? activity.activities : []).map((next) => (typeof next === 'string' ? next : (next?.code ?? next?.key))).filter(Boolean),
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
serializeFlow() {
|
|
188
|
+
const serialized = {};
|
|
189
|
+
Object.values(this.flow).forEach((activity) => {
|
|
190
|
+
// Graph-only state never belongs in the stored flow.
|
|
191
|
+
// eslint-disable-next-line no-unused-vars
|
|
192
|
+
const { node, id, parentId, _internalModel, ...stored } = activity;
|
|
193
|
+
serialized[activity.code] = { ...stored, activities: [...(activity.activities ?? [])] };
|
|
194
|
+
});
|
|
195
|
+
return JSON.stringify(serialized);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@action setNewActivityField(field, event) {
|
|
199
|
+
this.newActivity = { ...this.newActivity, [field]: event.target.value };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@action addActivity() {
|
|
203
|
+
if (!this.canAddActivity) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const code = this.newActivityCode;
|
|
208
|
+
const activity = {
|
|
209
|
+
key: code,
|
|
210
|
+
code,
|
|
211
|
+
status: this.newActivity.status.trim(),
|
|
212
|
+
details: this.newActivity.details.trim(),
|
|
213
|
+
color: this.newActivity.color || '#1f2937',
|
|
214
|
+
sequence: Object.keys(this.flow).length,
|
|
215
|
+
activities: [],
|
|
216
|
+
events: [],
|
|
217
|
+
logic: [],
|
|
218
|
+
actions: [],
|
|
219
|
+
entities: [],
|
|
220
|
+
complete: false,
|
|
221
|
+
require_pod: false,
|
|
222
|
+
pod_method: 'scan',
|
|
223
|
+
options: {},
|
|
224
|
+
internalId: generateUUID(),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.flow = { ...this.flow, [code]: activity };
|
|
228
|
+
if (this.lifecycle && !this.lifecycle.initial) {
|
|
229
|
+
this.lifecycle = { ...this.lifecycle, initial: code };
|
|
230
|
+
}
|
|
231
|
+
this.newActivity = { ...NEW_ACTIVITY };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@action removeActivity(code) {
|
|
235
|
+
const flow = { ...this.flow };
|
|
236
|
+
delete flow[code];
|
|
237
|
+
Object.keys(flow).forEach((key) => {
|
|
238
|
+
flow[key] = { ...flow[key], activities: flow[key].activities.filter((next) => next !== code) };
|
|
239
|
+
});
|
|
240
|
+
this.flow = flow;
|
|
241
|
+
|
|
242
|
+
if (this.lifecycle) {
|
|
243
|
+
const without = (value) => (value === code ? null : value);
|
|
244
|
+
this.lifecycle = {
|
|
245
|
+
...this.lifecycle,
|
|
246
|
+
initial: without(this.lifecycle.initial),
|
|
247
|
+
completed: without(this.lifecycle.completed),
|
|
248
|
+
canceled: without(this.lifecycle.canceled),
|
|
249
|
+
terminal: this.lifecycle.terminal.filter((item) => item !== code),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@action setActivityField(code, field, event) {
|
|
255
|
+
this.flow = { ...this.flow, [code]: { ...this.flow[code], [field]: event.target.value } };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@action setNextActivities(code, selected) {
|
|
259
|
+
this.flow = { ...this.flow, [code]: { ...this.flow[code], activities: (selected ?? []).map((activity) => activity.code) } };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Drops the default dispatch activities so a lifecycle can be designed from nothing. */
|
|
263
|
+
@action startBlankFlow() {
|
|
264
|
+
this.modalsManager.confirm({
|
|
265
|
+
title: this.intl.t('order-config-manager.lifecycle.blank-flow-title'),
|
|
266
|
+
body: this.intl.t('order-config-manager.lifecycle.blank-flow-body'),
|
|
267
|
+
acceptButtonText: this.intl.t('order-config-manager.lifecycle.blank-flow-confirm'),
|
|
268
|
+
confirm: (modal) => {
|
|
269
|
+
this.flow = {};
|
|
270
|
+
if (this.lifecycle) {
|
|
271
|
+
this.lifecycle = { ...this.lifecycle, initial: null, completed: null, canceled: null, terminal: [] };
|
|
272
|
+
}
|
|
273
|
+
modal.done();
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
normalize(lifecycle) {
|
|
279
|
+
if (!lifecycle || typeof lifecycle !== 'object' || Object.keys(lifecycle).length === 0) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
...DEFAULTS,
|
|
285
|
+
...lifecycle,
|
|
286
|
+
terminal: isArray(lifecycle.terminal) ? [...lifecycle.terminal] : [],
|
|
287
|
+
dispatch: !['false', '0', 0, false].includes(lifecycle.dispatch),
|
|
288
|
+
strict_transitions: Boolean(lifecycle.strict_transitions),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@action toggleEnabled(enabled) {
|
|
293
|
+
if (enabled) {
|
|
294
|
+
const flow = normalizeOrderConfigFlow(this.config?.flow ?? {});
|
|
295
|
+
this.lifecycle = this.saved ? { ...this.saved, terminal: [...this.saved.terminal] } : { ...DEFAULTS, terminal: [], initial: getOrderConfigFlowRootCode(flow) ?? null };
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (this.saved) {
|
|
300
|
+
this.modalsManager.confirm({
|
|
301
|
+
title: this.intl.t('order-config-manager.lifecycle.remove-title'),
|
|
302
|
+
body: this.intl.t('order-config-manager.lifecycle.remove-body'),
|
|
303
|
+
acceptButtonText: this.intl.t('order-config-manager.lifecycle.remove-confirm'),
|
|
304
|
+
confirm: async (modal) => {
|
|
305
|
+
modal.startLoading();
|
|
306
|
+
this.lifecycle = null;
|
|
307
|
+
await this.save.perform();
|
|
308
|
+
modal.done();
|
|
309
|
+
},
|
|
310
|
+
decline: (modal) => {
|
|
311
|
+
modal.done();
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.lifecycle = null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@action setActivity(key, activity) {
|
|
321
|
+
this.lifecycle = { ...this.lifecycle, [key]: activity?.code ?? null };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@action setTerminal(activities) {
|
|
325
|
+
this.lifecycle = { ...this.lifecycle, terminal: (activities ?? []).map((activity) => activity.code) };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@action setFlag(key, value) {
|
|
329
|
+
this.lifecycle = { ...this.lifecycle, [key]: Boolean(value) };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@task *save() {
|
|
333
|
+
const meta = { ...(this.config.meta ?? {}) };
|
|
334
|
+
if (this.lifecycle) {
|
|
335
|
+
meta.lifecycle = { ...this.lifecycle };
|
|
336
|
+
} else {
|
|
337
|
+
delete meta.lifecycle;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
this.config.set('meta', meta);
|
|
342
|
+
this.config.set('flow', JSON.parse(this.serializeFlow()));
|
|
343
|
+
yield this.config.save();
|
|
344
|
+
this.saved = this.normalize(this.lifecycle);
|
|
345
|
+
this.lifecycle = this.normalize(this.lifecycle);
|
|
346
|
+
this.savedFlow = this.serializeFlow();
|
|
347
|
+
this.notifications.success(this.intl.t('order-config-manager.lifecycle.saved', { orderConfigName: this.config.name }));
|
|
348
|
+
if (typeof this.args.onConfigUpdated === 'function') {
|
|
349
|
+
this.args.onConfigUpdated(this.config);
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
this.config.rollbackAttributes();
|
|
353
|
+
this.lifecycle = this.normalize(this.config.meta?.lifecycle);
|
|
354
|
+
this.flow = this.readFlow(this.config.flow);
|
|
355
|
+
this.notifications.serverError(error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -14,6 +14,7 @@ import applyContextComponentArguments from '@fleetbase/ember-core/utils/apply-co
|
|
|
14
14
|
import OrderConfigManagerDetailsComponent from './order-config-manager/details';
|
|
15
15
|
import OrderConfigManagerCustomFieldsComponent from './order-config-manager/custom-fields';
|
|
16
16
|
import OrderConfigManagerActivityFlowComponent from './order-config-manager/activity-flow';
|
|
17
|
+
import OrderConfigManagerLifecycleComponent from './order-config-manager/lifecycle';
|
|
17
18
|
import OrderConfigManagerEntitiesComponent from './order-config-manager/entities';
|
|
18
19
|
import findActiveTab from '../utils/find-active-tab';
|
|
19
20
|
|
|
@@ -45,6 +46,7 @@ export default class OrderConfigManagerComponent extends Component {
|
|
|
45
46
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.details'), null, { icon: 'circle-info', component: OrderConfigManagerDetailsComponent }),
|
|
46
47
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.custom-fields'), null, { icon: 'rectangle-list', component: OrderConfigManagerCustomFieldsComponent }),
|
|
47
48
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.activity-flow'), null, { icon: 'diagram-project', component: OrderConfigManagerActivityFlowComponent }),
|
|
49
|
+
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.lifecycle'), null, { icon: 'arrows-spin', component: OrderConfigManagerLifecycleComponent }),
|
|
48
50
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.entities'), null, { icon: 'boxes-packing', component: OrderConfigManagerEntitiesComponent }),
|
|
49
51
|
];
|
|
50
52
|
|
|
@@ -106,7 +108,9 @@ export default class OrderConfigManagerComponent extends Component {
|
|
|
106
108
|
*/
|
|
107
109
|
@task *loadOrderConfigs(options = {}) {
|
|
108
110
|
try {
|
|
109
|
-
|
|
111
|
+
// Always fetch: `findAll` otherwise resolves with whatever subset of configs the store
|
|
112
|
+
// already holds (e.g. from an order's embedded config) and only refreshes in the background.
|
|
113
|
+
const configs = yield this.store.findAll('order-config', { reload: true }).then(Array.from);
|
|
110
114
|
let currentConfig;
|
|
111
115
|
if (isArray(configs) && configs.length > 0) {
|
|
112
116
|
currentConfig = configs.find((c) => c.id === this.args.orderConfig);
|
|
@@ -8,7 +8,7 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
8
8
|
@service intl;
|
|
9
9
|
|
|
10
10
|
/** query params */
|
|
11
|
-
@tracked queryParams = ['page', 'limit', 'sort', 'query', 'public_id', 'name', 'created_at'];
|
|
11
|
+
@tracked queryParams = this.reportActions.queryParamsFor(['page', 'limit', 'sort', 'query', 'public_id', 'name', 'created_at']);
|
|
12
12
|
@tracked page = 1;
|
|
13
13
|
@tracked limit;
|
|
14
14
|
@tracked sort = '-created_at';
|
|
@@ -20,11 +20,13 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
20
20
|
get actionButtons() {
|
|
21
21
|
return [
|
|
22
22
|
{
|
|
23
|
+
id: 'refresh',
|
|
23
24
|
icon: 'refresh',
|
|
24
25
|
onClick: this.reportActions.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',
|
|
@@ -37,6 +39,7 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
37
39
|
get bulkActions() {
|
|
38
40
|
return [
|
|
39
41
|
{
|
|
42
|
+
id: 'bulk-delete',
|
|
40
43
|
label: 'Delete selected...',
|
|
41
44
|
class: 'text-red-500',
|
|
42
45
|
fn: this.reportActions.bulkDelete,
|
|
@@ -48,6 +51,7 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
48
51
|
get columns() {
|
|
49
52
|
return [
|
|
50
53
|
{
|
|
54
|
+
id: 'title',
|
|
51
55
|
sticky: true,
|
|
52
56
|
label: 'Title',
|
|
53
57
|
valuePath: 'title',
|
|
@@ -59,6 +63,7 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
59
63
|
filterComponent: 'filter/string',
|
|
60
64
|
},
|
|
61
65
|
{
|
|
66
|
+
id: 'public-id',
|
|
62
67
|
label: 'ID',
|
|
63
68
|
valuePath: 'public_id',
|
|
64
69
|
cellComponent: 'click-to-copy',
|
|
@@ -69,6 +74,7 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
69
74
|
filterComponent: 'filter/string',
|
|
70
75
|
},
|
|
71
76
|
{
|
|
77
|
+
id: 'row-actions',
|
|
72
78
|
label: '',
|
|
73
79
|
cellComponent: 'table/cell/dropdown',
|
|
74
80
|
ddButtonText: false,
|
|
@@ -81,11 +87,13 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
81
87
|
width: 60,
|
|
82
88
|
actions: [
|
|
83
89
|
{
|
|
90
|
+
id: 'view',
|
|
84
91
|
label: 'View report...',
|
|
85
92
|
fn: this.reportActions.transition.view,
|
|
86
93
|
permission: 'iam view report',
|
|
87
94
|
},
|
|
88
95
|
{
|
|
96
|
+
id: 'edit',
|
|
89
97
|
label: 'Edit report...',
|
|
90
98
|
fn: this.reportActions.transition.edit,
|
|
91
99
|
permission: 'iam update report',
|
|
@@ -94,6 +102,7 @@ export default class AnalyticsReportsIndexController extends Controller {
|
|
|
94
102
|
separator: true,
|
|
95
103
|
},
|
|
96
104
|
{
|
|
105
|
+
id: 'delete',
|
|
97
106
|
label: 'Delete report...',
|
|
98
107
|
fn: this.reportActions.delete,
|
|
99
108
|
permission: 'iam delete report',
|
|
@@ -17,7 +17,7 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
17
17
|
@service trailerActions;
|
|
18
18
|
|
|
19
19
|
/** query params */
|
|
20
|
-
@tracked queryParams = [
|
|
20
|
+
@tracked queryParams = this.deviceActions.queryParamsFor([
|
|
21
21
|
'name',
|
|
22
22
|
'status',
|
|
23
23
|
'attachment_state',
|
|
@@ -36,7 +36,7 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
36
36
|
'public_id',
|
|
37
37
|
'created_at',
|
|
38
38
|
'updated_at',
|
|
39
|
-
];
|
|
39
|
+
]);
|
|
40
40
|
@tracked page = 1;
|
|
41
41
|
@tracked limit;
|
|
42
42
|
@tracked sort = '-created_at';
|
|
@@ -56,11 +56,13 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
56
56
|
/** action buttons */
|
|
57
57
|
@tracked actionButtons = [
|
|
58
58
|
{
|
|
59
|
+
id: 'refresh',
|
|
59
60
|
icon: 'refresh',
|
|
60
61
|
onClick: this.deviceActions.refresh,
|
|
61
62
|
helpText: this.intl.t('common.refresh'),
|
|
62
63
|
},
|
|
63
64
|
{
|
|
65
|
+
id: 'create',
|
|
64
66
|
text: this.intl.t('common.new'),
|
|
65
67
|
type: 'primary',
|
|
66
68
|
icon: 'plus',
|
|
@@ -68,6 +70,7 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
68
70
|
permission: 'fleet-ops create device',
|
|
69
71
|
},
|
|
70
72
|
{
|
|
73
|
+
id: 'import',
|
|
71
74
|
text: this.intl.t('common.import'),
|
|
72
75
|
type: 'magic',
|
|
73
76
|
icon: 'upload',
|
|
@@ -75,6 +78,7 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
75
78
|
permission: 'fleet-ops import device',
|
|
76
79
|
},
|
|
77
80
|
{
|
|
81
|
+
id: 'export',
|
|
78
82
|
text: this.intl.t('common.export'),
|
|
79
83
|
icon: 'long-arrow-up',
|
|
80
84
|
iconClass: 'rotate-icon-45',
|
|
@@ -87,6 +91,7 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
87
91
|
/** bulk action buttons */
|
|
88
92
|
@tracked bulkActions = [
|
|
89
93
|
{
|
|
94
|
+
id: 'bulk-delete',
|
|
90
95
|
label: 'Delete selected...',
|
|
91
96
|
class: 'text-red-500',
|
|
92
97
|
fn: this.deviceActions.bulkDelete,
|
|
@@ -111,6 +116,7 @@ export default class ConnectivityDevicesIndexController extends Controller {
|
|
|
111
116
|
separator: true,
|
|
112
117
|
},
|
|
113
118
|
{
|
|
119
|
+
id: 'delete',
|
|
114
120
|
label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.device') }),
|
|
115
121
|
fn: this.deviceActions.delete,
|
|
116
122
|
permission: 'fleet-ops delete device',
|
|
@@ -78,7 +78,20 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
78
78
|
@service telematicActions;
|
|
79
79
|
|
|
80
80
|
/** query params */
|
|
81
|
-
@tracked queryParams = [
|
|
81
|
+
@tracked queryParams = this.deviceEventActions.queryParamsFor([
|
|
82
|
+
'page',
|
|
83
|
+
'limit',
|
|
84
|
+
'sort',
|
|
85
|
+
'query',
|
|
86
|
+
'telematic',
|
|
87
|
+
'device',
|
|
88
|
+
'event_type',
|
|
89
|
+
'severity',
|
|
90
|
+
'processed',
|
|
91
|
+
'occurred_at',
|
|
92
|
+
'created_at',
|
|
93
|
+
'updated_at',
|
|
94
|
+
]);
|
|
82
95
|
@tracked page = 1;
|
|
83
96
|
@tracked limit;
|
|
84
97
|
@tracked sort = '-created_at';
|
|
@@ -95,6 +108,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
95
108
|
/** action buttons */
|
|
96
109
|
@tracked actionButtons = [
|
|
97
110
|
{
|
|
111
|
+
id: 'refresh',
|
|
98
112
|
icon: 'refresh',
|
|
99
113
|
onClick: this.deviceEventActions.refresh,
|
|
100
114
|
helpText: this.intl.t('common.refresh'),
|
|
@@ -107,6 +121,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
107
121
|
/** columns */
|
|
108
122
|
@tracked columns = [
|
|
109
123
|
{
|
|
124
|
+
id: 'event-type',
|
|
110
125
|
sticky: true,
|
|
111
126
|
label: 'Event',
|
|
112
127
|
valuePath: 'event_type',
|
|
@@ -121,6 +136,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
121
136
|
filterComponent: 'filter/string',
|
|
122
137
|
},
|
|
123
138
|
{
|
|
139
|
+
id: 'device-display-name',
|
|
124
140
|
label: 'Device',
|
|
125
141
|
valuePath: 'device.displayName',
|
|
126
142
|
cellComponent: 'cell/device-identity',
|
|
@@ -137,6 +153,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
137
153
|
modelNamePath: 'displayName',
|
|
138
154
|
},
|
|
139
155
|
{
|
|
156
|
+
id: 'provider',
|
|
140
157
|
label: 'Provider',
|
|
141
158
|
valuePath: 'provider',
|
|
142
159
|
cellComponent: 'cell/telematic-identity',
|
|
@@ -152,6 +169,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
152
169
|
model: 'telematic',
|
|
153
170
|
},
|
|
154
171
|
{
|
|
172
|
+
id: 'severity',
|
|
155
173
|
label: 'Severity',
|
|
156
174
|
valuePath: 'severity',
|
|
157
175
|
cellComponent: 'table/cell/status',
|
|
@@ -165,12 +183,14 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
165
183
|
filterOptionValue: 'value',
|
|
166
184
|
},
|
|
167
185
|
{
|
|
186
|
+
id: 'message',
|
|
168
187
|
label: 'Message',
|
|
169
188
|
valuePath: 'message',
|
|
170
189
|
resizable: true,
|
|
171
190
|
sortable: false,
|
|
172
191
|
},
|
|
173
192
|
{
|
|
193
|
+
id: 'code',
|
|
174
194
|
label: 'Code',
|
|
175
195
|
valuePath: 'code',
|
|
176
196
|
resizable: true,
|
|
@@ -180,6 +200,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
180
200
|
filterComponent: 'filter/string',
|
|
181
201
|
},
|
|
182
202
|
{
|
|
203
|
+
id: 'ident',
|
|
183
204
|
label: 'IDENT',
|
|
184
205
|
valuePath: 'ident',
|
|
185
206
|
hidden: true,
|
|
@@ -187,6 +208,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
187
208
|
sortable: true,
|
|
188
209
|
},
|
|
189
210
|
{
|
|
211
|
+
id: 'protocol',
|
|
190
212
|
label: 'Protocol',
|
|
191
213
|
valuePath: 'protocol',
|
|
192
214
|
hidden: true,
|
|
@@ -194,6 +216,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
194
216
|
sortable: true,
|
|
195
217
|
},
|
|
196
218
|
{
|
|
219
|
+
id: 'state',
|
|
197
220
|
label: 'State',
|
|
198
221
|
valuePath: 'state',
|
|
199
222
|
hidden: true,
|
|
@@ -201,6 +224,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
201
224
|
sortable: true,
|
|
202
225
|
},
|
|
203
226
|
{
|
|
227
|
+
id: 'processed-at',
|
|
204
228
|
label: 'Processed',
|
|
205
229
|
valuePath: 'processedAt',
|
|
206
230
|
sortParam: 'processed_at',
|
|
@@ -214,6 +238,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
214
238
|
filterOptionValue: 'value',
|
|
215
239
|
},
|
|
216
240
|
{
|
|
241
|
+
id: 'occurred-at',
|
|
217
242
|
label: 'Occurred',
|
|
218
243
|
valuePath: 'occurredAt',
|
|
219
244
|
sortParam: 'occurred_at',
|
|
@@ -224,6 +249,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
224
249
|
filterComponent: 'filter/date',
|
|
225
250
|
},
|
|
226
251
|
{
|
|
252
|
+
id: 'created-at',
|
|
227
253
|
label: this.intl.t('column.created-at'),
|
|
228
254
|
valuePath: 'createdAt',
|
|
229
255
|
sortParam: 'created_at',
|
|
@@ -234,6 +260,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
234
260
|
filterComponent: 'filter/date',
|
|
235
261
|
},
|
|
236
262
|
{
|
|
263
|
+
id: 'updated-at',
|
|
237
264
|
label: this.intl.t('column.updated-at'),
|
|
238
265
|
valuePath: 'updatedAt',
|
|
239
266
|
sortParam: 'updated_at',
|
|
@@ -245,6 +272,7 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
245
272
|
hidden: true,
|
|
246
273
|
},
|
|
247
274
|
{
|
|
275
|
+
id: 'row-actions',
|
|
248
276
|
label: '',
|
|
249
277
|
cellComponent: 'table/cell/dropdown',
|
|
250
278
|
ddButtonText: false,
|
|
@@ -257,11 +285,13 @@ export default class ConnectivityEventsIndexController extends Controller {
|
|
|
257
285
|
width: 60,
|
|
258
286
|
actions: [
|
|
259
287
|
{
|
|
288
|
+
id: 'view',
|
|
260
289
|
label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.device-event') }),
|
|
261
290
|
fn: this.deviceEventActions.transition.view,
|
|
262
291
|
permission: 'fleet-ops view device-event',
|
|
263
292
|
},
|
|
264
293
|
{
|
|
294
|
+
id: 'mark-processed',
|
|
265
295
|
label: 'Mark processed',
|
|
266
296
|
fn: this.markProcessed,
|
|
267
297
|
permission: 'fleet-ops update device-event',
|
|
@@ -7,7 +7,7 @@ export default class ConnectivitySensorsIndexDetailsController extends Controlle
|
|
|
7
7
|
@service hostRouter;
|
|
8
8
|
|
|
9
9
|
get tabs() {
|
|
10
|
-
const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:
|
|
10
|
+
const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:sensor:details');
|
|
11
11
|
return [
|
|
12
12
|
{
|
|
13
13
|
route: 'connectivity.sensors.index.details.index',
|