@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,185 @@
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\CreateFuelTransactionRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdateFuelTransactionRequest;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\FuelTransaction as FuelTransactionResource;
10
+ use Fleetbase\FleetOps\Models\Driver;
11
+ use Fleetbase\FleetOps\Models\FuelProviderConnection;
12
+ use Fleetbase\FleetOps\Models\FuelProviderTransaction;
13
+ use Fleetbase\FleetOps\Models\FuelReport;
14
+ use Fleetbase\FleetOps\Models\Order;
15
+ use Fleetbase\FleetOps\Models\Vehicle;
16
+ use Fleetbase\FleetOps\Support\FuelProviders\FuelProviderService;
17
+ use Fleetbase\Http\Controllers\Controller;
18
+ use Illuminate\Http\JsonResponse;
19
+ use Illuminate\Http\Request;
20
+
21
+ class FuelTransactionController extends Controller
22
+ {
23
+ use ResolvesFleetOpsApiResources;
24
+
25
+ public function __construct(protected FuelProviderService $fuelProviderService)
26
+ {
27
+ }
28
+
29
+ public function create(CreateFuelTransactionRequest $request)
30
+ {
31
+ $this->rejectUuidIdentifiers($request);
32
+
33
+ $input = $this->input($request);
34
+ $input['company_uuid'] = session('company');
35
+
36
+ $transaction = FuelProviderTransaction::create($input)->load(['connection', 'vehicle', 'driver', 'order', 'fuelReport']);
37
+
38
+ return new FuelTransactionResource($transaction);
39
+ }
40
+
41
+ public function update(string $id, UpdateFuelTransactionRequest $request)
42
+ {
43
+ $this->rejectUuidIdentifiers($request);
44
+
45
+ try {
46
+ $transaction = $this->findTransaction($id);
47
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
48
+ return response()->json(['error' => 'FuelTransaction resource not found.'], 404);
49
+ }
50
+
51
+ $transaction->update($this->input($request));
52
+
53
+ return new FuelTransactionResource($transaction->refresh()->load(['connection', 'vehicle', 'driver', 'order', 'fuelReport']));
54
+ }
55
+
56
+ public function query(Request $request)
57
+ {
58
+ $this->rejectUuidIdentifiers($request);
59
+
60
+ $results = FuelProviderTransaction::queryWithRequest($request, function (&$query) {
61
+ $query->with(['connection', 'vehicle', 'driver', 'order', 'fuelReport']);
62
+ });
63
+
64
+ return FuelTransactionResource::collection($results);
65
+ }
66
+
67
+ public function find(string $id)
68
+ {
69
+ try {
70
+ $transaction = $this->findTransaction($id)->load(['connection', 'vehicle', 'driver', 'order', 'fuelReport']);
71
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
72
+ return response()->json(['error' => 'FuelTransaction resource not found.'], 404);
73
+ }
74
+
75
+ return new FuelTransactionResource($transaction);
76
+ }
77
+
78
+ public function delete(string $id)
79
+ {
80
+ try {
81
+ $transaction = $this->findTransaction($id);
82
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
83
+ return response()->json(['error' => 'FuelTransaction resource not found.'], 404);
84
+ }
85
+
86
+ $transaction->delete();
87
+
88
+ return new DeletedResource($transaction);
89
+ }
90
+
91
+ public function matchVehicle(Request $request, string $id): JsonResponse
92
+ {
93
+ $this->rejectUuidIdentifiers($request);
94
+
95
+ $request->validate(['vehicle' => 'required|string']);
96
+ $transaction = $this->findTransaction($id);
97
+ $vehicle = $this->resolveModel(Vehicle::class, $request->input('vehicle'));
98
+
99
+ return response()->json([
100
+ 'status' => 'ok',
101
+ 'transaction' => new FuelTransactionResource($this->fuelProviderService->matchVehicle($transaction, $vehicle)),
102
+ ]);
103
+ }
104
+
105
+ public function matchOrder(Request $request, string $id): JsonResponse
106
+ {
107
+ $this->rejectUuidIdentifiers($request);
108
+
109
+ $request->validate(['order' => 'required|string']);
110
+ $transaction = $this->findTransaction($id);
111
+ $order = $this->resolveModel(Order::class, $request->input('order'));
112
+
113
+ return response()->json([
114
+ 'status' => 'ok',
115
+ 'transaction' => new FuelTransactionResource($this->fuelProviderService->matchOrder($transaction, $order)),
116
+ ]);
117
+ }
118
+
119
+ public function reprocess(string $id): JsonResponse
120
+ {
121
+ $transaction = $this->findTransaction($id);
122
+
123
+ return response()->json([
124
+ 'status' => 'ok',
125
+ 'transaction' => new FuelTransactionResource($this->fuelProviderService->reprocessTransaction($transaction)),
126
+ ]);
127
+ }
128
+
129
+ public function review(Request $request, string $id): JsonResponse
130
+ {
131
+ $this->rejectUuidIdentifiers($request);
132
+
133
+ $request->validate(['status' => 'required|string|in:reviewed,ignored']);
134
+ $transaction = $this->findTransaction($id);
135
+
136
+ return response()->json([
137
+ 'status' => 'ok',
138
+ 'transaction' => new FuelTransactionResource($this->fuelProviderService->reviewTransaction($transaction, $request->input('status'))),
139
+ ]);
140
+ }
141
+
142
+ protected function findTransaction(string $id): FuelProviderTransaction
143
+ {
144
+ return $this->resolveModel(FuelProviderTransaction::class, $id);
145
+ }
146
+
147
+ protected function input(Request $request): array
148
+ {
149
+ $input = $request->only([
150
+ 'provider',
151
+ 'provider_transaction_id',
152
+ 'provider_vehicle_id',
153
+ 'vehicle_card_id',
154
+ 'internal_number',
155
+ 'structure_number',
156
+ 'plate_number',
157
+ 'vin',
158
+ 'serial_number',
159
+ 'call_sign',
160
+ 'trip_number',
161
+ 'station_name',
162
+ 'station_latitude',
163
+ 'station_longitude',
164
+ 'transaction_at',
165
+ 'volume',
166
+ 'metric_unit',
167
+ 'amount',
168
+ 'currency',
169
+ 'odometer',
170
+ 'sync_status',
171
+ 'matched_at',
172
+ 'normalized_payload',
173
+ 'raw_payload',
174
+ 'meta',
175
+ ]);
176
+
177
+ $this->applyPublicIdRelation($input, 'connection', 'fuel_provider_connection_uuid', FuelProviderConnection::class, $request);
178
+ $this->applyPublicIdRelation($input, 'fuel_report', 'fuel_report_uuid', FuelReport::class, $request);
179
+ $this->applyPublicIdRelation($input, 'vehicle', 'vehicle_uuid', Vehicle::class, $request);
180
+ $this->applyPublicIdRelation($input, 'driver', 'driver_uuid', Driver::class, $request);
181
+ $this->applyPublicIdRelation($input, 'order', 'order_uuid', Order::class, $request);
182
+
183
+ return $input;
184
+ }
185
+ }
@@ -0,0 +1,120 @@
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\CreatePartRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdatePartRequest;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\Part as PartResource;
10
+ use Fleetbase\FleetOps\Models\Part;
11
+ use Fleetbase\FleetOps\Models\Vendor;
12
+ use Fleetbase\FleetOps\Models\Warranty;
13
+ use Fleetbase\Http\Controllers\Controller;
14
+ use Fleetbase\Models\File;
15
+ use Illuminate\Http\Request;
16
+
17
+ class PartController extends Controller
18
+ {
19
+ use ResolvesFleetOpsApiResources;
20
+
21
+ public function create(CreatePartRequest $request)
22
+ {
23
+ $this->rejectUuidIdentifiers($request);
24
+
25
+ $input = $this->input($request);
26
+ $input['company_uuid'] = session('company');
27
+
28
+ $part = Part::create($input)->load(['vendor', 'warranty', 'photo', 'asset']);
29
+
30
+ return new PartResource($part);
31
+ }
32
+
33
+ public function update(string $id, UpdatePartRequest $request)
34
+ {
35
+ $this->rejectUuidIdentifiers($request);
36
+
37
+ try {
38
+ $part = $this->resolveModel(Part::class, $id);
39
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
40
+ return response()->json(['error' => 'Part resource not found.'], 404);
41
+ }
42
+
43
+ $part->update($this->input($request));
44
+
45
+ return new PartResource($part->refresh()->load(['vendor', 'warranty', 'photo', 'asset']));
46
+ }
47
+
48
+ public function query(Request $request)
49
+ {
50
+ $this->rejectUuidIdentifiers($request);
51
+
52
+ $results = Part::queryWithRequest($request, function (&$query) {
53
+ $query->with(['vendor', 'warranty', 'photo', 'asset']);
54
+ });
55
+
56
+ return PartResource::collection($results);
57
+ }
58
+
59
+ public function find(string $id)
60
+ {
61
+ try {
62
+ $part = $this->resolveModel(Part::class, $id)->load(['vendor', 'warranty', 'photo', 'asset']);
63
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
64
+ return response()->json(['error' => 'Part resource not found.'], 404);
65
+ }
66
+
67
+ return new PartResource($part);
68
+ }
69
+
70
+ public function delete(string $id)
71
+ {
72
+ try {
73
+ $part = $this->resolveModel(Part::class, $id);
74
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
75
+ return response()->json(['error' => 'Part resource not found.'], 404);
76
+ }
77
+
78
+ $part->delete();
79
+
80
+ return new DeletedResource($part);
81
+ }
82
+
83
+ protected function input(Request $request): array
84
+ {
85
+ $input = $request->only([
86
+ 'sku',
87
+ 'name',
88
+ 'manufacturer',
89
+ 'model',
90
+ 'serial_number',
91
+ 'barcode',
92
+ 'description',
93
+ 'quantity_on_hand',
94
+ 'unit_cost',
95
+ 'msrp',
96
+ 'currency',
97
+ 'type',
98
+ 'status',
99
+ 'specs',
100
+ 'meta',
101
+ ]);
102
+
103
+ $this->applyPublicIdRelation($input, 'vendor', 'vendor_uuid', Vendor::class, $request);
104
+ $this->applyPublicIdRelation($input, 'warranty', 'warranty_uuid', Warranty::class, $request);
105
+ $this->applyPublicIdRelation($input, 'photo', 'photo_uuid', File::class, $request);
106
+
107
+ if ($request->exists('asset')) {
108
+ if (blank($request->input('asset'))) {
109
+ $input['asset_type'] = null;
110
+ $input['asset_uuid'] = null;
111
+ } else {
112
+ [$type, $uuid] = $this->resolveMorph($request->input('asset_type'), $request->input('asset'));
113
+ $input['asset_type'] = $type;
114
+ $input['asset_uuid'] = $uuid;
115
+ }
116
+ }
117
+
118
+ return $input;
119
+ }
120
+ }
@@ -0,0 +1,132 @@
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\CreateSensorRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdateSensorRequest;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\Sensor as SensorResource;
10
+ use Fleetbase\FleetOps\Models\Device;
11
+ use Fleetbase\FleetOps\Models\Sensor;
12
+ use Fleetbase\FleetOps\Models\Telematic;
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\Request;
19
+
20
+ class SensorController extends Controller
21
+ {
22
+ use ResolvesFleetOpsApiResources;
23
+
24
+ public function create(CreateSensorRequest $request)
25
+ {
26
+ $this->rejectUuidIdentifiers($request);
27
+
28
+ $input = $this->input($request);
29
+ $input['company_uuid'] = session('company');
30
+
31
+ $sensor = Sensor::create($input)->load(['telematic', 'device', 'warranty', 'photo', 'sensorable']);
32
+
33
+ return new SensorResource($sensor);
34
+ }
35
+
36
+ public function update(string $id, UpdateSensorRequest $request)
37
+ {
38
+ $this->rejectUuidIdentifiers($request);
39
+
40
+ try {
41
+ $sensor = $this->resolveModel(Sensor::class, $id);
42
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
43
+ return response()->json(['error' => 'Sensor resource not found.'], 404);
44
+ }
45
+
46
+ $sensor->update($this->input($request));
47
+
48
+ return new SensorResource($sensor->refresh()->load(['telematic', 'device', 'warranty', 'photo', 'sensorable']));
49
+ }
50
+
51
+ public function query(Request $request)
52
+ {
53
+ $this->rejectUuidIdentifiers($request);
54
+
55
+ $results = Sensor::queryWithRequest($request, function (&$query) {
56
+ $query->with(['telematic', 'device', 'warranty', 'photo', 'sensorable']);
57
+ });
58
+
59
+ return SensorResource::collection($results);
60
+ }
61
+
62
+ public function find(string $id)
63
+ {
64
+ try {
65
+ $sensor = $this->resolveModel(Sensor::class, $id)->load(['telematic', 'device', 'warranty', 'photo', 'sensorable']);
66
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
67
+ return response()->json(['error' => 'Sensor resource not found.'], 404);
68
+ }
69
+
70
+ return new SensorResource($sensor);
71
+ }
72
+
73
+ public function delete(string $id)
74
+ {
75
+ try {
76
+ $sensor = $this->resolveModel(Sensor::class, $id);
77
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
78
+ return response()->json(['error' => 'Sensor resource not found.'], 404);
79
+ }
80
+
81
+ $sensor->delete();
82
+
83
+ return new DeletedResource($sensor);
84
+ }
85
+
86
+ protected function input(Request $request): array
87
+ {
88
+ $input = $request->only([
89
+ 'name',
90
+ 'type',
91
+ 'internal_id',
92
+ 'imei',
93
+ 'imsi',
94
+ 'firmware_version',
95
+ 'serial_number',
96
+ 'unit',
97
+ 'min_threshold',
98
+ 'max_threshold',
99
+ 'threshold_inclusive',
100
+ 'last_reading_at',
101
+ 'last_value',
102
+ 'calibration',
103
+ 'report_frequency_sec',
104
+ 'status',
105
+ 'meta',
106
+ ]);
107
+
108
+ if ($request->filled(['latitude', 'longitude'])) {
109
+ $input['last_position'] = new Point($request->input('latitude'), $request->input('longitude'));
110
+ } elseif ($request->filled('last_position')) {
111
+ $input['last_position'] = Utils::getPointFromCoordinates($request->input('last_position'));
112
+ }
113
+
114
+ $this->applyPublicIdRelation($input, 'device', 'device_uuid', Device::class, $request);
115
+ $this->applyPublicIdRelation($input, 'telematic', 'telematic_uuid', Telematic::class, $request);
116
+ $this->applyPublicIdRelation($input, 'warranty', 'warranty_uuid', Warranty::class, $request);
117
+ $this->applyPublicIdRelation($input, 'photo', 'photo_uuid', File::class, $request);
118
+
119
+ if ($request->exists('sensorable')) {
120
+ if (blank($request->input('sensorable'))) {
121
+ $input['sensorable_type'] = null;
122
+ $input['sensorable_uuid'] = null;
123
+ } else {
124
+ [$type, $uuid] = $this->resolveMorph($request->input('sensorable_type'), $request->input('sensorable'));
125
+ $input['sensorable_type'] = $type;
126
+ $input['sensorable_uuid'] = $uuid;
127
+ }
128
+ }
129
+
130
+ return $input;
131
+ }
132
+ }
@@ -0,0 +1,161 @@
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\CreateWorkOrderRequest;
7
+ use Fleetbase\FleetOps\Http\Requests\UpdateWorkOrderRequest;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\WorkOrder as WorkOrderResource;
10
+ use Fleetbase\FleetOps\Mail\WorkOrderDispatched;
11
+ use Fleetbase\FleetOps\Models\WorkOrder;
12
+ use Fleetbase\Http\Controllers\Controller;
13
+ use Illuminate\Http\JsonResponse;
14
+ use Illuminate\Http\Request;
15
+ use Illuminate\Support\Facades\Mail;
16
+
17
+ class WorkOrderController extends Controller
18
+ {
19
+ use ResolvesFleetOpsApiResources;
20
+
21
+ public function create(CreateWorkOrderRequest $request)
22
+ {
23
+ $this->rejectUuidIdentifiers($request);
24
+
25
+ $input = $this->input($request);
26
+ $input['company_uuid'] = session('company');
27
+
28
+ $workOrder = WorkOrder::create($input)->load(['target', 'assignee']);
29
+
30
+ return new WorkOrderResource($workOrder);
31
+ }
32
+
33
+ public function update(string $id, UpdateWorkOrderRequest $request)
34
+ {
35
+ $this->rejectUuidIdentifiers($request);
36
+
37
+ try {
38
+ $workOrder = $this->resolveModel(WorkOrder::class, $id);
39
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
40
+ return response()->json(['error' => 'WorkOrder resource not found.'], 404);
41
+ }
42
+
43
+ $workOrder->update($this->input($request));
44
+
45
+ return new WorkOrderResource($workOrder->refresh()->load(['target', 'assignee']));
46
+ }
47
+
48
+ public function query(Request $request)
49
+ {
50
+ $this->rejectUuidIdentifiers($request);
51
+
52
+ $results = WorkOrder::queryWithRequest($request, function (&$query) {
53
+ $query->with(['target', 'assignee']);
54
+ });
55
+
56
+ return WorkOrderResource::collection($results);
57
+ }
58
+
59
+ public function find(string $id)
60
+ {
61
+ try {
62
+ $workOrder = $this->resolveModel(WorkOrder::class, $id)->load(['target', 'assignee']);
63
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
64
+ return response()->json(['error' => 'WorkOrder resource not found.'], 404);
65
+ }
66
+
67
+ return new WorkOrderResource($workOrder);
68
+ }
69
+
70
+ public function delete(string $id)
71
+ {
72
+ try {
73
+ $workOrder = $this->resolveModel(WorkOrder::class, $id);
74
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
75
+ return response()->json(['error' => 'WorkOrder resource not found.'], 404);
76
+ }
77
+
78
+ $workOrder->delete();
79
+
80
+ return new DeletedResource($workOrder);
81
+ }
82
+
83
+ public function send(string $id): JsonResponse
84
+ {
85
+ try {
86
+ $workOrder = $this->resolveModel(WorkOrder::class, $id)->load(['target', 'assignee']);
87
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
88
+ return response()->json(['error' => 'WorkOrder resource not found.'], 404);
89
+ }
90
+
91
+ $assignee = $workOrder->assignee;
92
+ if (!$assignee) {
93
+ return response()->json(['error' => 'This work order has no assigned vendor.'], 422);
94
+ }
95
+
96
+ $email = $assignee->email ?? null;
97
+ if (!$email) {
98
+ return response()->json(['error' => 'The assigned vendor has no email address on file.'], 422);
99
+ }
100
+
101
+ Mail::to($email)->send(new WorkOrderDispatched($workOrder));
102
+
103
+ activity('work_order_sent')
104
+ ->performedOn($workOrder)
105
+ ->withProperties(['sent_to' => $email])
106
+ ->log('Work order emailed to vendor');
107
+
108
+ return response()->json([
109
+ 'status' => 'ok',
110
+ 'message' => 'Work order successfully sent to ' . $email,
111
+ ]);
112
+ }
113
+
114
+ protected function input(Request $request): array
115
+ {
116
+ $input = $request->only([
117
+ 'code',
118
+ 'subject',
119
+ 'category',
120
+ 'status',
121
+ 'priority',
122
+ 'opened_at',
123
+ 'due_at',
124
+ 'closed_at',
125
+ 'instructions',
126
+ 'checklist',
127
+ 'estimated_cost',
128
+ 'approved_budget',
129
+ 'actual_cost',
130
+ 'currency',
131
+ 'cost_breakdown',
132
+ 'cost_center',
133
+ 'budget_code',
134
+ 'meta',
135
+ ]);
136
+
137
+ if ($request->exists('target')) {
138
+ if (blank($request->input('target'))) {
139
+ $input['target_type'] = null;
140
+ $input['target_uuid'] = null;
141
+ } else {
142
+ [$type, $uuid] = $this->resolveMorph($request->input('target_type'), $request->input('target'));
143
+ $input['target_type'] = $type;
144
+ $input['target_uuid'] = $uuid;
145
+ }
146
+ }
147
+
148
+ if ($request->exists('assignee')) {
149
+ if (blank($request->input('assignee'))) {
150
+ $input['assignee_type'] = null;
151
+ $input['assignee_uuid'] = null;
152
+ } else {
153
+ [$type, $uuid] = $this->resolveMorph($request->input('assignee_type'), $request->input('assignee'));
154
+ $input['assignee_type'] = $type;
155
+ $input['assignee_uuid'] = $uuid;
156
+ }
157
+ }
158
+
159
+ return $input;
160
+ }
161
+ }
@@ -2,13 +2,17 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
+ use Fleetbase\FleetOps\Exports\DeviceExport;
5
6
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
6
7
  use Fleetbase\FleetOps\Models\Device;
