@fleetbase/fleetops-engine 0.6.54 → 0.6.56

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.
Files changed (220) hide show
  1. package/addon/components/admin/map-settings.hbs +1 -1
  2. package/addon/components/admin/routing-settings.hbs +2 -1
  3. package/addon/components/cell/attached-vehicle.hbs +10 -0
  4. package/addon/components/cell/attached-vehicle.js +58 -0
  5. package/addon/components/cell/device-identity.hbs +24 -0
  6. package/addon/components/cell/device-identity.js +129 -0
  7. package/addon/components/cell/driver-identity.hbs +33 -0
  8. package/addon/components/cell/driver-identity.js +143 -0
  9. package/addon/components/cell/equipment-identity.hbs +5 -0
  10. package/addon/components/cell/equipment-identity.js +48 -0
  11. package/addon/components/cell/part-identity.hbs +5 -0
  12. package/addon/components/cell/part-identity.js +63 -0
  13. package/addon/components/cell/telematic-device.hbs +38 -0
  14. package/addon/components/cell/telematic-device.js +44 -0
  15. package/addon/components/cell/telematic-provider.hbs +33 -16
  16. package/addon/components/cell/telematic-provider.js +50 -8
  17. package/addon/components/cell/vehicle-identity.hbs +33 -0
  18. package/addon/components/cell/vehicle-identity.js +138 -0
  19. package/addon/components/device/details.js +1 -1
  20. package/addon/components/device/form.hbs +2 -2
  21. package/addon/components/device/form.js +7 -0
  22. package/addon/components/device/panel-header.hbs +45 -20
  23. package/addon/components/device/panel-header.js +49 -1
  24. package/addon/components/device/panel-tabs/events.hbs +30 -0
  25. package/addon/components/device/panel-tabs/events.js +128 -0
  26. package/addon/components/device/panel-tabs/sensors.hbs +30 -0
  27. package/addon/components/device/panel-tabs/sensors.js +93 -0
  28. package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
  29. package/addon/components/device/panel-tabs/vehicle.js +90 -0
  30. package/addon/components/device-event/details.hbs +192 -0
  31. package/addon/components/device-event/details.js +151 -0
  32. package/addon/components/driver/schedule.js +1 -1
  33. package/addon/components/equipment/form.js +8 -1
  34. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  35. package/addon/components/layout/fleet-ops-sidebar.js +20 -6
  36. package/addon/components/map/drawer/device-event-listing.js +2 -0
  37. package/addon/components/map/drawer/driver-listing.js +2 -0
  38. package/addon/components/map/drawer/place-listing.js +2 -0
  39. package/addon/components/map/drawer/position-listing.hbs +11 -6
  40. package/addon/components/map/drawer/position-listing.js +17 -4
  41. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  42. package/addon/components/sensor/panel-header.hbs +47 -15
  43. package/addon/components/sensor/panel-header.js +59 -1
  44. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  45. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  46. package/addon/components/table/cell/dropdown.hbs +28 -0
  47. package/addon/components/table/cell/dropdown.js +91 -0
  48. package/addon/components/telematic/details.hbs +1 -1
  49. package/addon/components/telematic/form.hbs +2 -2
  50. package/addon/components/telematic/settings.hbs +1 -1
  51. package/addon/components/vendor/panel-header.hbs +82 -0
  52. package/addon/components/vendor/panel-header.js +48 -0
  53. package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
  54. package/addon/controllers/connectivity/devices/index/details/vehicle.js +50 -2
  55. package/addon/controllers/connectivity/devices/index.js +144 -129
  56. package/addon/controllers/connectivity/events/details.js +70 -0
  57. package/addon/controllers/connectivity/events/index.js +187 -11
  58. package/addon/controllers/connectivity/sensors/index.js +160 -14
  59. package/addon/controllers/connectivity/telematics/details/devices.js +126 -134
  60. package/addon/controllers/connectivity/telematics/details/events.js +1 -1
  61. package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
  62. package/addon/controllers/maintenance/equipment/index.js +1 -2
  63. package/addon/controllers/maintenance/parts/index.js +1 -2
  64. package/addon/controllers/management/drivers/index.js +82 -42
  65. package/addon/controllers/management/fuel-reports/index.js +63 -4
  66. package/addon/controllers/management/issues/index.js +63 -4
  67. package/addon/controllers/management/vehicles/index.js +33 -6
  68. package/addon/controllers/settings/map.js +15 -0
  69. package/addon/instance-initializers/register-vroom-allocation.js +1 -1
  70. package/addon/routes/connectivity/devices/index/details/vehicle.js +24 -2
  71. package/addon/routes/connectivity/devices/index.js +7 -0
  72. package/addon/routes/connectivity/events/details.js +28 -0
  73. package/addon/routes/connectivity/events/index.js +6 -2
  74. package/addon/routes/connectivity/sensors/index.js +7 -2
  75. package/addon/routes/connectivity/telematics/details/devices.js +2 -0
  76. package/addon/routes/management/vehicles/index/details.js +3 -1
  77. package/addon/services/device-actions.js +30 -0
  78. package/addon/services/device-event-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +1 -0
  80. package/addon/services/map-adapter/google.js +25 -0
  81. package/addon/services/map-adapter/leaflet.js +1 -1
  82. package/addon/services/map-adapter-interface.js +1 -1
  83. package/addon/services/map-manager.js +4 -1
  84. package/addon/services/map-settings.js +15 -0
  85. package/addon/services/sensor-actions.js +3 -0
  86. package/addon/services/service-rate-actions.js +39 -1
  87. package/addon/services/vehicle-actions.js +24 -12
  88. package/addon/services/vendor-actions.js +3 -0
  89. package/addon/styles/fleetops-engine.css +9 -1
  90. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +66 -2
  91. package/addon/templates/connectivity/events/details.hbs +40 -0
  92. package/addon/templates/management/vendors/index/details.hbs +2 -2
  93. package/addon/templates/settings/map.hbs +38 -9
  94. package/addon/templates/settings/notifications.hbs +2 -1
  95. package/addon/templates/settings/routing.hbs +1 -0
  96. package/addon/utils/device-table-columns.js +288 -0
  97. package/addon/utils/identity-cell-resource.js +20 -0
  98. package/addon/utils/leaflet-plugin-loader.js +5 -5
  99. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  100. package/addon/utils/route-colors.js +1 -1
  101. package/addon/utils/trackable-option.js +93 -0
  102. package/app/components/cell/attached-vehicle.js +1 -0
  103. package/app/components/cell/device-identity.js +1 -0
  104. package/app/components/cell/driver-identity.js +1 -0
  105. package/app/components/cell/equipment-identity.js +1 -0
  106. package/app/components/cell/part-identity.js +1 -0
  107. package/app/components/cell/telematic-device.js +1 -0
  108. package/app/components/cell/vehicle-identity.js +1 -0
  109. package/app/components/device/panel-tabs/events.hbs +30 -0
  110. package/app/components/device/panel-tabs/events.js +128 -0
  111. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  112. package/app/components/device/panel-tabs/sensors.js +93 -0
  113. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  114. package/app/components/device/panel-tabs/vehicle.js +90 -0
  115. package/app/components/device-event/details.hbs +192 -0
  116. package/app/components/device-event/details.js +1 -0
  117. package/app/components/table/cell/dropdown/action-item.js +1 -0
  118. package/app/components/table/cell/dropdown.js +1 -0
  119. package/app/components/vendor/panel-header.js +1 -0
  120. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  121. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  122. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  123. package/app/utils/map-drawer-dropdown-position.js +1 -0
  124. package/app/utils/trackable-option.js +1 -0
  125. package/composer.json +1 -1
  126. package/extension.json +1 -1
  127. package/package.json +4 -4
  128. package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
  129. package/server/src/Console/Commands/SyncTelematics.php +4 -4
  130. package/server/src/Exports/ContactExport.php +12 -5
  131. package/server/src/Exports/DeviceExport.php +92 -0
  132. package/server/src/Exports/DriverExport.php +28 -3
  133. package/server/src/Exports/EquipmentExport.php +94 -0
  134. package/server/src/Exports/FleetExport.php +22 -7
  135. package/server/src/Exports/FuelReportExport.php +16 -1
  136. package/server/src/Exports/IssueExport.php +25 -3
  137. package/server/src/Exports/MaintenanceExport.php +106 -0
  138. package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
  139. package/server/src/Exports/OrderExport.php +24 -5
  140. package/server/src/Exports/PartExport.php +99 -0
  141. package/server/src/Exports/PlaceExport.php +33 -7
  142. package/server/src/Exports/SensorExport.php +94 -0
  143. package/server/src/Exports/ServiceAreaExport.php +31 -7
  144. package/server/src/Exports/ServiceRateExport.php +41 -1
  145. package/server/src/Exports/TelematicExport.php +90 -0
  146. package/server/src/Exports/VehicleExport.php +274 -8
  147. package/server/src/Exports/VendorExport.php +15 -2
  148. package/server/src/Exports/WorkOrderExport.php +92 -0
  149. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
  150. package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
  151. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
  152. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
  153. package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
  154. package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
  155. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
  156. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +36 -1
  157. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +1 -1
  158. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
  159. package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
  160. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
  161. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
  162. package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
  163. package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
  164. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  165. package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
  166. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
  167. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
  168. package/server/src/Http/Filter/DeviceEventFilter.php +21 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +50 -4
  170. package/server/src/Http/Filter/EquipmentFilter.php +23 -0
  171. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
  172. package/server/src/Http/Filter/PartFilter.php +47 -0
  173. package/server/src/Http/Filter/SensorFilter.php +34 -6
  174. package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
  175. package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
  176. package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
  177. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
  178. package/server/src/Http/Requests/CreatePartRequest.php +40 -0
  179. package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
  180. package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
  181. package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
  182. package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
  183. package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
  184. package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
  185. package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
  186. package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
  187. package/server/src/Http/Resources/v1/Device.php +72 -0
  188. package/server/src/Http/Resources/v1/Equipment.php +57 -0
  189. package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
  190. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  191. package/server/src/Http/Resources/v1/Part.php +64 -0
  192. package/server/src/Http/Resources/v1/Sensor.php +68 -0
  193. package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
  194. package/server/src/Models/Device.php +2 -0
  195. package/server/src/Models/DeviceEvent.php +81 -0
  196. package/server/src/Models/Equipment.php +55 -0
  197. package/server/src/Models/Order.php +2 -1
  198. package/server/src/Observers/PurchaseRateObserver.php +2 -1
  199. package/server/src/Support/Analytics/RevenueTrend.php +3 -8
  200. package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
  201. package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
  202. package/server/src/Support/Metrics/EarningsMetric.php +1 -10
  203. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +105 -5
  204. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  205. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  206. package/server/src/Support/Telematics/Providers/SafeeProvider.php +567 -47
  207. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  208. package/server/src/Support/Telematics/TelematicService.php +28 -9
  209. package/server/src/routes.php +67 -15
  210. package/server/tests/DeviceFilterTest.php +9 -0
  211. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  212. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  213. package/server/tests/ExportCoverageTest.php +79 -0
  214. package/server/tests/MetricsRegistryTest.php +45 -5
  215. package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
  216. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  217. package/server/tests/TelematicsHardeningTest.php +873 -7
  218. package/addon/controllers/connectivity/events/index/details.js +0 -3
  219. package/addon/routes/connectivity/events/index/details.js +0 -3
  220. package/addon/templates/connectivity/events/index/details.hbs +0 -1
