Skip to content

v0.6.39 ~ added engine registry yield + use correct default asset url…#213

Open
roncodes wants to merge 203 commits intomainfrom
dev-v0.6.39
Open

v0.6.39 ~ added engine registry yield + use correct default asset url…#213
roncodes wants to merge 203 commits intomainfrom
dev-v0.6.39

Conversation

@roncodes
Copy link
Copy Markdown
Member

… for place, driver, vehicle icons

roncodes and others added 30 commits March 30, 2026 13:28
…k orders, equipment & parts

Completes the existing but incomplete maintenance section in FleetOps,
enabling full CRUD for all four maintenance resources: Maintenances,
Work Orders, Equipment, and Parts.

Frontend:
- Uncomment and activate the maintenance sidebar panel (fleet-ops-sidebar.js)
- Add Maintenances as first sidebar item with wrench icon
- Add maintenances route block to routes.js
- New route files for maintenances (index, new, edit, details, details/index)
- Fix all work-orders/equipment/parts details+edit routes: add model hook and permission guard
- New controllers for maintenances (index, new, edit, details, details/index)
- Complete controllers for work-orders, equipment, parts (full columns, save tasks, tabs, action buttons)
- New panel-header components for all four resources (HBS + JS)
- Fix all new.hbs templates: correct @resource binding (was this.place bug)
- Fix all details.hbs: add @headerComponent, TabNavigation with outlet
- Fix all edit.hbs: add @headerTitle with resource name
- New maintenances templates (maintenances.hbs, index, new, edit, details, details/index)
- Add 12 new Universe registries in extension.js for maintenance/work-order/equipment/part
- Fix maintenance-actions.js: use maintenance.summary instead of maintenance.name

Backend:
- Add maintenance, work-order, equipment, part resources to FleetOps auth schema
- Add MaintenanceManager policy with full CRUD on all four resources
- Update OperationsAdmin policy to include all four maintenance resources
- Add Maintenance Technician role
- New ProcessMaintenanceTriggers artisan command (time-based + odometer/engine-hour triggers)
- Register command and daily schedule in FleetOpsServiceProvider
Connects the FleetOps Driver Eloquent model to the core-api scheduling
system by adding polymorphic relationships:

- schedules(): MorphMany<Schedule> — driver as subject
- scheduleItems(): MorphMany<ScheduleItem> — driver as assignee
- activeShiftFor(date): ?ScheduleItem — convenience method for the
  AllocationPayloadBuilder to retrieve the driver's active shift for
  a given date and inject start_at/end_at as VROOM time_window constraints
- availabilities(): MorphMany<ScheduleAvailability> — time-off and
  preferred working hour records

Also adds required use statements for Schedule, ScheduleItem, MorphMany,
and ScheduleAvailability from the core-api namespace.

Refs: #214
…i scheduling system

## Backend

### server/src/Models/Driver.php
- Add schedules() MorphMany — driver as polymorphic subject on Schedule
- Add scheduleItems() MorphMany — driver as polymorphic assignee on ScheduleItem
- Add activeShiftFor(date) — convenience method for AllocationPayloadBuilder
  to retrieve the driver's active shift and inject time_window constraints
- Add availabilities() MorphMany — time-off and preferred working hours
- Add use statements for Schedule, ScheduleItem, ScheduleAvailability, MorphMany

### server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php (new)
- scheduleItems(id, request) — GET /{id}/schedule-items with date range filter
- availabilities(id, request) — GET /{id}/availabilities with date range filter
- hosStatus(id) — GET /{id}/hos-status, computes daily/weekly hours from shifts
- activeShift(id) — GET /{id}/active-shift, used by AllocationPayloadBuilder

### server/src/Http/Controllers/Internal/v1/DriverController.php
- Use DriverSchedulingTrait to expose the four scheduling endpoints

### server/src/routes.php
- Register four scheduling sub-routes under the internal drivers fleetbaseRoutes group

## Frontend

### addon/components/driver/schedule.js (rewritten)
- Inject fetch, intl, store, driverScheduling, modalsManager, notifications
- loadDriverSchedule task: queries schedule-items via driverScheduling service
- loadAvailability task: queries schedule-availability via store
- loadHOSStatus task: calls /drivers/{id}/hos-status, suppresses 404 gracefully
- addShift, editShift, deleteShift actions using modals/add-driver-shift and
  modals/driver-shift
- setAvailability, requestTimeOff, deleteAvailability actions using
  modals/set-driver-availability

### addon/components/driver/schedule.hbs (rewritten)
- HOS Status panel (hidden when hosStatus is null)
- Upcoming Shifts panel with edit/delete per-shift buttons
- Availability & Time Off panel with add/delete availability buttons
- Fleetbase UI styling (minimal padding, Tailwind, ContentPanel, Badge, Button)

