@fleetbase/fleetops-engine 0.6.55 → 0.6.57

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 (161) hide show
  1. package/addon/components/admin/map-settings.hbs +1 -1
  2. package/addon/components/ai/create-order-preview.hbs +297 -0
  3. package/addon/components/ai/create-order-preview.js +362 -0
  4. package/addon/components/ai/route-preview-map.hbs +79 -0
  5. package/addon/components/ai/route-preview-map.js +527 -0
  6. package/addon/components/device/details.js +1 -1
  7. package/addon/components/device-event/details.hbs +192 -0
  8. package/addon/components/device-event/details.js +151 -0
  9. package/addon/components/driver/schedule.js +1 -1
  10. package/addon/components/fleet/details.hbs +21 -8
  11. package/addon/components/fleet/details.js +55 -1
  12. package/addon/components/fleet/hierarchy-tree.hbs +137 -0
  13. package/addon/components/fleet/hierarchy-tree.js +528 -0
  14. package/addon/components/layout/fleet-ops-sidebar.js +15 -9
  15. package/addon/components/map/drawer/device-event-listing.js +1 -0
  16. package/addon/components/map/drawer/driver-listing.js +1 -0
  17. package/addon/components/map/drawer/place-listing.js +1 -0
  18. package/addon/components/map/drawer/vehicle-listing.js +1 -0
  19. package/addon/components/order/details.hbs +34 -1
  20. package/addon/components/order/form/details.hbs +4 -0
  21. package/addon/components/order/form.hbs +9 -1
  22. package/addon/components/telematic/details.hbs +1 -1
  23. package/addon/components/telematic/form.hbs +2 -2
  24. package/addon/components/telematic/settings.hbs +1 -1
  25. package/addon/components/vehicle/card.hbs +14 -2
  26. package/addon/components/vehicle/form.hbs +13 -1
  27. package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
  28. package/addon/controllers/connectivity/devices/index/details/vehicle.js +2 -2
  29. package/addon/controllers/connectivity/events/details.js +68 -1
  30. package/addon/controllers/connectivity/sensors/index.js +4 -0
  31. package/addon/controllers/connectivity/telematics/details/events.js +1 -1
  32. package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
  33. package/addon/controllers/settings/map.js +19 -0
  34. package/addon/extension.js +14 -0
  35. package/addon/instance-initializers/register-vroom-allocation.js +1 -1
  36. package/addon/routes/connectivity/events/details.js +26 -1
  37. package/addon/routes/management/fleets/index/details.js +1 -1
  38. package/addon/routes/management/vehicles/index/details.js +3 -1
  39. package/addon/services/map-adapter/google.js +66 -1
  40. package/addon/services/map-adapter/leaflet.js +1 -1
  41. package/addon/services/map-adapter-interface.js +1 -1
  42. package/addon/services/map-manager.js +16 -1
  43. package/addon/services/map-settings.js +15 -0
  44. package/addon/styles/fleetops-engine.css +487 -1
  45. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +2 -1
  46. package/addon/templates/connectivity/events/details.hbs +40 -1
  47. package/addon/templates/settings/map.hbs +38 -9
  48. package/addon/utils/leaflet-plugin-loader.js +5 -5
  49. package/addon/utils/map-drawer-dropdown-position.js +11 -13
  50. package/addon/utils/route-colors.js +1 -1
  51. package/app/components/ai/create-order-preview.js +1 -0
  52. package/app/components/ai/route-preview-map.js +1 -0
  53. package/app/components/device-event/details.hbs +192 -0
  54. package/app/components/device-event/details.js +1 -0
  55. package/app/components/fleet/hierarchy-tree.js +1 -0
  56. package/app/services/route-engine.js +1 -0
  57. package/composer.json +1 -1
  58. package/extension.json +1 -1
  59. package/package.json +6 -4
  60. package/scripts/normalize-rgba-alpha.js +90 -0
  61. package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
  62. package/server/src/Exports/ContactExport.php +12 -5
  63. package/server/src/Exports/DeviceExport.php +92 -0
  64. package/server/src/Exports/DriverExport.php +28 -3
  65. package/server/src/Exports/EquipmentExport.php +94 -0
  66. package/server/src/Exports/FleetExport.php +22 -7
  67. package/server/src/Exports/FuelReportExport.php +16 -1
  68. package/server/src/Exports/IssueExport.php +25 -3
  69. package/server/src/Exports/MaintenanceExport.php +106 -0
  70. package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
  71. package/server/src/Exports/OrderExport.php +24 -5
  72. package/server/src/Exports/PartExport.php +99 -0
  73. package/server/src/Exports/PlaceExport.php +33 -7
  74. package/server/src/Exports/SensorExport.php +94 -0
  75. package/server/src/Exports/ServiceAreaExport.php +31 -7
  76. package/server/src/Exports/ServiceRateExport.php +41 -1
  77. package/server/src/Exports/TelematicExport.php +90 -0
  78. package/server/src/Exports/VehicleExport.php +274 -8
  79. package/server/src/Exports/VendorExport.php +15 -2
  80. package/server/src/Exports/WorkOrderExport.php +92 -0
  81. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
  82. package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
  83. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
  84. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
  85. package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
  86. package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
  87. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
  88. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +18 -0
  89. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
  90. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
  91. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
  92. package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
  93. package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
  94. package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
  95. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
  96. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
  97. package/server/src/Http/Filter/DeviceFilter.php +33 -10
  98. package/server/src/Http/Filter/EquipmentFilter.php +23 -0
  99. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
  100. package/server/src/Http/Filter/PartFilter.php +47 -0
  101. package/server/src/Http/Filter/SensorFilter.php +34 -6
  102. package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
  103. package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
  104. package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
  105. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
  106. package/server/src/Http/Requests/CreatePartRequest.php +40 -0
  107. package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
  108. package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
  109. package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
  110. package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
  111. package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
  112. package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
  113. package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
  114. package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
  115. package/server/src/Http/Resources/v1/Device.php +72 -0
  116. package/server/src/Http/Resources/v1/Equipment.php +57 -0
  117. package/server/src/Http/Resources/v1/Fleet.php +8 -2
  118. package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
  119. package/server/src/Http/Resources/v1/Part.php +64 -0
  120. package/server/src/Http/Resources/v1/Sensor.php +68 -0
  121. package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
  122. package/server/src/Models/Driver.php +6 -0
  123. package/server/src/Models/Order.php +20 -7
  124. package/server/src/Models/Place.php +11 -3
  125. package/server/src/Observers/PurchaseRateObserver.php +2 -1
  126. package/server/src/Providers/FleetOpsServiceProvider.php +26 -0
  127. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +88 -0
  128. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +108 -0
  129. package/server/src/Support/Ai/Capabilities/ConsoleNavigationCapability.php +55 -0
  130. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +508 -0
  131. package/server/src/Support/Ai/Capabilities/DocsHelpCapability.php +49 -0
  132. package/server/src/Support/Ai/Capabilities/ImportOrdersPreviewCapability.php +66 -0
  133. package/server/src/Support/Ai/Capabilities/OperationalQueryCapability.php +319 -0
  134. package/server/src/Support/Ai/Capabilities/OptimizeOrderRouteCapability.php +240 -0
  135. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +95 -0
  136. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +195 -0
  137. package/server/src/Support/Ai/FleetOpsAiQueryResources.php +150 -0
  138. package/server/src/Support/Analytics/RevenueTrend.php +3 -8
  139. package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
  140. package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
  141. package/server/src/Support/Metrics/EarningsMetric.php +1 -10
  142. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +104 -5
  143. package/server/src/Support/Telematics/Providers/SafeeProvider.php +550 -54
  144. package/server/src/Support/Telematics/TelematicService.php +28 -9
  145. package/server/src/Support/Utils.php +52 -0
  146. package/server/src/routes.php +67 -14
  147. package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
  148. package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
  149. package/server/tests/ExportCoverageTest.php +79 -0
  150. package/server/tests/FleetHierarchyResourceTest.php +12 -0
  151. package/server/tests/MetricsRegistryTest.php +45 -5
  152. package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
  153. package/server/tests/PointResolutionTest.php +63 -0
  154. package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
  155. package/server/tests/TelematicsHardeningTest.php +636 -1
  156. package/addon/components/table/cell/dropdown/action-item.hbs +0 -31
  157. package/addon/components/table/cell/dropdown/action-item.js +0 -62
  158. package/addon/components/table/cell/dropdown.hbs +0 -28
  159. package/addon/components/table/cell/dropdown.js +0 -91
  160. package/app/components/table/cell/dropdown/action-item.js +0 -1
  161. package/app/components/table/cell/dropdown.js +0 -1
