Skip to content

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

Merged
roncodes merged 272 commits intomainfrom
dev-v0.6.39
Apr 14, 2026
Merged

v0.6.39 ~ added engine registry yield + use correct default asset url…#213
roncodes merged 272 commits intomainfrom
dev-v0.6.39

Conversation

@roncodes
Copy link
Copy Markdown
Member

… for place, driver, vehicle icons

Ronald A Richardson and others added 30 commits March 31, 2026 06:15
… 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 }
…se/fleetops into feat/complete-maintenance-module
… non-embedded relationship display

Three fixes applied:

1. Remove console.fleet-ops. route prefix (controllers + routes)
   All transitionTo and tab route strings in addon/controllers/maintenance/
   and addon/routes/maintenance/ were incorrectly prefixed with
   'console.fleet-ops.'.  The host router adds the engine prefix
   automatically, so the routes should start with 'maintenance.' directly.
   Fixed across all 15 controller files and 8 route files.

2. Add _name fallback attrs to details templates
   The subject, default_assignee, target, assignee, maintainable, and
   performed_by relationships are not sideloaded in the server response.
   Details templates now use (or relationship.displayName relationship.name
   resource._name) so the server-side convenience field is shown when the
   relationship object has not yet been loaded.

3. Serializer embedded attrs cleanup (fleetops-data companion commit)
   Removed embedded: always declarations for non-sideloaded polymorphic
   relationships in maintenance-schedule, work-order, and maintenance
   serializers to prevent Ember Data from expecting nested objects that
   the server never returns.
…ic relationships

Backend changes:
- Add $with = ['subject', 'defaultAssignee'] to MaintenanceSchedule model
- Add $with = ['target', 'assignee'] to WorkOrder model (remove from $hidden)
- Add $with = ['maintainable', 'performedBy'] to Maintenance model (remove from $hidden)
- Create Http/Resources/v1/MaintenanceSchedule.php resource transformer
  - Embeds subject and default_assignee via whenLoaded()
  - Outputs raw PHP class name for _type fields (serializer maps them)
- Create Http/Resources/v1/WorkOrder.php resource transformer
  - Embeds target and assignee via whenLoaded()
- Create Http/Resources/v1/Maintenance.php resource transformer
  - Embeds maintainable and performed_by via whenLoaded()

The resource transformers ensure that polymorphic relationship objects are always
included in API responses, enabling the frontend to use embedded: always in its
serializers without needing a separate request to load the related records.
…formers

All existing FleetbaseResource subclasses declare toArray($request) without a
return type annotation. Adding ': array' caused PHP to dispatch withCustomFields()
through __call() on the JsonResource base class instead of the concrete class,
resulting in BadMethodCallException.
withCustomFields() is a method provided by the HasCustomFields trait on the
model. FleetbaseResource proxies it via DelegatesToResource to the underlying
model instance. WorkOrder and Maintenance already had the trait applied;
MaintenanceSchedule was missing both the import and the use statement, causing
the BadMethodCallException when the resource transformer called withCustomFields().
…c objects

Ember Data resolves the model for an embedded polymorphic belongsTo by reading
the 'type' field inside the embedded object. Without it, Ember Data fell back to
the model's own 'type' attribute (e.g. 'fliit_asset') which is not a valid model
name.

Each resource transformer now:
- Calls Utils::toEmberResourceType() to convert PHP class names to shorthands
  for the *_type fields on the parent record.
- Injects type: 'maintenance-subject' (or 'facilitator') into the embedded
  object so Ember Data resolves the correct abstract polymorphic model.
- Injects subject_type / facilitator_type into the embedded object with the
  concrete subtype (e.g. 'maintenance-subject-vehicle').

Mirrors the exact pattern used by Order::setFacilitatorType().
…se/fleetops into feat/complete-maintenance-module
… maintenance module