### addon/components/modals/add-driver-shift.{js,hbs} (new)
- Creates a ScheduleItem with assignee_type=driver
- Supports both single-driver (from driver panel) and multi-driver (from global
  scheduler) modes via the hasManyDrivers computed property

### addon/components/modals/driver-shift.{js,hbs} (new)
- Edits an existing ScheduleItem, pre-populates fields from the passed item

### addon/components/modals/set-driver-availability.{js,hbs} (new)
- Shared modal for both Set Availability (is_available=true) and
  Request Time Off (is_available=false) flows

### addon/services/driver-actions.js
- Add Schedule tab (component: 'driver/schedule') to the driver view panel tabs

### addon/controllers/operations/scheduler/index.js
- Add driverAvailabilities tracked property
- Enhance events computed to render unavailable availability records as
  FullCalendar background events (red-300) so dispatchers see time-off blocks
- Add loadDriverAvailabilities task
- Call loadDriverAvailabilities when switching to 'drivers' view mode
- Use intl.t() for addDriverShift modal title and button text

Refs: #214
…etOps integration

## Backend

### Allocation Engine Architecture (server/src/Allocation/)
- AllocationEngineInterface: defines the allocate()/getName()/getIdentifier() contract
- AllocationEngineRegistry: singleton service-locator; engines register via resolving() hook
- AllocationPayloadBuilder: engine-agnostic normalizer — builds jobs/vehicles arrays from
  Order/Vehicle models, reads custom fields for skill codes, injects driver shift time_windows
  from Driver::activeShiftFor() (prerequisite: PR #216 driver scheduling integration)
- VroomAllocationEngine: default VROOM implementation; maps normalized payload to VROOM VRP
  wire format, handles integer ID mapping, parses routes/unassigned back to public_ids

### AllocationController (server/src/Http/Controllers/Internal/v1/)
- POST   fleet-ops/allocation/run      — run engine against unassigned orders + online vehicles
- POST   fleet-ops/allocation/commit   — commit assignments via Order::firstDispatchWithActivity()
- GET    fleet-ops/allocation/preview  — preview without side effects
- GET    fleet-ops/allocation/engines  — list registered engines (for settings dropdown)
- GET    fleet-ops/allocation/settings — get allocation settings
- PATCH  fleet-ops/allocation/settings — save allocation settings

### ProcessAllocationJob (server/src/Jobs/)
- Queueable, idempotent background job for auto-allocation on order creation or re-allocation
- Reads active engine from Setting::lookup('fleetops.allocation_engine', 'vroom')

### HandleDeliveryCompletion (server/src/Listeners/)
- Listens on OrderCompleted; dispatches ProcessAllocationJob when
  auto_reallocate_on_complete is enabled — closes the re-allocation loop

### Provider/Route wiring
- FleetOpsServiceProvider: registers AllocationEngineRegistry singleton + VroomAllocationEngine
- EventServiceProvider: adds HandleDeliveryCompletion to OrderCompleted listeners
- routes.php: adds /allocation group with 6 endpoints under internal v1 fleet-ops prefix

## Frontend

### Engine Registry Pattern (addon/services/)
- allocation-engine-interface.js: abstract base class with allocate() contract
- allocation-engine.js: registry service — register()/resolve()/has()/availableEngines
- vroom-allocation-engine.js: VROOM adapter — delegates to backend AllocationController
- order-allocation.js: orchestration service — run/commit/loadSettings/saveSettings tasks

### Instance Initializer (addon/instance-initializers/)
- register-vroom-allocation.js: registers VroomAllocationEngine into the allocation-engine
  registry at app boot — identical pattern to register-osrm.js for route optimization

### Dispatcher Workbench (addon/components/)
- order-allocation-workbench.js: three-panel workbench with Order Bucket, Proposed Plan view,
  Vehicle Bucket; runAllocation/commitPlan/discardPlan tasks; handleDrop for drag-and-drop
  override; planByVehicle computed groups assignments by vehicle for the plan view
- order-allocation-workbench.hbs: full Handlebars template with toolbar, three panels,
  per-vehicle route cards, unassigned warning banner, override badges, empty states

### Settings UI (addon/controllers/settings/ + addon/templates/settings/)
- order-allocation.js controller: loadSettings/saveSettings tasks, engineOptions from registry
- order-allocation.hbs template: engine selector (PowerSelect from registry), auto-allocate
  toggles, max travel time input, balance workload toggle

### Route/Navigation wiring
- routes.js: adds operations.allocation and settings.order-allocation routes
- routes/operations/allocation.js: ability-guarded route
- routes/settings/order-allocation.js: ability-guarded route with setupController hook
- templates/operations/allocation.hbs: renders OrderAllocationWorkbench
- extension.js: adds Allocation shortcut tile + fleet-ops:template:settings:order-allocation registry
- layout/fleet-ops-sidebar.js: adds Allocation to operations nav, Order Allocation to settings nav

Closes #214
… sections

- work-order/form: split into Identification, Assignment (polymorphic
  target + assignee with type-driven ModelSelect), Scheduling, and
  Instructions panels. Added targetTypeOptions, assigneeTypeOptions,
  onTargetTypeChange, onAssigneeTypeChange, assignTarget, assignAssignee
  actions. Removed hardcoded 'user' model assumption.

- maintenance/form: split into Identification, Asset & Work Order
  (polymorphic maintainable + performed-by), Scheduling & Readings
  (odometer, engine_hours, scheduled_at, started_at, completed_at),
  Costs (MoneyInput for labor_cost, parts_cost, tax, total_cost), and
  Notes panels. Added full polymorphic type handlers.

- equipment/form: split into Photo, Identification (name, code,
  serial_number, manufacturer, model, type, status), Assignment
  (polymorphic equipable), and Purchase & Warranty panels. Fixed photo
  upload to use fetch.uploadFile.perform pattern. Added
  onEquipableTypeChange / assignEquipable actions.

- part/form: split into Photo, Identification (name, sku, serial_number,
  barcode, manufacturer, model, type, status, description), Inventory
  (quantity_on_hand, unit_cost, msrp with MoneyInput), Compatibility
  (polymorphic asset), and Vendor & Warranty panels. Fixed photo upload
  to use fetch.uploadFile.perform pattern. Added onAssetTypeChange /
  assignAsset actions.

All forms: added MetadataEditor panel, RegistryYield hooks, and
CustomField::Yield. All option arrays cross-checked against PHP model
fillable arrays and fleetops-data Ember models.
… and part forms, fix all ContentPanel wrapperClass

- equipment/form.hbs: remove standalone Photo ContentPanel; photo block
  (Image + UploadButton, matching vehicle/form structure) is now the
  first child of the Identification ContentPanel before the field grid.
- part/form.hbs: same restructure as equipment.
- All four forms (work-order, maintenance, equipment, part): every
  ContentPanel now carries @wrapperclass="bordered-top", including the
  first panel. Previously work-order and maintenance first panels had no
  wrapperClass at all.
- equipment/form.js: equipableTypeOptions converted to { value, label }
  objects; added @Tracked selectedEquipableType; onEquipableTypeChange
  now receives option object and reads option.value.
- part/form.js: assetTypeOptions converted to { value, label } objects;
  added @Tracked selectedAssetType; onAssetTypeChange updated similarly.
- Both HBS files updated to bind @selected to the tracked option object
  and render {{option.label}} in the PowerSelect block.
…d-by type selectors

- maintainableTypeOptions: plain strings -> { value, label } objects
  (Vehicle, Equipment)
- performedByTypeOptions: plain strings -> { value, label } objects
  (Vendor, Driver, User) — added Vendor as a valid performer type
- Added selectedMaintainableType and selectedPerformedByType tracked
  properties so the PowerSelect trigger shows the human-readable label
- Both onChange actions now receive the full option object and write
  option.value to the model attribute
- Updated TYPE_TO_MODEL to include fleet-ops:vendor -> vendor
- HBS PowerSelect @selected bindings updated to use the tracked option
  objects; block params renamed from |type| to |option| with {{option.label}}
…ems panel

- Add migration to add public_id column to maintenances, work_orders,
  equipment, and parts tables (fixes SQLSTATE[42S22] unknown column error)
- Replace flat cost ContentPanel with new Maintenance::CostPanel component
  - Invoice-style line items table with description, qty, unit cost, line total
  - Inline add/edit/remove rows with optimistic UI updates
  - Labour and Tax inputs remain as direct MoneyInput fields
  - Computed totals summary (Labour + Parts + Tax = Total)
  - All mutations hit dedicated API endpoints and reflect server-recomputed totals
- Add addLineItem / updateLineItem / removeLineItem endpoints to MaintenanceController
- Register POST/PUT/DELETE line-item sub-routes in routes.php
Resolves Glimmer reactivity assertion error caused by MoneyInput's
autoNumerize modifier consuming and mutating @resource.currency in
the same render cycle.

Following the vehicle/form.hbs pattern, each form now has a single
CurrencySelect input at the top of the cost/pricing section. All
MoneyInput fields simply read @Currency without @canSelectCurrency
or @onCurrencyChange.

Files changed:
- maintenance/cost-panel.hbs: added CurrencySelect before labour/tax
  inputs; removed @canSelectCurrency from all MoneyInput fields
- equipment/form.hbs: added CurrencySelect before purchase_price;
  removed @canSelectCurrency/@onCurrencyChange from purchase_price
- part/form.hbs: added CurrencySelect before unit_cost/msrp;
  removed @canSelectCurrency/@onCurrencyChange from both fields
…se/fleetops into feat/complete-maintenance-module
- Add Equipment::Card component (photo, type, status, year, quick actions)
- Add Part::Card component (photo, type, qty, unit cost, quick actions)
- Equipment index controller: inject appCache, add @Tracked layout,
  convert actionButtons/bulkActions to getters, add layout toggle dropdown
- Parts index controller: same pattern as Equipment
- Equipment index template: conditional table vs CardsGrid layout
- Parts index template: conditional table vs CardsGrid layout
- Layout preference persisted via appCache (fleetops:equipment:layout,
  fleetops:parts:layout)
Vehicle row dropdown additions:
- Schedule Maintenance → opens schedule form pre-filled with vehicle
- Create Work Order → opens work order form pre-filled with vehicle
- Log Maintenance → opens maintenance form pre-filled with vehicle

Vehicle details panel — 3 new tabs:
- Schedules: lists active maintenance schedules for the vehicle,
  empty state with 'Add Schedule' CTA
- Work Orders: lists work orders targeting the vehicle,
  empty state with 'Create Work Order' CTA
- Maintenance History: lists completed maintenance records,
  empty state with 'Log Maintenance' CTA

Supporting changes:
- vehicle-actions.js: inject scheduleActions/workOrderActions/maintenanceActions,
  add scheduleMaintenance/createWorkOrder/logMaintenance @action methods
- routes.js: add schedules/work-orders/maintenance-history sub-routes under
  vehicles.index.details; add maintenance.schedules top-level route
- Translations: add vehicle.actions.schedule-maintenance/create-work-order/
  log-maintenance; add menu.schedules/maintenance-history;
  add resource.maintenance-schedule(s)
…mmand rewrite

Backend changes:
- Migration: create maintenance_schedules table with interval fields
  (time/distance/engine-hours), next-due thresholds, default assignee,
  and add schedule_uuid FK to work_orders for traceability
- MaintenanceSchedule model: isDue(), resetAfterCompletion(), pause(),
  resume(), complete() methods; polymorphic subject + defaultAssignee
  relationships; workOrders() hasMany
- WorkOrderObserver: on status → 'closed', auto-creates a Maintenance
  history record from completion data stored in work_order.meta and
  calls schedule.resetAfterCompletion() to restart the interval cycle
- ProcessMaintenanceTriggers rewrite: now reads MaintenanceSchedule
  instead of Maintenance; resolves vehicle odometer/engine-hours from
  the polymorphic subject; skips schedules with an existing open WO;
  auto-creates WorkOrder from schedule defaults on trigger
- MaintenanceScheduleController: CRUD via FleetOpsController base +
  custom pause/resume/trigger endpoints
- routes.php: register maintenance-schedules routes with pause/resume/
  trigger sub-routes before work-orders
- FleetOpsServiceProvider: register WorkOrderObserver
…r update, WO completion panel

Frontend changes:
- Sidebar: add 'Schedules' (calendar-alt icon) as first item in the
  Maintenance panel; rename 'Maintenances' entry to 'Maintenance History'
  (history icon) — order is now: Schedules, Work Orders, Maintenance
  History, Equipment, Parts
- MaintenanceSchedule Ember model: full attr mapping for interval fields,
  next-due thresholds, default assignee, status, subject polymorphic
- schedule-actions service: ResourceActionService subclass with
  transition/panel/modal patterns + pause(), resume(), triggerNow() actions
- schedule/form.hbs + form.js: full create/edit form with Schedule Details,
  Asset (polymorphic subject), Maintenance Interval (time/distance/hours),
  and Work Order Defaults (priority, default assignee, instructions) panels
- schedule/details.hbs + details.js: read-only details view component
- Routes: maintenance.schedules.index (+ new/edit/details sub-routes)
- Controllers: schedules/index (columns, actionButtons, bulkActions),
  schedules/index/details (tabs, actionButtons, edit/triggerNow/delete),
  schedules/index/new, schedules/index/edit
- Templates: schedules index (Layout::Resource::Tabular), new overlay,
  edit overlay, details overlay
- work-order/form.hbs: add Completion Details panel (odometer, engine
  hours, labour cost, parts cost, tax, notes) shown only when status
  is set to 'closed'; seeds the WorkOrderObserver auto-log creation
- work-order/form.js: add isCompleting getter + six @Tracked completion
  state fields
… under MySQL 64-char limit

The auto-generated name 'maintenance_schedules_default_assignee_type_default_assignee_uuid_index'
is 73 characters, exceeding MySQL's 64-character identifier limit.
Replaced with explicit short name 'ms_default_assignee_idx'.
…edit/details, fix TYPE_TO_MODEL keys, complete new/edit controllers with save task
… is a real DB column not a computed accessor
…/cell/base across all maintenance controllers
…rvice, calendar, namespace

1. Rename scheduleActions → maintenanceScheduleActions
   - addon/services/schedule-actions.js → maintenance-schedule-actions.js
   - app/services/maintenance-schedule-actions.js re-export added
   - All @service injections and this.scheduleActions refs updated in
     schedules/index, schedules/index/details, vehicle-actions

2. Convert @Tracked actionButtons/bulkActions/columns → getters
   - All 5 maintenance index controllers now use get() instead of @Tracked
   - Prevents Glimmer reactivity assertion errors on render

3. Fix broken @service menuService injection
   - All 5 details controllers: @service menuService →
     @service('universe/menu-service') menuService

4. Rename schedule/ component namespace → maintenance-schedule/
   - addon/components/schedule/ → addon/components/maintenance-schedule/
   - app/components/maintenance-schedule/ re-exports added
   - Templates updated: Schedule::Form/Details → MaintenanceSchedule::Form/Details
   - Class names updated to MaintenanceScheduleFormComponent etc.

5. Add calendar visualization to MaintenanceSchedule::Details
   - details.js: computeOccurrences() + buildCalendarGrid() helpers
   - Navigable month calendar with scheduled dates highlighted in blue
   - Upcoming occurrences list (next 6 dates)
   - Only shown for time-based schedules (interval_method === 'time')
…-orders index getters

The sed-based getter conversion left actionButtons and bulkActions getters
without their closing } in two controllers:
- maintenances/index.js: actionButtons and bulkActions both missing }
- work-orders/index.js: bulkActions missing }