@@ -0,0 +1,92 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Exports;
4
+
5
+ use Fleetbase\FleetOps\Models\WorkOrder;
6
+ use Maatwebsite\Excel\Concerns\FromCollection;
7
+ use Maatwebsite\Excel\Concerns\ShouldAutoSize;
8
+ use Maatwebsite\Excel\Concerns\WithColumnFormatting;
9
+ use Maatwebsite\Excel\Concerns\WithHeadings;
10
+ use Maatwebsite\Excel\Concerns\WithMapping;
11
+ use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
12
+
13
+ class WorkOrderExport implements FromCollection, WithHeadings, WithMapping, WithColumnFormatting, ShouldAutoSize
14
+ {
15
+ protected array $selections = [];
16
+
17
+ public function __construct(array $selections = [])
18
+ {
19
+ $this->selections = $selections;
20
+ }
21
+
22
+ public function map($workOrder): array
23
+ {
24
+ return [
25
+ $workOrder->public_id,
26
+ $workOrder->code,
27
+ $workOrder->subject,
28
+ $workOrder->category,
29
+ $workOrder->status,
30
+ $workOrder->priority,
31
+ $workOrder->target_name,
32
+ $workOrder->assignee_name,
33
+ $workOrder->opened_at,
34
+ $workOrder->due_at,
35
+ $workOrder->closed_at,
36
+ $workOrder->completion_percentage,
37
+ $this->yesNo($workOrder->is_overdue),
38
+ $workOrder->days_until_due,
39
+ $workOrder->created_at,
40
+ $workOrder->updated_at,
41
+ ];
42
+ }
43
+
44
+ public function headings(): array
45
+ {
46
+ return [
47
+ 'ID',
48
+ 'Code',
49
+ 'Subject',
50
+ 'Category',
51
+ 'Status',
52
+ 'Priority',
53
+ 'Target',
54
+ 'Assignee',
55
+ 'Opened At',
56
+ 'Due At',
57
+ 'Closed At',
58
+ 'Completion Percentage',
59
+ 'Overdue',
60
+ 'Days Until Due',
61
+ 'Date Created',
62
+ 'Date Updated',
63
+ ];
64
+ }
65
+
66
+ public function columnFormats(): array
67
+ {
68
+ return [
69
+ 'I' => NumberFormat::FORMAT_DATE_DDMMYYYY,
70
+ 'J' => NumberFormat::FORMAT_DATE_DDMMYYYY,
71
+ 'K' => NumberFormat::FORMAT_DATE_DDMMYYYY,
72
+ 'O' => NumberFormat::FORMAT_DATE_DDMMYYYY,
73
+ 'P' => NumberFormat::FORMAT_DATE_DDMMYYYY,
74
+ ];
75
+ }
76
+
77
+ public function collection()
78
+ {
79
+ $query = WorkOrder::where('company_uuid', session('company'))->with(['target', 'assignee']);
80
+
81
+ if ($this->selections) {
82
+ $query->whereIn('uuid', $this->selections);
83
+ }
84
+
85
+ return $query->get();
86
+ }
87
+
88
+ protected function yesNo($value): string
89
+ {
90
+ return $value ? 'Yes' : 'No';
91
+ }
92
+ }
@@ -0,0 +1,176 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Device;
7
+ use Fleetbase\FleetOps\Models\Driver;
8
+ use Fleetbase\FleetOps\Models\Equipment;
9
+ use Fleetbase\FleetOps\Models\FuelProviderConnection;
10
+ use Fleetbase\FleetOps\Models\FuelReport;
11
+ use Fleetbase\FleetOps\Models\Order;
12
+ use Fleetbase\FleetOps\Models\Part;
13
+ use Fleetbase\FleetOps\Models\Telematic;
14
+ use Fleetbase\FleetOps\Models\Vehicle;
15
+ use Fleetbase\FleetOps\Models\Vendor;
16
+ use Fleetbase\FleetOps\Models\Warranty;
17
+ use Fleetbase\FleetOps\Support\Utils;
18
+ use Fleetbase\Models\File;
19
+ use Illuminate\Database\Eloquent\Model;
20
+ use Illuminate\Database\Eloquent\ModelNotFoundException;
21
+ use Illuminate\Http\Request;
22
+ use Illuminate\Validation\ValidationException;
23
+
24
+ trait ResolvesFleetOpsApiResources
25
+ {
26
+ protected function resolveUuid(string $modelClass, ?string $id): ?string
27
+ {
28
+ if (empty($id)) {
29
+ return null;
30
+ }
31
+
32
+ return $this->resolveModel($modelClass, $id)->uuid;
33
+ }
34
+
35
+ protected function resolveModel(string $modelClass, string $id): Model
36
+ {
37
+ $instance = new $modelClass();
38
+ $query = $modelClass::query()->where(function ($query) use ($id, $instance) {
39
+ $query->whereRaw('0 = 1');
40
+
41
+ if (in_array('public_id', $instance->getFillable()) || method_exists($instance, 'getPublicIdType')) {
42
+ $query->orWhere('public_id', $id);
43
+ }
44
+
45
+ if (in_array('internal_id', $instance->getFillable())) {
46
+ $query->orWhere('internal_id', $id);
47
+ }
48
+ });
49
+
50
+ if (session('company') && $this->modelHasColumn($instance, 'company_uuid')) {
51
+ $query->where($instance->qualifyColumn('company_uuid'), session('company'));
52
+ }
53
+
54
+ $model = $query->first();
55
+ if ($model) {
56
+ return $model;
57
+ }
58
+
59
+ throw (new ModelNotFoundException())->setModel($modelClass, $id);
60
+ }
61
+
62
+ protected function resolveMorph(?string $type, ?string $id): array
63
+ {
64
+ if (empty($type) || empty($id)) {
65
+ return [null, null];
66
+ }
67
+
68
+ $modelClass = Utils::getMutationType($type);
69
+ $model = $this->resolveModel($modelClass, $id);
70
+
71
+ return [$modelClass, $model->uuid];
72
+ }
73
+
74
+ protected function rejectUuidIdentifiers(Request $request): void
75
+ {
76
+ $invalidKeys = array_unique(array_merge(
77
+ $this->collectUuidIdentifierKeys($request->query->all()),
78
+ $this->collectUuidIdentifierKeys($request->request->all())
79
+ ));
80
+
81
+ if (empty($invalidKeys)) {
82
+ return;
83
+ }
84
+
85
+ $messages = [];
86
+ foreach ($invalidKeys as $key) {
87
+ $messages[$key] = ['UUID identifiers are not accepted by the public API. Use public_id or internal_id values instead.'];
88
+ }
89
+
90
+ throw ValidationException::withMessages($messages);
91
+ }
92
+
93
+ protected function collectUuidIdentifierKeys(array $input, string $prefix = ''): array
94
+ {
95
+ $keys = [];
96
+
97
+ foreach ($input as $key => $value) {
98
+ $path = $prefix ? $prefix . '.' . $key : (string) $key;
99
+
100
+ if ($this->isUuidIdentifierKey((string) $key)) {
101
+ $keys[] = $path;
102
+ }
103
+
104
+ if (is_array($value)) {
105
+ $keys = array_merge($keys, $this->collectUuidIdentifierKeys($value, $path));
106
+ }
107
+ }
108
+
109
+ return $keys;
110
+ }
111
+
112
+ protected function isUuidIdentifierKey(string $key): bool
113
+ {
114
+ return preg_match('/(^uuid$|_uuid$|Uuid$|UUID$)/', $key) === 1;
115
+ }
116
+
117
+ protected function applyPublicIdRelation(array &$input, string $requestKey, string $column, string $modelClass, $request): void
118
+ {
119
+ if (!$request->exists($requestKey)) {
120
+ return;
121
+ }
122
+
123
+ $input[$column] = filled($request->input($requestKey))
124
+ ? $this->resolveUuid($modelClass, $request->input($requestKey))
125
+ : null;
126
+ }
127
+
128
+ protected function allowedMorphTypes(): array
129
+ {
130
+ return [
131
+ 'fleet-ops:vehicle' => Vehicle::class,
132
+ 'vehicle' => Vehicle::class,
133
+ Vehicle::class => Vehicle::class,
134
+ 'fleet-ops:driver' => Driver::class,
135
+ 'driver' => Driver::class,
136
+ Driver::class => Driver::class,
137
+ 'fleet-ops:equipment' => Equipment::class,
138
+ 'equipment' => Equipment::class,
139
+ Equipment::class => Equipment::class,
140
+ 'fleet-ops:part' => Part::class,
141
+ 'part' => Part::class,
142
+ Part::class => Part::class,
143
+ 'fleet-ops:vendor' => Vendor::class,
144
+ 'vendor' => Vendor::class,
145
+ Vendor::class => Vendor::class,
146
+ 'fleet-ops:contact' => Contact::class,
147
+ 'contact' => Contact::class,
148
+ Contact::class => Contact::class,
149
+ 'fleet-ops:device' => Device::class,
150
+ 'device' => Device::class,
151
+ Device::class => Device::class,
152
+ 'fleet-ops:telematic' => Telematic::class,
153
+ 'telematic' => Telematic::class,
154
+ Telematic::class => Telematic::class,
155
+ 'fleet-ops:warranty' => Warranty::class,
156
+ 'warranty' => Warranty::class,
157
+ Warranty::class => Warranty::class,
158
+ 'fleet-ops:fuel-report' => FuelReport::class,
159
+ 'fuel-report' => FuelReport::class,
160
+ FuelReport::class => FuelReport::class,
161
+ 'fleet-ops:fuel-provider-connection' => FuelProviderConnection::class,
162
+ 'fuel-provider-connection' => FuelProviderConnection::class,
163
+ FuelProviderConnection::class => FuelProviderConnection::class,
164
+ 'fleet-ops:order' => Order::class,
165
+ 'order' => Order::class,
166
+ Order::class => Order::class,
167
+ 'file' => File::class,
168
+ File::class => File::class,
169
+ ];
170
+ }
171
+
172
+ protected function modelHasColumn(Model $model, string $column): bool
173
+ {
174
+ return in_array($column, $model->getFillable()) || $column === $model->getKeyName();
175
+ }
176
+ }
@@ -0,0 +1,225 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
4
+
5
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns\ResolvesFleetOpsApiResources;
6
+ use Fleetbase\FleetOps\Http\Requests\CreateDeviceRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdateDeviceRequest;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\Device as DeviceResource;
10
+ use Fleetbase\FleetOps\Models\Device;
11
+ use Fleetbase\FleetOps\Models\Telematic;
12
+ use Fleetbase\FleetOps\Models\Vehicle;
13
+ use Fleetbase\FleetOps\Models\Warranty;
14
+ use Fleetbase\FleetOps\Support\Utils;
15
+ use Fleetbase\Http\Controllers\Controller;
16
+ use Fleetbase\LaravelMysqlSpatial\Types\Point;
17
+ use Fleetbase\Models\File;
18
+ use Illuminate\Http\JsonResponse;
19
+ use Illuminate\Http\Request;
20
+ use Illuminate\Support\Facades\Log;
21
+
22
+ class DeviceController extends Controller
23
+ {
24
+ use ResolvesFleetOpsApiResources;
25
+
26
+ public function create(CreateDeviceRequest $request)
27
+ {
28
+ $this->rejectUuidIdentifiers($request);
29
+
30
+ $input = $this->input($request);
31
+ $input['company_uuid'] = session('company');
32
+
33
+ $device = Device::create($input)->load(['telematic', 'warranty', 'attachable', 'photo'])->loadCount('sensors');
34
+
35
+ return new DeviceResource($device);
36
+ }
37
+
38
+ public function update(string $id, UpdateDeviceRequest $request)
39
+ {
40
+ $this->rejectUuidIdentifiers($request);
41
+
42
+ try {
43
+ $device = $this->resolveModel(Device::class, $id);
44
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
45
+ return response()->json(['error' => 'Device resource not found.'], 404);
46
+ }
47
+
48
+ $device->update($this->input($request));
49
+
50
+ return new DeviceResource($device->refresh()->load(['telematic', 'warranty', 'attachable', 'photo'])->loadCount('sensors'));
51
+ }
52
+
53
+ public function query(Request $request)
54
+ {
55
+ $this->rejectUuidIdentifiers($request);
56
+
57
+ $results = Device::queryWithRequest($request, function (&$query) {
58
+ $query->with(['telematic', 'warranty', 'attachable', 'photo'])->withCount('sensors');
59
+ });
60
+
61
+ return DeviceResource::collection($results);
62
+ }
63
+
64
+ public function find(string $id)
65
+ {
66
+ try {
67
+ $device = $this->resolveModel(Device::class, $id)->load(['telematic', 'warranty', 'attachable', 'photo']);
68
+ $device->loadCount('sensors');
69
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
70
+ return response()->json(['error' => 'Device resource not found.'], 404);
71
+ }
72
+
73
+ return new DeviceResource($device);
74
+ }
75
+
76
+ public function delete(string $id)
77
+ {
78
+ try {
79
+ $device = $this->resolveModel(Device::class, $id);
80
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
81
+ return response()->json(['error' => 'Device resource not found.'], 404);
82
+ }
83
+
84
+ $device->delete();
85
+
86
+ return new DeletedResource($device);
87
+ }
88
+
89
+ public function attach(Request $request, string $id): JsonResponse
90
+ {
91
+ $this->rejectUuidIdentifiers($request);
92
+
93
+ $request->validate([
94
+ 'vehicle' => 'required_without:attachable|string|nullable',
95
+ ]);
96
+
97
+ $deviceId = $id;
98
+ $vehicleId = $request->input('vehicle') ?? $request->input('attachable');
99
+
100
+ try {
101
+ $device = $this->resolveModel(Device::class, $deviceId);
102
+ $vehicle = $this->resolveModel(Vehicle::class, $vehicleId);
103
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
104
+ $this->logDeviceAttachmentLookupFailure('attach', $deviceId, $vehicleId);
105
+
106
+ return response()->json(['error' => 'Device or vehicle resource not found.'], 404);
107
+ }
108
+
109
+ try {
110
+ $device->attachTo($vehicle);
111
+ $device->load(['telematic', 'warranty', 'attachable', 'photo'])->loadCount('sensors');
112
+ } catch (\Throwable $e) {
113
+ $this->logDeviceAttachmentFailure('attach', $device, $vehicle, $e);
114
+
115
+ return response()->json(['error' => 'Unable to attach device to vehicle.'], 500);
116
+ }
117
+
118
+ return response()->json([
119
+ 'status' => 'ok',
120
+ 'device' => new DeviceResource($device),
121
+ ]);
122
+ }
123
+
124
+ public function detach(string $id): JsonResponse
125
+ {
126
+ try {
127
+ $device = $this->resolveModel(Device::class, $id);
128
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
129
+ $this->logDeviceAttachmentLookupFailure('detach', $id, null);
130
+
131
+ return response()->json(['error' => 'Device resource not found.'], 404);
132
+ }
133
+
134
+ try {
135
+ $device->detach();
136
+ $device->load(['telematic', 'warranty', 'attachable', 'photo'])->loadCount('sensors');
137
+ } catch (\Throwable $e) {
138
+ $this->logDeviceAttachmentFailure('detach', $device, null, $e);
139
+
140
+ return response()->json(['error' => 'Unable to detach device from vehicle.'], 500);
141
+ }
142
+
143
+ return response()->json([
144
+ 'status' => 'ok',
145
+ 'device' => new DeviceResource($device),
146
+ ]);
147
+ }
148
+
149
+ protected function input(Request $request): array
150
+ {
151
+ $input = $request->only([
152
+ 'type',
153
+ 'device_id',
154
+ 'internal_id',
155
+ 'imei',
156
+ 'imsi',
157
+ 'firmware_version',
158
+ 'provider',
159
+ 'name',
160
+ 'model',
161
+ 'location',
162
+ 'manufacturer',
163
+ 'serial_number',
164
+ 'installation_date',
165
+ 'last_maintenance_date',
166
+ 'meta',
167
+ 'data',
168
+ 'options',
169
+ 'online',
170
+ 'status',
171
+ 'data_frequency',
172
+ 'notes',
173
+ 'last_online_at',
174
+ ]);
175
+
176
+ if ($request->filled(['latitude', 'longitude'])) {
177
+ $input['last_position'] = new Point($request->input('latitude'), $request->input('longitude'));
178
+ } elseif ($request->filled('last_position')) {
179
+ $input['last_position'] = Utils::getPointFromCoordinates($request->input('last_position'));
180
+ }
181
+
182
+ $this->applyPublicIdRelation($input, 'telematic', 'telematic_uuid', Telematic::class, $request);
183
+ $this->applyPublicIdRelation($input, 'warranty', 'warranty_uuid', Warranty::class, $request);
184
+ $this->applyPublicIdRelation($input, 'photo', 'photo_uuid', File::class, $request);
185
+
186
+ if ($request->exists('attachable')) {
187
+ if (blank($request->input('attachable'))) {
188
+ $input['attachable_type'] = null;
189
+ $input['attachable_uuid'] = null;
190
+ } else {
191
+ [$type, $uuid] = $this->resolveMorph($request->input('attachable_type'), $request->input('attachable'));
192
+ $input['attachable_type'] = $type;
193
+ $input['attachable_uuid'] = $uuid;
194
+ }
195
+ }
196
+
197
+ return $input;
198
+ }
199
+
200
+ protected function logDeviceAttachmentLookupFailure(string $action, string $deviceId, ?string $vehicleId): void
201
+ {
202
+ Log::warning('Public API device attachment lookup failed', [
203
+ 'action' => $action,
204
+ 'device_id' => $deviceId,
205
+ 'vehicle_id' => $vehicleId,
206
+ 'company_uuid' => session('company'),
207
+ 'request_id' => request()->headers->get('X-Request-ID') ?? request()->headers->get('X-Correlation-ID'),
208
+ ]);
209
+ }
210
+
211
+ protected function logDeviceAttachmentFailure(string $action, Device $device, ?Vehicle $vehicle, \Throwable $exception): void
212
+ {
213
+ Log::error('Public API device attachment failed', [
214
+ 'action' => $action,
215
+ 'device_uuid' => $device->uuid,
216
+ 'device_id' => $device->public_id,
217
+ 'vehicle_uuid' => $vehicle?->uuid,
218
+ 'vehicle_id' => $vehicle?->public_id,
219
+ 'company_uuid' => session('company'),
220
+ 'request_id' => request()->headers->get('X-Request-ID') ?? request()->headers->get('X-Correlation-ID'),
221
+ 'exception_class' => get_class($exception),
222
+ 'exception' => $exception->getMessage(),
223
+ ]);
224
+ }
225
+ }
@@ -0,0 +1,114 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
4
+
5
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns\ResolvesFleetOpsApiResources;
6
+ use Fleetbase\FleetOps\Http\Requests\CreateEquipmentRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdateEquipmentRequest;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\Equipment as EquipmentResource;
10
+ use Fleetbase\FleetOps\Models\Equipment;
11
+ use Fleetbase\FleetOps\Models\Warranty;
12
+ use Fleetbase\Http\Controllers\Controller;
13
+ use Fleetbase\Models\File;
14
+ use Illuminate\Http\Request;
15
+
16
+ class EquipmentController extends Controller
17
+ {
18
+ use ResolvesFleetOpsApiResources;
19
+
20
+ public function create(CreateEquipmentRequest $request)
21
+ {
22
+ $this->rejectUuidIdentifiers($request);
23
+
24
+ $input = $this->input($request);
25
+ $input['company_uuid'] = session('company');
26
+
27
+ $equipment = Equipment::create($input)->load(['warranty', 'photo', 'equipable']);
28
+
29
+ return new EquipmentResource($equipment);
30
+ }
31
+
32
+ public function update(string $id, UpdateEquipmentRequest $request)
33
+ {
34
+ $this->rejectUuidIdentifiers($request);
35
+
36
+ try {
37
+ $equipment = $this->resolveModel(Equipment::class, $id);
38
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
39
+ return response()->json(['error' => 'Equipment resource not found.'], 404);
40
+ }
41
+
42
+ $equipment->update($this->input($request));
43
+
44
+ return new EquipmentResource($equipment->refresh()->load(['warranty', 'photo', 'equipable']));
45
+ }
46
+
47
+ public function query(Request $request)
48
+ {
49
+ $this->rejectUuidIdentifiers($request);
50
+
51
+ $results = Equipment::queryWithRequest($request, function (&$query) {
52
+ $query->with(['warranty', 'photo', 'equipable']);
53
+ });
54
+
55
+ return EquipmentResource::collection($results);
56
+ }
57
+
58
+ public function find(string $id)
59
+ {
60
+ try {
61
+ $equipment = $this->resolveModel(Equipment::class, $id)->load(['warranty', 'photo', 'equipable']);
62
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
63
+ return response()->json(['error' => 'Equipment resource not found.'], 404);
64
+ }
65
+
66
+ return new EquipmentResource($equipment);
67
+ }
68
+
69
+ public function delete(string $id)
70
+ {
71
+ try {
72
+ $equipment = $this->resolveModel(Equipment::class, $id);
73
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
74
+ return response()->json(['error' => 'Equipment resource not found.'], 404);
75
+ }
76
+
77
+ $equipment->delete();
78
+
79
+ return new DeletedResource($equipment);
80
+ }
81
+
82
+ protected function input(Request $request): array
83
+ {
84
+ $input = $request->only([
85
+ 'name',
86
+ 'code',
87
+ 'type',
88
+ 'status',
89
+ 'serial_number',
90
+ 'manufacturer',
91
+ 'model',
92
+ 'purchased_at',
93
+ 'purchase_price',
94
+ 'currency',
95
+ 'meta',
96
+ ]);
97
+
98
+ $this->applyPublicIdRelation($input, 'warranty', 'warranty_uuid', Warranty::class, $request);
99
+ $this->applyPublicIdRelation($input, 'photo', 'photo_uuid', File::class, $request);
100
+
101
+ if ($request->exists('equipable')) {
102
+ if (blank($request->input('equipable'))) {
103
+ $input['equipable_type'] = null;
104
+ $input['equipable_uuid'] = null;
105
+ } else {
106
+ [$type, $uuid] = $this->resolveMorph($request->input('equipable_type'), $request->input('equipable'));
107
+ $input['equipable_type'] = $type;
108
+ $input['equipable_uuid'] = $uuid;
109
+ }
110
+ }
111
+
112
+ return $input;
113
+ }
114
+ }