- cost-panel.hbs: add @onchange to labor_cost, tax MoneyInput fields; add
  @onchange={{this.setDraftUnitCost}} to inline edit/add row MoneyInput;
  fix column widths using colgroup percentage layout; use format-currency
  helper (cents-based) throughout totals summary and read rows
- cost-panel.js: add setDraftUnitCost @action to receive cents from MoneyInput
  @onchange; document that draftUnitCost and all monetary values are in cents;
  clarify startEdit loads unit_cost already in cents from API
- part/form.hbs: add @onchange={{fn (mut @resource.unit_cost)}} and
  @onchange={{fn (mut @resource.msrp)}} to MoneyInput fields
- equipment/form.hbs: add @onchange={{fn (mut @resource.purchase_price)}} to
  MoneyInput field
- work-order-actions.js: fix prepareForSave to not double-convert cents;
  MoneyInput @onchange already emits cents so toCents (x100) was wrong;
  replaced with toIntCents (parseInt only)
- server models: add Money cast for all monetary attributes (cents storage)
- migration: fix parts table monetary columns to BIGINT for cents storage
… Order email

## Import Functionality (Equipment, Parts, Maintenances, Work Orders, Maintenance Schedules)

### Backend
- Added Import classes: EquipmentImport, PartImport, MaintenanceImport, WorkOrderImport, MaintenanceScheduleImport
  - Each implements Laravel Excel's ToModel and WithHeadingRow contracts
  - Resolves related records (vehicles, vendors, equipment) by name/public_id
  - Monetary values expected in cents (integers) matching Money cast storage
- Added createFromImport() static method to all five Eloquent models
- Added import() action to all five HTTP controllers (EquipmentController, PartController, MaintenanceController, WorkOrderController, MaintenanceScheduleController)
- Registered POST import routes for all five resources in routes.php
- POST /work-orders/{id}/send route registered for Send Work Order feature

### Frontend
- Added Import toolbar button (type: magic, icon: upload) to all five index controllers
- Import templates must be uploaded to S3 at:
  flb-assets.s3.ap-southeast-1.amazonaws.com/import-templates/
  - Fleetbase_Equipment_Import_Template.xlsx
  - Fleetbase_Part_Import_Template.xlsx
  - Fleetbase_Maintenance_Import_Template.xlsx
  - Fleetbase_Work_Order_Import_Template.xlsx
  - Fleetbase_Maintenance_Schedule_Import_Template.xlsx

## Send Work Order Email

### Backend
- Added WorkOrderDispatched Mail class (server/src/Mail/WorkOrderDispatched.php)
- Added work-order-dispatched Blade email template
- Added sendEmail() action to WorkOrderController
  - Resolves assignee email, validates vendor has email, sends mail, logs activity

### Frontend
- Added sendEmail @action to WorkOrderActionsService
  - Shows confirmation modal before sending
  - POSTs to work-orders/{id}/send
  - Shows success/error notification
- Added 'Send Work Order to Vendor' row action in work-orders index controller

## Monetary Attribute Type Fix (fleetops-data)
- Note: @attr('string') fix for monetary fields is in fleetops-data repo (separate commit)
…se/fleetops into feat/complete-maintenance-module
1. Monetary getters (laborCost, tax, partsCost, totalCost, lineTotal,
   draftLineTotal) now wrap values with parseInt(numbersOnly(...)) via a
   _toCents() helper. Required because monetary attrs are @attr('string')
   on the Ember model. Without this, string arithmetic produced NaN.

2. Line item mutations (add, edit, remove) are now purely in-memory.
   Previous implementation made individual API requests to
   fleet-ops/maintenances/{id}/line-items which was broken for new
   unsaved records and used the wrong URI prefix. Now each mutation
   writes a new array back onto @resource.line_items via _commitItems()
   and recomputes parts_cost and total_cost locally. The parent form
   save() call persists everything in a single request for both
   create and edit flows.

3. Removed ember-concurrency tasks from addLineItem, saveEdit,
   removeLineItem - now plain @actions. Updated HBS to use plain
   fn/onClick instead of perform for the affected buttons.