schedules/index.js, equipment/index.js, and parts/index.js were unaffected.
…O tab, vehicle prefill, cost-panel re-export

- ProcessMaintenanceTriggers: auto-generate WO code (WO-YYYYMMDD-XXXXX) and set opened_at on creation
- WorkOrder::Details: full details component with overview, assignment, scheduling, and cost breakdown panels
  (cost breakdown reads from meta.completion_data, shown only when status is closed)
- WorkOrder::Form: add prepareForSave action that packs completion tracked fields into meta before save
- work-orders new/edit controllers: track formComponent and call prepareForSave before workOrder.save()
- Schedules details: add Work Orders tab (route + template) showing all WOs created by this schedule
- vehicle-actions: fix subject_type to use namespaced type strings (fleet-ops:vehicle etc) so schedule form
  pre-selects the correct asset type when opened from the vehicles index row dropdown
- app/components/maintenance/cost-panel.js: add missing re-export shim
- app/components/maintenance/panel-header.js: add missing re-export shim
…hip accessors

Replace all raw _type / _uuid attr reads and writes with proper
@belongsTo relationship accessors across the maintenance module.

Changes:
- addon/models/maintenance-schedule.js
  • Replace subject_type/subject_uuid/subject_name attrs with
    @belongsTo('maintenance-subject', {polymorphic:true}) subject
  • Replace default_assignee_type/default_assignee_uuid attrs with
    @belongsTo('facilitator', {polymorphic:true}) default_assignee
  • Add interval_method attr (was missing)
  • Remove obsolete raw type/uuid attrs

