Add first-class Trailer operations - #309
Open
roncodes wants to merge 23 commits into
Open
Conversation
This was referenced Sep 3, 2026
roncodes
force-pushed
the
feature/first-class-trailers
branch
8 times, most recently
from
September 3, 2026 11:01
e870041 to
c313a8d
Compare
roncodes
force-pushed
the
feature/first-class-trailers
branch
from
September 3, 2026 11:11
c313a8d to
dbb29dc
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #309 +/- ##
=============================================
Coverage 100.00% 100.00%
- Complexity 10007 10274 +267
=============================================
Files 531 545 +14
Lines 38592 39684 +1092
=============================================
+ Hits 38592 39684 +1092
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Public API - Adopt the public relation contract: category_id, vendor_id, warranty_id, photo_id and vehicle_id are always present; category, vendor, warranty, photo and positions expand through with/expand like Vehicles. - Strip device and event UUIDs from the public telematics snapshot. - Return 409 responses instead of aborting inside transactions for towing conflicts; resolve vehicles by public or internal id; fall back to the container request in find(); ignore null-island track observations. - Expose towing connections with vehicle_id/trailer_id identifiers and full models for the console. - Expose currently attached trailers on Vehicles via with=trailers. Internal API - Consistent error responses, delete guard for attached trailers (single and bulk), statuses/types endpoints for console filters, resource-wrapped attach/detach responses, and vehicle attach/detach equipment endpoints. - Console filters submit arrays and UUIDs: TrailerFilter accepts both and resolves vehicle/vendor/category identifiers; EquipmentFilter::equipable resolves the asset instead of whereHas() on a morphTo relation, which silently matched nothing. - Always embed connections/devices/equipments for the console so Ember Data stops warning about missing embedded relationships. - Share status/type/ownership constants between validation, import and the model; make the test fixtures re-boot models after replacing the event dispatcher so hooks survive cross-file runs.
- Rebuild the trailer form with grouped sections, localized labels, placeholders and help text, unit-aware dimension/weight/temperature fields driven by the measurement system, select controls for type, status, coupling, brakes, ownership and odometer unit, money/date/currency inputs, and conditional reefer and lease sections. - Rebuild the details overview (identity, towing connection with attach/ detach, telematics, dimensions, running gear, refrigeration, ownership, notes/timestamps) and add dedicated Towing history, Equipment, Schedules, Work orders and Maintenance tabs with tables, empty states and actions. - Rebuild the index with localized enum cells (new cell/translated-value), towing vehicle link, multi-option/date/model filters mapped to backend params, bulk delete, and strip empty query params before querying. - Rework trailer-actions (panel/modal APIs, attach/detach vehicle with towing position, device and equipment attach/detach with confirmations, locate, maintenance shortcuts) and add attach trailer/equipment actions to vehicle-actions with matching Vehicle detail tabs. - Make Device::Manager target the resource's own endpoint so trailer device attachment no longer posts to the vehicle route. - Add the missing app/ re-exports for trailer components, controllers, routes and templates; modal components no longer import a non-existent ember-core base. - Localize every trailer string in all nine locales (translated, with placeholder parity), plus vehicle attach-trailer/equipment strings.
…ole flows - Assert the rendered Resources menu places Trailers directly after Vehicles (stubbing the console-only universe services the dummy app lacks) instead of inspecting component source. - Cover store.query/queryRecord normalization of the trailers/trailer envelopes, including the empty collection. - Cover trailer-actions defaults, panel tabs, and the attach/detach requests; the details controller tabs and attach/detach actions; the index route query params against the controller columns; the trailer form's placeholders, unit labels and conditional sections; and the translated-value cell. - Provide an inert stand-in for the console extensions module so the dummy app can boot the universe services.
…elds The public resource contract test inspects the resource source for $this->when(Http::isInternalRequest(), ...) on every uuid-bearing field; caching the check in a local broke that inspection.
- Style the attached/detached, in-use, out-of-service and retired badges, which ember-ui's palette does not define. - Rebuild the trailer card body as an image stage plus a padded summary strip (status, connectivity, towing vehicle link) instead of badges on the raw body. - Make the details tab content the scroll container (tab-navigation-fill) so the last overview sections are reachable; apply the same to Vehicles. - Give route-backed detail tabs stable ids so TabNavigation can compute its More menu instead of clipping the trailing tabs. - Introduce a shared AttachmentList component plus Equipment and Trailer pills, and use them for the devices, equipment, vehicle trailers and towing history tabs so they share one header, row and empty-state structure. Towing history becomes a compact timeline rather than a seven-column table. - Ship an inline trailer placeholder image (overridable through defaultValues.trailerImage) instead of falling back to the vehicle photo. - Translate the new strings in all locales; add attachment-list and placeholder tests.
The live vehicles feed eager-loads each vehicle's current trailers next to its devices, and the index resource emits a compact trailers list. Both map providers render Trailers and Devices cells with an online dot per row: the Leaflet marker popup/tooltip templates and the Google info-window builder, which escapes names before injecting them as HTML. Covered by resource, live endpoint and popover-builder tests.
Share one asset-card structure between vehicles and trailers: an image stage with the resource placeholder, a padded summary strip with status and online/offline badges plus the assigned-driver link (or the no-driver state), and footer actions with tooltips and permissions. Adds the common.offline and vehicle.fields.updated-at strings the card uses in every locale.
Cards now share one structure: a title row with the online indicator, an image body on the card background, and a footer with the status badge beside the actions followed by meta rows that style linked records the same way. The vehicle card lists the assigned driver and the trailers it is towing; the trailer card lists the towing vehicle and connectivity; the driver card links its assigned vehicle. The internal vehicle payload carries a compact list of current trailers (shared TrailerSummary helper, also used by the live map resource) and the vehicles index requests it. Adds the shared common strings in every locale.
Adds the table/grid layout switch (remembered per listing) and card components for vendors and contacts, reusing the shared resource-card structure. Customers reuse the contact card through the customer actions service.
The repository ignores `vendor` directories for Composer output, which also hid the new Vendor::Card files from the grid-view commit.
The driver badge rendered its raw status value through the block form, which skips the badge's humanizer. Vehicle and trailer images now cover the body like the driver photo instead of sitting padded inside it.
Attaching a device that is already installed on another vehicle or trailer silently moved it, so an operator could take a tracker off one asset with no feedback (fleetops#177). Device::attachTo now throws DeviceAlreadyAttachedException and every attach endpoint (internal device, vehicle and trailer controllers, public device API) answers 409 with the asset the device is still attached to. Re-attaching to the same asset stays idempotent.
Vehicles: filter by one or more drivers, trailers or devices (new filter/multi-model component over ModelSelectMultiple), by whether a trailer, driver or device is attached (with the inverse), and by several internal ids at once; the vendor placeholder resolves through the common namespace. Trailers: filter by devices; the refrigerated checkbox drops its redundant group label and moves to the end of the panel. Orders: the internal id filter is a tag input so a batch of scanned or pasted ids matches in one query (fleetbase#576). Backend filters accept the comma-separated lists and relation presence flags, with contract tests.
The small form-input height capped the multiple-select trigger at 34px with an internal scroller, so the placeholder was clipped before anything was chosen. The trigger now starts at the input height, wraps its chips and grows with them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Trailer becomes a first-class Fleet-Ops resource: its own navigation entry directly below Vehicles, list/grid/details/create/edit flows, effective-dated towing connections with Vehicles, polymorphic Device and Equipment attachment, telematics ingestion, maintenance integration, import/export, search, reporting, AI query resources, permissions, and full localization in all nine locales.
Architecture
TrailerextendsAsseton the sharedassetstable with anasset_class = trailerglobal scope, its owntrailer_public-id namespace and atrailerpayload identity, so every internal envelope is{ "trailers": [...], "meta": {...} }/{ "trailer": {...} }and neverassets. Shared status, type and ownership constants back validation, import and the console.asset_connectionsstores effective-dated connections (connectorVehicle,connectedTrailer, towing position, source, audit). Uniqueactive_connected_uuid/active_connector_positioncolumns make single-attachment and position rules database-enforced; attach and detach are idempotent and company-scoped; attaching to another vehicle or an occupied position answers409.Device::attachTo()and the Equipment assignment acceptfleet-ops:trailer;device_installationskeeps installation history. Trailer telemetry flows throughTelematicService(device events, with stale-event rejection) and the publictrackendpoint (stale and null-island observations acknowledged but ignored), persists real spatial positions, and broadcaststrailer.location_changed./v1/trailersfollows the Vehicle conventions from Expand public Fleet, Vehicle, and Driver API contracts #311: additive relation identifiers (category_id,vendor_id,warranty_id,photo_id,vehicle_id),with/expandexpansions forcategory,vendor,warranty,photo,positions; towing state, devices and equipment are part of the base representation; no private identifiers anywhere (the publictelematicssnapshot drops device/event UUIDs).GET /v1/vehicles/{id}/trailersandGET /v1/vehicles/{id}?with=trailersexpose the vehicle side.fleetbaseRoutes('trailers')plus attach/detach vehicle, device and equipment, statuses/types, export and import. Deleting an attached trailer is refused (single and bulk); vehicles gain attach/detach equipment endpoints.Migrations
assets: nullable uniquepublic_id(historical Asset rows are not rewritten),asset_class,photo_uuid,online,last_online_at,telematics,purchased_at, dimension/weight/running-gear/reefer columns and a company/class/status index.asset_connectionsanddevice_installationstables.Review fixes on top of the original branch
app.lookup('service:intl'); the Trailer sidebar item renders directly below Vehicles after priority sorting (asserted from the rendered menu).attachment_state(missing Ember attribute), the Devices tab posted to the vehicle route,EquipmentFilter::equipableusedwhereHason amorphTorelation and matched nothing, modal/tab components lacked theapp/re-exports the host needs, and the modal classes imported a non-existent ember-core base.abort()HTML, relation filters ignored console UUIDs and multi-option arrays, internal attach responses leaked class-name morph types, the public snapshot leaked device UUIDs, and the post-save transition was aborted.UI polish round (owner review feedback)
attached,detached,in_use,out_of_serviceandretiredhad no entry in ember-ui's badge palette and rendered unstyled. They are now styled in the engine stylesheet with the same 800/700/100 convention.TabNavigationnow uses thetab-navigation-filllayout so the tab content is the scroll container and the last sections (Dimensions & capacity onwards) are reachable. Applied to Vehicles too, which had the same clipping.ids so ember-ui's overflow bookkeeping can compute the "More" menu; the root cause (identity keyed on a missingid) is fixed upstream in fix(tab-navigation): overflow menu for route-backed tabs without ids ember-ui#171.AttachmentListcomponent (header with description and primary action, uniform rows with a pill, status badge and row actions, one empty state). NewEquipment::PillandTrailer::PillcomplementDevice::Pill. Towing history is a compact timeline (vehicle pill, connected/disconnected rows, position, duration, source, state badge) instead of a seven-column table.trailer-placeholder-imagehelper); a console can override it throughdefaultValues.trailerImage.trailerslist, and both map providers (Leaflet template and Google info window builder) show Trailers and Devices cells with an online dot per row. Names are HTML-escaped in the builder.fleetops-card-meta*). The vehicle card lists the assigned driver and the trailers it tows; the trailer card lists the towing vehicle and connectivity; the driver card links its assigned vehicle. The internal vehicle payload carries a compacttrailerslist (sharedTrailerSummaryhelper, also used by the live-map resource) and the vehicles index requestscurrentTrailers.Vendor::CardandContact::Card(customers reuse the contact card through the customer actions service).filter/multi-model), "trailer attached / driver assigned / device attached" yes-no filters and a batch internal-id tag input; trailer index gains a devices filter and the refrigerated checkbox loses its redundant label and moves last; orders internal-id filter is a tag input for batch scanning (Feature Request: Support multi-Internal ID filtering in orders table view fleetbase#576). Backend filters accept comma-separated lists and presence flags.Device::attachTothrowsDeviceAlreadyAttachedExceptionwhen the device is installed on another asset; the internal device, vehicle and trailer attach endpoints and the public device API answer 409 naming the asset. The attach modal already lists only unattached devices, so the UI and API now agree.mainat v0.6.64 (nested-resource expansion fix); trailer and vehicle contract suites re-run green after the merge.Verified in the in-app browser pane against the local stack: attached badge colour, card view, details scroll to the last section, More menu listing Schedules/Work orders/Maintenance, Devices/Equipment/Towing history tabs with and without rows, Vehicle Trailers tab, and the CEN-01 popover listing its trailer and device; vehicles, drivers, trailers, vendors, contacts and customers grid views with the unified cards.
Testing
composer test:lintclean for the touched files; Trailer suites (TrailerControllerLifecycleTest,TrailerSupportCoverageTest,TrailerTest,TelematicServiceTelemetryTest) pass; a coverage slice reports 100% line coverage for every changed server file.composer test:unit(the CI runner, every test file in its own process) passes: 441 files, exit 0. Repository-widephp-cs-fixer --dry-runonly reports pre-existing files outside this change.pnpm lint:js,pnpm lint:hbs,pnpm lint:css,pnpm lint:intlandpnpm buildpass. New unit/integration tests cover rendered navigation order,store.query('trailer')normalization, trailer-actions, the details controller, the index route params, the form and the translated-value cell. They were validated only partially here: the engine's dummy app cannot boot ember-core's universe services because ember-core does not declare itstracked-built-insdependency (see below), and the browser test runner was stopped at the user's request.Manual verification (local stack, console + API)
Extension startup clean; Trailers directly below Vehicles; empty index state; create a reefer via the UI (type selection reveals the reefer section); index with localized type/status/attachment/connectivity badges; details overview and every tab; attach to a vehicle from the overview (modal with towing position) and see it from the Vehicle's Trailers tab; attach and detach equipment and a device from the tabs; edit and save; public API lifecycle via curl (create flatbed, expansions, position conflict 409, re-attach idempotent, attach elsewhere 409, delete-while-attached 409, vehicle trailer list,
with=trailers, track, stale and null-island observations, connections, detach twice); device-event telemetry ingestion updates the trailer. No feature-related console errors.Not re-verified after the last two front-end fixes (post-save transition, image fallback), and not exercised: delete from the UI, index filtering/sorting from the UI, dark mode and narrow viewports. See the final review notes.
Known limitations
tracked-built-inswithout declaring it; anysetupTestin this engine's dummy app fails until that is fixed upstream (CI does not runember testfor this package).common.search-input,select-resource-filter-by) surface on the index as they do on Vehicles.Related PRs
0590548)