4. Removed @service fetch injection (no longer needed).
… details

- Created modals/send-work-order.hbs — a component-based confirmation modal
  that displays:
    - Work order subject (or target_name fallback), public_id, status, and
      due date so the user can confirm they have the correct work order
    - Vendor card showing name, email, and phone so the user knows exactly
      who the email will be sent to
    - Amber warning when no vendor is assigned
    - Red error banner when the vendor has no email address on file, with
      the Send button disabled to prevent a failed send

- Created modals/send-work-order.js — minimal Glimmer component class

- Updated work-order-actions.js sendEmail action:
    - Replaced modalsManager.confirm() with modalsManager.show() using the
      new component-based modal
    - Resolves vendorName, vendorEmail, vendorPhone from workOrder.assignee
      with fallback to assignee_name for display-only contexts
    - Disables the accept button when vendorEmail is absent
    - Runs the POST request inside modal.startLoading()/modal.done() for
      proper loading state feedback

- Updated work-orders/index/details.js:
    - Added sendEmail @action that delegates to workOrderActions.sendEmail
    - Added 'Send to Vendor' button (paper-plane icon) as the first
      actionButton in the details panel toolbar
… on boot

- Removed 'slug' from $fillable — the work_orders table has no slug column,
  causing the SQLSTATE[42S22] column not found error on insert
- Added boot() method with a static::creating() hook that generates a unique
  WO-XXXXXXXX code (8 random uppercase chars) when no code is provided
- Added Illuminate\Support\Str import for Str::random()
…and maintenances

- Add public_id column directly into create_equipments_table, create_parts_table,
  create_work_orders_table, and create_maintenances_table migrations so the column
  is always present on fresh installs (HasPublicId::generatePublicId queries this
  column before insert; missing column caused SQLSTATE 42S22 errors)
- The existing add_public_id_to_maintenance_tables migration already has
  Schema::hasColumn guards so it remains safe to run on both fresh and existing DBs
- Remove photo_uuid from Equipment and Part fillable arrays (no such column exists
  in either table; caused SQLSTATE 42S22 on insert)
- Remove slug from Maintenance fillable (no slug column in maintenances table)
- Change unit_cost/msrp in create_parts_table migration from decimal(12,2) to
  bigInteger (cents) to match the Money cast and monetary storage standard
- Guard fix_monetary_columns_in_parts_table migration with column type check so
  it is idempotent on fresh installs that already have bigInteger columns
- Revert the incorrect public_id additions to the original create_* migrations.
  Those migrations have already shipped in previous releases and would never re-run
  on existing installs. The existing add_public_id_to_maintenance_tables migration
  (with Schema::hasColumn guards) is the correct mechanism for existing deployments.

- Add new migration 2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables
  which adds the photo_uuid FK column to equipments and parts tables. Both models
  have a photo() BelongsTo relationship and getPhotoUrlAttribute() accessor but the
  backing column was missing from the original create migrations. The migration is
  guarded with Schema::hasTable and Schema::hasColumn checks so it is safe to run
  on both fresh and existing databases.

- Restore photo_uuid to Equipment and Part $fillable arrays now that the column
  will exist after the migration runs.
roncodes and others added 28 commits April 13, 2026 15:33
… labels, single expand/collapse toggle, default greedy engine

- Add RouteSequencingEngine: optimize_routes mode now groups orders by
  already-assigned vehicle and sequences stops using nearest-neighbour TSP
  with pickup-before-dropoff precedence constraints, instead of re-running
  the allocation engine
- OrchestrationController: optimize_routes mode uses RouteSequencingEngine
  instead of allocate() so vehicle assignments are preserved
- orchestrator-workbench.js: add getStopLabel() action returning A/B/C...
  letter labels matching the map markers
- plan-viewer.hbs: stop rows now show coloured letter badges (A, B, C...)
  matching the map markers instead of plain sequence numbers