- addon/components/maintenance-schedule/form.js
  • Add MODEL_TO_TYPE + ASSIGNEE_MODEL_TO_TYPE reverse-lookup maps
  • Constructor now reads type from resource.subject.constructor.modelName
    and resource.default_assignee.constructor.modelName instead of raw attrs
  • onSubjectTypeChange / onAssigneeTypeChange clear the relationship
    instead of writing _type/_uuid
  • assignSubject / assignDefaultAssignee set the relationship only

- addon/components/maintenance-schedule/form.hbs
  • @selectedModel binding updated from defaultAssignee → default_assignee

- addon/components/maintenance-schedule/details.hbs
  • Asset field reads subject.displayName|name instead of subject_name

- addon/components/work-order/form.js
  • Add TARGET_MODEL_TO_TYPE + ASSIGNEE_MODEL_TO_TYPE reverse-lookup maps
  • Constructor reads type from target/assignee relationship model names
  • onTargetTypeChange / onAssigneeTypeChange clear relationship only
  • assignTarget / assignAssignee set relationship only

- addon/components/work-order/details.hbs
  • Assignment panel uses target.displayName / assignee.displayName
  • Schedule panel uses schedule.name instead of schedule_uuid

- addon/components/maintenance/form.js
  • Add MAINTAINABLE_MODEL_TO_TYPE + PERFORMED_BY_MODEL_TO_TYPE maps
  • Constructor reads type from maintainable/performed_by relationship
  • onMaintainableTypeChange / onPerformedByTypeChange clear relationship
  • assignMaintainable / assignPerformedBy set relationship only

