@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
package/RELEASE.md
CHANGED
|
@@ -1,58 +1,32 @@
|
|
|
1
|
-
> v0.6.
|
|
1
|
+
> v0.6.71 ~ "Extensible tables and panels, configured order lifecycles and order presentation profiles"
|
|
2
2
|
|
|
3
3
|
---
|
|
4
|
-
##
|
|
5
|
-
- **Telematics Data settings.** A new Fleet-Ops → Settings → Telematics Data page lets each organization decide how long telemetry is kept: device events, raw event payloads, positions, processed and quarantined delivery envelopes, and sync run diagnostics. Administrators set system-wide defaults from the admin console (Fleet-Ops Config → Telematics Data). A value of 0 keeps rows forever.
|
|
6
|
-
- **Scheduled retention sweep.** `fleetops:prune-telematics-data` runs every fifteen minutes and applies each organization's policy in bounded batches, so a large backlog is drained gradually without stalling ingestion. Defaults: events 30 days, raw payloads stripped after 7 days, positions 90 days, processed deliveries 24 hours, quarantined deliveries 7 days, sync runs 7 days.
|
|
7
|
-
- **Storage usage and on-demand cleanup.** The settings page shows rows, oldest row and estimated size per table for your organization, and "Run cleanup now" queues an immediate retention run.
|
|
4
|
+
## Highlights
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
- Telemetry-driven saves (device, event, position, vehicle, sensors) no longer write to the activity log on every poll. Organizations that want them can enable "Log telemetry activity" in Telematics Data settings. Manual edits are logged as before.
|
|
13
|
-
- Delivery inbox and sync run retention moved from the drain command into the retention sweep; `fleetops:drain-telematic-inbox` now only recovers deliveries and finishes interrupted runs.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
## Upgrade notes
|
|
17
|
-
- New indexes on `device_events (company_uuid, created_at)`, `positions (company_uuid, created_at)` and `telematic_sync_runs (telematic_uuid, updated_at)`; on very large tables run `php artisan migrate` in a maintenance window.
|
|
18
|
-
- Retention is enabled by default. Existing rows older than the defaults are removed over successive runs after upgrading; set the values to 0 before upgrading if you need to keep history indefinitely.
|
|
19
|
-
- Deleting rows frees space inside the database files, not on disk. Run `OPTIMIZE TABLE` off-peak to return space to the operating system.
|
|
20
|
-
- Run `php artisan fleetbase:create-permissions` to register the `telematics-settings` permission and the Telematics Settings Manager policy and role.
|
|
6
|
+
- **Resource view registries.** Extensions can add the following through `fleet-ops:<resource>:table:<slot>` and `fleet-ops:<resource>:details:<slot>`:
|
|
7
|
+
- columns, row actions, bulk actions and toolbar buttons on every top-level Fleet-Ops table;
|
|
8
|
+
- header buttons and "…" menu items on its details panels and side panels.
|
|
21
9
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- Column labels drop the table name ("Type", not "Order Type"). Distance and duration are labelled in meters and seconds, and money columns are marked "(minor units)".
|
|
36
|
-
- **A cleaner default dashboard.** Fleet-Ops widgets declare their place on the default dashboard: Radar first, then Active Orders and Drivers Online, a full-width Live Fleet map, and Revenue Trend, Top Drivers and Maintenance side by side.
|
|
37
|
-
- **Live Fleet widget cards match the live map.** Clicking a driver or vehicle on the widget map shows the same card as **Operations › Live Map**.
|
|
10
|
+
Built-in items carry stable ids, so registered items can be placed before or after them.
|
|
11
|
+
- **Configured order lifecycles.** An order config can define its own lifecycle in `meta.lifecycle`: `initial`, `completed`, `canceled` and `terminal` activities, `dispatch: false` and `strict_transitions`. The internal API starts orders at the initial activity, refuses to dispatch when dispatch is off, accepts only the flow's transitions when strict, and cancels to the configured activity. Configs without a lifecycle behave as before.
|
|
12
|
+
- **Lifecycle tab** in Order Configuration, to view and edit a config's lifecycle.
|
|
13
|
+
- **The board** shows a configured lifecycle's activities as its columns.
|
|
14
|
+
- **The activity flow editor** roots at the configured initial activity, and handles flows with cycles.
|
|
15
|
+
- **Order presentation profiles.** An extension can give its order type its own order form sections, extra and hidden fields, details view sections and hidden actions, registered in `fleet-ops:order-presentation`. **Edit details** opens the profiled form. Other order types are unchanged.
|
|
16
|
+
- **The Order Configuration manager lists every config**, including configs created while the console is open.
|
|
17
|
+
- **An order's activity timeline follows status changes made on the server.**
|
|
18
|
+
- **Fix: the Activity Flow crashed on a flow with a cycle.**
|
|
19
|
+
- **Fix: order details now load the assigned vehicle with the order.**
|
|
20
|
+
- **Fix: the order form failed when its custom fields couldn't load.**
|
|
21
|
+
- **Fix: registered details tabs.** Opening a driver, vehicle or trailer side panel removed the route from tabs other extensions had registered, which broke them in the full details view. Side panels also left out the tabs they could render.
|
|
22
|
+
- **Fix: the sensor details view showed the tabs registered for places.**
|
|
38
23
|
|
|
39
24
|
---
|
|
40
|
-
##
|
|
41
|
-
-
|
|
42
|
-
- The report schema no longer references 13 columns and join keys that don't exist: driver name, email and phone and the driver's vehicle, the vehicle's driver, and the fuel report cost, odometer and date. Transaction summaries sum `transaction.amount`.
|
|
43
|
-
- Every report table, and the item join, leaves soft-deleted rows out on core-api v1.6.64 and later.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
## Dependencies
|
|
47
|
-
- Order items, JSON totals and soft deletes in reports need `fleetbase/core-api` v1.6.64. The schema still registers on older versions.
|
|
48
|
-
- The report builder and the default dashboard order ship in `@fleetbase/ember-ui` v0.4.4.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
## Testing
|
|
52
|
-
- Every declared report column and join key was checked against the database schema, and order reports were run against real storefront orders on MySQL in strict mode.
|
|
53
|
-
- Tests cover the report schema at 100% line coverage, the Live Fleet map cards and the default dashboard order.
|
|
25
|
+
## Upgrading
|
|
26
|
+
Needs fleetbase/ember-core v0.3.25 and fleetbase/ember-ui v0.4.5.
|
|
54
27
|
|
|
55
28
|
---
|
|
56
29
|
## Need help?
|
|
57
30
|
- [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions)
|
|
58
31
|
- [Discord](https://discord.gg/HnTqQ6zAVn)
|
|
32
|
+
---
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
@isLoading={{or @isLoadingActivity this.loadActivity.isRunning @isLoading}}
|
|
6
6
|
@wrapperClass="bordered-top"
|
|
7
7
|
>
|
|
8
|
-
{{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
<div {{did-update this.onStatusChanged @resource.status}}>
|
|
9
|
+
{{#if (eq this.layout "timeline")}}
|
|
10
|
+
<Order::ActivityTimeline @resource={{@resource}} @activity={{this.parentOrderActivity}} />
|
|
11
|
+
{{else}}
|
|
12
|
+
<Order::ActivityList @resource={{@resource}} @activity={{this.parentOrderActivity}} />
|
|
13
|
+
{{/if}}
|
|
14
|
+
</div>
|
|
15
|
+
</ContentPanel>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
4
5
|
import { task } from 'ember-concurrency';
|
|
5
6
|
|
|
6
7
|
export default class OrderDetailsActivityComponent extends Component {
|
|
@@ -64,6 +65,14 @@ export default class OrderDetailsActivityComponent extends Component {
|
|
|
64
65
|
];
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A status change recorded outside this panel (a configured lifecycle advancing on save, an
|
|
70
|
+
* extension action, a realtime update) means new tracking statuses exist server-side.
|
|
71
|
+
*/
|
|
72
|
+
@action onStatusChanged() {
|
|
73
|
+
this.loadActivity.perform();
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
@task *loadActivity() {
|
|
68
77
|
if (!this.args.resource) {
|
|
69
78
|
return;
|
|
@@ -44,6 +44,35 @@
|
|
|
44
44
|
)
|
|
45
45
|
)
|
|
46
46
|
}}
|
|
47
|
+
{{else if this.profile}}
|
|
48
|
+
{{#each this.profileSections key="key" as |section|}}
|
|
49
|
+
{{#if section.native}}
|
|
50
|
+
{{#let
|
|
51
|
+
(component
|
|
52
|
+
section.componentName
|
|
53
|
+
resource=@resource
|
|
54
|
+
onChange=section.onChange
|
|
55
|
+
isLoading=@isLoading
|
|
56
|
+
isLoadingActivity=@isLoadingActivity
|
|
57
|
+
reloadToken=this.effectiveProofReloadToken
|
|
58
|
+
)
|
|
59
|
+
as |NativeSection|
|
|
60
|
+
}}
|
|
61
|
+
<NativeSection />
|
|
62
|
+
{{/let}}
|
|
63
|
+
{{else}}
|
|
64
|
+
<LazyEngineComponent @component={{section.component}} as |ProfileSection|>
|
|
65
|
+
<ProfileSection
|
|
66
|
+
@resource={{@resource}}
|
|
67
|
+
@controller={{@controller}}
|
|
68
|
+
@permission={{get-write-permission @resource}}
|
|
69
|
+
@isLoading={{@isLoading}}
|
|
70
|
+
@profile={{this.profile}}
|
|
71
|
+
@onChange={{@onDetailsChanged}}
|
|
72
|
+
/>
|
|
73
|
+
</LazyEngineComponent>
|
|
74
|
+
{{/if}}
|
|
75
|
+
{{/each}}
|
|
47
76
|
{{else}}
|
|
48
77
|
<RegistryYield @registry="fleet-ops:component:order:details:start" as |RegistryComponent|>
|
|
49
78
|
<RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} @isLoading={{@isLoading}} />
|
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { action } from '@ember/object';
|
|
3
3
|
import { tracked } from '@glimmer/tracking';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
4
5
|
|
|
5
6
|
export default class OrderDetailsComponent extends Component {
|
|
7
|
+
@service orderPresentation;
|
|
6
8
|
@tracked proofReloadToken = 0;
|
|
7
9
|
|
|
8
10
|
get effectiveProofReloadToken() {
|
|
9
11
|
return `${this.args.proofReloadToken ?? 0}:${this.proofReloadToken}`;
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
/** The extension presentation profile applying to this order's config, if any. */
|
|
15
|
+
get profile() {
|
|
16
|
+
return this.orderPresentation.profileFor(this.args.resource);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Profile sections with the change handler each native section expects. */
|
|
20
|
+
get profileSections() {
|
|
21
|
+
const handlers = {
|
|
22
|
+
activity: this.handleActivityChanged,
|
|
23
|
+
detail: this.args.onDetailsChanged,
|
|
24
|
+
'custom-fields': this.args.onCustomFieldsChanged,
|
|
25
|
+
notes: this.args.onNotesChanged,
|
|
26
|
+
route: this.args.onRouteChanged,
|
|
27
|
+
payload: this.args.onPayloadChanged,
|
|
28
|
+
documents: this.args.onDocumentsChanged,
|
|
29
|
+
comments: this.args.onCommentsChanged,
|
|
30
|
+
metadata: this.args.onMetadataChanged,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return this.orderPresentation.renderableSectionsFor(this.profile, 'details').map((section) => {
|
|
34
|
+
return section.native ? { ...section, onChange: handlers[section.name] } : section;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
12
38
|
@action handleActivityChanged(optionsOrActivity) {
|
|
13
39
|
if (typeof this.args.onActivityChanged === 'function') {
|
|
14
40
|
this.args.onActivityChanged(optionsOrActivity?.activityCreated ?? optionsOrActivity);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
@onChange={{perform this.selectOrderConfig}}
|
|
10
10
|
@placeholder={{t "order.fields.order-type-placeholder"}}
|
|
11
11
|
@triggerClass="form-select form-input"
|
|
12
|
-
@disabled={{cannot-write @resource}}
|
|
12
|
+
@disabled={{or (cannot-write @resource) this.orderTypeLocked}}
|
|
13
13
|
as |orderConfig|
|
|
14
14
|
>
|
|
15
15
|
<SelectOption::OrderConfig @option={{orderConfig}} />
|
|
@@ -17,8 +17,19 @@
|
|
|
17
17
|
</div>
|
|
18
18
|
</InputGroup>
|
|
19
19
|
|
|
20
|
+
{{#if this.profileDetailFields}}
|
|
21
|
+
<div class="contents">
|
|
22
|
+
<LazyEngineComponent @component={{this.profileDetailFields}} as |ProfileDetailFields|>
|
|
23
|
+
<ProfileDetailFields @resource={{@resource}} @order={{@resource}} @profile={{this.profile}} />
|
|
24
|
+
</LazyEngineComponent>
|
|
25
|
+
</div>
|
|
26
|
+
{{/if}}
|
|
27
|
+
|
|
28
|
+
{{#if this.show.internalId}}
|
|
20
29
|
<InputGroup @name={{t "order.fields.internal-id"}} @value={{@resource.internal_id}} @helpText={{t "order.fields.internal-id-help-text"}} @disabled={{cannot-write @resource}} />
|
|
30
|
+
{{/if}}
|
|
21
31
|
|
|
32
|
+
{{#if this.show.schedule}}
|
|
22
33
|
<InputGroup>
|
|
23
34
|
<InputLabel @labelText={{t "order.fields.schedule"}} @helpText={{t "order.fields.schedule-help-text"}} />
|
|
24
35
|
<DateTimeInput
|
|
@@ -30,7 +41,9 @@
|
|
|
30
41
|
@disabled={{or @resource.dispatched (cannot-write @resource)}}
|
|
31
42
|
/>
|
|
32
43
|
</InputGroup>
|
|
44
|
+
{{/if}}
|
|
33
45
|
|
|
46
|
+
{{#if this.show.customer}}
|
|
34
47
|
<InputGroup @name={{t "order.fields.customer"}} @helpText={{t "order.fields.customer-help-text"}}>
|
|
35
48
|
<ModelSelect
|
|
36
49
|
@modelName="customer"
|
|
@@ -48,7 +61,9 @@
|
|
|
48
61
|
<SelectOption::Customer @option={{model}} />
|
|
49
62
|
</ModelSelect>
|
|
50
63
|
</InputGroup>
|
|
64
|
+
{{/if}}
|
|
51
65
|
|
|
66
|
+
{{#if this.show.facilitator}}
|
|
52
67
|
<InputGroup @name={{t "order.fields.facilitator"}} @helpText={{t "order.fields.facilitator-help-text"}}>
|
|
53
68
|
<ModelSelect
|
|
54
69
|
@modelName="facilitator"
|
|
@@ -66,8 +81,9 @@
|
|
|
66
81
|
<SelectOption::Facilitator @option={{model}} />
|
|
67
82
|
</ModelSelect>
|
|
68
83
|
</InputGroup>
|
|
84
|
+
{{/if}}
|
|
69
85
|
|
|
70
|
-
{{#if (and @resource.facilitator.isIntegratedVendor @resource.facilitator.service_types.length)}}
|
|
86
|
+
{{#if (and this.show.serviceType @resource.facilitator.isIntegratedVendor @resource.facilitator.service_types.length)}}
|
|
71
87
|
<InputGroup @name={{t "order.fields.service-type"}} @helpText={{concat (t "order.fields.service-type-help-text") @resource.facilitator.name "."}}>
|
|
72
88
|
<Select
|
|
73
89
|
class="w-full"
|
|
@@ -82,6 +98,7 @@
|
|
|
82
98
|
</InputGroup>
|
|
83
99
|
{{/if}}
|
|
84
100
|
|
|
101
|
+
{{#if this.show.driver}}
|
|
85
102
|
<InputGroup @name={{t "order.fields.assign-driver"}} @helpText={{t "order.fields.assign-driver-help-text"}}>
|
|
86
103
|
<ModelSelect
|
|
87
104
|
@modelName="driver"
|
|
@@ -101,7 +118,9 @@
|
|
|
101
118
|
<SelectOption::Driver @option={{model}} />
|
|
102
119
|
</ModelSelect>
|
|
103
120
|
</InputGroup>
|
|
121
|
+
{{/if}}
|
|
104
122
|
|
|
123
|
+
{{#if this.show.vehicle}}
|
|
105
124
|
<InputGroup @name={{t "order.fields.assign-vehicle"}} @helpText={{t "order.fields.assign-vehicle-help-text"}}>
|
|
106
125
|
<ModelSelect
|
|
107
126
|
@modelName="vehicle"
|
|
@@ -120,8 +139,9 @@
|
|
|
120
139
|
<SelectOption::Vehicle @option={{model}} />
|
|
121
140
|
</ModelSelect>
|
|
122
141
|
</InputGroup>
|
|
142
|
+
{{/if}}
|
|
123
143
|
|
|
124
|
-
{{#if (and @resource.pod_required (not @resource.facilitator.isIntegratedVendor))}}
|
|
144
|
+
{{#if (and this.show.pod @resource.pod_required (not @resource.facilitator.isIntegratedVendor))}}
|
|
125
145
|
<InputGroup @name={{t "order.fields.proof-delivery"}} @helpText={{t "order.fields.proof-delivery-help-text"}} @required={{true}}>
|
|
126
146
|
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
127
147
|
<PowerSelect
|
|
@@ -142,7 +162,7 @@
|
|
|
142
162
|
</InputGroup>
|
|
143
163
|
{{/if}}
|
|
144
164
|
|
|
145
|
-
{{#if @resource.adhoc}}
|
|
165
|
+
{{#if (and this.show.adhoc @resource.adhoc)}}
|
|
146
166
|
<div>
|
|
147
167
|
<InputGroup
|
|
148
168
|
@name={{t "order.fields.adhoc-ping"}}
|
|
@@ -158,6 +178,7 @@
|
|
|
158
178
|
</div>
|
|
159
179
|
{{/if}}
|
|
160
180
|
|
|
181
|
+
{{#if this.show.adhoc}}
|
|
161
182
|
<div class="lg:col-span-2">
|
|
162
183
|
<InputGroup @wrapperClass="pt-2">
|
|
163
184
|
<Toggle
|
|
@@ -170,7 +191,9 @@
|
|
|
170
191
|
/>
|
|
171
192
|
</InputGroup>
|
|
172
193
|
</div>
|
|
194
|
+
{{/if}}
|
|
173
195
|
|
|
196
|
+
{{#if this.show.dispatch}}
|
|
174
197
|
<div class="lg:col-span-2">
|
|
175
198
|
<InputGroup @wrapperClass="pt-1">
|
|
176
199
|
<Toggle
|
|
@@ -183,7 +206,9 @@
|
|
|
183
206
|
/>
|
|
184
207
|
</InputGroup>
|
|
185
208
|
</div>
|
|
209
|
+
{{/if}}
|
|
186
210
|
|
|
211
|
+
{{#if this.show.pod}}
|
|
187
212
|
<div class="lg:col-span-2">
|
|
188
213
|
<InputGroup @wrapperClass="pt-1">
|
|
189
214
|
<Toggle
|
|
@@ -196,6 +221,7 @@
|
|
|
196
221
|
/>
|
|
197
222
|
</InputGroup>
|
|
198
223
|
</div>
|
|
224
|
+
{{/if}}
|
|
199
225
|
|
|
200
226
|
<RegistryYield @registry="fleet-ops:component:order:form:details" as |RegistryComponent|>
|
|
201
227
|
<RegistryComponent @order={{@resource}} @resource={{@resource}} />
|
|
@@ -13,6 +13,7 @@ export default class OrderFormDetailsComponent extends Component {
|
|
|
13
13
|
@service leafletMapManager;
|
|
14
14
|
@service leafletLayerVisibilityManager;
|
|
15
15
|
@service currentUser;
|
|
16
|
+
@service orderPresentation;
|
|
16
17
|
@tracked customFields;
|
|
17
18
|
|
|
18
19
|
constructor() {
|
|
@@ -20,6 +21,40 @@ export default class OrderFormDetailsComponent extends Component {
|
|
|
20
21
|
this.orderConfigActions.loadAll.perform();
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/** The presentation profile applying to the order's current config, if any. */
|
|
25
|
+
get profile() {
|
|
26
|
+
return this.orderPresentation.profileFor(this.args.resource);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Extension fields rendered inside the details grid when a profile applies. */
|
|
30
|
+
get profileDetailFields() {
|
|
31
|
+
return this.profile?.form?.detailFields ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A persisted order keeps its profiled order type; reclassification is not supported. */
|
|
35
|
+
get orderTypeLocked() {
|
|
36
|
+
return Boolean(this.profile) && this.args.resource?.isNew === false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Field visibility; everything is visible unless a presentation profile hides it. */
|
|
40
|
+
get show() {
|
|
41
|
+
const hidden = this.args.hiddenFields ?? this.orderPresentation.hiddenFieldsFor(this.args.resource);
|
|
42
|
+
const visible = (key) => !hidden.includes(key);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
internalId: visible('internal-id'),
|
|
46
|
+
schedule: visible('schedule'),
|
|
47
|
+
customer: visible('customer'),
|
|
48
|
+
facilitator: visible('facilitator'),
|
|
49
|
+
serviceType: visible('service-type'),
|
|
50
|
+
driver: visible('driver'),
|
|
51
|
+
vehicle: visible('vehicle'),
|
|
52
|
+
pod: visible('pod'),
|
|
53
|
+
adhoc: visible('adhoc'),
|
|
54
|
+
dispatch: visible('dispatch'),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
get integratedVendorServiceType() {
|
|
24
59
|
return this.args.resource?.type;
|
|
25
60
|
}
|
|
@@ -38,12 +73,23 @@ export default class OrderFormDetailsComponent extends Component {
|
|
|
38
73
|
|
|
39
74
|
@task *selectOrderConfig(orderConfig) {
|
|
40
75
|
if (!orderConfig) return;
|
|
76
|
+
const previousProfile = this.orderPresentation.profileFor(this.args.resource);
|
|
41
77
|
this.args.resource.setProperties({
|
|
42
78
|
order_config_uuid: orderConfig.id,
|
|
43
79
|
order_config: orderConfig,
|
|
44
80
|
type: orderConfig.key,
|
|
45
81
|
});
|
|
46
82
|
this.args.resource.payload.set('type', orderConfig.key);
|
|
83
|
+
|
|
84
|
+
// Let presentation profiles clean up or prepare the draft when the order type changes.
|
|
85
|
+
const nextProfile = this.orderPresentation.profileFor(this.args.resource);
|
|
86
|
+
if (previousProfile && previousProfile !== nextProfile && typeof previousProfile.release === 'function') {
|
|
87
|
+
previousProfile.release(this.args.resource);
|
|
88
|
+
}
|
|
89
|
+
if (nextProfile && nextProfile !== previousProfile) {
|
|
90
|
+
this.orderPresentation.prepare(this.args.resource);
|
|
91
|
+
}
|
|
92
|
+
|
|
47
93
|
this.requestServiceQuoteRefresh('details.order_config.changed');
|
|
48
94
|
|
|
49
95
|
try {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
<ContentPanel @title={{t "order.fields.route"}} @open={{true}} @wrapperClass="bordered-top">
|
|
1
|
+
<ContentPanel @title={{or this.profileTitle (t "order.fields.route")}} @open={{true}} @wrapperClass="bordered-top">
|
|
2
2
|
<div class="flex flex-row items-center justify-between mb-4">
|
|
3
3
|
<div class="flex">
|
|
4
|
-
|
|
4
|
+
{{#if this.allowsMultipleWaypoints}}
|
|
5
|
+
<Toggle @isToggled={{this.multipleWaypoints}} @onToggle={{this.toggleWaypoints}} @label={{t "order.fields.route-label"}} />
|
|
6
|
+
{{/if}}
|
|
5
7
|
</div>
|
|
6
8
|
<div class="flex flex-1 justify-end space-x-2">
|
|
7
9
|
{{#if this.multipleWaypoints}}
|
|
@@ -22,6 +22,7 @@ export default class OrderFormRouteComponent extends Component {
|
|
|
22
22
|
@service notifications;
|
|
23
23
|
@service placeActions;
|
|
24
24
|
@service orderCreation;
|
|
25
|
+
@service orderPresentation;
|
|
25
26
|
@tracked multipleWaypoints = false;
|
|
26
27
|
@tracked routingControl;
|
|
27
28
|
@tracked route;
|
|
@@ -35,6 +36,16 @@ export default class OrderFormRouteComponent extends Component {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/** A presentation profile may hide multi-drop routing and rename the route panel. */
|
|
40
|
+
get allowsMultipleWaypoints() {
|
|
41
|
+
const hidden = this.args.hiddenFields ?? this.orderPresentation.hiddenFieldsFor(this.args.resource);
|
|
42
|
+
return !hidden.includes('multiple-waypoints');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get profileTitle() {
|
|
46
|
+
return this.orderPresentation.profileFor(this.args.resource)?.form?.titles?.route ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
get coordinates() {
|
|
39
50
|
return this.routePoints.map(({ place }) => [place.latitude, place.longitude]);
|
|
40
51
|
}
|
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
RegistryYieldEnd=(component "registry-yield" registry="fleet-ops:component:order:form:end" order=@resource)
|
|
17
17
|
)
|
|
18
18
|
}}
|
|
19
|
+
{{else if this.profile}}
|
|
20
|
+
{{#each this.profileSections key="key" as |section|}}
|
|
21
|
+
{{#if section.native}}
|
|
22
|
+
{{#let (component section.componentName resource=@resource hiddenFields=this.hiddenFields) as |NativeSection|}}
|
|
23
|
+
<NativeSection />
|
|
24
|
+
{{/let}}
|
|
25
|
+
{{else}}
|
|
26
|
+
<LazyEngineComponent @component={{section.component}} as |ProfileSection|>
|
|
27
|
+
<ProfileSection @resource={{@resource}} @order={{@resource}} @controller={{@controller}} @profile={{this.profile}} @hiddenFields={{this.hiddenFields}} />
|
|
28
|
+
</LazyEngineComponent>
|
|
29
|
+
{{/if}}
|
|
30
|
+
{{/each}}
|
|
19
31
|
{{else}}
|
|
20
32
|
<RegistryYield @registry="fleet-ops:component:order:form:start" as |RegistryComponent|>
|
|
21
33
|
<RegistryComponent @order={{@resource}} @resource={{@resource}} />
|
|
@@ -11,6 +11,7 @@ export default class OrderFormComponent extends Component {
|
|
|
11
11
|
@service customFieldsRegistry;
|
|
12
12
|
@service mapManager;
|
|
13
13
|
@service currentUser;
|
|
14
|
+
@service orderPresentation;
|
|
14
15
|
@tracked customFields;
|
|
15
16
|
|
|
16
17
|
constructor() {
|
|
@@ -18,6 +19,19 @@ export default class OrderFormComponent extends Component {
|
|
|
18
19
|
this.orderConfigActions.loadAll.perform();
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/** The extension presentation profile applying to this order's config, if any. */
|
|
23
|
+
get profile() {
|
|
24
|
+
return this.orderPresentation.profileFor(this.args.resource);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get profileSections() {
|
|
28
|
+
return this.orderPresentation.renderableSectionsFor(this.profile, 'form');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get hiddenFields() {
|
|
32
|
+
return this.orderPresentation.hiddenFieldsFor(this.args.resource);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
@action selectFacilitator(model) {
|
|
22
36
|
this.args.resource.set('facilitator', model);
|
|
23
37
|
this.args.resource.set('driver', null);
|
|
@@ -32,7 +46,13 @@ export default class OrderFormComponent extends Component {
|
|
|
32
46
|
});
|
|
33
47
|
this.args.resource.payload.set('type', orderConfig.key);
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
// loadSubjectCustomFields rejects when the custom fields cannot be loaded (ember-core 0.3.25).
|
|
50
|
+
try {
|
|
51
|
+
this.customFields = yield this.customFieldsRegistry.loadSubjectCustomFields.perform(orderConfig);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
this.customFields = undefined;
|
|
54
|
+
debug('Unable to load order custom fields: ' + err.message);
|
|
55
|
+
}
|
|
36
56
|
}
|
|
37
57
|
|
|
38
58
|
@task *selectDriver(driver) {
|
|
@@ -13,7 +13,9 @@ export default class OrderKanbanComponent extends Component {
|
|
|
13
13
|
@service fetch;
|
|
14
14
|
@service notifications;
|
|
15
15
|
@service intl;
|
|
16
|
+
@service store;
|
|
16
17
|
@tracked statuses = [];
|
|
18
|
+
@tracked orderConfigRecord = null;
|
|
17
19
|
@tracked orders = this.args.orders ?? [];
|
|
18
20
|
@tracked orderConfig = this.args.orderConfig ?? null;
|
|
19
21
|
|
|
@@ -23,6 +25,11 @@ export default class OrderKanbanComponent extends Component {
|
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
get columns() {
|
|
28
|
+
const lifecycleColumns = this.#lifecycleColumns();
|
|
29
|
+
if (lifecycleColumns) {
|
|
30
|
+
return lifecycleColumns;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
const start = this.#defaultStatuses.start ?? [];
|
|
27
34
|
const end = this.#defaultStatuses.end ?? [];
|
|
28
35
|
const loaded = isArray(this.statuses) ? this.statuses : [];
|
|
@@ -112,6 +119,64 @@ export default class OrderKanbanComponent extends Component {
|
|
|
112
119
|
this.loadStatuses.perform();
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Columns for an order config with a configured lifecycle (`meta.lifecycle`): the flow's own
|
|
124
|
+
* activities in graph order from the initial activity, titled with their status labels.
|
|
125
|
+
* No default dispatch columns are injected. Returns null for configs without a lifecycle.
|
|
126
|
+
*/
|
|
127
|
+
#lifecycleColumns() {
|
|
128
|
+
const config = this.orderConfigRecord;
|
|
129
|
+
const lifecycle = config?.meta?.lifecycle;
|
|
130
|
+
const flow = config?.flow;
|
|
131
|
+
if (!lifecycle || !flow || typeof flow !== 'object') {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const activities = Object.values(flow).filter((activity) => activity && activity.code);
|
|
136
|
+
const byKeyOrCode = new Map();
|
|
137
|
+
for (const activity of activities) {
|
|
138
|
+
byKeyOrCode.set(activity.key ?? activity.code, activity);
|
|
139
|
+
byKeyOrCode.set(activity.code, activity);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Breadth-first walk from the initial activity, then any activities not reachable from it.
|
|
143
|
+
const ordered = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
const queue = [byKeyOrCode.get(lifecycle.initial)].filter(Boolean);
|
|
146
|
+
while (queue.length) {
|
|
147
|
+
const activity = queue.shift();
|
|
148
|
+
if (seen.has(activity.code)) continue;
|
|
149
|
+
seen.add(activity.code);
|
|
150
|
+
ordered.push(activity);
|
|
151
|
+
for (const child of activity.activities ?? []) {
|
|
152
|
+
const next = byKeyOrCode.get(typeof child === 'string' ? child : (child?.key ?? child?.code));
|
|
153
|
+
if (next && !seen.has(next.code)) queue.push(next);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const activity of activities.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0))) {
|
|
157
|
+
if (!seen.has(activity.code)) {
|
|
158
|
+
seen.add(activity.code);
|
|
159
|
+
ordered.push(activity);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Keep orders whose status is not part of the flow visible rather than silently dropping them.
|
|
164
|
+
const columns = ordered.map((activity) => ({ id: activity.code, title: this.#activityTitle(activity) }));
|
|
165
|
+
for (const status of isArray(this.statuses) ? this.statuses : []) {
|
|
166
|
+
if (status && !seen.has(status)) {
|
|
167
|
+
seen.add(status);
|
|
168
|
+
columns.push({ id: status, title: titleize(smartHumanize(status)) });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return columns.map((column, index) => ({ ...column, position: index, cards: this.#getOrdersByStatus(column.id, this.orders) }));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#activityTitle(activity) {
|
|
176
|
+
const label = typeof activity.status === 'string' ? activity.status.trim() : '';
|
|
177
|
+
return label && !label.includes('{') ? label : titleize(smartHumanize(activity.code));
|
|
178
|
+
}
|
|
179
|
+
|
|
115
180
|
#getOrdersByStatus(status, orders = []) {
|
|
116
181
|
let filteredOrders = orders.filter((order) => order.status === status);
|
|
117
182
|
if (this.orderConfig) {
|
|
@@ -125,6 +190,13 @@ export default class OrderKanbanComponent extends Component {
|
|
|
125
190
|
const params = {};
|
|
126
191
|
if (this.orderConfig) params.order_config_uuid = this.orderConfig;
|
|
127
192
|
|
|
193
|
+
try {
|
|
194
|
+
this.orderConfigRecord = this.orderConfig ? yield this.store.findRecord('order-config', this.orderConfig) : null;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
this.orderConfigRecord = null;
|
|
197
|
+
debug('Unable to load order config for board: ' + err.message);
|
|
198
|
+
}
|
|
199
|
+
|
|
128
200
|
try {
|
|
129
201
|
const statuses = yield this.fetch.get('orders/statuses', params);
|
|
130
202
|
this.statuses = isArray(statuses) ? statuses : [];
|