- plan-viewer.js/hbs: replace two expand/collapse buttons with a single
  toggle link that shows 'Expand all' or 'Collapse all' based on state
- phase-builder.js: default engine changed from 'vroom' to 'greedy'
…ut DB commit

- Frontend: pass prior_assignments (current proposedPlan) in every phase
  run request so the server can use them for phase-aware resolution
- Server: accept prior_assignments keyed by order_id; for assign_drivers
  mode, use prior phase vehicle assignments to find eligible orders even
  before the plan is committed to the DB; augment Order models with the
  prior-phase vehicle_assigned_uuid so DriverAssignmentEngine can group
  correctly; for assign_vehicles mode, exclude orders already assigned in
  a prior uncommitted phase to prevent double-assignment
…mode

DriverAssignmentEngine:
- Change require_active_shift default to false — drivers are available
  by default unless explicitly restricted
- Remove hard online + vehicle_uuid=null filters; treat online status
  and active shift as soft scoring bonuses instead of hard gates
- Drivers with NO schedule items are always available; only drivers
  with a schedule but no active shift are excluded when
  require_active_shift is true
- Add standalone mode: when orders have no vehicle_assigned_uuid, assign
  each order to the nearest available vehicle + best matching driver pair
  in a single pass (no prior Assign Vehicles phase required)
- Add online bonus (+30) and proximity-aware vehicle selection in
  standalone mode

OrchestrationController:
- assign_drivers standalone: when no prior_assignments and no DB vehicle
  assignments exist, use all selected orders (not just vehicle-assigned
  ones) so the engine can run in standalone mode
location is a spatial Point cast attribute, not a relationship —
->with(['location']) throws 'undefined relationship' error. Removed
from the with() call; ->location is accessed directly as a
model attribute.
…ver relation

location is a spatial Point cast on Driver, not a relationship.
Removed from ->with(['location', 'scheduleItems']) in the prior-phase
vehicle resolution query.
location is a spatial cast attribute on Vehicle and Driver models,
NOT a relationship — it cannot be used in ->load() or ->with().

Replaced:
  $orders->load(['vehicle.driver.location', 'vehicle.location'])
With:
  $orders->load(['vehicle', 'vehicle.driver'])

The RouteSequencingEngine accesses $vehicle->location and
$driver->location directly as cast attributes, which works correctly
without eager-loading.
…+dropoff, per-vehicle routes, timeline, marker labels

Issue 1 + 4 — Group by driver (not just vehicle) when assign_drivers phase ran:
- Add @Tracked ranPhaseTypes Set to track which phase modes have been executed
- Populate ranPhaseTypes in _runSinglePhase, reset on discardPlan/clearRunError
- Add hasDriverPhase getter (true when assign_drivers has run)
- Pass @hasDriverPhase to PlanViewer component
- plan-viewer.hbs: show driver icon + driver name as primary label on route cards
  when hasDriverPhase is true; vehicle becomes the secondary sub-label
- plan-viewer.js calendarResources: use driver name as resource row title when
  hasDriverPhase is true
- plan-viewer.js renderResourceLabel: swap primary/secondary labels based on
  hasDriverPhase so timeline rows show driver first

Issue 2 — Show pickup AND dropoff in route stop list (not just dropoff):
- plan-viewer.hbs: render 'pickup → dropoff' address line when pickup.address
  is present; fall back to dropoff-only for delivery-only orders