- addon/components/maintenance/form.hbs
  • @selectedModel binding updated from performedBy → performed_by

- addon/components/maintenance/details.hbs
  • Maintainable / Performed By fields use relationship accessors

- addon/services/vehicle-actions.js
  • scheduleMaintenance: pass { subject: vehicle } only
  • createWorkOrder: pass { target: vehicle } only
  • logMaintenance: pass { maintainable: vehicle } only

- addon/components/vehicle/details/schedules.js
  • Fix service injection: @service scheduleActions → @service('maintenance-schedule-actions')

- addon/components/vehicle/details/schedules.hbs
  • Add Schedule button passes { subject: @vehicle }

- addon/components/vehicle/details/work-orders.hbs
  • Create Work Order button passes { target: @vehicle }

- addon/components/vehicle/details/maintenance-history.hbs
  • Log Maintenance button passes { maintainable: @vehicle }
roncodes and others added 30 commits April 9, 2026 11:18
…point

getCurrentDestinationLocation() returns new Point(0, 0) as a fallback
when an order has no dropoff or waypoint set. These 0,0 values were
being included in the coordinates response, causing the location service
to resolve to the Gulf of Guinea (0,0) instead of a real location.

Added the same null + 0,0 filter already used in the drivers, vehicles,
and places endpoints — consistent with the existing pattern.
…es, configurable card fields

