+ {{#if (or @resource.work_order_uuid @resource.work_order_subject)}}
+
-
Overdue Status
-
- Overdue
-
+ {{! — Summary & Notes — }}
+ {{#if @resource.summary}}
+
+ Summary
- {{else if @resource.days_until_due}}
-
-
Days Until Due
-
{{@resource.days_until_due}} days
+
{{/if}}
+ {{#if @resource.notes}}
+
+ Notes
+
+
+ {{/if}}
+
+
-
-
Notes
-
{{n-a @resource.notes}}
+ {{! ===== PARTS & SERVICES (invoice-style line items + totals) ===== }}
+
+ {{#if @resource.line_items.length}}
+
+
+
+
+
+
+
+
+
+ | Description |
+ Qty |
+ Unit Cost |
+ Total |
+
+
+
+ {{#each @resource.line_items as |item|}}
+
+ | {{n-a item.description}} |
+ {{n-a item.quantity}} |
+
+ {{format-currency item.unit_cost (or item.currency @resource.currency)}}
+ |
+
+ {{format-currency (mult item.quantity item.unit_cost) (or item.currency @resource.currency)}}
+ |
+
+ {{/each}}
+
+
+ {{else}}
+ No line items recorded.
+ {{/if}}
+
+ {{! Totals }}
+
+
+ Labour
+ {{format-currency @resource.labor_cost @resource.currency}}
+
+
+ Parts
+ {{format-currency @resource.parts_cost @resource.currency}}
+
+
+ Tax
+ {{format-currency @resource.tax @resource.currency}}
+
+
+ Total
+ {{format-currency @resource.total_cost @resource.currency}}
+ {{! ===== CUSTOM FIELDS ===== }}
+
+
diff --git a/addon/components/maintenance/form.hbs b/addon/components/maintenance/form.hbs
index e61554bb9..569ee0df0 100644
--- a/addon/components/maintenance/form.hbs
+++ b/addon/components/maintenance/form.hbs
@@ -1,10 +1,24 @@
-
diff --git a/addon/components/maintenance/form.js b/addon/components/maintenance/form.js
index 4e596d7f5..cd633049e 100644
--- a/addon/components/maintenance/form.js
+++ b/addon/components/maintenance/form.js
@@ -1,18 +1,143 @@
import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { action } from '@ember/object';
+
+/**
+ * Maps a polymorphic type value to the Ember Data model name used by ModelSelect.
+ */
+const TYPE_TO_MODEL = {
+ 'fleet-ops:vehicle': 'vehicle',
+ 'fleet-ops:driver': 'driver',
+ 'fleet-ops:equipment': 'equipment',
+ 'fleet-ops:vendor': 'vendor',
+ 'Auth:User': 'user',
+};
+
+/**
+ * Maps a concrete Ember Data model name back to the backend polymorphic type string.
+ * Used to restore the type selector when editing an existing record that has a
+ * loaded @belongsTo('maintenance-subject', {polymorphic: true}) relationship.
+ */
+const MAINTAINABLE_MODEL_TO_TYPE = {
+ 'maintenance-subject-vehicle': 'fleet-ops:vehicle',
+ 'maintenance-subject-equipment': 'fleet-ops:equipment',
+ // Fall-through for raw models passed from vehicle-actions.js
+ vehicle: 'fleet-ops:vehicle',
+ equipment: 'fleet-ops:equipment',
+};
+
+/**
+ * Maps a concrete Ember Data model name back to the backend polymorphic type string
+ * for the performed_by relationship.
+ */
+const PERFORMED_BY_MODEL_TO_TYPE = {
+ 'facilitator-vendor': 'fleet-ops:vendor',
+ 'facilitator-contact': 'fleet-ops:contact',
+ 'facilitator-integrated-vendor': 'fleet-ops:vendor',
+ vendor: 'fleet-ops:vendor',
+ driver: 'fleet-ops:driver',
+ user: 'Auth:User',
+};
export default class MaintenanceFormComponent extends Component {
+ /** Maintenance type options — the category of maintenance activity. */
+ maintenanceTypeOptions = ['preventive', 'corrective', 'predictive', 'routine', 'emergency', 'inspection', 'repair', 'replacement', 'calibration'];
+
+ /** Status options for a maintenance record. */
+ statusOptions = ['scheduled', 'in_progress', 'completed', 'cancelled', 'on_hold'];
+
+ /** Priority options for a maintenance record. */
+ priorityOptions = ['low', 'medium', 'high', 'critical'];
+
/**
- * Maintenance type options
+ * Polymorphic maintainable type options — the asset being maintained.
+ * Uses label/value objects so the PowerSelect trigger shows a human-readable
+ * label instead of the raw model type string.
*/
- maintenanceTypeOptions = ['preventive', 'corrective', 'predictive', 'routine', 'emergency', 'inspection', 'repair', 'replacement', 'calibration'];
+ maintainableTypeOptions = [
+ { value: 'fleet-ops:vehicle', label: 'Vehicle' },
+ { value: 'fleet-ops:equipment', label: 'Equipment' },
+ ];
/**
- * Status options for maintenance
+ * Polymorphic performed-by type options — who carried out the maintenance.
+ * Uses label/value objects so the PowerSelect trigger shows a human-readable
+ * label instead of the raw model type string.
*/
- statusOptions = ['scheduled', 'in_progress', 'completed', 'cancelled', 'on_hold'];
+ performedByTypeOptions = [
+ { value: 'fleet-ops:vendor', label: 'Vendor' },
+ { value: 'fleet-ops:driver', label: 'Driver' },
+ { value: 'Auth:User', label: 'User' },
+ ];
+
+ /** The currently selected maintainable type option object. */
+ @tracked selectedMaintainableType = null;
+
+ /** The currently selected performed-by type option object. */
+ @tracked selectedPerformedByType = null;
+
+ /** Derived Ember Data model name for the currently selected maintainable type. */
+ @tracked maintainableModelName = null;
+
+ /** Derived Ember Data model name for the currently selected performed-by type. */
+ @tracked performedByModelName = null;
+
+ constructor(owner, args) {
+ super(owner, args);
+ const { resource } = args;
+ // Restore maintainable type selection from the polymorphic relationship.
+ // When editing an existing record, resource.maintainable is a loaded MaintenanceSubject model.
+ // When creating from vehicle-actions.js, resource.maintainable may be a raw vehicle/equipment model.
+ const maintainable = resource?.maintainable;
+ if (maintainable) {
+ const modelName = maintainable.constructor?.modelName ?? maintainable.modelName;
+ const typeValue = MAINTAINABLE_MODEL_TO_TYPE[modelName] ?? null;
+ if (typeValue) {
+ this.selectedMaintainableType = this.maintainableTypeOptions.find((o) => o.value === typeValue) ?? null;
+ this.maintainableModelName = TYPE_TO_MODEL[typeValue] ?? null;
+ }
+ }
+ // Restore performed_by type selection from the polymorphic relationship.
+ const performedBy = resource?.performed_by;
+ if (performedBy) {
+ const modelName = performedBy.constructor?.modelName ?? performedBy.modelName;
+ const typeValue = PERFORMED_BY_MODEL_TO_TYPE[modelName] ?? null;
+ if (typeValue) {
+ this.selectedPerformedByType = this.performedByTypeOptions.find((o) => o.value === typeValue) ?? null;
+ this.performedByModelName = TYPE_TO_MODEL[typeValue] ?? null;
+ }
+ }
+ }
/**
- * Priority options
+ * Handles a change to the maintainable type selector. Resets the
+ * maintainable relationship so a stale association is not persisted.
*/
- priorityOptions = ['low', 'medium', 'high', 'critical'];
+ @action onMaintainableTypeChange(option) {
+ this.selectedMaintainableType = option;
+ // Clear the maintainable relationship — user must re-select the asset
+ this.args.resource.maintainable = null;
+ this.maintainableModelName = TYPE_TO_MODEL[option?.value] ?? null;
+ }
+
+ /** Assigns the selected maintainable model to the resource. */
+ @action assignMaintainable(model) {
+ this.args.resource.maintainable = model;
+ }
+
+ /**
+ * Handles a change to the performed-by type selector. Resets the
+ * performed-by relationship so a stale association is not persisted.
+ */
+ @action onPerformedByTypeChange(option) {
+ this.selectedPerformedByType = option;
+ // Clear the performed_by relationship — user must re-select
+ this.args.resource.performed_by = null;
+ this.performedByModelName = TYPE_TO_MODEL[option?.value] ?? null;
+ }
+
+ /** Assigns the selected performer model to the resource. */
+ @action assignPerformedBy(model) {
+ this.args.resource.performed_by = model;
+ }
}
diff --git a/addon/components/maintenance/panel-header.hbs b/addon/components/maintenance/panel-header.hbs
new file mode 100644
index 000000000..64b344487
--- /dev/null
+++ b/addon/components/maintenance/panel-header.hbs
@@ -0,0 +1,31 @@
+
+
+
+
+
{{n-a @resource.summary}}
+ {{smart-humanize @resource.status}}
+
+
+
{{smart-humanize @resource.type}}
+ {{#if @resource.priority}}
+
{{t "column.priority"}}: {{smart-humanize @resource.priority}}
+ {{/if}}
+ {{#if @resource.scheduled_at}}
+
{{t "column.scheduled-at"}}: {{format-date @resource.scheduled_at}}
+ {{/if}}
+
+
+
+
+
diff --git a/addon/components/maintenance/panel-header.js b/addon/components/maintenance/panel-header.js
new file mode 100644
index 000000000..f15c64652
--- /dev/null
+++ b/addon/components/maintenance/panel-header.js
@@ -0,0 +1,2 @@
+import Component from '@glimmer/component';
+export default class MaintenancePanelHeaderComponent extends Component {}
diff --git a/addon/components/modals/send-work-order.hbs b/addon/components/modals/send-work-order.hbs
new file mode 100644
index 000000000..20fb91ccf
--- /dev/null
+++ b/addon/components/modals/send-work-order.hbs
@@ -0,0 +1,91 @@
+
+
+
+ Review the details below before sending. The work order will be emailed to the assigned vendor.
+
+
+ {{! ── Work Order details ── }}
+
+
+ Work Order
+
+
+
+ Subject
+
+ {{n-a (or @options.workOrder.subject @options.workOrder.target_name)}}
+
+
+
+ ID
+
+ {{n-a @options.workOrder.public_id}}
+
+
+
+ Status
+
+ {{smart-humanize @options.workOrder.status}}
+
+
+ {{#if @options.workOrder.due_at}}
+
+ Due
+
+ {{format-date-fns @options.workOrder.due_at}}
+
+
+ {{/if}}
+
+
+
+ {{! ── Recipient (vendor) details ── }}
+
+
+ Recipient
+
+ {{#if @options.vendorName}}
+
+
+
+
+
+
+
+ {{@options.vendorName}}
+
+ {{#if @options.vendorEmail}}
+
+
+ {{@options.vendorEmail}}
+
+ {{/if}}
+ {{#if @options.vendorPhone}}
+
+
+ {{@options.vendorPhone}}
+
+ {{/if}}
+
+
+
+ {{else}}
+
+
+
+ No vendor assigned to this work order.
+
+
+ {{/if}}
+
+
+ {{#unless @options.vendorEmail}}
+
+
+
+ The assigned vendor does not have an email address on file. Please update the vendor record before sending.
+
+
+ {{/unless}}
+
+
diff --git a/addon/components/modals/send-work-order.js b/addon/components/modals/send-work-order.js
new file mode 100644
index 000000000..f13eb3caa
--- /dev/null
+++ b/addon/components/modals/send-work-order.js
@@ -0,0 +1,3 @@
+import Component from '@glimmer/component';
+
+export default class ModalsSendWorkOrderComponent extends Component {}
diff --git a/addon/components/part/card.hbs b/addon/components/part/card.hbs
new file mode 100644
index 000000000..102cb1871
--- /dev/null
+++ b/addon/components/part/card.hbs
@@ -0,0 +1,49 @@
+
+
+
+
+
+ {{#if @resource.type}}
+
+
+ {{humanize @resource.type}}
+
+ {{/if}}
+ {{#if @resource.quantity_on_hand}}
+
+
+ Qty: {{@resource.quantity_on_hand}}
+
+ {{/if}}
+ {{#if @resource.unit_cost}}
+
+
+ {{format-currency @resource.unit_cost @resource.currency}}
+
+ {{/if}}
+
+ {{#if (has-block "body")}}
+ {{yield to="body"}}
+ {{/if}}
+
+
+
+{{yield}}
diff --git a/addon/components/part/card.js b/addon/components/part/card.js
new file mode 100644
index 000000000..c15f8fcdc
--- /dev/null
+++ b/addon/components/part/card.js
@@ -0,0 +1,6 @@
+import Component from '@glimmer/component';
+import { inject as service } from '@ember/service';
+
+export default class PartCardComponent extends Component {
+ @service partActions;
+}
diff --git a/addon/components/part/details.hbs b/addon/components/part/details.hbs
index 58ddfed12..120e6d25d 100644
--- a/addon/components/part/details.hbs
+++ b/addon/components/part/details.hbs
@@ -1,51 +1,82 @@
-
-
-
+
+
+ {{! ===== OVERVIEW ===== }}
+
+
+
+ {{! — Photo — }}
+ {{#if (or @resource.photo_url (config "defaultValues.partImage"))}}
+
+
+
+
+
+
{{n-a @resource.name}}
+
{{n-a @resource.public_id}}
+
+
+ {{/if}}
+
+ {{! — Identity & Classification — }}
+
+ Identity & Classification
+
+
+
ID
+
{{n-a @resource.public_id}}
+
SKU
-
{{n-a @resource.sku}}
+
{{n-a @resource.sku}}
-
Name
{{n-a @resource.name}}
-
Type
-
{{smart-humanize @resource.type}}
+
{{n-a (smart-humanize @resource.type)}}
-
-
Status
-
- {{smart-humanize @resource.status}}
-
+
Barcode
+
{{n-a @resource.barcode}}
+
+
+
Serial Number
+
{{n-a @resource.serial_number}}
+ {{! — Make & Model — }}
+
+ Make & Model
+
Manufacturer
{{n-a @resource.manufacturer}}
-
Model
{{n-a @resource.model}}
-
-
Serial Number
-
{{n-a @resource.serial_number}}
+ {{! — Inventory — }}
+
+ Inventory
-
-
Barcode
-
{{n-a @resource.barcode}}
+
Status
+
+ {{smart-humanize @resource.status}}
+
-
-
Quantity On Hand
-
+
Quantity on Hand
+
{{n-a @resource.quantity_on_hand}}
{{#if @resource.is_low_stock}}
Low Stock
@@ -56,53 +87,68 @@
{{/if}}
+ {{#if @resource.asset_name}}
+
+
Fitted To
+
{{n-a @resource.asset_name}}
+
+ {{/if}}
+
+
+ {{! ===== PRICING ===== }}
+
+
Unit Cost
-
{{format-currency @resource.unit_cost}}
+
{{format-currency @resource.unit_cost @resource.currency}}
-
MSRP
-
{{format-currency @resource.msrp}}
+
{{format-currency @resource.msrp @resource.currency}}
-
-
Total Value
-
{{format-currency @resource.total_value}}
+
Total Inventory Value
+
{{format-currency @resource.total_value @resource.currency}}
-
-
Vendor
-
{{n-a @resource.vendor_name}}
-
-
-
-
Warranty
-
{{n-a @resource.warranty_name}}
-
-
-
-
Asset
-
{{n-a @resource.asset_name}}
-
-
-
-
Description
-
{{n-a @resource.description}}
+
Currency
+
{{n-a @resource.currency}}
+ {{! ===== SUPPLIER & WARRANTY ===== }}
+ {{#if (or @resource.vendor_name @resource.warranty_name)}}
+
+
+ {{#if @resource.vendor_name}}
+
+
Supplier / Vendor
+
{{n-a @resource.vendor_name}}
+
+ {{/if}}
+ {{#if @resource.warranty_name}}
+
+
Warranty
+
{{n-a @resource.warranty_name}}
+
+ {{/if}}
+
+
+ {{/if}}
+
+ {{! ===== DESCRIPTION ===== }}
+ {{#if @resource.description}}
+
+
+ {{@resource.description}}
+
+
+ {{/if}}
+
+ {{! ===== CUSTOM FIELDS ===== }}
-
-
-
+
diff --git a/addon/components/part/form.hbs b/addon/components/part/form.hbs
index c6d104519..0e87a1e56 100644
--- a/addon/components/part/form.hbs
+++ b/addon/components/part/form.hbs
@@ -1,10 +1,20 @@
-