Issue 3 — Draw one route polyline per vehicle group (not one merged route):
- Replace leaflet-routing-machine addRoutingControl() loop with direct OSRM
  route/v1 HTTP calls per group (fixes the single-control-per-map limitation
  documented at perliedman/leaflet-routing-machine#219)
- Import polyline from @fleetbase/ember-core/utils/polyline and
  getRoutingHost from @fleetbase/ember-core/utils/get-routing-host
- Decode OSRM polyline geometry and draw each group as L.polyline with the
  group's routeColor; fit map bounds to all drawn routes after rendering
- _clearRoutingControls now removes plain Leaflet layers (not routing controls)

Issue 5 — Match map marker letter labels to route list sequence labels:
- planByVehicle getter now annotates each order item with _labelledStops:
  an array of stops where label = getStopLabel(orderIdx) for single-stop
  orders, or getStopLabel(orderIdx) + stopNumber for multi-stop orders
  (e.g. A, B, C for single-stop; A1/A2, B1/B2 for pickup+dropoff)
- orchestrator-workbench.hbs map markers now iterate item._labelledStops
  instead of calling getOrderStops — labels now always match the route list
…ne rendering

Issue 1 (driver grouping):
- Root cause: optimize_routes called $orders->load(['vehicle','vehicle.driver'])
  which reloaded from DB and OVERWROTE the in-memory setRelation('driver') set
  by the augmentation loop, losing the uncommitted driver assignment.
- Fix: replace ->load() with a per-order selective load that only fetches from
  DB when the vehicle relation is not already loaded (standalone mode).
  Multi-phase runs preserve the prior-phase driver via the augmentation loop.

Issue 2 (only 2 of 4 polylines drawn):
- Root cause: _placeCoords required loc.type === 'Point' (strict GeoJSON check)
  but LaravelMysqlSpatial SpatialExpression may serialize without the type field.
  Also used truthy check (if lat && lng) which fails for coordinate 0.
- Fix: remove type requirement, check only for coordinates array; use isFinite()
  instead of truthy check; add fallbacks for flat lat/lng and array formats.

Issue 3 (only dropoff shown in route stop list):
- Root cause: plan-viewer.hbs used item.order.payload.pickup.address directly,
  which fails when the location format is not parsed by _placeCoords.
- Fix: use item._firstStop.address / item._lastStop.address (pre-computed by
  _getOrderStops in planByVehicle getter) so address display is format-agnostic
  and consistent with the map markers.
- Added _firstStop/_lastStop convenience properties to annotated order items
  to avoid needing a sub/minus template helper for last-index access.
… maintenance/schedules, and operations/scheduler
…e-only, add constraint detail views, clarify route limits
JS fixes:
- orchestrator-import.js: wire SAMPLE_ROW into downloadTemplate for dynamic XLSX generation (fixes no-unused-vars); wrap nullish coalescing in parens (prettier)
- orchestrator-workbench.js: remove unused routeStyleForStatus import; prefix unused _vehicle param with underscore
- orchestrator/plan-viewer.js: wrap ternary nullish coalescing branches in parens (prettier)
- maintenance/schedules/index.js: wrap nullish coalescing in parens (prettier)

HBS fixes (static → Tailwind, dynamic → html-safe):
- orchestrator-workbench.hbs: h-80 / max-h-96 replace inline height styles
- driver/schedule.hbs: text-[9px] replaces inline font-size:9px
- maintenance/cost-panel.hbs: w-[X%] classes replace col width styles; remove autofocus attribute
- maintenance/details.hbs: w-[X%] classes replace col width styles
- orchestrator/order-pool.hbs: w-8 / class replaces inline width/vertical-align on search icon
- orchestrator/plan-viewer.hbs: html-safe helper for all three dynamic routeColor style bindings
- settings/scheduling.hbs: html-safe helper for dynamic template.color style binding
- operations/scheduler/index.hbs: w-8 / class replaces inline width/vertical-align on search icon
- orchestrator/resource-panel.hbs: div[role=button] → <button type=button> for vehicle and driver cards (require-presentational-children)
- order/schedule-card.hbs: remove redundant (fn @onselect) wrapper → @onselect directly
- templates/application.hbs: template-lint-disable no-potential-path-strings for registry key string
@roncodes roncodes merged commit df5f78b into main Apr 14, 2026
7 checks passed
@roncodes roncodes deleted the dev-v0.6.39 branch April 14, 2026 04:27
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