## Backend
- Add Manifest and ManifestStop models with full relationships (vehicle, driver, order, place, waypoint)
- Add 3 migrations: create_manifests_table, create_manifest_stops_table, add_manifest_uuid_to_orders_table
- Add ManifestController (index, show, showStop, updateStop, cancel, destroy)
- Rewrite AllocationController: commit now generates Manifests with ordered ManifestStops
- Add vehicle-only allocation mode (no driver required, uses vehicle.location as fallback)
- Add DriverAssignmentEngine for greedy shift-aware driver-to-vehicle matching
- Patch AllocationPayloadBuilder with buildVehiclesOnly() method
- Add SettingController::getOrchestratorCardFields / saveOrchestratorCardFields
- Register all new routes: manifests, manifest-stops, orchestrator-card-fields

## Frontend
- Decompose OrchestratorWorkbench monolith into sub-components:
  - Orchestrator::OrderPool — filterable order list with drag support
  - Orchestrator::ResourcePanel — vehicles + drivers tabs with selection
  - Orchestrator::PhaseBuilder — compose multi-phase runs (mode, filters, constraints)
  - Orchestrator::PlanViewer — post-run route cards with ordered stop rows
  - Orchestrator::CardFieldsSettings — grouped card field config by order config
- Rewrite orchestrator-workbench.js: phase state machine, planByVehicle grouping,
  manual drag-and-drop overrides, multi-phase execution with auto-commit option
- Rewrite orchestrator-workbench.hbs: 3-panel layout with collapsible panels,
  phase builder panel, card fields panel, plan viewer / resource panel toggle
- Add Orchestrator::CardFieldsSettings panel to Settings > Orchestrator page

## Translations
- Add 60+ new keys: phase builder, allocation modes, card field labels,
  resource panel filters, plan viewer, general workbench labels
- 169 unique keys in orchestrator section, zero duplicates
…rectory

- app/components/orchestrator/order-pool.js
- app/components/orchestrator/resource-panel.js
- app/components/orchestrator/phase-builder.js
- app/components/orchestrator/plan-viewer.js
- app/components/orchestrator/card-fields-settings.js
…, leaflet container, card styling, toggle bar

- Fix Glimmer render error: cardFieldsForOrder, priorityStatus, isExpanded
  were plain methods called as helpers in HBS; decorated as @action so they
  are callable with arguments from templates
- Fix search inputs: pl-7 → pl-7i on all three search inputs
  (order-pool, resource-panel vehicles, resource-panel drivers)
- Wrap LeafletMap with fleetbase-leaflet-map-container div in main workbench
- Restore original vehicle/driver card styling: full avatar + status badge +
  all field rows (driver name, plate, call sign, phone, email, vehicle)
  matching pre-rewrite order-pool-card and vehicle-route-card patterns
- Restore original long border-style toggle collapse bars (workbench-panel-toggle
  class, w-4 full-height bar with border) replacing the floating button style
- Restore original plan-viewer vehicle-route-card layout with colour-coded
  left border, expand/collapse chevron, and assigned-stop drag rows
…rd fields, and leaflet map grey screen

- AllocationController::orderConfigFields: use ->with('customFields') relationship
  instead of selecting non-existent 'custom_fields' column (fixes SQLSTATE[42S22])
- order-pool.hbs: remove hardcoded dl block; all card fields now driven exclusively
  through resolvedCardFields() — eliminates the duplicate lowercase field rows
- order-pool.js: add DEFAULT_FIELDS fallback with icon/highlight metadata so cards
  display correctly even when no card-field settings have been saved; update
  resolvedCardFields to return {label, value, icon, iconClass, highlight} objects
- orchestrator-workbench.hbs: fix LeafletMap from named block syntax (<:layers>)
  back to standard block syntax (as |layers|) — named blocks are not supported by
  ember-leaflet and caused the grey map screen
… click-select, route viz markers/polylines

- card-fields-settings: fix response key (configs not order_configs), fix
  config.fields (not config.custom_fields), decorate isStandardSelected/
  isConfigFieldSelected/isMetaKeySelected as @action to avoid Glimmer
  method-as-helper errors
