@fleetbase/fleetops-engine 0.6.63 → 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 -6
- 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/Concerns/ResolvesPublicRelationFields.php +29 -3
- 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/Fleet.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/Api/VehiclePublicContractTest.php +23 -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/FleetResourceTest.php +17 -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/server/tests/VehicleResourceContractsTest.php +39 -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
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Exceptions\DeviceAlreadyAttachedException;
|
|
6
|
+
use Fleetbase\FleetOps\Exports\TrailerExport;
|
|
7
|
+
use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
8
|
+
use Fleetbase\FleetOps\Http\Resources\v1\AssetConnection as AssetConnectionResource;
|
|
9
|
+
use Fleetbase\FleetOps\Http\Resources\v1\Trailer as TrailerResource;
|
|
10
|
+
use Fleetbase\FleetOps\Imports\TrailerImport;
|
|
11
|
+
use Fleetbase\FleetOps\Models\AssetConnection;
|
|
12
|
+
use Fleetbase\FleetOps\Models\Device;
|
|
13
|
+
use Fleetbase\FleetOps\Models\Equipment;
|
|
14
|
+
use Fleetbase\FleetOps\Models\Trailer;
|
|
15
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
16
|
+
use Fleetbase\Http\Requests\ExportRequest;
|
|
17
|
+
use Fleetbase\Http\Requests\ImportRequest;
|
|
18
|
+
use Fleetbase\Support\Resolve;
|
|
19
|
+
use Illuminate\Http\Request;
|
|
20
|
+
use Illuminate\Support\Facades\DB;
|
|
21
|
+
use Illuminate\Support\Str;
|
|
22
|
+
use Maatwebsite\Excel\Facades\Excel;
|
|
23
|
+
|
|
24
|
+
class TrailerController extends FleetOpsController
|
|
25
|
+
{
|
|
26
|
+
/**
|
|
27
|
+
* The resource to query.
|
|
28
|
+
*
|
|
29
|
+
* @var string
|
|
30
|
+
*/
|
|
31
|
+
public $resource = 'trailer';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handle post save transactions.
|
|
35
|
+
*/
|
|
36
|
+
public function afterSave(Request $request, Trailer $trailer): void
|
|
37
|
+
{
|
|
38
|
+
$values = $request->array('trailer.custom_field_values');
|
|
39
|
+
|
|
40
|
+
if ($values) {
|
|
41
|
+
$trailer->syncCustomFieldValues($values);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Deleting a trailer that is still coupled to a vehicle would orphan the active
|
|
47
|
+
* towing connection, so it must be detached first.
|
|
48
|
+
*/
|
|
49
|
+
public function deleteRecord($id, Request $request)
|
|
50
|
+
{
|
|
51
|
+
$trailer = $this->resolveTrailer($id);
|
|
52
|
+
|
|
53
|
+
if ($trailer && $this->activeConnectionQuery($trailer)->exists()) {
|
|
54
|
+
return response()->error('Detach the trailer from its vehicle before deleting it.', 409);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return $this->deleteTrailerRecord($id, $request);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// @codeCoverageIgnoreStart
|
|
61
|
+
// Thin seam over the framework delete flow; the guard above is what this controller owns.
|
|
62
|
+
protected function deleteTrailerRecord($id, Request $request)
|
|
63
|
+
{
|
|
64
|
+
return parent::deleteRecord($id, $request);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// @codeCoverageIgnoreEnd
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Attach the trailer to a vehicle. Re-attaching to the same vehicle is idempotent.
|
|
71
|
+
*/
|
|
72
|
+
public function attach(Request $request, string $id)
|
|
73
|
+
{
|
|
74
|
+
$request->validate(['vehicle' => ['required', 'string'], 'position' => ['nullable', 'integer', 'min:1']]);
|
|
75
|
+
|
|
76
|
+
$trailer = $this->resolveTrailer($id);
|
|
77
|
+
$vehicle = $this->resolveVehicle($request->input('vehicle'));
|
|
78
|
+
|
|
79
|
+
if (!$trailer) {
|
|
80
|
+
return response()->error('Trailer not found or not available for this organization.', 404);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!$vehicle) {
|
|
84
|
+
return response()->error('Vehicle not found or not available for this organization.', 404);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
$result = DB::transaction(function () use ($trailer, $vehicle, $request) {
|
|
88
|
+
$active = $this->activeConnectionQuery($trailer)->lockForUpdate()->first();
|
|
89
|
+
|
|
90
|
+
if ($active) {
|
|
91
|
+
if ($active->connector_uuid !== $vehicle->uuid) {
|
|
92
|
+
return 'Trailer is already attached to another vehicle. Detach it before moving.';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return $active;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
$position = $request->integer('position', 1);
|
|
99
|
+
$positionKey = $vehicle->uuid . ':' . $position;
|
|
100
|
+
|
|
101
|
+
if (AssetConnection::where('company_uuid', session('company'))->where('active_connector_position', $positionKey)->lockForUpdate()->exists()) {
|
|
102
|
+
return 'Another trailer already occupies this towing position.';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return AssetConnection::create([
|
|
106
|
+
'company_uuid' => session('company'),
|
|
107
|
+
'connector_type' => Vehicle::class,
|
|
108
|
+
'connector_uuid' => $vehicle->uuid,
|
|
109
|
+
'connected_type' => Trailer::class,
|
|
110
|
+
'connected_uuid' => $trailer->uuid,
|
|
111
|
+
'active_connected_uuid' => $trailer->uuid,
|
|
112
|
+
'active_connector_position' => $positionKey,
|
|
113
|
+
'relationship_type' => 'towing',
|
|
114
|
+
'position' => $position,
|
|
115
|
+
'connected_at' => now(),
|
|
116
|
+
'source' => 'manual',
|
|
117
|
+
'created_by_uuid' => session('user'),
|
|
118
|
+
'updated_by_uuid' => session('user'),
|
|
119
|
+
]);
|
|
120
|
+
}, 3);
|
|
121
|
+
|
|
122
|
+
if (is_string($result)) {
|
|
123
|
+
return response()->error($result, 409);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return response()->json([
|
|
127
|
+
'status' => 'ok',
|
|
128
|
+
'message' => 'Trailer attached to vehicle.',
|
|
129
|
+
'trailer' => new TrailerResource($trailer->fresh(['currentConnection.vehicle'])),
|
|
130
|
+
'connection' => new AssetConnectionResource($result->load(['vehicle', 'trailer'])),
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* End the active towing connection. Detaching an unattached trailer is a no-op.
|
|
136
|
+
*/
|
|
137
|
+
public function detach(string $id)
|
|
138
|
+
{
|
|
139
|
+
$trailer = $this->resolveTrailer($id);
|
|
140
|
+
|
|
141
|
+
if (!$trailer) {
|
|
142
|
+
return response()->error('Trailer not found or not available for this organization.', 404);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
DB::transaction(fn () => $this->activeConnectionQuery($trailer)->lockForUpdate()->update([
|
|
146
|
+
'active_connected_uuid' => null,
|
|
147
|
+
'active_connector_position' => null,
|
|
148
|
+
'disconnected_at' => now(),
|
|
149
|
+
'updated_by_uuid' => session('user'),
|
|
150
|
+
'updated_at' => now(),
|
|
151
|
+
]));
|
|
152
|
+
|
|
153
|
+
return response()->json([
|
|
154
|
+
'status' => 'ok',
|
|
155
|
+
'message' => 'Trailer detached from vehicle.',
|
|
156
|
+
'trailer' => new TrailerResource($trailer->fresh(['currentConnection.vehicle'])),
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public function attachDevice(Request $request, string $id)
|
|
161
|
+
{
|
|
162
|
+
$request->validate(['device' => ['required', 'string']]);
|
|
163
|
+
|
|
164
|
+
$trailer = $this->resolveTrailer($id);
|
|
165
|
+
$device = $this->resolveDevice($request->input('device'));
|
|
166
|
+
|
|
167
|
+
if (!$trailer) {
|
|
168
|
+
return response()->error('Trailer not found or not available for this organization.', 404);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!$device) {
|
|
172
|
+
return response()->error('Device not found or not available for this organization.', 404);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
$device->attachTo($trailer);
|
|
177
|
+
} catch (DeviceAlreadyAttachedException $e) {
|
|
178
|
+
return response()->error($e->getMessage(), 409);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return response()->json([
|
|
182
|
+
'status' => 'ok',
|
|
183
|
+
'message' => 'Device attached to trailer.',
|
|
184
|
+
'trailer' => new TrailerResource($trailer->fresh(['devices'])),
|
|
185
|
+
'device' => Resolve::httpResourceForModel($device->fresh(['attachable'])),
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public function detachDevice(Request $request, string $id)
|
|
190
|
+
{
|
|
191
|
+
$request->validate(['device' => ['required', 'string']]);
|
|
192
|
+
|
|
193
|
+
$trailer = $this->resolveTrailer($id);
|
|
194
|
+
$device = $this->resolveDevice($request->input('device'));
|
|
195
|
+
|
|
196
|
+
if (!$trailer) {
|
|
197
|
+
return response()->error('Trailer not found or not available for this organization.', 404);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!$device) {
|
|
201
|
+
return response()->error('Device not found or not available for this organization.', 404);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if ($device->attachable_uuid !== $trailer->uuid || $device->attachable_type !== Trailer::class) {
|
|
205
|
+
return response()->error('This device is not attached to the selected trailer.', 422);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
$device->detach();
|
|
209
|
+
|
|
210
|
+
return response()->json([
|
|
211
|
+
'status' => 'ok',
|
|
212
|
+
'message' => 'Device detached from trailer.',
|
|
213
|
+
'trailer' => new TrailerResource($trailer->fresh(['devices'])),
|
|
214
|
+
'device' => Resolve::httpResourceForModel($device->fresh(['attachable'])),
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public function attachEquipment(Request $request, string $id)
|
|
219
|
+
{
|
|
220
|
+
$request->validate(['equipment' => ['required', 'string']]);
|
|
221
|
+
|
|
222
|
+
$trailer = $this->resolveTrailer($id);
|
|
223
|
+
$equipment = $this->resolveEquipment($request->input('equipment'));
|
|
224
|
+
|
|
225
|
+
if (!$trailer) {
|
|
226
|
+
return response()->error('Trailer not found or not available for this organization.', 404);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!$equipment) {
|
|
230
|
+
return response()->error('Equipment not found or not available for this organization.', 404);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
DB::transaction(function () use ($equipment, $trailer) {
|
|
234
|
+
Equipment::where('uuid', $equipment->uuid)->lockForUpdate()->firstOrFail()->update(['equipable_type' => Trailer::class, 'equipable_uuid' => $trailer->uuid]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return response()->json([
|
|
238
|
+
'status' => 'ok',
|
|
239
|
+
'message' => 'Equipment attached to trailer.',
|
|
240
|
+
'trailer' => new TrailerResource($trailer->fresh(['equipments'])),
|
|
241
|
+
'equipment' => Resolve::httpResourceForModel($equipment->fresh(['equipable'])),
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public function detachEquipment(Request $request, string $id)
|
|
246
|
+
{
|
|
247
|
+
$request->validate(['equipment' => ['required', 'string']]);
|
|
248
|
+
|
|
249
|
+
$trailer = $this->resolveTrailer($id);
|
|
250
|
+
$equipment = $this->resolveEquipment($request->input('equipment'));
|
|
251
|
+
|
|
252
|
+
if (!$trailer) {
|
|
253
|
+
return response()->error('Trailer not found or not available for this organization.', 404);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!$equipment) {
|
|
257
|
+
return response()->error('Equipment not found or not available for this organization.', 404);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if ($equipment->equipable_uuid !== $trailer->uuid || $equipment->equipable_type !== Trailer::class) {
|
|
261
|
+
return response()->error('This equipment is not attached to the selected trailer.', 422);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
DB::transaction(fn () => Equipment::where('uuid', $equipment->uuid)->lockForUpdate()->update(['equipable_type' => null, 'equipable_uuid' => null]));
|
|
265
|
+
|
|
266
|
+
return response()->json([
|
|
267
|
+
'status' => 'ok',
|
|
268
|
+
'message' => 'Equipment detached from trailer.',
|
|
269
|
+
'trailer' => new TrailerResource($trailer->fresh(['equipments'])),
|
|
270
|
+
'equipment' => Resolve::httpResourceForModel($equipment->fresh(['equipable'])),
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Lifecycle statuses available to trailers, used by the console status filter.
|
|
276
|
+
*/
|
|
277
|
+
public function statuses()
|
|
278
|
+
{
|
|
279
|
+
return response()->json(Trailer::STATUSES);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Trailer classification types, used by the console type filter.
|
|
284
|
+
*/
|
|
285
|
+
public function types()
|
|
286
|
+
{
|
|
287
|
+
return response()->json(Trailer::TYPES);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public function export(ExportRequest $request)
|
|
291
|
+
{
|
|
292
|
+
$format = $request->input('format', 'xlsx');
|
|
293
|
+
|
|
294
|
+
return $this->downloadExport(new TrailerExport($request->array('selections')), Str::slug('trailers-' . date('Y-m-d-H:i')) . '.' . $format);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public function import(ImportRequest $request)
|
|
298
|
+
{
|
|
299
|
+
$count = 0;
|
|
300
|
+
|
|
301
|
+
foreach ($request->resolveFilesFromIds() as $file) {
|
|
302
|
+
$import = $this->createImport();
|
|
303
|
+
$this->importFile($import, $file->path, $request->input('disk', config('filesystems.default')));
|
|
304
|
+
$count += $import->imported;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return response()->json(['status' => 'ok', 'message' => 'Import completed', 'imported' => $count]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
protected function resolveTrailer(string $id): ?Trailer
|
|
311
|
+
{
|
|
312
|
+
return Trailer::where('company_uuid', session('company'))
|
|
313
|
+
->where(fn ($query) => $query->where('uuid', $id)->orWhere('public_id', $id))
|
|
314
|
+
->first();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
protected function resolveVehicle(string $id): ?Vehicle
|
|
318
|
+
{
|
|
319
|
+
return Vehicle::where('company_uuid', session('company'))
|
|
320
|
+
->where(fn ($query) => $query->where('uuid', $id)->orWhere('public_id', $id)->orWhere('internal_id', $id))
|
|
321
|
+
->first();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
protected function resolveDevice(string $id): ?Device
|
|
325
|
+
{
|
|
326
|
+
return Device::where('company_uuid', session('company'))
|
|
327
|
+
->where(fn ($query) => $query->where('uuid', $id)->orWhere('public_id', $id))
|
|
328
|
+
->first();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
protected function resolveEquipment(string $id): ?Equipment
|
|
332
|
+
{
|
|
333
|
+
return Equipment::where('company_uuid', session('company'))
|
|
334
|
+
->where(fn ($query) => $query->where('uuid', $id)->orWhere('public_id', $id))
|
|
335
|
+
->first();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
protected function activeConnectionQuery(Trailer $trailer)
|
|
339
|
+
{
|
|
340
|
+
return AssetConnection::where('company_uuid', session('company'))->where('active_connected_uuid', $trailer->uuid);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// @codeCoverageIgnoreStart
|
|
344
|
+
// Thin spreadsheet adapter seams are exercised through export/import caller behavior.
|
|
345
|
+
protected function downloadExport(TrailerExport $export, string $fileName)
|
|
346
|
+
{
|
|
347
|
+
return Excel::download($export, $fileName);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
protected function createImport(): TrailerImport
|
|
351
|
+
{
|
|
352
|
+
return new TrailerImport();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
protected function importFile(TrailerImport $import, string $path, string $disk): void
|
|
356
|
+
{
|
|
357
|
+
Excel::import($import, $path, $disk);
|
|
358
|
+
}
|
|
359
|
+
// @codeCoverageIgnoreEnd
|
|
360
|
+
}
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Exceptions\DeviceAlreadyAttachedException;
|
|
5
6
|
use Fleetbase\FleetOps\Exports\VehicleExport;
|
|
6
7
|
use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
7
8
|
use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as IndexOrderResource;
|
|
8
9
|
use Fleetbase\FleetOps\Imports\VehicleImport;
|
|
9
10
|
use Fleetbase\FleetOps\Models\Device;
|
|
10
11
|
use Fleetbase\FleetOps\Models\Driver;
|
|
12
|
+
use Fleetbase\FleetOps\Models\Equipment;
|
|
11
13
|
use Fleetbase\FleetOps\Models\Order;
|
|
12
14
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
13
15
|
use Fleetbase\Http\Requests\ExportRequest;
|
|
14
16
|
use Fleetbase\Http\Requests\ImportRequest;
|
|
17
|
+
use Fleetbase\Support\Resolve;
|
|
15
18
|
use Illuminate\Http\JsonResponse;
|
|
16
19
|
use Illuminate\Http\Request;
|
|
17
20
|
use Illuminate\Support\Arr;
|
|
@@ -200,6 +203,8 @@ class VehicleController extends FleetOpsController
|
|
|
200
203
|
$device->attachTo($vehicle);
|
|
201
204
|
$device->load(['telematic', 'warranty', 'attachable']);
|
|
202
205
|
$vehicle->load(['driver', 'devices']);
|
|
206
|
+
} catch (DeviceAlreadyAttachedException $e) {
|
|
207
|
+
return response()->error($e->getMessage(), 409);
|
|
203
208
|
} catch (\Throwable $e) {
|
|
204
209
|
$this->logDeviceAttachmentFailure('attach-device', $vehicle, $device, $e);
|
|
205
210
|
|
|
@@ -256,6 +261,66 @@ class VehicleController extends FleetOpsController
|
|
|
256
261
|
]);
|
|
257
262
|
}
|
|
258
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Attach equipment to the vehicle. Equipment can only be equipped to one asset at a
|
|
266
|
+
* time, so attaching moves it from any previous vehicle or trailer.
|
|
267
|
+
*/
|
|
268
|
+
public function attachEquipment(Request $request, string $id): JsonResponse
|
|
269
|
+
{
|
|
270
|
+
$request->validate(['equipment' => 'required|string']);
|
|
271
|
+
|
|
272
|
+
$vehicle = $this->resolveVehicle($id);
|
|
273
|
+
$equipment = $this->resolveEquipment($request->input('equipment'));
|
|
274
|
+
|
|
275
|
+
if (!$vehicle) {
|
|
276
|
+
return response()->error('Vehicle not found or not available for this organization.', 404);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!$equipment) {
|
|
280
|
+
return response()->error('Equipment not found or not available for this organization.', 404);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
DB::transaction(function () use ($equipment, $vehicle) {
|
|
284
|
+
Equipment::where('uuid', $equipment->uuid)->lockForUpdate()->firstOrFail()->update(['equipable_type' => Vehicle::class, 'equipable_uuid' => $vehicle->uuid]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return response()->json([
|
|
288
|
+
'status' => 'ok',
|
|
289
|
+
'message' => 'Equipment attached to vehicle.',
|
|
290
|
+
'vehicle' => Resolve::httpResourceForModel($vehicle->fresh(['equipments'])),
|
|
291
|
+
'equipment' => Resolve::httpResourceForModel($equipment->fresh(['equipable'])),
|
|
292
|
+
]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public function detachEquipment(Request $request, string $id): JsonResponse
|
|
296
|
+
{
|
|
297
|
+
$request->validate(['equipment' => 'required|string']);
|
|
298
|
+
|
|
299
|
+
$vehicle = $this->resolveVehicle($id);
|
|
300
|
+
$equipment = $this->resolveEquipment($request->input('equipment'));
|
|
301
|
+
|
|
302
|
+
if (!$vehicle) {
|
|
303
|
+
return response()->error('Vehicle not found or not available for this organization.', 404);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!$equipment) {
|
|
307
|
+
return response()->error('Equipment not found or not available for this organization.', 404);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if ($equipment->equipable_uuid !== $vehicle->uuid || $equipment->equipable_type !== Vehicle::class) {
|
|
311
|
+
return response()->error('This equipment is not attached to the selected vehicle.', 422);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
DB::transaction(fn () => Equipment::where('uuid', $equipment->uuid)->lockForUpdate()->update(['equipable_type' => null, 'equipable_uuid' => null]));
|
|
315
|
+
|
|
316
|
+
return response()->json([
|
|
317
|
+
'status' => 'ok',
|
|
318
|
+
'message' => 'Equipment detached from vehicle.',
|
|
319
|
+
'vehicle' => Resolve::httpResourceForModel($vehicle->fresh(['equipments'])),
|
|
320
|
+
'equipment' => Resolve::httpResourceForModel($equipment->fresh(['equipable'])),
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
323
|
+
|
|
259
324
|
protected function findVehicle(string $id): Vehicle
|
|
260
325
|
{
|
|
261
326
|
return Vehicle::where(function ($query) use ($id) {
|
|
@@ -283,6 +348,15 @@ class VehicleController extends FleetOpsController
|
|
|
283
348
|
->firstOrFail();
|
|
284
349
|
}
|
|
285
350
|
|
|
351
|
+
protected function resolveEquipment(string $id): ?Equipment
|
|
352
|
+
{
|
|
353
|
+
return Equipment::where(function ($query) use ($id) {
|
|
354
|
+
$query->where('uuid', $id)->orWhere('public_id', $id);
|
|
355
|
+
})
|
|
356
|
+
->where('company_uuid', session('company'))
|
|
357
|
+
->first();
|
|
358
|
+
}
|
|
359
|
+
|
|
286
360
|
protected function resolveDevice(string $id): ?Device
|
|
287
361
|
{
|
|
288
362
|
return Device::where(function ($query) use ($id) {
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Filter;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Trailer;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
5
8
|
use Fleetbase\Http\Filter\Filter;
|
|
9
|
+
use Fleetbase\Support\Http;
|
|
6
10
|
|
|
7
11
|
class EquipmentFilter extends Filter
|
|
8
12
|
{
|
|
@@ -20,4 +24,51 @@ class EquipmentFilter extends Filter
|
|
|
20
24
|
{
|
|
21
25
|
$this->builder->search($searchQuery);
|
|
22
26
|
}
|
|
27
|
+
|
|
28
|
+
public function attachmentState(?string $state)
|
|
29
|
+
{
|
|
30
|
+
$state === 'attached' ? $this->builder->whereNotNull('equipable_uuid') : $this->builder->whereNull('equipable_uuid');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public function equipableType(?string $type)
|
|
34
|
+
{
|
|
35
|
+
$class = match ($type) {
|
|
36
|
+
'vehicle', 'fleet-ops:vehicle' => Vehicle::class, 'trailer', 'fleet-ops:trailer' => Trailer::class, default => null,
|
|
37
|
+
};
|
|
38
|
+
$class ? $this->builder->where('equipable_type', $class) : $this->builder->whereRaw('0 = 1');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Filter by the asset the equipment is equipped to. `equipable` is a morphTo relation,
|
|
43
|
+
* which `whereHas` cannot traverse, so the identifier is resolved to the asset UUID
|
|
44
|
+
* across every equipable model first. The public API filters by public id; the
|
|
45
|
+
* console may also pass the asset UUID.
|
|
46
|
+
*/
|
|
47
|
+
public function equipable($id)
|
|
48
|
+
{
|
|
49
|
+
$identifiers = array_values(array_filter(array_map('strval', (array) $id), 'strlen'));
|
|
50
|
+
|
|
51
|
+
if (empty($identifiers)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$allowUuid = Http::isInternalRequest($this->request);
|
|
56
|
+
$uuids = [];
|
|
57
|
+
|
|
58
|
+
foreach ([Vehicle::class, Trailer::class, Driver::class] as $modelClass) {
|
|
59
|
+
$uuids = array_merge($uuids, $modelClass::query()
|
|
60
|
+
->where('company_uuid', $this->session->get('company'))
|
|
61
|
+
->where(function ($query) use ($identifiers, $allowUuid) {
|
|
62
|
+
$query->whereIn('public_id', $identifiers);
|
|
63
|
+
|
|
64
|
+
if ($allowUuid) {
|
|
65
|
+
$query->orWhereIn('uuid', $identifiers);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
->pluck('uuid')
|
|
69
|
+
->all());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$this->builder->whereIn('equipable_uuid', $uuids);
|
|
73
|
+
}
|
|
23
74
|
}
|
|
@@ -366,6 +366,21 @@ class OrderFilter extends Filter
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Exact match on one or many operator-side ids. The console's tag-input filter
|
|
371
|
+
* joins scanned or pasted ids with commas so a batch of labels resolves in one query.
|
|
372
|
+
*/
|
|
373
|
+
public function internalId($internalId)
|
|
374
|
+
{
|
|
375
|
+
$ids = array_values(array_filter(array_map('trim', explode(',', (string) $internalId)), 'strlen'));
|
|
376
|
+
|
|
377
|
+
if ($ids === []) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
$this->builder->whereIn('internal_id', $ids);
|
|
382
|
+
}
|
|
383
|
+
|
|
369
384
|
public function bulkQuery($ids)
|
|
370
385
|
{
|
|
371
386
|
$ids = Utils::arrayFrom($ids);
|