@fleetbase/fleetops-engine 0.6.37 → 0.6.39
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/addon/components/activity/form.hbs +2 -12
- package/addon/components/activity/logic-builder.hbs +1 -1
- package/addon/components/custom-entity/form.hbs +4 -20
- package/addon/components/device/form.hbs +3 -15
- package/addon/components/device/panel-header.hbs +13 -2
- package/addon/components/driver/details.hbs +33 -2
- package/addon/components/driver/form.hbs +42 -0
- package/addon/components/driver/panel-header.hbs +8 -1
- package/addon/components/driver/schedule.hbs +115 -76
- package/addon/components/driver/schedule.js +349 -157
- package/addon/components/driver-onboard-settings.hbs +2 -8
- package/addon/components/entity-field-editing-settings.hbs +2 -5
- package/addon/components/equipment/card.hbs +49 -0
- package/addon/components/equipment/card.js +6 -0
- package/addon/components/equipment/details.hbs +83 -44
- package/addon/components/equipment/form.hbs +111 -41
- package/addon/components/equipment/form.js +78 -10
- package/addon/components/equipment/panel-header.hbs +36 -0
- package/addon/components/equipment/panel-header.js +2 -0
- package/addon/components/fleet/driver-listing.hbs +3 -1
- package/addon/components/fleet/vehicle-listing.hbs +4 -7
- package/addon/components/fleet-panel/vehicle-listing.hbs +1 -6
- package/addon/components/fuel-report/form.hbs +1 -5
- package/addon/components/layout/fleet-ops-sidebar.hbs +40 -36
- package/addon/components/layout/fleet-ops-sidebar.js +61 -10
- package/addon/components/maintenance/cost-panel.hbs +176 -0
- package/addon/components/maintenance/cost-panel.js +241 -0
- package/addon/components/maintenance/details.hbs +123 -60
- package/addon/components/maintenance/form.hbs +138 -78
- package/addon/components/maintenance/form.js +131 -6
- package/addon/components/maintenance/panel-header.hbs +31 -0
- package/addon/components/maintenance/panel-header.js +2 -0
- package/addon/components/maintenance-schedule/details.hbs +260 -0
- package/addon/components/maintenance-schedule/details.js +158 -0
- package/addon/components/maintenance-schedule/form.hbs +287 -0
- package/addon/components/maintenance-schedule/form.js +199 -0
- package/addon/components/map/container.hbs +1 -1
- package/addon/components/map/drawer/device-event-listing.hbs +1 -1
- package/addon/components/map/drawer/driver-listing.hbs +1 -6
- package/addon/components/map/drawer/place-listing.hbs +1 -6
- package/addon/components/map/drawer/vehicle-listing.hbs +1 -6
- package/addon/components/map/drawer.hbs +8 -1
- package/addon/components/map/leaflet-live-map.hbs +2 -1
- package/addon/components/map/toolbar/visibility-control-panel.hbs +1 -1
- package/addon/components/map/toolbar.hbs +4 -29
- package/addon/components/modals/add-driver-shift.hbs +155 -0
- package/addon/components/modals/add-driver-shift.js +210 -0
- package/addon/components/modals/bulk-assign-orders.hbs +67 -0
- package/addon/components/modals/driver-shift.hbs +43 -0
- package/addon/components/modals/driver-shift.js +56 -0
- package/addon/components/modals/entity-form.hbs +5 -28
- package/addon/components/modals/orchestrator-import.hbs +351 -0
- package/addon/components/modals/orchestrator-import.js +807 -0
- package/addon/components/modals/order-config-new-status.hbs +1 -5
- package/addon/components/modals/scheduling-conflict.hbs +47 -0
- package/addon/components/modals/scheduling-conflict.js +53 -0
- package/addon/components/modals/send-work-order.hbs +91 -0
- package/addon/components/modals/send-work-order.js +3 -0
- package/addon/components/modals/service-area-form.hbs +1 -6
- package/addon/components/modals/set-driver-availability.hbs +50 -0
- package/addon/components/modals/set-driver-availability.js +57 -0
- package/addon/components/modals/update-order-activity.hbs +13 -9
- package/addon/components/modals/user-form.hbs +1 -5
- package/addon/components/modals/vehicle-form.hbs +17 -102
- package/addon/components/modals/vendor-form.hbs +15 -82
- package/addon/components/orchestrator/card-fields-settings.hbs +76 -0
- package/addon/components/orchestrator/card-fields-settings.js +134 -0
- package/addon/components/orchestrator/order-pool.hbs +264 -0
- package/addon/components/orchestrator/order-pool.js +394 -0
- package/addon/components/orchestrator/phase-builder.hbs +162 -0
- package/addon/components/orchestrator/phase-builder.js +162 -0
- package/addon/components/orchestrator/plan-viewer.hbs +278 -0
- package/addon/components/orchestrator/plan-viewer.js +342 -0
- package/addon/components/orchestrator/resource-panel.hbs +301 -0
- package/addon/components/orchestrator/resource-panel.js +106 -0
- package/addon/components/orchestrator-workbench.hbs +318 -0
- package/addon/components/orchestrator-workbench.js +1087 -0
- package/addon/components/order/details/custom-fields.hbs +10 -1
- package/addon/components/order/details/detail.hbs +37 -2
- package/addon/components/order/details/detail.js +0 -1
- package/addon/components/order/details/integrated-vendor-details.hbs +4 -4
- package/addon/components/order/details/notes.hbs +1 -7
- package/addon/components/order/details/route.hbs +1 -5
- package/addon/components/order/form/details.hbs +44 -10
- package/addon/components/order/form/details.js +57 -0
- package/addon/components/order/form/notes.hbs +1 -7
- package/addon/components/order/form/payload.hbs +1 -7
- package/addon/components/order/form/route.hbs +3 -15
- package/addon/components/order/header.hbs +1 -7
- package/addon/components/order/route-editor.hbs +4 -25
- package/addon/components/order/schedule-card.hbs +102 -95
- package/addon/components/order/schedule-card.js +8 -3
- package/addon/components/order-config-manager/details.hbs +2 -10
- package/addon/components/order-config-manager/entities.hbs +1 -7
- package/addon/components/order-config-manager.hbs +1 -6
- package/addon/components/part/card.hbs +49 -0
- package/addon/components/part/card.js +6 -0
- package/addon/components/part/details.hbs +102 -56
- package/addon/components/part/form.hbs +131 -56
- package/addon/components/part/form.js +78 -11
- package/addon/components/part/panel-header.hbs +36 -0
- package/addon/components/part/panel-header.js +2 -0
- package/addon/components/place/form.hbs +1 -7
- package/addon/components/sensor/details.hbs +1 -1
- package/addon/components/sensor/form.hbs +5 -3
- package/addon/components/sensor/panel-header.hbs +8 -1
- package/addon/components/service-area/form.hbs +1 -6
- package/addon/components/service-rate/form.hbs +12 -60
- package/addon/components/telematic/form.hbs +6 -2
- package/addon/components/vehicle/details/maintenance-history.hbs +42 -0
- package/addon/components/vehicle/details/maintenance-history.js +32 -0
- package/addon/components/vehicle/details/schedules.hbs +40 -0
- package/addon/components/vehicle/details/schedules.js +32 -0
- package/addon/components/vehicle/details/work-orders.hbs +42 -0
- package/addon/components/vehicle/details/work-orders.js +32 -0
- package/addon/components/vehicle/details.hbs +30 -0
- package/addon/components/vehicle/form.hbs +39 -0
- package/addon/components/vehicle/panel-header.hbs +19 -49
- package/addon/components/warranty/details.hbs +3 -2
- package/addon/components/warranty/form.hbs +3 -17
- package/addon/components/work-order/details.hbs +135 -40
- package/addon/components/work-order/form.hbs +178 -45
- package/addon/components/work-order/form.js +197 -4
- package/addon/components/work-order/panel-header.hbs +31 -0
- package/addon/components/work-order/panel-header.js +2 -0
- package/addon/controllers/connectivity/devices/index/details.js +1 -1
- package/addon/controllers/maintenance/equipment/index/details/index.js +0 -1
- package/addon/controllers/maintenance/equipment/index/details.js +36 -1
- package/addon/controllers/maintenance/equipment/index/edit.js +56 -1
- package/addon/controllers/maintenance/equipment/index/new.js +32 -1
- package/addon/controllers/maintenance/equipment/index.js +127 -113
- package/addon/controllers/maintenance/maintenances/index/details/index.js +3 -0
- package/addon/controllers/maintenance/maintenances/index/details.js +54 -0
- package/addon/controllers/maintenance/maintenances/index/edit.js +68 -0
- package/addon/controllers/maintenance/maintenances/index/new.js +34 -0
- package/addon/controllers/maintenance/maintenances/index.js +191 -0
- package/addon/controllers/maintenance/parts/index/details/index.js +0 -1
- package/addon/controllers/maintenance/parts/index/details.js +36 -1
- package/addon/controllers/maintenance/parts/index/edit.js +56 -1
- package/addon/controllers/maintenance/parts/index/new.js +32 -1
- package/addon/controllers/maintenance/parts/index.js +135 -113
- package/addon/controllers/maintenance/schedules/index/details.js +115 -0
- package/addon/controllers/maintenance/schedules/index/edit.js +41 -0
- package/addon/controllers/maintenance/schedules/index/new.js +33 -0
- package/addon/controllers/maintenance/schedules/index.js +280 -0
- package/addon/controllers/maintenance/work-orders/index/details.js +41 -1
- package/addon/controllers/maintenance/work-orders/index/edit.js +67 -1
- package/addon/controllers/maintenance/work-orders/index/new.js +43 -1
- package/addon/controllers/maintenance/work-orders/index.js +105 -113
- package/addon/controllers/management/drivers/index/details.js +6 -1
- package/addon/controllers/management/vehicles/index/details.js +65 -0
- package/addon/controllers/management/vehicles/index.js +18 -0
- package/addon/controllers/operations/orchestrator.js +10 -0
- package/addon/controllers/operations/orders/index/details.js +5 -0
- package/addon/controllers/operations/orders/index.js +6 -0
- package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
- package/addon/controllers/operations/scheduler/index.js +799 -275
- package/addon/controllers/operations/scheduler.js +21 -0
- package/addon/controllers/settings/orchestrator.js +70 -0
- package/addon/controllers/settings/scheduling.js +155 -0
- package/addon/extension.js +74 -1
- package/addon/instance-initializers/register-vroom-allocation.js +27 -0
- package/addon/models/maintenance-schedule.js +61 -0
- package/addon/routes/maintenance/equipment/index/details.js +27 -1
- package/addon/routes/maintenance/equipment/index/edit.js +27 -1
- package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
- package/addon/routes/maintenance/maintenances/index/details.js +29 -0
- package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
- package/addon/routes/maintenance/maintenances/index/new.js +3 -0
- package/addon/routes/maintenance/maintenances/index.js +23 -0
- package/addon/routes/maintenance/maintenances.js +3 -0
- package/addon/routes/maintenance/parts/index/details.js +27 -1
- package/addon/routes/maintenance/parts/index/edit.js +27 -1
- package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
- package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
- package/addon/routes/maintenance/schedules/index/details.js +25 -0
- package/addon/routes/maintenance/schedules/index/edit.js +25 -0
- package/addon/routes/maintenance/schedules/index/new.js +2 -0
- package/addon/routes/maintenance/schedules/index.js +21 -0
- package/addon/routes/maintenance/schedules.js +2 -0
- package/addon/routes/maintenance/work-orders/index/details.js +27 -1
- package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
- package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
- package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
- package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
- package/addon/routes/operations/orchestrator.js +23 -0
- package/addon/routes/operations/orders/index/details/virtual.js +23 -0
- package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
- package/addon/routes/operations/scheduler/index.js +48 -26
- package/addon/routes/operations/scheduler.js +14 -1
- package/addon/routes/settings/orchestrator.js +27 -0
- package/addon/routes/settings/scheduling.js +3 -0
- package/addon/routes.js +33 -1
- package/addon/services/driver-actions.js +40 -7
- package/addon/services/driver-scheduling.js +4 -1
- package/addon/services/equipment-actions.js +15 -5
- package/addon/services/leaflet-map-manager.js +14 -6
- package/addon/services/maintenance-actions.js +17 -14
- package/addon/services/maintenance-schedule-actions.js +118 -0
- package/addon/services/orchestration-engine-interface.js +49 -0
- package/addon/services/orchestration-engine.js +74 -0
- package/addon/services/order-actions.js +15 -0
- package/addon/services/order-allocation.js +116 -0
- package/addon/services/part-actions.js +12 -2
- package/addon/services/scheduling.js +316 -0
- package/addon/services/vehicle-actions.js +70 -7
- package/addon/services/vroom-allocation-engine.js +45 -0
- package/addon/services/work-order-actions.js +80 -0
- package/addon/styles/fleetops-engine.css +1658 -0
- package/addon/templates/analytics/reports/index/edit.hbs +1 -1
- package/addon/templates/analytics/reports/index/new.hbs +1 -1
- package/addon/templates/application.hbs +6 -1
- package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
- package/addon/templates/connectivity/devices.hbs +0 -1
- package/addon/templates/connectivity/events/index/details.hbs +0 -1
- package/addon/templates/connectivity/events.hbs +0 -1
- package/addon/templates/connectivity/sensors.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
- package/addon/templates/connectivity/telematics.hbs +0 -1
- package/addon/templates/connectivity/tracking.hbs +0 -1
- package/addon/templates/connectivity.hbs +0 -1
- package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
- package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
- package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
- package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
- package/addon/templates/maintenance/equipment/index.hbs +48 -13
- package/addon/templates/maintenance/equipment.hbs +0 -1
- package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
- package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
- package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
- package/addon/templates/maintenance/maintenances/index.hbs +14 -0
- package/addon/templates/maintenance/maintenances.hbs +1 -0
- package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
- package/addon/templates/maintenance/parts/index/details.hbs +15 -2
- package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
- package/addon/templates/maintenance/parts/index/new.hbs +1 -2
- package/addon/templates/maintenance/parts/index.hbs +48 -13
- package/addon/templates/maintenance/parts.hbs +0 -1
- package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
- package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
- package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
- package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
- package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
- package/addon/templates/maintenance/schedules/index.hbs +40 -0
- package/addon/templates/maintenance/schedules.hbs +1 -0
- package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
- package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
- package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
- package/addon/templates/maintenance/work-orders.hbs +0 -1
- package/addon/templates/management/contacts/customers/edit.hbs +1 -2
- package/addon/templates/management/contacts/customers/new.hbs +1 -2
- package/addon/templates/management/contacts/customers.hbs +1 -1
- package/addon/templates/management/contacts/index/edit.hbs +1 -2
- package/addon/templates/management/contacts/index/new.hbs +1 -2
- package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
- package/addon/templates/management/drivers/index/edit.hbs +1 -2
- package/addon/templates/management/drivers/index/new.hbs +1 -2
- package/addon/templates/management/fleets/index/edit.hbs +1 -2
- package/addon/templates/management/fleets/index/new.hbs +1 -2
- package/addon/templates/management/fleets/index.hbs +1 -2
- package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
- package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
- package/addon/templates/management/fuel-reports/index.hbs +1 -2
- package/addon/templates/management/issues/index/edit.hbs +1 -2
- package/addon/templates/management/issues/index/new.hbs +1 -2
- package/addon/templates/management/issues/index.hbs +1 -2
- package/addon/templates/management/places/index/details/activity.hbs +0 -1
- package/addon/templates/management/places/index/details/comments.hbs +0 -1
- package/addon/templates/management/places/index/details/documents.hbs +0 -1
- package/addon/templates/management/places/index/details/map.hbs +0 -1
- package/addon/templates/management/places/index/details/operations.hbs +0 -1
- package/addon/templates/management/places/index/details/performance.hbs +0 -1
- package/addon/templates/management/places/index/details/rules.hbs +0 -1
- package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
- package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
- package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
- package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -1
- package/addon/templates/management/vehicles/index/edit.hbs +1 -2
- package/addon/templates/management/vehicles/index/new.hbs +1 -2
- package/addon/templates/management/vendors/index/edit.hbs +1 -2
- package/addon/templates/management/vendors/index/new.hbs +1 -2
- package/addon/templates/management/vendors/index.hbs +1 -2
- package/addon/templates/management/vendors/integrated.hbs +1 -2
- package/addon/templates/operations/orchestrator.hbs +1 -0
- package/addon/templates/operations/orders/index/details/virtual.hbs +1 -0
- package/addon/templates/operations/orders/index.hbs +6 -1
- package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
- package/addon/templates/operations/scheduler/index.hbs +147 -88
- package/addon/templates/operations/scheduler.hbs +7 -1
- package/addon/templates/settings/avatars.hbs +1 -1
- package/addon/templates/settings/orchestrator.hbs +65 -0
- package/addon/templates/settings/payments/index.hbs +1 -5
- package/addon/templates/settings/scheduling.hbs +82 -0
- package/addon/utils/create-full-calendar-event-from-order.js +52 -14
- package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
- package/addon/utils/fleet-ops-options.js +254 -0
- package/addon/utils/route-colors.js +99 -0
- package/addon/utils/to-calendar-date.js +70 -0
- package/app/components/driver/schedule.js +1 -0
- package/app/components/maintenance/cost-panel.js +1 -0
- package/app/components/maintenance/panel-header.js +1 -0
- package/app/components/maintenance-schedule/details.js +1 -0
- package/app/components/maintenance-schedule/form.js +1 -0
- package/app/components/modals/add-driver-shift.js +1 -0
- package/app/components/modals/bulk-assign-orders.js +1 -0
- package/app/components/modals/driver-shift.js +1 -0
- package/app/components/modals/orchestrator-import.js +1 -0
- package/app/components/modals/scheduling-conflict.js +1 -0
- package/app/components/modals/send-work-order.js +1 -0
- package/app/components/modals/set-driver-availability.js +1 -0
- package/app/components/orchestrator/card-fields-settings.js +1 -0
- package/app/components/orchestrator/order-pool.js +1 -0
- package/app/components/orchestrator/phase-builder.js +1 -0
- package/app/components/orchestrator/plan-viewer.js +1 -0
- package/app/components/orchestrator/resource-panel.js +1 -0
- package/app/components/orchestrator-workbench.js +1 -0
- package/app/components/vehicle/details/maintenance-history.js +1 -0
- package/app/components/vehicle/details/schedules.js +1 -0
- package/app/components/vehicle/details/work-orders.js +1 -0
- package/app/controllers/operations/orchestrator.js +1 -0
- package/app/controllers/settings/orchestrator.js +1 -0
- package/app/controllers/settings/scheduling.js +1 -0
- package/app/routes/operations/orchestrator.js +1 -0
- package/app/routes/operations/orders/index/details/virtual.js +1 -0
- package/app/routes/settings/orchestrator.js +1 -0
- package/app/routes/settings/scheduling.js +1 -0
- package/app/services/maintenance-schedule-actions.js +1 -0
- package/app/services/orchestration-engine-interface.js +1 -0
- package/app/services/orchestration-engine.js +1 -0
- package/app/services/order-allocation.js +1 -0
- package/app/services/scheduling.js +1 -0
- package/app/services/vroom-allocation-engine.js +1 -0
- package/app/templates/operations/orders/index/details/virtual.js +1 -0
- package/app/templates/settings/scheduling.js +1 -0
- package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
- package/app/utils/route-colors.js +1 -0
- package/composer.json +5 -3
- package/extension.json +1 -1
- package/package.json +6 -5
- package/server/config/fleetops.php +20 -1
- package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
- package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
- package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
- package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
- package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
- package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
- package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
- package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
- package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +58 -0
- package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
- package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
- package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +41 -0
- package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
- package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
- package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
- package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
- package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
- package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
- package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
- package/server/src/Auth/Schemas/FleetOps.php +44 -0
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
- package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
- package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
- package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
- package/server/src/Http/Resources/v1/Driver.php +1 -0
- package/server/src/Http/Resources/v1/Maintenance.php +138 -0
- package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
- package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
- package/server/src/Http/Resources/v1/Order.php +7 -4
- package/server/src/Http/Resources/v1/Waypoint.php +7 -0
- package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
- package/server/src/Imports/EquipmentImport.php +32 -0
- package/server/src/Imports/MaintenanceImport.php +32 -0
- package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
- package/server/src/Imports/PartImport.php +32 -0
- package/server/src/Imports/WorkOrderImport.php +32 -0
- package/server/src/Jobs/ProcessAllocationJob.php +119 -0
- package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
- package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
- package/server/src/Mail/WorkOrderDispatched.php +58 -0
- package/server/src/Models/Asset.php +2 -2
- package/server/src/Models/Device.php +1 -1
- package/server/src/Models/Driver.php +82 -4
- package/server/src/Models/Equipment.php +62 -2
- package/server/src/Models/Maintenance.php +127 -9
- package/server/src/Models/MaintenanceSchedule.php +353 -0
- package/server/src/Models/Manifest.php +214 -0
- package/server/src/Models/ManifestStop.php +162 -0
- package/server/src/Models/Order.php +70 -0
- package/server/src/Models/OrderConfig.php +5 -2
- package/server/src/Models/Part.php +69 -3
- package/server/src/Models/Payload.php +7 -2
- package/server/src/Models/Place.php +1 -1
- package/server/src/Models/Sensor.php +1 -1
- package/server/src/Models/ServiceQuote.php +1 -1
- package/server/src/Models/Vehicle.php +20 -1
- package/server/src/Models/Warranty.php +1 -1
- package/server/src/Models/Waypoint.php +7 -1
- package/server/src/Models/WorkOrder.php +122 -12
- package/server/src/Notifications/DriverShiftChanged.php +110 -0
- package/server/src/Observers/WorkOrderObserver.php +107 -0
- package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
- package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
- package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
- package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
- package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +290 -0
- package/server/src/Providers/EventServiceProvider.php +7 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
- package/server/src/routes.php +65 -4
- package/translations/ar-ae.yml +44 -12
- package/translations/bg-bg.yaml +51 -10
- package/translations/en-us.yaml +444 -1
- package/translations/fr-fr.yaml +51 -10
- package/translations/mn-mn.yaml +51 -10
- package/translations/pt-br.yaml +51 -10
- package/translations/ru-ru.yaml +51 -10
- package/translations/vi-vn.yaml +48 -12
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
<div class="mb-3 flex items-center justify-between">
|
|
4
4
|
<div class="flex items-center">
|
|
5
5
|
<div class="fleet-vehicle-listing-search">
|
|
6
|
-
<Input
|
|
7
|
-
@type="text"
|
|
8
|
-
{{on "input" this.onInput}}
|
|
9
|
-
placeholder={{t "fleet-panel.vehicle-listing.search-vehicle"}}
|
|
10
|
-
class="w-64 form-input form-input-sm"
|
|
11
|
-
/>
|
|
6
|
+
<Input @type="text" {{on "input" this.onInput}} placeholder={{t "fleet-panel.vehicle-listing.search-vehicle"}} class="w-64 form-input form-input-sm" />
|
|
12
7
|
</div>
|
|
13
8
|
{{#if this.search.isRunning}}
|
|
14
9
|
<div class="mx-2">
|
|
@@ -77,11 +77,7 @@
|
|
|
77
77
|
@disabled={{cannot-write @resource}}
|
|
78
78
|
/>
|
|
79
79
|
</InputGroup>
|
|
80
|
-
<InputGroup
|
|
81
|
-
@name={{t "fuel-report.fields.fuel-volume"}}
|
|
82
|
-
@helpText={{t "fuel-report.fields.select-volume-help-text"}}
|
|
83
|
-
@wrapperClass="w-full"
|
|
84
|
-
>
|
|
80
|
+
<InputGroup @name={{t "fuel-report.fields.fuel-volume"}} @helpText={{t "fuel-report.fields.select-volume-help-text"}} @wrapperClass="w-full">
|
|
85
81
|
<UnitInput
|
|
86
82
|
class="w-full"
|
|
87
83
|
@measurement="volume"
|
|
@@ -135,44 +135,48 @@
|
|
|
135
135
|
>{{if menuItem.intl (t menuItem.intl) menuItem.title}}</Layout::Sidebar::Item>
|
|
136
136
|
{{/if}}
|
|
137
137
|
{{/each}}
|
|
138
|
-
{{#
|
|
139
|
-
{{#each
|
|
140
|
-
{{#if
|
|
141
|
-
{{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
138
|
+
{{#let (array "operations" "management" "connectivity" "maintenance" "analytics" "settings") as |sectionsToRender|}}
|
|
139
|
+
{{#each sectionsToRender as |section|}}
|
|
140
|
+
{{#if (eq menuPanel.routePrefix section)}}
|
|
141
|
+
{{#each (get this (concat "universe" (capitalize section) "MenuItems")) as |menuItem|}}
|
|
142
|
+
{{#if menuItem.renderComponentInPlace}}
|
|
143
|
+
{{component menuItem.component}}
|
|
144
|
+
{{else}}
|
|
145
|
+
<Layout::Sidebar::Item
|
|
146
|
+
@onClick={{fn this.universe.transitionMenuItem (concat this.routePrefix "virtual") menuItem}}
|
|
147
|
+
@menuItem={{menuItem}}
|
|
148
|
+
@icon={{menuItem.icon}}
|
|
149
|
+
@iconComponent={{menuItem.iconComponent}}
|
|
150
|
+
@iconComponentOptions={{menuItem.iconComponentOptions}}
|
|
151
|
+
@iconSize={{menuItem.iconSize}}
|
|
152
|
+
@iconPrefix={{menuItem.iconPrefix}}
|
|
153
|
+
@iconClass={{menuItem.iconClass}}
|
|
154
|
+
@rightSideComponent={{menuItem.rightSideComponent}}
|
|
155
|
+
@rightSideComponentContext={{menuItem.rightSideComponentContext}}
|
|
156
|
+
@rightSideStatus={{menuItem.rightSideStatus}}
|
|
157
|
+
@rightSideStatusText={{menuItem.rightSideStatusText}}
|
|
158
|
+
@hideRightSideStatusDot={{menuItem.hideRightSideStatusDot}}
|
|
159
|
+
@rightSideStatusClass={{menuItem.rightSideStatusClass}}
|
|
160
|
+
@disableRightSideStatusHumanize={{menuItem.disableRightSideStatusHumanize}}
|
|
161
|
+
@dropdownButton={{menuItem.dropdownButton}}
|
|
162
|
+
@dropdownButtonActions={{menuItem.dropdownButtonActions}}
|
|
163
|
+
@dropdownButtonIcon={{menuItem.dropdownButtonIcon}}
|
|
164
|
+
@dropdownButtonIconPrefix={{menuItem.dropdownButtonIconPrefix}}
|
|
165
|
+
@dropdownButtonText={{menuItem.dropdownButtonText}}
|
|
166
|
+
@dropdownButtonCalculatePosition={{menuItem.dropdownButtonCalculatePosition}}
|
|
167
|
+
@dropdownButtonRenderInPlace={{menuItem.dropdownButtonRenderInPlace}}
|
|
168
|
+
@dropdownButtonMenuLabel={{menuItem.dropdownButtonMenuLabel}}
|
|
169
|
+
@dropdownButtonWrapperClass={{menuItem.dropdownButtonWrapperClass}}
|
|
170
|
+
@dropdownButtonTriggerClass={{menuItem.dropdownButtonTriggerClass}}
|
|
171
|
+
@registerDropdownButtonAPI={{menuItem.registerDropdownButtonAPI}}
|
|
172
|
+
@permission={{menuItem.permission}}
|
|
173
|
+
@visible={{menuItem.visible}}
|
|
174
|
+
>{{if menuItem.intl (t menuItem.intl) menuItem.title}}</Layout::Sidebar::Item>
|
|
175
|
+
{{/if}}
|
|
176
|
+
{{/each}}
|
|
173
177
|
{{/if}}
|
|
174
178
|
{{/each}}
|
|
175
|
-
{{/
|
|
179
|
+
{{/let}}
|
|
176
180
|
</Layout::Sidebar::Panel>
|
|
177
181
|
{{/each}}
|
|
178
182
|
{{#each this.universeMenuPanels as |menuPanel|}}
|
|
@@ -15,6 +15,11 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
15
15
|
@tracked routePrefix = 'console.fleet-ops.';
|
|
16
16
|
@tracked menuPanels = [];
|
|
17
17
|
@tracked universeMenuItems = [];
|
|
18
|
+
@tracked universeOperationsMenuItems = [];
|
|
19
|
+
@tracked universeManagementMenuItems = [];
|
|
20
|
+
@tracked universeConnectivityMenuItems = [];
|
|
21
|
+
@tracked universeMaintenanceMenuItems = [];
|
|
22
|
+
@tracked universeAnalyticsMenuItems = [];
|
|
18
23
|
@tracked universeSettingsMenuItems = [];
|
|
19
24
|
@tracked universeMenuPanels = [];
|
|
20
25
|
|
|
@@ -26,8 +31,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
26
31
|
|
|
27
32
|
createMenuItemsFromUniverseRegistry() {
|
|
28
33
|
const registeredMenuItems = this.menuService.getMenuItems('engine:fleet-ops');
|
|
34
|
+
console.log('Registered menu items for engine:fleet-ops:', registeredMenuItems);
|
|
29
35
|
this.universeMenuPanels = this.menuService.getMenuPanels('engine:fleet-ops');
|
|
30
36
|
this.universeMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === undefined);
|
|
37
|
+
this.universeOperationsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'operations');
|
|
38
|
+
this.universeManagementMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'management');
|
|
39
|
+
this.universeMaintenanceMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'maintenance');
|
|
40
|
+
this.universeConnectivityMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'connectivity');
|
|
41
|
+
this.universeAnalyticsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'analytics');
|
|
31
42
|
this.universeSettingsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'settings');
|
|
32
43
|
}
|
|
33
44
|
|
|
@@ -43,12 +54,12 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
43
54
|
visible: this.abilities.can('fleet-ops see order'),
|
|
44
55
|
},
|
|
45
56
|
{
|
|
46
|
-
intl: 'menu.
|
|
47
|
-
title: this.intl.t('menu.
|
|
48
|
-
icon: '
|
|
49
|
-
route: 'operations.
|
|
50
|
-
permission: 'fleet-ops list
|
|
51
|
-
visible: this.abilities.can('fleet-ops see
|
|
57
|
+
intl: 'menu.orchestrator',
|
|
58
|
+
title: this.intl.t('menu.orchestrator'),
|
|
59
|
+
icon: 'circle-nodes',
|
|
60
|
+
route: 'operations.orchestrator',
|
|
61
|
+
permission: 'fleet-ops list order',
|
|
62
|
+
visible: this.abilities.can('fleet-ops see order'),
|
|
52
63
|
},
|
|
53
64
|
{
|
|
54
65
|
intl: 'menu.scheduler',
|
|
@@ -66,6 +77,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
66
77
|
permission: 'fleet-ops list order-config',
|
|
67
78
|
visible: this.abilities.can('fleet-ops see order-config'),
|
|
68
79
|
},
|
|
80
|
+
{
|
|
81
|
+
intl: 'menu.service-rates',
|
|
82
|
+
title: this.intl.t('menu.service-rates'),
|
|
83
|
+
icon: 'file-invoice-dollar',
|
|
84
|
+
route: 'operations.service-rates',
|
|
85
|
+
permission: 'fleet-ops list service-rate',
|
|
86
|
+
visible: this.abilities.can('fleet-ops see service-rate'),
|
|
87
|
+
},
|
|
69
88
|
];
|
|
70
89
|
|
|
71
90
|
const resourcesItems = [
|
|
@@ -183,6 +202,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
183
202
|
];
|
|
184
203
|
|
|
185
204
|
const maintenanceItems = [
|
|
205
|
+
{
|
|
206
|
+
intl: 'menu.schedules',
|
|
207
|
+
title: this.intl.t('menu.schedules'),
|
|
208
|
+
icon: 'calendar-alt',
|
|
209
|
+
route: 'maintenance.schedules',
|
|
210
|
+
permission: 'fleet-ops list maintenance-schedule',
|
|
211
|
+
visible: this.abilities.can('fleet-ops see maintenance-schedule'),
|
|
212
|
+
},
|
|
186
213
|
{
|
|
187
214
|
intl: 'menu.work-orders',
|
|
188
215
|
title: this.intl.t('menu.work-orders'),
|
|
@@ -191,6 +218,14 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
191
218
|
permission: 'fleet-ops list work-order',
|
|
192
219
|
visible: this.abilities.can('fleet-ops see work-order'),
|
|
193
220
|
},
|
|
221
|
+
{
|
|
222
|
+
intl: 'menu.maintenances',
|
|
223
|
+
title: this.intl.t('menu.maintenances'),
|
|
224
|
+
icon: 'history',
|
|
225
|
+
route: 'maintenance.maintenances',
|
|
226
|
+
permission: 'fleet-ops list maintenance',
|
|
227
|
+
visible: this.abilities.can('fleet-ops see maintenance'),
|
|
228
|
+
},
|
|
194
229
|
{
|
|
195
230
|
intl: 'menu.equipment',
|
|
196
231
|
title: this.intl.t('menu.equipment'),
|
|
@@ -253,6 +288,22 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
253
288
|
permission: 'fleet-ops view routing-settings',
|
|
254
289
|
visible: this.abilities.can('fleet-ops see routing-settings'),
|
|
255
290
|
},
|
|
291
|
+
{
|
|
292
|
+
intl: 'menu.orchestrator',
|
|
293
|
+
title: this.intl.t('menu.orchestrator'),
|
|
294
|
+
icon: 'circle-nodes',
|
|
295
|
+
route: 'settings.orchestrator',
|
|
296
|
+
permission: 'fleet-ops view routing-settings',
|
|
297
|
+
visible: this.abilities.can('fleet-ops see routing-settings'),
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
intl: 'menu.scheduling',
|
|
301
|
+
title: this.intl.t('menu.scheduling'),
|
|
302
|
+
icon: 'calendar-days',
|
|
303
|
+
route: 'settings.scheduling',
|
|
304
|
+
permission: 'fleet-ops view scheduling-settings',
|
|
305
|
+
visible: this.abilities.can('fleet-ops see scheduling-settings'),
|
|
306
|
+
},
|
|
256
307
|
{
|
|
257
308
|
intl: 'menu.custom-fields',
|
|
258
309
|
title: this.intl.t('menu.custom-fields'),
|
|
@@ -289,10 +340,10 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
|
|
|
289
340
|
open: this.appCache.get('fleet-ops:sidebar:management:open', true),
|
|
290
341
|
onToggle: (open) => this.appCache.set('fleet-ops:sidebar:management:open', open),
|
|
291
342
|
}),
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
343
|
+
createPanel('menu.maintenance', 'maintenance', maintenanceItems, {
|
|
344
|
+
open: this.appCache.get('fleet-ops:sidebar:maintenance:open', false),
|
|
345
|
+
onToggle: (open) => this.appCache.set('fleet-ops:sidebar:maintenance:open', open),
|
|
346
|
+
}),
|
|
296
347
|
createPanel('menu.connectivity', 'connectivity', connectivityItems, {
|
|
297
348
|
open: this.appCache.get('fleet-ops:sidebar:connectivity:open', false),
|
|
298
349
|
onToggle: (open) => this.appCache.set('fleet-ops:sidebar:connectivity:open', open),
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
{{! ===== COST PANEL — invoice-style line items ===== }}
|
|
2
|
+
<ContentPanel @title="Costs" @open={{true}} @wrapperClass="bordered-top">
|
|
3
|
+
|
|
4
|
+
{{! ── Currency selector (single, shared by all money fields) ── }}
|
|
5
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs mb-2">
|
|
6
|
+
<InputGroup @name="Currency">
|
|
7
|
+
<CurrencySelect @currency={{@resource.currency}} @onCurrencyChange={{fn (mut @resource.currency)}} @triggerClass="w-full form-select" @disabled={{this.isDisabled}} />
|
|
8
|
+
</InputGroup>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
{{! ── Labour & Tax inputs (always editable on the form) ── }}
|
|
12
|
+
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs mb-4">
|
|
13
|
+
<InputGroup @name="Labour Cost">
|
|
14
|
+
<MoneyInput class="w-full" @value={{@resource.labor_cost}} @currency={{this.currency}} @onChange={{fn (mut @resource.labor_cost)}} @disabled={{this.isDisabled}} />
|
|
15
|
+
</InputGroup>
|
|
16
|
+
<InputGroup @name="Tax">
|
|
17
|
+
<MoneyInput class="w-full" @value={{@resource.tax}} @currency={{this.currency}} @onChange={{fn (mut @resource.tax)}} @disabled={{this.isDisabled}} />
|
|
18
|
+
</InputGroup>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
{{! ── Line items table ── }}
|
|
22
|
+
<div class="text-[11px] uppercase tracking-wide text-gray-500 dark:text-gray-400 font-semibold mb-2">
|
|
23
|
+
Parts & Services
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<table class="w-full text-xs border-collapse mb-2 table-fixed">
|
|
27
|
+
<colgroup>
|
|
28
|
+
<col class="w-[45%]" />
|
|
29
|
+
<col class="w-[10%]" />
|
|
30
|
+
<col class="w-[20%]" />
|
|
31
|
+
<col class="w-[15%]" />
|
|
32
|
+
{{#unless this.isDisabled}}
|
|
33
|
+
<col class="w-[10%]" />
|
|
34
|
+
{{/unless}}
|
|
35
|
+
</colgroup>
|
|
36
|
+
<thead>
|
|
37
|
+
<tr class="border-b border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 text-left">
|
|
38
|
+
<th class="pb-1 pr-2 font-medium">Description</th>
|
|
39
|
+
<th class="pb-1 pr-2 font-medium text-right">Qty</th>
|
|
40
|
+
<th class="pb-1 pr-2 font-medium text-right">Unit Cost</th>
|
|
41
|
+
<th class="pb-1 pr-2 font-medium text-right">Total</th>
|
|
42
|
+
{{#unless this.isDisabled}}
|
|
43
|
+
<th class="pb-1"></th>
|
|
44
|
+
{{/unless}}
|
|
45
|
+
</tr>
|
|
46
|
+
</thead>
|
|
47
|
+
<tbody>
|
|
48
|
+
{{#each this.lineItems as |item index|}}
|
|
49
|
+
{{#if (eq this.editingIndex index)}}
|
|
50
|
+
{{! ── Inline edit row ── }}
|
|
51
|
+
<tr class="border-b border-gray-100 dark:border-gray-800">
|
|
52
|
+
<td class="py-1 pr-2">
|
|
53
|
+
<Input @value={{this.draftDescription}} class="form-input w-full text-xs" placeholder="Description" />
|
|
54
|
+
</td>
|
|
55
|
+
<td class="py-1 pr-2">
|
|
56
|
+
<Input @value={{this.draftQuantity}} @type="number" class="form-input w-full text-xs text-right" min="0" />
|
|
57
|
+
</td>
|
|
58
|
+
<td class="py-1 pr-2">
|
|
59
|
+
<MoneyInput class="w-full" @value={{this.draftUnitCost}} @currency={{this.currency}} @onChange={{this.setDraftUnitCost}} />
|
|
60
|
+
</td>
|
|
61
|
+
<td class="py-1 pr-2 text-right text-gray-500 dark:text-gray-400">
|
|
62
|
+
{{format-currency this.draftLineTotal this.currency}}
|
|
63
|
+
</td>
|
|
64
|
+
<td class="py-1">
|
|
65
|
+
<div class="flex items-center space-x-1">
|
|
66
|
+
<Button @size="xs" @type="primary" @onClick={{this.saveEdit}} class="px-2 py-0.5">
|
|
67
|
+
Save
|
|
68
|
+
</Button>
|
|
69
|
+
<Button @size="xs" @type="default" @onClick={{this.cancelEdit}} class="px-2 py-0.5">
|
|
70
|
+
Cancel
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
{{else}}
|
|
76
|
+
{{! ── Read row ── }}
|
|
77
|
+
<tr class="border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/40 group">
|
|
78
|
+
<td class="py-1.5 pr-2 text-gray-800 dark:text-gray-200 truncate">{{item.description}}</td>
|
|
79
|
+
<td class="py-1.5 pr-2 text-right text-gray-600 dark:text-gray-400">{{item.quantity}}</td>
|
|
80
|
+
<td class="py-1.5 pr-2 text-right text-gray-600 dark:text-gray-400">
|
|
81
|
+
{{format-currency (this._toCents item.unit_cost) this.currency}}
|
|
82
|
+
</td>
|
|
83
|
+
<td class="py-1.5 pr-2 text-right font-medium text-gray-800 dark:text-gray-200">
|
|
84
|
+
{{format-currency (this.lineTotal item) this.currency}}
|
|
85
|
+
</td>
|
|
86
|
+
{{#unless this.isDisabled}}
|
|
87
|
+
<td class="py-1.5">
|
|
88
|
+
<div class="flex items-center space-x-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
class="text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
|
|
92
|
+
title="Edit"
|
|
93
|
+
{{on "click" (fn this.startEdit index)}}
|
|
94
|
+
>
|
|
95
|
+
<FaIcon @icon="pencil" @size="xs" />
|
|
96
|
+
</button>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
class="text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors"
|
|
100
|
+
title="Remove"
|
|
101
|
+
{{on "click" (fn this.removeLineItem index)}}
|
|
102
|
+
>
|
|
103
|
+
<FaIcon @icon="trash" @size="xs" />
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</td>
|
|
107
|
+
{{/unless}}
|
|
108
|
+
</tr>
|
|
109
|
+
{{/if}}
|
|
110
|
+
{{/each}}
|
|
111
|
+
|
|
112
|
+
{{! ── Add new item row ── }}
|
|
113
|
+
{{#if this.isAddingItem}}
|
|
114
|
+
<tr class="border-b border-gray-100 dark:border-gray-800">
|
|
115
|
+
<td class="py-1 pr-2">
|
|
116
|
+
<Input @value={{this.draftDescription}} class="form-input w-full text-xs" placeholder="Description" />
|
|
117
|
+
</td>
|
|
118
|
+
<td class="py-1 pr-2">
|
|
119
|
+
<Input @value={{this.draftQuantity}} @type="number" class="form-input w-full text-xs text-right" min="0" />
|
|
120
|
+
</td>
|
|
121
|
+
<td class="py-1 pr-2">
|
|
122
|
+
<MoneyInput class="w-full" @value={{this.draftUnitCost}} @currency={{this.currency}} @onChange={{this.setDraftUnitCost}} />
|
|
123
|
+
</td>
|
|
124
|
+
<td class="py-1 pr-2 text-right text-gray-500 dark:text-gray-400">
|
|
125
|
+
{{format-currency this.draftLineTotal this.currency}}
|
|
126
|
+
</td>
|
|
127
|
+
<td class="py-1">
|
|
128
|
+
<div class="flex items-center space-x-1">
|
|
129
|
+
<Button @size="xs" @type="primary" @onClick={{this.addLineItem}} class="px-2 py-0.5">
|
|
130
|
+
Add
|
|
131
|
+
</Button>
|
|
132
|
+
<Button @size="xs" @type="default" @onClick={{this.cancelAdd}} class="px-2 py-0.5">
|
|
133
|
+
Cancel
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
</td>
|
|
137
|
+
</tr>
|
|
138
|
+
{{/if}}
|
|
139
|
+
</tbody>
|
|
140
|
+
</table>
|
|
141
|
+
|
|
142
|
+
{{! ── Add item button ── }}
|
|
143
|
+
{{#unless this.isDisabled}}
|
|
144
|
+
{{#unless this.isAddingItem}}
|
|
145
|
+
<button
|
|
146
|
+
type="button"
|
|
147
|
+
class="flex items-center space-x-1 text-xs text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 font-medium mb-3 transition-colors"
|
|
148
|
+
{{on "click" this.showAddRow}}
|
|
149
|
+
>
|
|
150
|
+
<FaIcon @icon="plus" @size="xs" />
|
|
151
|
+
<span>Add line item</span>
|
|
152
|
+
</button>
|
|
153
|
+
{{/unless}}
|
|
154
|
+
{{/unless}}
|
|
155
|
+
|
|
156
|
+
{{! ── Totals summary ── }}
|
|
157
|
+
<div class="border-t border-gray-200 dark:border-gray-700 pt-2 mt-1 space-y-1 text-xs">
|
|
158
|
+
<div class="flex justify-between text-gray-500 dark:text-gray-400">
|
|
159
|
+
<span>Labour</span>
|
|
160
|
+
<span>{{format-currency this.laborCost this.currency}}</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="flex justify-between text-gray-500 dark:text-gray-400">
|
|
163
|
+
<span>Parts</span>
|
|
164
|
+
<span>{{format-currency this.partsCost this.currency}}</span>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="flex justify-between text-gray-500 dark:text-gray-400">
|
|
167
|
+
<span>Tax</span>
|
|
168
|
+
<span>{{format-currency this.tax this.currency}}</span>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="flex justify-between font-semibold text-gray-800 dark:text-gray-100 border-t border-gray-200 dark:border-gray-700 pt-1 mt-1">
|
|
171
|
+
<span>Total</span>
|
|
172
|
+
<span>{{format-currency this.totalCost this.currency}}</span>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
</ContentPanel>
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
5
|
+
import { isArray } from '@ember/array';
|
|
6
|
+
import numbersOnly from '@fleetbase/ember-core/utils/numbers-only';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `Maintenance::CostPanel`
|
|
10
|
+
*
|
|
11
|
+
* Renders an invoice-style line items editor for a maintenance record.
|
|
12
|
+
*
|
|
13
|
+
* Line items are stored in the `line_items` JSON column on the maintenance
|
|
14
|
+
* record (`@attr('raw')`). Mutations are purely in-memory — the array is
|
|
15
|
+
* written back onto `@resource.line_items` and the parent form's normal
|
|
16
|
+
* save flow persists everything together in a single request. This means
|
|
17
|
+
* the component works correctly for both new (unsaved) and existing records.
|
|
18
|
+
*
|
|
19
|
+
* All monetary values (unit_cost, labor_cost, tax, parts_cost, total_cost)
|
|
20
|
+
* are stored as **cents** (integers) on the backend via the Money::class cast
|
|
21
|
+
* and surfaced as strings by `@attr('string')` on the Ember model. The
|
|
22
|
+
* `numbersOnly()` utility strips any non-digit characters and `parseInt`
|
|
23
|
+
* converts to a safe integer before arithmetic.
|
|
24
|
+
*
|
|
25
|
+
* The `MoneyInput` component handles the cents ↔ display conversion:
|
|
26
|
+
* - `@value` receives cents (e.g. 1000 = $10.00)
|
|
27
|
+
* - `@onChange` emits cents (e.g. 1000 = $10.00)
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* <Maintenance::CostPanel @resource={{this.maintenance}} @disabled={{cannot-write this.maintenance}} />
|
|
31
|
+
*/
|
|
32
|
+
export default class MaintenanceCostPanelComponent extends Component {
|
|
33
|
+
@service notifications;
|
|
34
|
+
@service intl;
|
|
35
|
+
|
|
36
|
+
/** Whether the "Add line item" row is currently visible */
|
|
37
|
+
@tracked isAddingItem = false;
|
|
38
|
+
|
|
39
|
+
/** Tracks which line item index is currently being edited (null = none) */
|
|
40
|
+
@tracked editingIndex = null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Draft values for the add/edit form row.
|
|
44
|
+
* `draftUnitCost` is stored in **cents** (integer) to match the backend
|
|
45
|
+
* Money::class cast and the MoneyInput component's expected input/output format.
|
|
46
|
+
*/
|
|
47
|
+
@tracked draftDescription = '';
|
|
48
|
+
@tracked draftQuantity = 1;
|
|
49
|
+
@tracked draftUnitCost = 0;
|
|
50
|
+
|
|
51
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Safely parse a monetary string attribute (cents stored as string) to int.
|
|
55
|
+
*/
|
|
56
|
+
@action _toCents(value) {
|
|
57
|
+
if (value === null || value === undefined || value === '') return 0;
|
|
58
|
+
const parsed = parseInt(numbersOnly(String(value)), 10);
|
|
59
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Computed getters ─────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
get lineItems() {
|
|
65
|
+
return isArray(this.args.resource?.line_items) ? this.args.resource.line_items : [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get currency() {
|
|
69
|
+
return this.args.resource?.currency ?? 'USD';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get laborCost() {
|
|
73
|
+
return this._toCents(this.args.resource?.labor_cost);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get tax() {
|
|
77
|
+
return this._toCents(this.args.resource?.tax);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get partsCost() {
|
|
81
|
+
return this.lineItems.reduce((sum, item) => {
|
|
82
|
+
const qty = parseInt(item.quantity, 10) || 0;
|
|
83
|
+
const cost = this._toCents(item.unit_cost);
|
|
84
|
+
return sum + qty * cost;
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get totalCost() {
|
|
89
|
+
return this.laborCost + this.partsCost + this.tax;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Live preview of the draft line total (in cents).
|
|
94
|
+
* Displayed via `format-currency` which also expects cents.
|
|
95
|
+
*/
|
|
96
|
+
get draftLineTotal() {
|
|
97
|
+
return (parseInt(this.draftQuantity, 10) || 0) * this._toCents(this.draftUnitCost);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get isDisabled() {
|
|
101
|
+
return this.args.disabled ?? false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Draft unit cost setter ───────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Called by MoneyInput's `@onChange`. The value is already in cents.
|
|
108
|
+
*/
|
|
109
|
+
@action
|
|
110
|
+
setDraftUnitCost(cents) {
|
|
111
|
+
this.draftUnitCost = cents ?? 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Line item helpers ────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns the line total for a single item (quantity × unit_cost).
|
|
118
|
+
* Both values are in cents, so the result is also in cents.
|
|
119
|
+
* Used in the template as (this.lineTotal item).
|
|
120
|
+
*/
|
|
121
|
+
@action
|
|
122
|
+
lineTotal(item) {
|
|
123
|
+
const qty = parseInt(item?.quantity, 10) || 0;
|
|
124
|
+
const cost = this._toCents(item?.unit_cost);
|
|
125
|
+
return qty * cost;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Write a new copy of the line items array back onto the resource so
|
|
130
|
+
* Ember Data tracks the change and includes it in the next save().
|
|
131
|
+
*/
|
|
132
|
+
_commitItems(items) {
|
|
133
|
+
this.args.resource.set('line_items', [...items]);
|
|
134
|
+
// Recompute parts_cost and total_cost locally so the summary updates
|
|
135
|
+
// immediately without waiting for a server round-trip.
|
|
136
|
+
const partsCost = items.reduce((sum, item) => {
|
|
137
|
+
return sum + (parseInt(item.quantity, 10) || 0) * this._toCents(item.unit_cost);
|
|
138
|
+
}, 0);
|
|
139
|
+
this.args.resource.set('parts_cost', String(partsCost));
|
|
140
|
+
this.args.resource.set('total_cost', String(this.laborCost + partsCost + this.tax));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Add item ────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
@action
|
|
146
|
+
showAddRow() {
|
|
147
|
+
this.isAddingItem = true;
|
|
148
|
+
this.editingIndex = null;
|
|
149
|
+
this._resetDraft();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@action
|
|
153
|
+
cancelAdd() {
|
|
154
|
+
this.isAddingItem = false;
|
|
155
|
+
this._resetDraft();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@action
|
|
159
|
+
addLineItem() {
|
|
160
|
+
if (!this.draftDescription?.trim()) {
|
|
161
|
+
this.notifications.warning('Please enter a description for the line item.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const newItem = {
|
|
166
|
+
description: this.draftDescription.trim(),
|
|
167
|
+
quantity: parseInt(this.draftQuantity, 10) || 1,
|
|
168
|
+
unit_cost: this.draftUnitCost,
|
|
169
|
+
currency: this.currency,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
this._commitItems([...this.lineItems, newItem]);
|
|
173
|
+
this.isAddingItem = false;
|
|
174
|
+
this._resetDraft();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Edit item ───────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
@action
|
|
180
|
+
startEdit(index) {
|
|
181
|
+
const item = this.lineItems[index];
|
|
182
|
+
if (!item) return;
|
|
183
|
+
this.editingIndex = index;
|
|
184
|
+
this.isAddingItem = false;
|
|
185
|
+
this.draftDescription = item.description ?? '';
|
|
186
|
+
this.draftQuantity = item.quantity ?? 1;
|
|
187
|
+
// unit_cost from the record is already in cents
|
|
188
|
+
this.draftUnitCost = this._toCents(item.unit_cost);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@action
|
|
192
|
+
cancelEdit() {
|
|
193
|
+
this.editingIndex = null;
|
|
194
|
+
this._resetDraft();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@action
|
|
198
|
+
saveEdit() {
|
|
199
|
+
if (!this.draftDescription?.trim()) {
|
|
200
|
+
this.notifications.warning('Please enter a description for the line item.');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const index = this.editingIndex;
|
|
205
|
+
const updatedItems = this.lineItems.map((item, i) => {
|
|
206
|
+
if (i !== index) return item;
|
|
207
|
+
return {
|
|
208
|
+
...item,
|
|
209
|
+
description: this.draftDescription.trim(),
|
|
210
|
+
quantity: parseInt(this.draftQuantity, 10) || 1,
|
|
211
|
+
unit_cost: this.draftUnitCost,
|
|
212
|
+
currency: this.currency,
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
this._commitItems(updatedItems);
|
|
217
|
+
this.editingIndex = null;
|
|
218
|
+
this._resetDraft();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── Remove item ─────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
@action
|
|
224
|
+
removeLineItem(index) {
|
|
225
|
+
const updatedItems = this.lineItems.filter((_, i) => i !== index);
|
|
226
|
+
this._commitItems(updatedItems);
|
|
227
|
+
// If we were editing this item, close the edit row
|
|
228
|
+
if (this.editingIndex === index) {
|
|
229
|
+
this.editingIndex = null;
|
|
230
|
+
this._resetDraft();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── Private helpers ─────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
_resetDraft() {
|
|
237
|
+
this.draftDescription = '';
|
|
238
|
+
this.draftQuantity = 1;
|
|
239
|
+
this.draftUnitCost = 0;
|
|
240
|
+
}
|
|
241
|
+
}
|