- order-pool: replace order-filter-chip CSS class with explicit px-2 py-0.5
  rounded text-xs transition-colors pattern matching vehicle/driver panel
  filter buttons; add hover:ring-1 outline on unselected cards so click-to-
  select is visually clear before clicking
- orchestrator-workbench: import colorForId, routeStyleForStatus,
  waypointIconHtml from new route-colors utility (ported from
  feature/route-visualization-improvements); replace ROUTE_COLORS array;
  planByVehicle now uses colorForId for deterministic per-vehicle colors and
  routeStyleForStatus for two-layer cased polyline styles; map markers now
  use waypointIconHtml divIcon (green P = pickup, red D = dropoff)
- route-colors.js: copy utility + app re-export from
  feature/route-visualization-improvements
- fleetops-engine.css: append route visualization CSS from feature branch
… markers

Replace the plain {{hash className=... html=...}} @ICON pattern with the
correct ember-leaflet {{div-icon}} inline helper. The hash pattern passes a
POJO to Leaflet which rejects it because it lacks createIcon() — {{div-icon}}
constructs a proper L.DivIcon instance internally.

Also restore driver location markers (green/red dot) that were dropped in
the route-viz refactor, and add popup/tooltip to order dropoff markers.
…e handles, byConfig key mismatch

- AllocationController::orderConfigFields now returns 'uuid' alongside 'id'
  so the byConfig key in saved settings matches order.order_config_uuid
- OrderConfig::customFields() fixed from wrong belongsTo to correct morphMany
  so eager-loading via ->with('customFields') actually returns custom field rows
- card-fields-settings: use config.uuid (not config.id/public_id) as byConfig key
  in both toggleConfigField and isConfigFieldSelected so selections persist correctly
- card-fields-settings: all ContentPanel blocks now have @OPEN={{true}} and
  @wrapperclass='bordered-top' as requested
- settings/orchestrator.hbs: Card Fields ContentPanel changed to @OPEN={{true}}
- orchestrator-workbench.js: added @Tracked leftPanelWidth/rightPanelWidth (288/320px)
  and @action startLeftResize / startRightResize with document mousemove/mouseup
  drag handlers, min/max clamping, and cursor feedback
- orchestrator-workbench.hbs: resize handle divs already present from previous
  commit; JS actions now back them up
…int; unify checkbox styling

- AllocationController::orderConfigFields now filters out any config whose
  custom fields collection is empty after eager-load + fallback query, so
  the Card Fields settings panel only shows configs that actually have fields