7
8
  use Fleetbase\FleetOps\Models\Vehicle;
8
9
  use Fleetbase\FleetOps\Support\Utils;
10
+ use Fleetbase\Http\Requests\ExportRequest;
9
11
  use Illuminate\Http\JsonResponse;
10
12
  use Illuminate\Http\Request;
11
13
  use Illuminate\Support\Facades\Log;
14
+ use Illuminate\Support\Str;
15
+ use Maatwebsite\Excel\Facades\Excel;
12
16
 
13
17
  class DeviceController extends FleetOpsController
14
18
  {
@@ -19,6 +23,20 @@ class DeviceController extends FleetOpsController
19
23
  */
20
24
  public $resource = 'device';
21
25
 
26
+ /**
27
+ * Export devices to excel or csv.
28
+ *
29
+ * @return \Illuminate\Http\Response
30
+ */
31
+ public function export(ExportRequest $request)
32
+ {
33
+ $format = $request->input('format', 'xlsx');
34
+ $selections = $request->array('selections');
35
+ $fileName = trim(Str::slug('devices-' . date('Y-m-d-H:i')) . '.' . $format);
36
+
37
+ return Excel::download(new DeviceExport($selections), $fileName);
38
+ }
39
+
22
40
  /**
23
41
  * Query callback when querying record.
24
42
  *
@@ -28,6 +46,7 @@ class DeviceController extends FleetOpsController
28
46
  public static function onQueryRecord($query, $request): void
29
47
  {
30
48
  $query->with(['telematic', 'warranty', 'attachable']);
49
+ $query->withCount('sensors');
31
50
 
32
51
  if ($request->filled('attachment_state')) {
33
52
  match ($request->input('attachment_state')) {
@@ -38,13 +57,17 @@ class DeviceController extends FleetOpsController
38
57
  }
39
58
 
40
59
  if ($request->filled('vehicle')) {
41
- $query->where('attachable_uuid', $request->input('vehicle'));
60
+ static::applyVehicleFilter($query, $request->input('vehicle'));
42
61
  }
43
62
 
44
63
  if ($request->filled('device_id')) {
45
64
  $query->where('device_id', 'like', '%' . $request->input('device_id') . '%');
46
65
  }
47
66
 
67
+ if ($request->filled('serial_number')) {
68
+ $query->where('serial_number', 'like', '%' . $request->input('serial_number') . '%');
69
+ }
70
+
48
71
  if ($request->filled('connection_status')) {
49
72
  $statuses = Utils::arrayFrom($request->input('connection_status'));
50
73
 
@@ -80,6 +103,7 @@ class DeviceController extends FleetOpsController
80
103
  public static function onFindRecord($query, $request): void
81
104
  {
82
105
  $query->with(['telematic', 'warranty', 'attachable']);
106
+ $query->withCount('sensors');
83
107
  }
84
108
 
85
109
  /**
@@ -183,6 +207,17 @@ class DeviceController extends FleetOpsController
183
207
  }
184
208
  }
185
209
 
210
+ protected static function applyVehicleFilter($query, string $vehicle): void
211
+ {
212
+ $query->where(function ($vehicleQuery) use ($vehicle) {
213
+ $vehicleQuery->where('attachable_uuid', $vehicle)
214
+ ->orWhereIn('attachable_uuid', Vehicle::query()
215
+ ->where('company_uuid', session('company'))
216
+ ->where('public_id', $vehicle)
217
+ ->pluck('uuid'));
218
+ });
219
+ }
220
+
186
221
  protected function logDeviceAttachmentLookupFailure(string $action, string $missingResource, string $deviceId, ?string $vehicleId): void
187
222
  {
188
223
  Log::warning('Device attachment lookup failed', [