@fleetbase/fleetops-engine 0.6.64 → 0.6.65
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/RELEASE.md +7 -5
- package/addon/components/attachment-list.hbs +65 -0
- package/addon/components/attachment-list.js +8 -0
- package/addon/components/cell/attached-vehicle.hbs +1 -1
- package/addon/components/cell/attached-vehicle.js +3 -3
- package/addon/components/cell/driver-identity.js +4 -4
- package/addon/components/cell/driver-name.hbs +1 -1
- package/addon/components/cell/translated-value.hbs +9 -0
- package/addon/components/cell/translated-value.js +57 -0
- package/addon/components/contact/card.hbs +58 -0
- package/addon/components/contact/card.js +19 -0
- package/addon/components/contact/form.hbs +1 -1
- package/addon/components/contact/panel-header.hbs +1 -1
- package/addon/components/customer/form.hbs +2 -2
- package/addon/components/device/manager.hbs +26 -28
- package/addon/components/device/manager.js +28 -2
- package/addon/components/device/panel-tabs/vehicle.hbs +13 -13
- package/addon/components/device/panel-tabs/vehicle.js +8 -0
- package/addon/components/driver/card.hbs +24 -13
- package/addon/components/driver/card.js +20 -6
- package/addon/components/driver/form.hbs +3 -3
- package/addon/components/driver/panel-header.hbs +2 -2
- package/addon/components/driver/pill.hbs +2 -2
- package/addon/components/equipment/form.js +11 -4
- package/addon/components/equipment/pill.hbs +20 -0
- package/addon/components/equipment/pill.js +3 -0
- package/addon/components/filter/multi-model.hbs +19 -0
- package/addon/components/filter/multi-model.js +90 -0
- package/addon/components/fleet/driver-listing.hbs +1 -1
- package/addon/components/fleet/vehicle-listing.hbs +1 -1
- package/addon/components/fleet-panel/driver-listing.hbs +1 -1
- package/addon/components/fleet-panel/vehicle-listing.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +9 -7
- package/addon/components/maintenance/form.js +12 -4
- package/addon/components/maintenance-schedule/form.js +13 -7
- package/addon/components/map/leaflet-live-map.hbs +52 -0
- package/addon/components/map/order-list-overlay/order.hbs +4 -4
- package/addon/components/modals/attach-equipment.hbs +22 -0
- package/addon/components/modals/attach-equipment.js +3 -0
- package/addon/components/modals/attach-telematic-device.hbs +13 -8
- package/addon/components/modals/attach-telematic-device.js +11 -1
- package/addon/components/modals/attach-trailer.hbs +30 -0
- package/addon/components/modals/attach-trailer.js +3 -0
- package/addon/components/modals/contact-form.hbs +2 -2
- package/addon/components/modals/convert-customer-to-vendor.hbs +1 -1
- package/addon/components/modals/driver-form.hbs +2 -2
- package/addon/components/modals/select-trailer.hbs +35 -0
- package/addon/components/modals/select-trailer.js +3 -0
- package/addon/components/modals/vehicle-form.hbs +2 -2
- package/addon/components/orchestrator/resource-panel.hbs +2 -2
- package/addon/components/order/customer-avatar-stack.hbs +2 -2
- package/addon/components/order/details/detail.hbs +4 -4
- package/addon/components/order/details/payload.hbs +2 -2
- package/addon/components/order/kanban-card.hbs +4 -4
- package/addon/components/order-list-overlay/order.hbs +4 -4
- package/addon/components/trailer/card.hbs +68 -0
- package/addon/components/trailer/card.js +22 -0
- package/addon/components/trailer/details/connections.hbs +57 -0
- package/addon/components/trailer/details/connections.js +59 -0
- package/addon/components/trailer/details/equipment.hbs +25 -0
- package/addon/components/trailer/details/equipment.js +52 -0
- package/addon/components/trailer/details/maintenance-history.hbs +47 -0
- package/addon/components/trailer/details/maintenance-history.js +40 -0
- package/addon/components/trailer/details/schedules.hbs +45 -0
- package/addon/components/trailer/details/schedules.js +40 -0
- package/addon/components/trailer/details/work-orders.hbs +47 -0
- package/addon/components/trailer/details/work-orders.js +40 -0
- package/addon/components/trailer/details.hbs +399 -0
- package/addon/components/trailer/details.js +118 -0
- package/addon/components/trailer/form.hbs +447 -0
- package/addon/components/trailer/form.js +165 -0
- package/addon/components/trailer/panel-header.hbs +45 -0
- package/addon/components/trailer/panel-header.js +3 -0
- package/addon/components/trailer/pill.hbs +21 -0
- package/addon/components/trailer/pill.js +3 -0
- package/addon/components/vehicle/card.hbs +42 -11
- package/addon/components/vehicle/card.js +31 -0
- package/addon/components/vehicle/details/equipment.hbs +25 -0
- package/addon/components/vehicle/details/equipment.js +55 -0
- package/addon/components/vehicle/details/trailers.hbs +35 -0
- package/addon/components/vehicle/details/trailers.js +55 -0
- package/addon/components/vehicle/form.hbs +2 -2
- package/addon/components/vehicle/panel-header.hbs +2 -2
- package/addon/components/vehicle/pill.hbs +2 -2
- package/addon/components/vendor/card.hbs +58 -0
- package/addon/components/vendor/card.js +6 -0
- package/addon/components/vendor/panel-header.hbs +2 -2
- package/addon/components/vendor/personnel-manager.hbs +2 -2
- package/addon/components/work-order/form.js +12 -4
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +11 -0
- package/addon/controllers/connectivity/devices/index/details.js +3 -3
- package/addon/controllers/connectivity/devices/index.js +1 -0
- package/addon/controllers/connectivity/telematics/details/attachments.js +28 -47
- package/addon/controllers/connectivity/telematics/details/devices.js +1 -0
- package/addon/controllers/management/contacts/customers.js +4 -0
- package/addon/controllers/management/contacts/index.js +32 -0
- package/addon/controllers/management/trailers/index/details.js +119 -0
- package/addon/controllers/management/trailers/index/edit.js +67 -0
- package/addon/controllers/management/trailers/index/new.js +43 -0
- package/addon/controllers/management/trailers/index.js +599 -0
- package/addon/controllers/management/vehicles/index/details.js +16 -0
- package/addon/controllers/management/vehicles/index.js +78 -4
- package/addon/controllers/management/vendors/index.js +32 -0
- package/addon/controllers/operations/orders/index.js +3 -1
- package/addon/controllers/settings/custom-fields.js +8 -0
- package/addon/extension.js +7 -0
- package/addon/helpers/placeholder-image.js +6 -0
- package/addon/helpers/resource-image.js +10 -0
- package/addon/helpers/trailer-placeholder-image.js +6 -0
- package/addon/routes/management/trailers/index/details/connections.js +2 -0
- package/addon/routes/management/trailers/index/details/devices.js +2 -0
- package/addon/routes/management/trailers/index/details/equipment.js +2 -0
- package/addon/routes/management/trailers/index/details/index.js +2 -0
- package/addon/routes/management/trailers/index/details/maintenance-history.js +2 -0
- package/addon/routes/management/trailers/index/details/positions.js +2 -0
- package/addon/routes/management/trailers/index/details/schedules.js +2 -0
- package/addon/routes/management/trailers/index/details/work-orders.js +2 -0
- package/addon/routes/management/trailers/index/details.js +30 -0
- package/addon/routes/management/trailers/index/edit.js +23 -0
- package/addon/routes/management/trailers/index/new.js +14 -0
- package/addon/routes/management/trailers/index.js +42 -0
- package/addon/routes/management/vehicles/index/details/trailers.js +2 -0
- package/addon/routes/management/vehicles/index.js +7 -1
- package/addon/routes.js +17 -0
- package/addon/services/device-actions.js +26 -16
- package/addon/services/trailer-actions.js +394 -0
- package/addon/services/vehicle-actions.js +111 -0
- package/addon/styles/fleetops-engine.css +349 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +19 -19
- package/addon/templates/connectivity/telematics/details/attachments.hbs +15 -15
- package/addon/templates/management/contacts/customers.hbs +48 -29
- package/addon/templates/management/contacts/index.hbs +52 -33
- package/addon/templates/management/trailers/index/details/connections.hbs +1 -0
- package/addon/templates/management/trailers/index/details/devices.hbs +1 -0
- package/addon/templates/management/trailers/index/details/equipment.hbs +1 -0
- package/addon/templates/management/trailers/index/details/index.hbs +1 -0
- package/addon/templates/management/trailers/index/details/maintenance-history.hbs +1 -0
- package/addon/templates/management/trailers/index/details/positions.hbs +1 -0
- package/addon/templates/management/trailers/index/details/schedules.hbs +1 -0
- package/addon/templates/management/trailers/index/details/work-orders.hbs +1 -0
- package/addon/templates/management/trailers/index/details.hbs +15 -0
- package/addon/templates/management/trailers/index/edit.hbs +12 -0
- package/addon/templates/management/trailers/index/new.hbs +11 -0
- package/addon/templates/management/trailers/index.hbs +67 -0
- package/addon/templates/management/vehicles/index/details/equipment.hbs +1 -1
- package/addon/templates/management/vehicles/index/details/trailers.hbs +1 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -1
- package/addon/templates/management/vendors/index.hbs +66 -32
- package/addon/utils/device-table-columns.js +10 -6
- package/addon/utils/live-map-card-content.js +56 -0
- package/addon/utils/placeholder-images.js +71 -0
- package/addon/utils/trailer-placeholder-image.js +11 -0
- package/app/components/attachment-list.js +1 -0
- package/app/components/cell/translated-value.js +1 -0
- package/app/components/contact/card.js +1 -0
- package/app/components/equipment/pill.js +1 -0
- package/app/components/filter/multi-model.js +1 -0
- package/app/components/modals/attach-equipment.js +1 -0
- package/app/components/modals/attach-trailer.js +1 -0
- package/app/components/modals/select-trailer.js +1 -0
- package/app/components/trailer/card.js +1 -0
- package/app/components/trailer/details/connections.js +1 -0
- package/app/components/trailer/details/equipment.js +1 -0
- package/app/components/trailer/details/maintenance-history.js +1 -0
- package/app/components/trailer/details/schedules.js +1 -0
- package/app/components/trailer/details/work-orders.js +1 -0
- package/app/components/trailer/details.js +1 -0
- package/app/components/trailer/form.js +1 -0
- package/app/components/trailer/panel-header.js +1 -0
- package/app/components/trailer/pill.js +1 -0
- package/app/components/vehicle/details/equipment.js +1 -0
- package/app/components/vehicle/details/trailers.js +1 -0
- package/app/components/vendor/card.js +1 -0
- package/app/controllers/management/trailers/index/details.js +1 -0
- package/app/controllers/management/trailers/index/edit.js +1 -0
- package/app/controllers/management/trailers/index/new.js +1 -0
- package/app/controllers/management/trailers/index.js +1 -0
- package/app/helpers/placeholder-image.js +1 -0
- package/app/helpers/resource-image.js +1 -0
- package/app/helpers/trailer-placeholder-image.js +1 -0
- package/app/routes/management/trailers/index/details/connections.js +1 -0
- package/app/routes/management/trailers/index/details/devices.js +1 -0
- package/app/routes/management/trailers/index/details/equipment.js +1 -0
- package/app/routes/management/trailers/index/details/index.js +1 -0
- package/app/routes/management/trailers/index/details/maintenance-history.js +1 -0
- package/app/routes/management/trailers/index/details/positions.js +1 -0
- package/app/routes/management/trailers/index/details/schedules.js +1 -0
- package/app/routes/management/trailers/index/details/work-orders.js +1 -0
- package/app/routes/management/trailers/index/details.js +1 -0
- package/app/routes/management/trailers/index/edit.js +1 -0
- package/app/routes/management/trailers/index/new.js +1 -0
- package/app/routes/management/trailers/index.js +1 -0
- package/app/routes/management/vehicles/index/details/trailers.js +1 -0
- package/app/services/trailer-actions.js +1 -0
- package/app/templates/management/trailers/index/details/connections.js +1 -0
- package/app/templates/management/trailers/index/details/devices.js +1 -0
- package/app/templates/management/trailers/index/details/equipment.js +1 -0
- package/app/templates/management/trailers/index/details/index.js +1 -0
- package/app/templates/management/trailers/index/details/maintenance-history.js +1 -0
- package/app/templates/management/trailers/index/details/positions.js +1 -0
- package/app/templates/management/trailers/index/details/schedules.js +1 -0
- package/app/templates/management/trailers/index/details/work-orders.js +1 -0
- package/app/templates/management/trailers/index/details.js +1 -0
- package/app/templates/management/trailers/index/edit.js +1 -0
- package/app/templates/management/trailers/index/new.js +1 -0
- package/app/templates/management/trailers/index.js +1 -0
- package/app/templates/management/vehicles/index/details/trailers.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/migrations/2026_09_03_000001_add_first_class_trailer_columns_to_assets_table.php +58 -0
- package/server/migrations/2026_09_03_000002_create_asset_connections_table.php +43 -0
- package/server/migrations/2026_09_03_000003_create_device_installations_table.php +33 -0
- package/server/src/Auth/Schemas/FleetOps.php +7 -0
- package/server/src/Events/TrailerLocationChanged.php +70 -0
- package/server/src/Exceptions/DeviceAlreadyAttachedException.php +18 -0
- package/server/src/Exports/TrailerExport.php +31 -0
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +8 -2
- package/server/src/Http/Controllers/Api/v1/DeviceController.php +22 -15
- package/server/src/Http/Controllers/Api/v1/EquipmentController.php +47 -0
- package/server/src/Http/Controllers/Api/v1/TrailerController.php +362 -0
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +3 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +16 -0
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +360 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +74 -0
- package/server/src/Http/Filter/EquipmentFilter.php +51 -0
- package/server/src/Http/Filter/OrderFilter.php +15 -0
- package/server/src/Http/Filter/TrailerFilter.php +269 -0
- package/server/src/Http/Filter/VehicleFilter.php +70 -3
- package/server/src/Http/Requests/CreateDeviceRequest.php +1 -1
- package/server/src/Http/Requests/CreateEquipmentRequest.php +1 -1
- package/server/src/Http/Requests/CreateTrailerRequest.php +54 -0
- package/server/src/Http/Requests/UpdateTrailerRequest.php +7 -0
- package/server/src/Http/Resources/v1/AssetConnection.php +90 -0
- package/server/src/Http/Resources/v1/Device.php +3 -1
- package/server/src/Http/Resources/v1/Equipment.php +3 -1
- package/server/src/Http/Resources/v1/Index/Vehicle.php +10 -0
- package/server/src/Http/Resources/v1/Trailer.php +199 -0
- package/server/src/Http/Resources/v1/Vehicle.php +2 -0
- package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +3 -0
- package/server/src/Imports/TrailerImport.php +33 -0
- package/server/src/Models/Asset.php +39 -10
- package/server/src/Models/AssetConnection.php +43 -0
- package/server/src/Models/Device.php +39 -8
- package/server/src/Models/DeviceInstallation.php +27 -0
- package/server/src/Models/Equipment.php +2 -0
- package/server/src/Models/Trailer.php +181 -0
- package/server/src/Models/Vehicle.php +13 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +4 -2
- package/server/src/Support/Ai/FleetOpsAiQueryResources.php +22 -0
- package/server/src/Support/Reporting/FleetOpsReportSchema.php +38 -0
- package/server/src/Support/Telematics/TelematicService.php +35 -0
- package/server/src/Support/TrailerSummary.php +35 -0
- package/server/src/routes.php +29 -0
- package/server/tests/CompactResourceSerializationTest.php +36 -0
- package/server/tests/ControllerFilterContractsTest.php +29 -0
- package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +30 -13
- package/server/tests/Feature/Http/Api/TrailerControllerLifecycleTest.php +640 -0
- package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +20 -5
- package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +19 -0
- package/server/tests/ModelAccessorContractsTest.php +10 -0
- package/server/tests/OrderFilterExecutionTest.php +5 -0
- package/server/tests/PublicApiControllerInputContractsTest.php +11 -0
- package/server/tests/PublicFleetOpsRestEndpointsTest.php +15 -3
- package/server/tests/ReportSchemaContractsTest.php +4 -0
- package/server/tests/TelematicsHardeningTest.php +4 -2
- package/server/tests/TrailerSupportCoverageTest.php +524 -0
- package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +35 -0
- package/server/tests/Unit/Models/EquipmentTest.php +22 -0
- package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +7 -3
- package/server/tests/Unit/Models/TrailerTest.php +204 -0
- package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +93 -0
- package/server/tests/VehicleControllerHelperContractsTest.php +14 -0
- package/translations/ar-ae.yml +396 -0
- package/translations/bg-bg.yaml +424 -0
- package/translations/en-us.yaml +423 -0
- package/translations/es-pa.yaml +425 -0
- package/translations/fr-fr.yaml +424 -0
- package/translations/mn-mn.yaml +424 -0
- package/translations/pt-br.yaml +424 -0
- package/translations/ru-ru.yaml +424 -0
- package/translations/vi-vn.yaml +435 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<?php
|
|
2
2
|
|
|
3
|
+
use Fleetbase\FleetOps\Exceptions\DeviceAlreadyAttachedException;
|
|
3
4
|
use Fleetbase\FleetOps\Http\Controllers\Internal\v1\DeviceController;
|
|
4
5
|
use Fleetbase\FleetOps\Models\Device;
|
|
5
6
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
@@ -39,11 +40,12 @@ class FleetOpsInternalDeviceControllerProbe extends DeviceController
|
|
|
39
40
|
|
|
40
41
|
class FleetOpsInternalDeviceFake extends Device
|
|
41
42
|
{
|
|
42
|
-
public array $attachedTo
|
|
43
|
-
public array $detaches
|
|
44
|
-
public array $loads
|
|
45
|
-
public bool $throwAttach
|
|
46
|
-
public bool $throwDetach
|
|
43
|
+
public array $attachedTo = [];
|
|
44
|
+
public array $detaches = [];
|
|
45
|
+
public array $loads = [];
|
|
46
|
+
public bool $throwAttach = false;
|
|
47
|
+
public bool $throwDetach = false;
|
|
48
|
+
public bool $throwAlreadyAttached = false;
|
|
47
49
|
|
|
48
50
|
public function attachTo(Model $attachable): bool
|
|
49
51
|
{
|
|
@@ -51,6 +53,10 @@ class FleetOpsInternalDeviceFake extends Device
|
|
|
51
53
|
throw new RuntimeException('attach failed');
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
if ($this->throwAlreadyAttached) {
|
|
57
|
+
throw DeviceAlreadyAttachedException::for('Truck 1');
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
$this->attachedTo[] = [$attachable::class, $attachable->uuid];
|
|
55
61
|
$this->forceFill([
|
|
56
62
|
'attachable_type' => $attachable::class,
|
|
@@ -350,6 +356,12 @@ test('internal device controller reports lookup and persistence failures', funct
|
|
|
350
356
|
->attach(new Request(['vehicle' => 'vehicle_public']), 'device_public')
|
|
351
357
|
);
|
|
352
358
|
|
|
359
|
+
$conflictDevice = fleetopsInternalDevice();
|
|
360
|
+
$conflictDevice->throwAlreadyAttached = true;
|
|
361
|
+
$conflictResponse = fleetopsInternalDeviceController($conflictDevice, fleetopsInternalDeviceVehicle())
|
|
362
|
+
->attach(new Request(['vehicle' => 'vehicle_public']), 'device_public');
|
|
363
|
+
$conflict = fleetopsInternalDeviceJson($conflictResponse);
|
|
364
|
+
|
|
353
365
|
$detachFailureDevice = fleetopsInternalDevice();
|
|
354
366
|
$detachFailureDevice->throwDetach = true;
|
|
355
367
|
$detachFailure = fleetopsInternalDeviceJson(
|
|
@@ -365,6 +377,9 @@ test('internal device controller reports lookup and persistence failures', funct
|
|
|
365
377
|
expect($missingDevice['error'])->toBe('Device not found or not available for this organization.')
|
|
366
378
|
->and($missingVehicle['error'])->toBe('Vehicle not found or not available for this organization.')
|
|
367
379
|
->and($attachFailure['error'])->toBe('Unable to attach device to vehicle. Please try again or contact support.')
|
|
380
|
+
// A device installed elsewhere is refused with a conflict, not re-homed or reported as a crash.
|
|
381
|
+
->and($conflict['error'])->toBe('Device is already attached to Truck 1. Detach it before attaching it elsewhere.')
|
|
382
|
+
->and($conflictResponse->getStatusCode())->toBe(409)
|
|
368
383
|
->and($detachFailure['error'])->toBe('Unable to detach device from vehicle. Please try again or contact support.')
|
|
369
384
|
->and($missingDetachDevice['error'])->toBe('Device not found or not available for this organization.');
|
|
370
385
|
});
|
|
@@ -123,6 +123,8 @@ function fleetopsLiveEndpointsBoot(): SQLiteConnection
|
|
|
123
123
|
'users' => ['uuid', 'public_id', 'company_uuid', 'name', 'phone', 'email', 'status', 'type', 'avatar_uuid'],
|
|
124
124
|
'vehicles' => ['uuid', 'public_id', 'company_uuid', 'vendor_uuid', 'photo_uuid', 'name', 'display_name', 'plate_number', 'serial_number', 'fuel_card_number', 'vin', 'vin_data', 'make', 'model', 'year', 'trim', 'class', 'color', 'call_sign', 'specs', 'telematics', 'status', 'online', 'location', 'meta', 'avatar_url', 'internal_id', 'speed', 'heading', 'altitude'],
|
|
125
125
|
'devices' => ['uuid', 'public_id', 'company_uuid', 'device_id', 'device_type', 'device_provider', 'owner_uuid', 'owner_type', 'attachable_uuid', 'attachable_type', 'status'],
|
|
126
|
+
'assets' => ['uuid', 'public_id', 'company_uuid', 'asset_class', 'name', 'display_name', 'type', 'plate_number', 'status', 'last_online_at', 'online'],
|
|
127
|
+
'asset_connections' => ['uuid', 'public_id', 'company_uuid', 'connector_type', 'connector_uuid', 'connected_type', 'connected_uuid', 'active_connected_uuid', 'active_connector_position', 'relationship_type', 'position', 'connected_at', 'disconnected_at', 'source'],
|
|
126
128
|
'vendors' => ['uuid', 'public_id', 'company_uuid', 'name'],
|
|
127
129
|
'files' => ['uuid', 'public_id', 'company_uuid', 'type', 'path', 'disk'],
|
|
128
130
|
'entities' => ['uuid', 'public_id', 'company_uuid', 'payload_uuid', 'name', 'type'],
|
|
@@ -216,6 +218,16 @@ test('drivers and vehicles list located records within viewport bounds', functio
|
|
|
216
218
|
['uuid' => 'driver-2', 'company_uuid' => 'company-1', 'user_uuid' => 'user-1', 'online' => '1', 'location' => null],
|
|
217
219
|
]);
|
|
218
220
|
$connection->table('vehicles')->insert(['uuid' => 'vehicle-1', 'company_uuid' => 'company-1', 'name' => 'Truck', 'location' => 'POINT(1 1)']);
|
|
221
|
+
$connection->table('assets')->insert([
|
|
222
|
+
['uuid' => 'trailer-1', 'company_uuid' => 'company-1', 'asset_class' => 'trailer', 'name' => 'Reefer 12', 'type' => 'reefer', 'plate_number' => 'TRL-12', 'status' => 'in_use'],
|
|
223
|
+
['uuid' => 'trailer-2', 'company_uuid' => 'company-1', 'asset_class' => 'trailer', 'name' => 'Flatbed 3', 'type' => 'flatbed', 'plate_number' => null, 'status' => 'available'],
|
|
224
|
+
]);
|
|
225
|
+
$connection->table('asset_connections')->insert([
|
|
226
|
+
// Active towing connection: appears on the vehicle's map popover.
|
|
227
|
+
['uuid' => 'connection-1', 'company_uuid' => 'company-1', 'connector_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle', 'connector_uuid' => 'vehicle-1', 'connected_type' => 'Fleetbase\\FleetOps\\Models\\Trailer', 'connected_uuid' => 'trailer-1', 'active_connected_uuid' => 'trailer-1', 'relationship_type' => 'towing', 'position' => '1', 'connected_at' => '2026-09-01 08:00:00', 'source' => 'manual'],
|
|
228
|
+
// Ended connection: history only, never surfaced as a current trailer.
|
|
229
|
+
['uuid' => 'connection-2', 'company_uuid' => 'company-1', 'connector_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle', 'connector_uuid' => 'vehicle-1', 'connected_type' => 'Fleetbase\\FleetOps\\Models\\Trailer', 'connected_uuid' => 'trailer-2', 'relationship_type' => 'towing', 'position' => '1', 'connected_at' => '2026-08-01 08:00:00', 'disconnected_at' => '2026-08-02 08:00:00', 'source' => 'manual'],
|
|
230
|
+
]);
|
|
219
231
|
|
|
220
232
|
$drivers = (new LiveController())->drivers(Request::create('/x', 'GET'));
|
|
221
233
|
expect($drivers->count())->toBe(1);
|
|
@@ -228,6 +240,13 @@ test('drivers and vehicles list located records within viewport bounds', functio
|
|
|
228
240
|
|
|
229
241
|
$vehicles = (new LiveController())->vehicles(Request::create('/x', 'GET'));
|
|
230
242
|
expect($vehicles->count())->toBe(1);
|
|
243
|
+
|
|
244
|
+
// The live feed eager-loads the trailers currently coupled to each vehicle so the
|
|
245
|
+
// map popover can list them without a second request per marker.
|
|
246
|
+
$vehicle = $vehicles->collection->first()->resource;
|
|
247
|
+
expect($vehicle->relationLoaded('currentTrailers'))->toBeTrue()
|
|
248
|
+
->and($vehicle->relationLoaded('devices'))->toBeTrue()
|
|
249
|
+
->and($vehicle->currentTrailers->pluck('uuid')->all())->toBe(['trailer-1']);
|
|
231
250
|
expect((new LiveController())->vehicles(Request::create('/x', 'GET', ['bounds' => [0, 0, 1, 1]]))->count())->toBe(0);
|
|
232
251
|
});
|
|
233
252
|
|
|
@@ -21,6 +21,7 @@ if (!function_exists('Fleetbase\FleetOps\Models\activity')) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
use Fleetbase\FleetOps\Exceptions\CustomerUserConflictException;
|
|
24
|
+
use Fleetbase\FleetOps\Exceptions\DeviceAlreadyAttachedException;
|
|
24
25
|
use Fleetbase\FleetOps\Flow\Activity;
|
|
25
26
|
use Fleetbase\FleetOps\Models\Contact;
|
|
26
27
|
use Fleetbase\FleetOps\Models\Device;
|
|
@@ -990,6 +991,15 @@ test('device accessors connection state configuration and command guards are sta
|
|
|
990
991
|
'attachable_uuid' => 'vehicle-uuid',
|
|
991
992
|
]);
|
|
992
993
|
|
|
994
|
+
// An installed device must be detached before it can move to another asset;
|
|
995
|
+
// attaching used to silently re-home it with no feedback to the operator.
|
|
996
|
+
$device->attachable_type = Vehicle::class;
|
|
997
|
+
$device->attachable_uuid = 'other-vehicle-uuid';
|
|
998
|
+
expect(fn () => $device->attachTo($vehicle))->toThrow(DeviceAlreadyAttachedException::class, 'Detach it before attaching it elsewhere.');
|
|
999
|
+
expect(DeviceAlreadyAttachedException::for('Truck 99')->getMessage())->toBe('Device is already attached to Truck 99. Detach it before attaching it elsewhere.')
|
|
1000
|
+
->and(DeviceAlreadyAttachedException::for(null)->getMessage())->toBe('Device is already attached to another asset. Detach it before attaching it elsewhere.')
|
|
1001
|
+
->and($device->updates)->toHaveCount(3);
|
|
1002
|
+
|
|
993
1003
|
$device->attachable_type = Vehicle::class;
|
|
994
1004
|
$device->attachable_uuid = 'vehicle-uuid';
|
|
995
1005
|
|
|
@@ -181,7 +181,12 @@ test('order filter identity and relation filters support uuid and public identif
|
|
|
181
181
|
$filter->driver('driver_public');
|
|
182
182
|
$filter->driverAssigned('driver_public');
|
|
183
183
|
|
|
184
|
+
// Batch label scans arrive as one comma-separated list of internal ids; blanks are ignored.
|
|
185
|
+
$filter->internalId('ORD-1, ORD-2,');
|
|
186
|
+
$filter->internalId(' ');
|
|
187
|
+
|
|
184
188
|
expect($builder->called('whereIn'))->toBeTrue()
|
|
189
|
+
->and($builder->calls)->toContain(['whereIn', 'internal_id', ['ORD-1', 'ORD-2']])
|
|
185
190
|
->and($builder->called('removeWhereFromQuery'))->toBeTrue()
|
|
186
191
|
->and($builder->called('whereHas'))->toBeTrue()
|
|
187
192
|
->and($builder->called('orWhereHas'))->toBeTrue();
|
|
@@ -11,6 +11,7 @@ use Fleetbase\FleetOps\Models\FuelReport;
|
|
|
11
11
|
use Fleetbase\FleetOps\Models\Order;
|
|
12
12
|
use Fleetbase\FleetOps\Models\Part;
|
|
13
13
|
use Fleetbase\FleetOps\Models\Telematic;
|
|
14
|
+
use Fleetbase\FleetOps\Models\Trailer;
|
|
14
15
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
15
16
|
use Fleetbase\FleetOps\Models\Vendor;
|
|
16
17
|
use Fleetbase\FleetOps\Models\Warranty;
|
|
@@ -246,6 +247,9 @@ test('public api resource resolver exposes every supported morph alias', functio
|
|
|
246
247
|
'fleet-ops:vehicle' => Vehicle::class,
|
|
247
248
|
'vehicle' => Vehicle::class,
|
|
248
249
|
Vehicle::class => Vehicle::class,
|
|
250
|
+
'fleet-ops:trailer' => Trailer::class,
|
|
251
|
+
'trailer' => Trailer::class,
|
|
252
|
+
Trailer::class => Trailer::class,
|
|
249
253
|
'fleet-ops:driver' => Driver::class,
|
|
250
254
|
'driver' => Driver::class,
|
|
251
255
|
Driver::class => Driver::class,
|
|
@@ -284,6 +288,13 @@ test('public api resource resolver exposes every supported morph alias', functio
|
|
|
284
288
|
]);
|
|
285
289
|
});
|
|
286
290
|
|
|
291
|
+
test('public api resource resolver rejects unsupported morph aliases', function () {
|
|
292
|
+
$controller = new FleetOpsWorkOrderControllerProbe();
|
|
293
|
+
|
|
294
|
+
expect(fn () => $controller->callHelper('resolveMorph', 'unsupported-resource', 'resource_public'))
|
|
295
|
+
->toThrow(ValidationException::class);
|
|
296
|
+
});
|
|
297
|
+
|
|
287
298
|
test('public api resource resolver detects fillable columns and primary keys', function () {
|
|
288
299
|
$controller = new FleetOpsWorkOrderControllerProbe();
|
|
289
300
|
$model = new FleetOpsPublicApiResolverModelFake();
|
|
@@ -5,18 +5,20 @@ use Fleetbase\FleetOps\Http\Controllers\Api\v1\EquipmentController;
|
|
|
5
5
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\FuelTransactionController;
|
|
6
6
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\PartController;
|
|
7
7
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\SensorController;
|
|
8
|
+
use Fleetbase\FleetOps\Http\Controllers\Api\v1\TrailerController;
|
|
8
9
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\WorkOrderController;
|
|
9
10
|
use Fleetbase\FleetOps\Http\Resources\v1\Device;
|
|
10
11
|
use Fleetbase\FleetOps\Http\Resources\v1\Equipment;
|
|
11
12
|
use Fleetbase\FleetOps\Http\Resources\v1\FuelTransaction;
|
|
12
13
|
use Fleetbase\FleetOps\Http\Resources\v1\Part;
|
|
13
14
|
use Fleetbase\FleetOps\Http\Resources\v1\Sensor;
|
|
15
|
+
use Fleetbase\FleetOps\Http\Resources\v1\Trailer;
|
|
14
16
|
use Fleetbase\FleetOps\Http\Resources\v1\WorkOrder;
|
|
15
17
|
|
|
16
18
|
test('new first-class fleetops rest routes are registered in the consumable v1 namespace', function () {
|
|
17
19
|
$routes = file_get_contents(dirname(__DIR__) . '/src/routes.php');
|
|
18
20
|
|
|
19
|
-
foreach (['equipment', 'parts', 'work-orders', 'devices', 'sensors', 'fuel-transactions'] as $prefix) {
|
|
21
|
+
foreach (['equipment', 'parts', 'work-orders', 'devices', 'sensors', 'fuel-transactions', 'trailers'] as $prefix) {
|
|
20
22
|
expect($routes)->toContain("\$router->group(['prefix' => '{$prefix}']");
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -30,7 +32,12 @@ test('new first-class fleetops rest routes are registered in the consumable v1 n
|
|
|
30
32
|
->toContain('FuelTransactionController@matchVehicle')
|
|
31
33
|
->toContain('FuelTransactionController@matchOrder')
|
|
32
34
|
->toContain('FuelTransactionController@reprocess')
|
|
33
|
-
->toContain('FuelTransactionController@review')
|
|
35
|
+
->toContain('FuelTransactionController@review')
|
|
36
|
+
->toContain('TrailerController@track')
|
|
37
|
+
->toContain('TrailerController@attach')
|
|
38
|
+
->toContain('TrailerController@detach')
|
|
39
|
+
->toContain('TrailerController@connections')
|
|
40
|
+
->toContain('TrailerController@vehicleTrailers');
|
|
34
41
|
});
|
|
35
42
|
|
|
36
43
|
test('public fuel transaction api keeps provider naming internal only', function () {
|
|
@@ -54,6 +61,7 @@ test('new public controllers expose the expected rest methods', function () {
|
|
|
54
61
|
WorkOrderController::class => ['create', 'query', 'find', 'update', 'delete', 'send'],
|
|
55
62
|
DeviceController::class => ['create', 'query', 'find', 'update', 'delete', 'attach', 'detach'],
|
|
56
63
|
SensorController::class => ['create', 'query', 'find', 'update', 'delete'],
|
|
64
|
+
TrailerController::class => ['create', 'query', 'find', 'update', 'delete', 'track', 'attach', 'detach', 'connections', 'vehicleTrailers'],
|
|
57
65
|
FuelTransactionController::class => ['create', 'query', 'find', 'update', 'delete', 'matchVehicle', 'matchOrder', 'reprocess', 'review'],
|
|
58
66
|
];
|
|
59
67
|
|
|
@@ -65,7 +73,7 @@ test('new public controllers expose the expected rest methods', function () {
|
|
|
65
73
|
});
|
|
66
74
|
|
|
67
75
|
test('new public resources preserve internal uuid fields behind internal request checks', function () {
|
|
68
|
-
foreach ([Equipment::class, Part::class, WorkOrder::class, Device::class, Sensor::class, FuelTransaction::class] as $resource) {
|
|
76
|
+
foreach ([Equipment::class, Part::class, WorkOrder::class, Device::class, Sensor::class, FuelTransaction::class, Trailer::class] as $resource) {
|
|
69
77
|
expect(class_exists($resource))->toBeTrue();
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -76,6 +84,7 @@ test('new public resources preserve internal uuid fields behind internal request
|
|
|
76
84
|
'Sensor.php' => ['uuid', 'company_uuid', 'device_uuid', 'warranty_uuid', 'telematic_uuid', 'photo_uuid', 'sensorable_uuid'],
|
|
77
85
|
'FuelTransaction.php' => ['uuid', 'fuel_provider_connection_uuid', 'fuel_report_uuid', 'vehicle_uuid', 'driver_uuid', 'order_uuid'],
|
|
78
86
|
'WorkOrder.php' => ['uuid', 'company_uuid', 'schedule_uuid', 'created_by_uuid', 'updated_by_uuid', 'target_uuid', 'assignee_uuid'],
|
|
87
|
+
'Trailer.php' => ['uuid', 'company_uuid'],
|
|
79
88
|
];
|
|
80
89
|
|
|
81
90
|
foreach ($internalFields as $resourceFile => $fields) {
|
|
@@ -99,6 +108,7 @@ test('new shared resources do not expose unguarded uuid fields', function () {
|
|
|
99
108
|
'Sensor.php',
|
|
100
109
|
'FuelTransaction.php',
|
|
101
110
|
'WorkOrder.php',
|
|
111
|
+
'Trailer.php',
|
|
102
112
|
] as $resourceFile) {
|
|
103
113
|
$source = file_get_contents(dirname(__DIR__) . '/src/Http/Resources/v1/' . $resourceFile);
|
|
104
114
|
|
|
@@ -147,6 +157,7 @@ test('public controllers reject uuid shaped request keys', function () {
|
|
|
147
157
|
'DeviceController.php',
|
|
148
158
|
'SensorController.php',
|
|
149
159
|
'FuelTransactionController.php',
|
|
160
|
+
'TrailerController.php',
|
|
150
161
|
] as $controllerFile) {
|
|
151
162
|
$source = file_get_contents(dirname(__DIR__) . '/src/Http/Controllers/Api/v1/' . $controllerFile);
|
|
152
163
|
|
|
@@ -162,6 +173,7 @@ test('new public list endpoints have company scoped filters', function () {
|
|
|
162
173
|
'DeviceFilter.php',
|
|
163
174
|
'SensorFilter.php',
|
|
164
175
|
'FuelProviderTransactionFilter.php',
|
|
176
|
+
'TrailerFilter.php',
|
|
165
177
|
] as $filterFile) {
|
|
166
178
|
$source = file_get_contents(dirname(__DIR__) . '/src/Http/Filter/' . $filterFile);
|
|
167
179
|
|
|
@@ -214,6 +214,7 @@ test('fleetops report schema registers every table with columns computed columns
|
|
|
214
214
|
'orders',
|
|
215
215
|
'drivers',
|
|
216
216
|
'vehicles',
|
|
217
|
+
'assets',
|
|
217
218
|
'places',
|
|
218
219
|
'contacts',
|
|
219
220
|
'vendors',
|
|
@@ -241,6 +242,9 @@ test('fleetops report schema registers every table with columns computed columns
|
|
|
241
242
|
->and(fleetOpsReportTableName(fleetOpsReportRelationship($tables['drivers'], 'current_vehicle')))->toBe('vehicles')
|
|
242
243
|
->and(fleetOpsReportTableMeta($tables['vehicles'], 'category'))->toBe('Fleet')
|
|
243
244
|
->and(fleetOpsReportTableName(fleetOpsReportRelationship($tables['vehicles'], 'current_driver')))->toBe('drivers')
|
|
245
|
+
->and(fleetOpsReportTableMeta($tables['assets'], 'label'))->toBe('Trailers and Assets')
|
|
246
|
+
->and(fleetOpsReportTableMeta($tables['assets'], 'category'))->toBe('Fleet')
|
|
247
|
+
->and(fleetOpsReportColumnFlag(fleetOpsReportColumn($tables['assets'], 'asset_class'), 'filterable'))->toBeTrue()
|
|
244
248
|
->and(fleetOpsReportTableMeta($tables['places'], 'category'))->toBe('Geography')
|
|
245
249
|
->and(fleetOpsReportTableMeta($tables['contacts'], 'category'))->toBe('CRM')
|
|
246
250
|
->and(fleetOpsReportTableMeta($tables['vendors'], 'category'))->toBe('CRM')
|
|
@@ -2092,8 +2092,10 @@ test('device attachment morph types are normalized and legacy aliases are tolera
|
|
|
2092
2092
|
expect($serviceProvider)
|
|
2093
2093
|
->toContain('use Illuminate\Database\Eloquent\Relations\Relation;')
|
|
2094
2094
|
->toContain('$this->registerMorphMap();')
|
|
2095
|
-
->toContain("'Fleetbase\\\\Models\\\\Vehicle'
|
|
2096
|
-
->toContain("'\\\\Fleetbase\\\\Models\\\\Vehicle'
|
|
2095
|
+
->toContain("'Fleetbase\\\\Models\\\\Vehicle'")
|
|
2096
|
+
->toContain("'\\\\Fleetbase\\\\Models\\\\Vehicle'")
|
|
2097
|
+
->toContain("'fleet-ops:trailer'")
|
|
2098
|
+
->toContain('\\Fleetbase\\FleetOps\\Models\\Trailer::class');
|
|
2097
2099
|
});
|
|
2098
2100
|
|
|
2099
2101
|
test('internal device attachment endpoints return specific api errors instead of raw model misses', function () {
|