- card-fields-settings.hbs: removed the {{#if config.fields.length}} / {{else}}
  block (no longer needed since backend filters) and replaced the two-line
  div label (label + key subtext) with the same single-line label/span pattern
  used by standard fields; field.key is preserved as a title= tooltip on hover
…ustom field value rendering

AllocationPayloadBuilder:
- Added static safeMeta($model, $key, $default) helper that wraps getMeta()
  in a try/catch to handle rows where the meta column is stored as a raw JSON
  string instead of a decoded array (causes TypeError: Return value must be of
  type array, string returned in HasMetaAttributes::getAllMeta())
- All getMeta() calls in buildVehicles() and buildVehiclesOnly() now go through
  safeMeta() so a single bad vehicle row does not abort the entire allocation run

order-pool.js resolvedCardFields:
- Custom field values are flattened onto the order payload by withCustomFields()
  using Str::snake(Str::lower(label)) as the top-level key — they are NOT nested
  under a custom_field_values array on the frontend object
- Previous code called order.custom_field_values?.find(v => v.field_key === key)
  which returned a full CustomFieldValue model instance; Glimmer rendered it as
  [object Object]
- New logic uses three strategies in order:
  1. Direct flat property access: order[fieldKey] (covers most cases)
  2. Scan the custom_field_values collection matching on customField.name or label
  3. Scalar extraction with JSON.stringify fallback for object values
- All resolved values are coerced to String before being pushed to the fields array
…custom fields, date locale

VroomAllocationEngine:
- Was calling POST {host}/ — the verso-optim.com API requires POST {base_uri}/solve
- Now reads base URI from config('vroom.base_uri') (set by fleetbase/vroom extension
  when installed) → VROOM_HOST env → production default https://api.verso-optim.com/vrp/v1
- API key resolved in order: company Setting 'vroom.api_key' (written by the
  fleetbase/vroom extension settings UI) → VROOM_API_KEY env → no key (self-hosted)
- Engine remains fully self-contained; fleetbase/vroom extension is NOT required
- Improved error message includes actionable hint about greedy fallback

GreedyAllocationEngine (new):
- Built-in nearest-vehicle greedy assignment — no external service required
- Registered as 'greedy' in AllocationEngineRegistry via FleetOpsServiceProvider
- Sorts orders by priority desc then scheduled_at asc before assigning
- Useful as a fallback when VROOM is unavailable or for simple deployments

AllocationController:
- Engine call wrapped in try/catch(\RuntimeException) returning structured 503 JSON
  instead of an unhandled exception HTML page
- $engineId hoisted before the try block so it is always defined in the catch

fleetops.php config:
- Removed the vroom.host/timeout config block (the engine now reads vroom.base_uri
  from the extension config, not a fleetops-specific key)
- Added documentation comment explaining API key resolution order

order-pool.js:
- _formatDate: use 'en-US' locale explicitly instead of undefined (browser/OS
  locale was Arabic on some deployments)
- resolvedCardFields custom field lookup:
  - Strategy 1: order[fieldKey] ?? order.get(fieldKey) — covers flat properties
    pushed by withCustomFields() using Str::snake(Str::lower(label))
  - Strategy 2: scan custom_field_values HasMany collection, match on cf.name or
    cf.label, extract scalar .value — handles cases where the flat key differs
  - Both Ember Data model proxy (.get()) and plain object access patterns used
- Default allocation engine changed from 'vroom' to 'greedy' everywhere:
  * _legacyPhase() in orchestrator-workbench.js
  * phase.engine fallback in _runSinglePhase
  * loadEngines() fallback when API is unavailable
  * Backend AllocationController::run() Setting::lookup fallback

- Fix custom field values not rendering on order cards:
  * Change orders query from with=customFields to
    with=customFieldValues.customField so the hasMany relation
    is actually eager-loaded (customFields was the field definitions
    relation, not the values)
  * Add custom_field_values to Index/Order resource so the serialised
    response includes the CFV array with nested custom_field object
    (name, label, type) when the relation is loaded
  * Simplify resolvedCardFields in order-pool.js — remove the dead
    Strategy 1 flat-key approach (Ember Data drops unknown attrs) and
    rely solely on scanning the custom_field_values collection matching
    by custom_field.name (machine key) or custom_field.label
…dd OrchestrationController + OrchestratorOrderResource; update all frontend endpoints to fleet-ops/orchestrator/*

- Revert Index/Order resource (no custom_field_values — keeps tabular view fast)
- New Orchestration/ namespace: OrchestrationEngineRegistry, OrchestrationEngineInterface,
  VroomOrchestrationEngine, GreedyOrchestrationEngine, OrchestrationPayloadBuilder,
  DriverAssignmentEngine (namespace only)
- New OrchestrationController replaces AllocationController with all existing methods
  plus a new GET orchestrator/orders endpoint using OrchestratorOrderResource
- OrchestratorOrderResource: lightweight order shape + custom_field_values with nested
  custom_field definition — used exclusively by the workbench
- Route prefix changed: allocation/ → orchestrator/
- ProcessAllocationJob updated to OrchestrationEngineRegistry + greedy default
- Frontend: all fleet-ops/allocation/* calls → fleet-ops/orchestrator/*
- loadOrders now calls orchestrator/orders (plain JSON) instead of store.query
- order-allocation service: corrected default engine to greedy, updated all endpoints
- vroom-allocation-engine: updated endpoint + passes engine: vroom in options
…ionController; clean up all stale references

- Deleted server/src/Allocation/ entirely (AllocationEngineRegistry,
  AllocationEngineInterface, VroomAllocationEngine, GreedyAllocationEngine,
  AllocationPayloadBuilder, DriverAssignmentEngine)
- Deleted AllocationController.php (replaced by OrchestrationController)
- Renamed addon/services/allocation-engine-interface.js → orchestration-engine-interface.js
- Renamed addon/services/allocation-engine.js → orchestration-engine.js
- Updated all class names: AllocationEngineInterfaceService → OrchestrationEngineInterfaceService
- Updated register-vroom-allocation initializer to use service:orchestration-engine
- Updated orchestrator settings controller to inject service:orchestration-engine
- Fixed all stale docblock comments referencing old Allocation class names
- Zero remaining references to the old Allocation namespace anywhere in the codebase
…e filenames

- app/services/allocation-engine-interface.js → orchestration-engine-interface.js
- app/services/allocation-engine.js → orchestration-engine.js
- Updated re-export targets to point at the renamed addon service files
- vroom-allocation-engine and order-allocation shims unchanged (filenames kept)
…trationPayloadBuilder::buildVehicles

Vehicles without an assigned driver (vehicle-only mode) caused an
ErrorException when buildVehicles tried to read time_window_start,
time_window_end, max_travel_time, skills, and custom_fields directly
on a null $driver. Changed all bare $driver-> accesses to the null-safe
$driver?-> operator so driverless vehicles are handled gracefully.
…l translation

The commitPlan success notification was calling intl.t('orchestrator.committed')
without the required {count} variable, causing a FORMAT_ERROR. Now passes
result?.committed?.length with a fallback to finalAssignments.length.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant