diff --git a/addon/components/order-config-manager/activity-flow.js b/addon/components/order-config-manager/activity-flow.js index 88c444cca..814a0fb79 100644 --- a/addon/components/order-config-manager/activity-flow.js +++ b/addon/components/order-config-manager/activity-flow.js @@ -34,7 +34,17 @@ export default class OrderConfigManagerActivityFlowComponent extends Component { * An array of activity codes that should not be modified. * @type {Array.} */ - @tracked immutableActivities = ['created', 'dispatched', 'started']; + /** + * Activities that cannot be edited or removed. Configs with their own lifecycle + * (`meta.lifecycle`) are not bound to the default dispatch activities. + */ + get immutableActivities() { + return this.lifecycle ? [] : ['created', 'dispatched', 'started']; + } + + get lifecycle() { + return this.config?.meta?.lifecycle ?? null; + } /** * The configuration data for the activity flow. @@ -180,6 +190,12 @@ export default class OrderConfigManagerActivityFlowComponent extends Component { this.clearGraph({ onAfter: () => { this.flow = {}; + // A configured lifecycle resets to its saved flow, never to the default dispatch flow. + if (this.lifecycle && this.config?.flow && Object.keys(this.config.flow).length) { + this.deserializeFlow(this.config.flow); + return; + } + const defaultActivities = this.getDefaultActivities(); this.addActivityToGraph(defaultActivities); }, @@ -361,7 +377,7 @@ export default class OrderConfigManagerActivityFlowComponent extends Component { } }); - const rootCode = getOrderConfigFlowRootCode(deserializedFlow); + const rootCode = getOrderConfigFlowRootCode(deserializedFlow, this.lifecycle?.initial); if (rootCode && deserializedFlow[rootCode]) { this.addDeserializedActivityToGraph(deserializedFlow[rootCode]); } @@ -823,16 +839,26 @@ export default class OrderConfigManagerActivityFlowComponent extends Component { const addButton = this.createAddActivityButton(activity, { x: width }); let tools = []; - if (activity.get('code') === 'created' || activity.get('code') === 'dispatched') { - tools = []; - } + const code = activity.get('code'); + const canUpdate = this.abilities.can('fleet-ops update order-config'); - if (activity.get('code') === 'started' && this.abilities.can('fleet-ops update order-config')) { - tools = [addButton]; - } + if (this.lifecycle) { + // The configured initial activity is the root: it can gain children but not be removed. + if (canUpdate) { + tools = code === this.lifecycle.initial ? [addButton] : [removeButton, addButton]; + } + } else { + if (code === 'created' || code === 'dispatched') { + tools = []; + } - if (!this.immutableActivities.includes(activity.get('code')) && this.abilities.can('fleet-ops update order-config')) { - tools = [removeButton, addButton]; + if (code === 'started' && canUpdate) { + tools = [addButton]; + } + + if (!this.immutableActivities.includes(code) && canUpdate) { + tools = [removeButton, addButton]; + } } const activityNodeView = activityNode.findView(this.paper); diff --git a/addon/components/order/details.hbs b/addon/components/order/details.hbs index 1efdb1cbb..9e2ca09e4 100644 --- a/addon/components/order/details.hbs +++ b/addon/components/order/details.hbs @@ -44,6 +44,35 @@ ) ) }} + {{else if this.profile}} + {{#each this.profileSections key="key" as |section|}} + {{#if section.native}} + {{#let + (component + section.componentName + resource=@resource + onChange=section.onChange + isLoading=@isLoading + isLoadingActivity=@isLoadingActivity + reloadToken=this.effectiveProofReloadToken + ) + as |NativeSection| + }} + + {{/let}} + {{else}} + + + + {{/if}} + {{/each}} {{else}} diff --git a/addon/components/order/details.js b/addon/components/order/details.js index 4e6976747..279bc7837 100644 --- a/addon/components/order/details.js +++ b/addon/components/order/details.js @@ -1,14 +1,40 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; export default class OrderDetailsComponent extends Component { + @service orderPresentation; @tracked proofReloadToken = 0; get effectiveProofReloadToken() { return `${this.args.proofReloadToken ?? 0}:${this.proofReloadToken}`; } + /** The extension presentation profile applying to this order's config, if any. */ + get profile() { + return this.orderPresentation.profileFor(this.args.resource); + } + + /** Profile sections with the change handler each native section expects. */ + get profileSections() { + const handlers = { + activity: this.handleActivityChanged, + detail: this.args.onDetailsChanged, + 'custom-fields': this.args.onCustomFieldsChanged, + notes: this.args.onNotesChanged, + route: this.args.onRouteChanged, + payload: this.args.onPayloadChanged, + documents: this.args.onDocumentsChanged, + comments: this.args.onCommentsChanged, + metadata: this.args.onMetadataChanged, + }; + + return this.orderPresentation.renderableSectionsFor(this.profile, 'details').map((section) => { + return section.native ? { ...section, onChange: handlers[section.name] } : section; + }); + } + @action handleActivityChanged(optionsOrActivity) { if (typeof this.args.onActivityChanged === 'function') { this.args.onActivityChanged(optionsOrActivity?.activityCreated ?? optionsOrActivity); diff --git a/addon/components/order/form.hbs b/addon/components/order/form.hbs index 54f41171c..8fc2a1e56 100644 --- a/addon/components/order/form.hbs +++ b/addon/components/order/form.hbs @@ -16,6 +16,18 @@ RegistryYieldEnd=(component "registry-yield" registry="fleet-ops:component:order:form:end" order=@resource) ) }} + {{else if this.profile}} + {{#each this.profileSections key="key" as |section|}} + {{#if section.native}} + {{#let (component section.componentName resource=@resource hiddenFields=this.hiddenFields) as |NativeSection|}} + + {{/let}} + {{else}} + + + + {{/if}} + {{/each}} {{else}} diff --git a/addon/components/order/form.js b/addon/components/order/form.js index dadf47380..c2fbd0502 100644 --- a/addon/components/order/form.js +++ b/addon/components/order/form.js @@ -11,6 +11,7 @@ export default class OrderFormComponent extends Component { @service customFieldsRegistry; @service mapManager; @service currentUser; + @service orderPresentation; @tracked customFields; constructor() { @@ -18,6 +19,19 @@ export default class OrderFormComponent extends Component { this.orderConfigActions.loadAll.perform(); } + /** The extension presentation profile applying to this order's config, if any. */ + get profile() { + return this.orderPresentation.profileFor(this.args.resource); + } + + get profileSections() { + return this.orderPresentation.renderableSectionsFor(this.profile, 'form'); + } + + get hiddenFields() { + return this.orderPresentation.hiddenFieldsFor(this.args.resource); + } + @action selectFacilitator(model) { this.args.resource.set('facilitator', model); this.args.resource.set('driver', null); diff --git a/addon/components/order/form/details.hbs b/addon/components/order/form/details.hbs index 32095e9c4..fec97dabe 100644 --- a/addon/components/order/form/details.hbs +++ b/addon/components/order/form/details.hbs @@ -9,7 +9,7 @@ @onChange={{perform this.selectOrderConfig}} @placeholder={{t "order.fields.order-type-placeholder"}} @triggerClass="form-select form-input" - @disabled={{cannot-write @resource}} + @disabled={{or (cannot-write @resource) this.orderTypeLocked}} as |orderConfig| > @@ -17,8 +17,19 @@ + {{#if this.profileDetailFields}} +
+ + + +
+ {{/if}} + + {{#if this.show.internalId}} + {{/if}} + {{#if this.show.schedule}} + {{/if}} + {{#if this.show.customer}} + {{/if}} + {{#if this.show.facilitator}} + {{/if}} - {{#if (and @resource.facilitator.isIntegratedVendor @resource.facilitator.service_types.length)}} + {{#if (and this.show.serviceType @resource.facilitator.isIntegratedVendor @resource.facilitator.service_types.length)}}