@@ -4,6 +4,7 @@ use Fleetbase\FleetOps\Support\Metrics\AbstractMetric;
4
4
  use Fleetbase\FleetOps\Support\Metrics\OrdersInProgressMetric;
5
5
  use Fleetbase\FleetOps\Support\Metrics\Registry;
6
6
  use Fleetbase\FleetOps\Support\Metrics\TotalTimeTraveledMetric;
7
+ use Fleetbase\Models\Transaction;
7
8
 
8
9
  test('registry exposes every known metric slug', function () {
9
10
  $slugs = Registry::slugs();
@@ -66,13 +67,52 @@ test('money metrics return float values to preserve cents', function () {
66
67
  });
67
68
 
68
69
  test('money metrics filter by currency to avoid mixed-currency sums', function () {
69
- $earnings = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/EarningsMetric.php');
70
- $fuelCosts = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/FuelCostsMetric.php');
71
- $aov = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/AvgOrderValueMetric.php');
70
+ $earnings = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/EarningsMetric.php');
71
+ $fuelCosts = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/FuelCostsMetric.php');
72
+ $aov = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/AvgOrderValueMetric.php');
73
+ $activeRevenue = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/ActiveRevenueQuery.php');
72
74
 
73
- expect($earnings)->toContain("->where('currency'");
75
+ expect($earnings)->toContain('ActiveRevenueQuery::forCompany');
74
76
  expect($fuelCosts)->toContain("->where('currency'");
75
- expect($aov)->toContain("->where('currency'");
77
+ expect($aov)->toContain('ActiveRevenueQuery::forCompany');
78
+ expect($activeRevenue)->toContain("->where('currency'");
79
+ });
80
+
81
+ test('earnings use the shared active revenue query', function () {
82
+ $earnings = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/EarningsMetric.php');
83
+ $aov = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/AvgOrderValueMetric.php');
84
+ $revenueTrend = file_get_contents(dirname(__DIR__) . '/src/Support/Analytics/RevenueTrend.php');
85
+
86
+ expect($earnings)->toContain('ActiveRevenueQuery::forCompany');
87
+ expect($aov)->toContain('ActiveRevenueQuery::forCompany');
88
+ expect($revenueTrend)->toContain('ActiveRevenueQuery::forCompany');
89
+ });
90
+
91
+ test('active revenue query excludes inactive financial and operational lifecycle records', function () {
92
+ $source = file_get_contents(dirname(__DIR__) . '/src/Support/Metrics/ActiveRevenueQuery.php');
93
+
94
+ expect($source)->toContain("where('direction', Transaction::DIRECTION_CREDIT)");
95
+ expect($source)->toContain("whereIn('status', self::ACTIVE_STATUSES)");
96
+ expect($source)->toContain("whereNull('voided_at')");
97
+ expect($source)->toContain("whereNull('reversed_at')");
98
+ expect($source)->toContain("whereNull('parent_transaction_uuid')");
99
+ expect($source)->toContain('excludeInactiveOrders');
100
+ expect($source)->toContain('excludeInactiveInvoices');
101
+ expect($source)->toContain('ledger_invoices.deleted_at');
102
+ expect($source)->toContain('orders.deleted_at');
103
+ expect(Fleetbase\FleetOps\Support\Metrics\ActiveRevenueQuery::ACTIVE_STATUSES)->toBe([Transaction::STATUS_SUCCESS]);
104
+ expect(Fleetbase\FleetOps\Support\Metrics\ActiveRevenueQuery::ACTIVE_STATUSES)->not->toContain('completed');
105
+ expect(Fleetbase\FleetOps\Support\Metrics\ActiveRevenueQuery::ACTIVE_STATUSES)->not->toContain('paid');
106
+ });
107
+
108
+ test('active revenue query treats invoice status as invalidation only', function () {
109
+ $inactiveInvoiceStatuses = Fleetbase\FleetOps\Support\Metrics\ActiveRevenueQuery::INACTIVE_INVOICE_STATUSES;
110
+
111
+ expect($inactiveInvoiceStatuses)->not->toContain('draft');
112
+ expect($inactiveInvoiceStatuses)->toContain('void');
113
+ expect($inactiveInvoiceStatuses)->toContain('voided');
114
+ expect($inactiveInvoiceStatuses)->toContain('cancelled');
115
+ expect($inactiveInvoiceStatuses)->toContain('canceled');
76
116
  });
77
117
 
78
118
  test('ordersInProgress uses an explicit allowlist rather than an exclusion list', function () {
@@ -0,0 +1,53 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Models\Order;
4
+
5
+ function fleetopsOrderPayloadResolutionProbe(bool $hasExistingUuid)
6
+ {
7
+ return new class($hasExistingUuid) extends Order {
8
+ public function __construct(private bool $hasExistingUuid = false)
9
+ {
10
+ parent::__construct();
11
+ }
12
+
13
+ public function shouldResolveForTest(array $attributes, string $role): bool
14
+ {
15
+ return $this->shouldResolvePayloadPlace($attributes, $role);
16
+ }
17
+
18
+ protected function hasExistingPayloadPlaceUuid(?array $attributes, string $role): bool
19
+ {
20
+ return $this->hasExistingUuid;
21
+ }
22
+ };
23
+ }
24
+
25
+ test('order payload creation prefers existing place uuid over embedded place object', function () {
26
+ $order = fleetopsOrderPayloadResolutionProbe(true);
27
+
28
+ expect($order->shouldResolveForTest([
29
+ 'pickup_uuid' => '628892cc-271c-89fd-cdaa-39d02b40bd13',
30
+ 'pickup' => [
31
+ 'uuid' => '628892cc-271c-89fd-cdaa-39d02b40bd13',
32
+ 'location' => [
33
+ 'bbox' => [103.851, 1.2816, 103.851, 1.2816],
34
+ 'type' => 'Point',
35
+ 'coordinates' => [103.851, 1.2816],
36
+ ],
37
+ ],
38
+ ], 'pickup'))->toBeFalse();
39
+ });
40
+
41
+ test('order payload creation resolves embedded place when no valid uuid exists', function () {
42
+ $order = fleetopsOrderPayloadResolutionProbe(false);
43
+
44
+ expect($order->shouldResolveForTest([
45
+ 'pickup' => [
46
+ 'location' => [
47
+ 'bbox' => [103.851, 1.2816, 103.851, 1.2816],
48
+ 'type' => 'Point',
49
+ 'coordinates' => [103.851, 1.2816],
50
+ ],
51
+ ],
52
+ ], 'pickup'))->toBeTrue();
53
+ });
@@ -0,0 +1,63 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Models\Place;
4
+ use Fleetbase\FleetOps\Support\Utils;
5
+
6
+ function fleetopsBboxPoint(): array
7
+ {
8
+ return [
9
+ 'bbox' => [103.851, 1.2816, 103.851, 1.2816],
10
+ 'type' => 'Point',
11
+ 'coordinates' => [103.851, 1.2816],
12
+ ];
13
+ }
14
+
15
+ test('point resolution accepts GeoJSON point with bbox', function () {
16
+ $point = Utils::getPointFromMixed(fleetopsBboxPoint());
17
+
18
+ expect($point->getLat())->toBe(1.2816)
19
+ ->and($point->getLng())->toBe(103.851);
20
+ });
21
+
22
+ test('point resolution accepts feature wrapped GeoJSON point with bbox', function () {
23
+ $point = Utils::getPointFromMixed([
24
+ 'type' => 'Feature',
25
+ 'geometry' => fleetopsBboxPoint(),
26
+ ]);
27
+
28
+ expect($point->getLat())->toBe(1.2816)
29
+ ->and($point->getLng())->toBe(103.851);
30
+ });
31
+
32
+ test('point resolution rejects malformed feature points cleanly', function () {
33
+ expect(fn () => Utils::getPointFromMixed([
34
+ 'type' => 'Feature',
35
+ 'geometry' => [
36
+ 'type' => 'Point',
37
+ 'coordinates' => [],
38
+ ],
39
+ ]))->toThrow(Exception::class, 'Attempted to resolve Point from invalid location.');
40
+ });
41
+
42
+ test('coordinate helper does not collapse bbox GeoJSON point to zero', function () {
43
+ $point = Utils::getPointFromCoordinates([
44
+ 'type' => 'Feature',
45
+ 'geometry' => fleetopsBboxPoint(),
46
+ ]);
47
+
48
+ expect($point->getLat())->toBe(1.2816)
49
+ ->and($point->getLng())->toBe(103.851);
50
+ });
51
+
52
+ test('structured place normalization accepts bbox GeoJSON point locations', function () {
53
+ $place = Place::mergeStructuredPlaceAttributes([
54
+ 'name' => 'AI Pickup',
55
+ 'street1' => '16 Simon Walk',
56
+ 'city' => 'Singapore',
57
+ 'country' => 'SG',
58
+ 'location' => fleetopsBboxPoint(),
59
+ ]);
60
+
61
+ expect($place['location']->getLat())->toBe(1.2816)
62
+ ->and($place['location']->getLng())->toBe(103.851);
63
+ });
@@ -0,0 +1,191 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\DeviceController;
4
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\EquipmentController;
5
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\FuelTransactionController;
6
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\PartController;
7
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\SensorController;
8
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\WorkOrderController;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\Device;
10
+ use Fleetbase\FleetOps\Http\Resources\v1\Equipment;
11
+ use Fleetbase\FleetOps\Http\Resources\v1\FuelTransaction;
12
+ use Fleetbase\FleetOps\Http\Resources\v1\Part;
13
+ use Fleetbase\FleetOps\Http\Resources\v1\Sensor;
14
+ use Fleetbase\FleetOps\Http\Resources\v1\WorkOrder;
15
+
16
+ test('new first-class fleetops rest routes are registered in the consumable v1 namespace', function () {
17
+ $routes = file_get_contents(dirname(__DIR__) . '/src/routes.php');
18
+
19
+ foreach (['equipment', 'parts', 'work-orders', 'devices', 'sensors', 'fuel-transactions'] as $prefix) {
20
+ expect($routes)->toContain("\$router->group(['prefix' => '{$prefix}']");
21
+ }
22
+
23
+ expect($routes)
24
+ ->toContain('EquipmentController@create')
25
+ ->toContain('PartController@create')
26
+ ->toContain('WorkOrderController@send')
27
+ ->toContain('DeviceController@attach')
28
+ ->toContain('DeviceController@detach')
29
+ ->toContain('SensorController@create')
30
+ ->toContain('FuelTransactionController@matchVehicle')
31
+ ->toContain('FuelTransactionController@matchOrder')
32
+ ->toContain('FuelTransactionController@reprocess')
33
+ ->toContain('FuelTransactionController@review');
34
+ });
35
+
36
+ test('public fuel transaction api keeps provider naming internal only', function () {
37
+ $publicRoutes = file_get_contents(dirname(__DIR__) . '/src/routes.php');
38
+ $publicGroup = substr($publicRoutes, 0, strpos($publicRoutes, 'Internal FleetOps API Routes'));
39
+ $controller = file_get_contents(dirname(__DIR__) . '/src/Http/Controllers/Api/v1/FuelTransactionController.php');
40
+
41
+ expect($publicGroup)
42
+ ->toContain("['prefix' => 'fuel-transactions']")
43
+ ->not->toContain("['prefix' => 'fuel-provider-transactions']")
44
+ ->and($controller)
45
+ ->toContain('class FuelTransactionController')
46
+ ->toContain('FuelProviderTransaction')
47
+ ->toContain("'FuelTransaction resource not found.'");
48
+ });
49
+
50
+ test('new public controllers expose the expected rest methods', function () {
51
+ $controllers = [
52
+ EquipmentController::class => ['create', 'query', 'find', 'update', 'delete'],
53
+ PartController::class => ['create', 'query', 'find', 'update', 'delete'],
54
+ WorkOrderController::class => ['create', 'query', 'find', 'update', 'delete', 'send'],
55
+ DeviceController::class => ['create', 'query', 'find', 'update', 'delete', 'attach', 'detach'],
56
+ SensorController::class => ['create', 'query', 'find', 'update', 'delete'],
57
+ FuelTransactionController::class => ['create', 'query', 'find', 'update', 'delete', 'matchVehicle', 'matchOrder', 'reprocess', 'review'],
58
+ ];
59
+
60
+ foreach ($controllers as $controller => $methods) {
61
+ foreach ($methods as $method) {
62
+ expect(method_exists($controller, $method))->toBeTrue("{$controller}::{$method} missing");
63
+ }
64
+ }
65
+ });
66
+
67
+ test('new public resources preserve internal uuid fields behind internal request checks', function () {
68
+ foreach ([Equipment::class, Part::class, WorkOrder::class, Device::class, Sensor::class, FuelTransaction::class] as $resource) {
69
+ expect(class_exists($resource))->toBeTrue();
70
+ }
71
+
72
+ $internalFields = [
73
+ 'Equipment.php' => ['uuid', 'company_uuid', 'warranty_uuid', 'photo_uuid', 'equipable_uuid'],
74
+ 'Part.php' => ['uuid', 'company_uuid', 'vendor_uuid', 'warranty_uuid', 'photo_uuid', 'asset_uuid'],
75
+ 'Device.php' => ['uuid', 'company_uuid', 'telematic_uuid', 'attachable_uuid', 'warranty_uuid', 'photo_uuid'],
76
+ 'Sensor.php' => ['uuid', 'company_uuid', 'device_uuid', 'warranty_uuid', 'telematic_uuid', 'photo_uuid', 'sensorable_uuid'],
77
+ 'FuelTransaction.php' => ['uuid', 'fuel_provider_connection_uuid', 'fuel_report_uuid', 'vehicle_uuid', 'driver_uuid', 'order_uuid'],
78
+ 'WorkOrder.php' => ['uuid', 'company_uuid', 'schedule_uuid', 'created_by_uuid', 'updated_by_uuid', 'target_uuid', 'assignee_uuid'],
79
+ ];
80
+
81
+ foreach ($internalFields as $resourceFile => $fields) {
82
+ $source = file_get_contents(dirname(__DIR__) . '/src/Http/Resources/v1/' . $resourceFile);
83
+
84
+ expect($source)->toContain("'id'");
85
+
86
+ foreach ($fields as $field) {
87
+ expect($source)
88
+ ->toContain("'{$field}'")
89
+ ->toMatch("/'{$field}'\\s*=>\\s*\\\$this->when\\(Http::isInternalRequest\\(\\)/");
90
+ }
91
+ }
92
+ });
93
+
94
+ test('new shared resources do not expose unguarded uuid fields', function () {
95
+ foreach ([
96
+ 'Equipment.php',
97
+ 'Part.php',
98
+ 'Device.php',
99
+ 'Sensor.php',
100
+ 'FuelTransaction.php',
101
+ 'WorkOrder.php',
102
+ ] as $resourceFile) {
103
+ $source = file_get_contents(dirname(__DIR__) . '/src/Http/Resources/v1/' . $resourceFile);
104
+
105
+ preg_match_all("/'([^']*uuid)'\\s*=>\\s*([^,\\n]+)/i", $source, $matches, PREG_SET_ORDER);
106
+
107
+ foreach ($matches as $match) {
108
+ expect($match[2])->toContain('Http::isInternalRequest()');
109
+ }
110
+ }
111
+ });
112
+
113
+ test('new shared resources preserve public and internal relationship shapes', function () {
114
+ foreach ([
115
+ 'Equipment.php',
116
+ 'Part.php',
117
+ 'Device.php',
118
+ 'Sensor.php',
119
+ 'FuelTransaction.php',
120
+ ] as $resourceFile) {
121
+ $source = file_get_contents(dirname(__DIR__) . '/src/Http/Resources/v1/' . $resourceFile);
122
+
123
+ expect($source)
124
+ ->toContain('resolveLoadedRelation')
125
+ ->toContain('Resolve::httpResourceForModel($model)')
126
+ ->toContain('$model->public_id');
127
+ }
128
+ });
129
+
130
+ test('new public resolver rejects uuid lookup while preserving public and internal identifiers', function () {
131
+ $resolver = file_get_contents(dirname(__DIR__) . '/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php');
132
+
133
+ expect($resolver)
134
+ ->not->toContain("where('uuid', \$id)")
135
+ ->not->toContain("orWhere('uuid', \$id)")
136
+ ->toContain("orWhere('public_id', \$id)")
137
+ ->toContain("orWhere('internal_id', \$id)")
138
+ ->toContain('rejectUuidIdentifiers')
139
+ ->toContain('Use public_id or internal_id values instead');
140
+ });
141
+
142
+ test('public controllers reject uuid shaped request keys', function () {
143
+ foreach ([
144
+ 'EquipmentController.php',
145
+ 'PartController.php',
146
+ 'WorkOrderController.php',
147
+ 'DeviceController.php',
148
+ 'SensorController.php',
149
+ 'FuelTransactionController.php',
150
+ ] as $controllerFile) {
151
+ $source = file_get_contents(dirname(__DIR__) . '/src/Http/Controllers/Api/v1/' . $controllerFile);
152
+
153
+ expect($source)->toContain('rejectUuidIdentifiers($request)');
154
+ }
155
+ });
156
+
157
+ test('new public list endpoints have company scoped filters', function () {
158
+ foreach ([
159
+ 'EquipmentFilter.php',
160
+ 'PartFilter.php',
161
+ 'WorkOrderFilter.php',
162
+ 'DeviceFilter.php',
163
+ 'SensorFilter.php',
164
+ 'FuelProviderTransactionFilter.php',
165
+ ] as $filterFile) {
166
+ $source = file_get_contents(dirname(__DIR__) . '/src/Http/Filter/' . $filterFile);
167
+
168
+ expect($source)
169
+ ->toContain('public function queryForPublic()')
170
+ ->toContain("where('company_uuid', \$this->session->get('company'))");
171
+ }
172
+ });
173
+
174
+ test('fuel transaction filter applies public company scoping and public id relation filters without raw uuid matching', function () {
175
+ $filter = file_get_contents(dirname(__DIR__) . '/src/Http/Filter/FuelProviderTransactionFilter.php');
176
+
177
+ expect($filter)
178
+ ->toContain('public function queryForPublic()')
179
+ ->toContain('$this->queryForInternal();')
180
+ ->toContain('public function vehicle(?string $vehicle)')
181
+ ->toContain('public function driver(?string $driver)')
182
+ ->toContain('public function order(?string $order)')
183
+ ->toContain('public function fuelReport(?string $fuelReport)')
184
+ ->toContain("where('public_id', \$identifier)")
185
+ ->toContain("orWhere('internal_id', \$identifier)")
186
+ ->toContain('if ($allowUuid)')
187
+ ->not->toContain("where('vehicle_uuid', \$vehicle)")
188
+ ->not->toContain("where('driver_uuid', \$driver)")
189
+ ->not->toContain("where('order_uuid', \$order)")
190
+ ->not->toContain("where('fuel_report_uuid', \$fuelReport)");
191
+ });