@fleetbase/fleetops-engine 0.6.49 → 0.6.51

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 (271) hide show
  1. package/README.md +185 -67
  2. package/addon/components/contact/panel-header.hbs +11 -48
  3. package/addon/components/customer/create-order-form.hbs +2 -2
  4. package/addon/components/customer/details.hbs +1 -2
  5. package/addon/components/customer/form.hbs +62 -50
  6. package/addon/components/customer/form.js +27 -1
  7. package/addon/components/customer/order-form.hbs +2 -2
  8. package/addon/components/customer/orders.hbs +50 -2
  9. package/addon/components/customer/orders.js +67 -5
  10. package/addon/components/display-place.hbs +2 -2
  11. package/addon/components/driver/form.hbs +1 -1
  12. package/addon/components/home/getting-started-guidance.hbs +113 -0
  13. package/addon/components/home/getting-started-guidance.js +107 -0
  14. package/addon/components/issue/details/comments.hbs +3 -0
  15. package/addon/components/issue/details/comments.js +3 -0
  16. package/addon/components/issue/details/correspondence.hbs +9 -0
  17. package/addon/components/issue/details/correspondence.js +83 -0
  18. package/addon/components/issue/details/documents.hbs +69 -0
  19. package/addon/components/issue/details/documents.js +85 -0
  20. package/addon/components/issue/details.hbs +220 -59
  21. package/addon/components/issue/details.js +245 -0
  22. package/addon/components/issue/form.hbs +19 -1
  23. package/addon/components/issue/panel-header.hbs +43 -0
  24. package/addon/components/issue/panel-header.js +22 -0
  25. package/addon/components/issue/timeline.hbs +40 -0
  26. package/addon/components/issue/timeline.js +45 -0
  27. package/addon/components/leaflet-tracking-marker.js +3 -0
  28. package/addon/components/map/drawer/position-listing.js +3 -0
  29. package/addon/components/map/google-live-map.js +6 -2
  30. package/addon/components/map/leaflet-live-map.hbs +48 -14
  31. package/addon/components/map/leaflet-live-map.js +201 -20
  32. package/addon/components/map/order-list-overlay.hbs +4 -4
  33. package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
  34. package/addon/components/modals/convert-customer-to-vendor.js +55 -0
  35. package/addon/components/modals/issue-assign.hbs +20 -0
  36. package/addon/components/modals/issue-assign.js +49 -0
  37. package/addon/components/modals/issue-close.hbs +30 -0
  38. package/addon/components/modals/issue-close.js +73 -0
  39. package/addon/components/modals/issue-status.hbs +20 -0
  40. package/addon/components/modals/issue-status.js +43 -0
  41. package/addon/components/modals/order-change-destination.hbs +46 -0
  42. package/addon/components/modals/order-change-destination.js +106 -0
  43. package/addon/components/modals/reset-customer-credentials.js +12 -6
  44. package/addon/components/modals/stop-activity.hbs +28 -0
  45. package/addon/components/modals/stop-activity.js +52 -0
  46. package/addon/components/modals/update-order-activity.hbs +78 -2
  47. package/addon/components/modals/update-order-activity.js +197 -5
  48. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  49. package/addon/components/orchestrator/resource-panel.js +2 -4
  50. package/addon/components/order/activity-list.hbs +2 -2
  51. package/addon/components/order/activity-timeline.hbs +31 -29
  52. package/addon/components/order/details/activity.hbs +3 -3
  53. package/addon/components/order/details/activity.js +13 -2
  54. package/addon/components/order/details/documents.js +2 -0
  55. package/addon/components/order/details/proof.hbs +40 -38
  56. package/addon/components/order/details/proof.js +31 -4
  57. package/addon/components/order/details/purchase-rate.hbs +2 -2
  58. package/addon/components/order/details/route.hbs +11 -3
  59. package/addon/components/order/details/route.js +91 -14
  60. package/addon/components/order/details/tracking.hbs +101 -72
  61. package/addon/components/order/details/tracking.js +149 -2
  62. package/addon/components/order/details.hbs +5 -5
  63. package/addon/components/order/details.js +19 -1
  64. package/addon/components/order/form/details.hbs +3 -3
  65. package/addon/components/order/form/documents.js +11 -4
  66. package/addon/components/order/form/route.hbs +108 -104
  67. package/addon/components/order/form/route.js +37 -1
  68. package/addon/components/order/form/service-rate.hbs +2 -2
  69. package/addon/components/order/header.hbs +1 -1
  70. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  71. package/addon/components/order-config-manager/activity-flow.js +178 -12
  72. package/addon/components/place/address.js +1 -1
  73. package/addon/components/route-list.hbs +17 -29
  74. package/addon/components/route-list.js +42 -10
  75. package/addon/components/service-rate/details.hbs +42 -1
  76. package/addon/components/service-rate/form.hbs +119 -1
  77. package/addon/components/service-rate/form.js +59 -0
  78. package/addon/components/vehicle/form.js +1 -1
  79. package/addon/components/vehicle/pill.hbs +1 -1
  80. package/addon/components/vendor/personnel-manager.hbs +166 -0
  81. package/addon/components/vendor/personnel-manager.js +143 -0
  82. package/addon/components/widget/live-fleet.hbs +1 -0
  83. package/addon/components/widget/live-fleet.js +2 -7
  84. package/addon/components/widget/revenue-trend.hbs +1 -1
  85. package/addon/components/widget/revenue-trend.js +75 -2
  86. package/addon/controllers/management/contacts/customers/details.js +31 -13
  87. package/addon/controllers/management/contacts/customers.js +16 -5
  88. package/addon/controllers/management/contacts/index/details.js +31 -13
  89. package/addon/controllers/management/contacts/index.js +7 -0
  90. package/addon/controllers/management/issues/index/details.js +52 -6
  91. package/addon/controllers/management/vehicles/index/new.js +1 -1
  92. package/addon/controllers/management/vendors/index/details.js +4 -0
  93. package/addon/controllers/operations/orders/index/details.js +15 -7
  94. package/addon/controllers/operations/orders/index.js +15 -1
  95. package/addon/extension.js +19 -12
  96. package/addon/helpers/format-duration.js +1 -38
  97. package/addon/helpers/place-address-html.js +2 -2
  98. package/addon/initializers/load-leaflet-assets.js +5 -6
  99. package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
  100. package/addon/routes/management/issues/index/details.js +1 -1
  101. package/addon/routes/operations/service-rates/index.js +1 -1
  102. package/addon/routes.js +1 -0
  103. package/addon/services/contact-actions.js +231 -1
  104. package/addon/services/geofence.js +163 -19
  105. package/addon/services/getting-started.js +44 -0
  106. package/addon/services/issue-actions.js +71 -0
  107. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  108. package/addon/services/leaflet-map-manager.js +2 -0
  109. package/addon/services/map-adapter/leaflet.js +19 -0
  110. package/addon/services/service-areas.js +3 -0
  111. package/addon/services/vehicle-actions.js +1 -1
  112. package/addon/styles/fleetops-engine.css +1127 -1
  113. package/addon/templates/management/contacts/customers/details.hbs +1 -1
  114. package/addon/templates/management/contacts/index/details.hbs +1 -1
  115. package/addon/templates/management/contacts.hbs +2 -2
  116. package/addon/templates/management/issues/index/details/index.hbs +3 -1
  117. package/addon/templates/management/issues/index/details.hbs +3 -2
  118. package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
  119. package/addon/templates/operations/orders/index/details/index.hbs +2 -2
  120. package/addon/templates/operations/orders/index.hbs +1 -1
  121. package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
  122. package/addon/templates/operations/service-rates/index/new.hbs +1 -0
  123. package/addon/templates/settings/payments/onboard.hbs +8 -8
  124. package/addon/utils/fleet-ops-options.js +2 -1
  125. package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
  126. package/addon/utils/leaflet-plugin-loader.js +219 -0
  127. package/addon/utils/normalize-order-config-flow.js +41 -0
  128. package/addon/utils/place-address-html.js +3 -2
  129. package/addon/utils/route-visualization.js +54 -7
  130. package/addon/utils/setup-customer-portal.js +4 -64
  131. package/app/components/home/getting-started-guidance.js +1 -0
  132. package/app/components/issue/details/comments.js +1 -0
  133. package/app/components/issue/details/correspondence.js +1 -0
  134. package/app/components/issue/details/documents.js +1 -0
  135. package/app/components/issue/panel-header.js +1 -0
  136. package/app/components/modals/convert-customer-to-vendor.js +1 -0
  137. package/app/components/modals/issue-assign.js +1 -0
  138. package/app/components/modals/issue-close.js +1 -0
  139. package/app/components/modals/issue-status.js +1 -0
  140. package/app/components/modals/order-change-destination.js +1 -0
  141. package/app/components/modals/stop-activity.js +1 -0
  142. package/app/components/vendor/personnel-manager.js +1 -0
  143. package/app/helpers/format-duration.js +1 -1
  144. package/app/services/getting-started.js +1 -0
  145. package/app/utils/leaflet-draw-namespace-guard.js +1 -0
  146. package/app/utils/leaflet-plugin-loader.js +1 -0
  147. package/composer.json +1 -1
  148. package/extension.json +1 -1
  149. package/package.json +5 -5
  150. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  151. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  152. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  153. package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
  154. package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
  155. package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
  156. package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
  157. package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
  158. package/server/resources/views/labels/default.php +57 -49
  159. package/server/resources/views/labels/waypoint-label.php +27 -20
  160. package/server/resources/views/mail/customer-credentials.blade.php +16 -13
  161. package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
  162. package/server/seeders/Testing/FleetSeeder.php +22 -22
  163. package/server/seeders/Testing/OrdersSeeder.php +6 -22
  164. package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
  165. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  166. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  167. package/server/src/Expansions/UserFilterExpansion.php +28 -0
  168. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  169. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  170. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  171. package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
  172. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  173. package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
  174. package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
  175. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  176. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  177. package/server/src/Http/Controllers/Internal/v1/ContactController.php +154 -0
  178. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
  179. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
  180. package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
  181. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  182. package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
  183. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
  184. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  185. package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
  186. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  187. package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
  188. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  189. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  190. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  191. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  192. package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
  193. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  194. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  195. package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
  196. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  197. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  198. package/server/src/Http/Resources/v1/Customer.php +79 -0
  199. package/server/src/Http/Resources/v1/Index/Order.php +12 -9
  200. package/server/src/Http/Resources/v1/Issue.php +25 -0
  201. package/server/src/Http/Resources/v1/Order.php +20 -18
  202. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  203. package/server/src/Http/Resources/v1/Payload.php +23 -20
  204. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  205. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  206. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  207. package/server/src/Mail/CustomerCredentialsMail.php +5 -6
  208. package/server/src/Models/Contact.php +122 -66
  209. package/server/src/Models/Customer.php +64 -0
  210. package/server/src/Models/Driver.php +12 -3
  211. package/server/src/Models/Entity.php +2 -2
  212. package/server/src/Models/Issue.php +47 -6
  213. package/server/src/Models/Order.php +62 -10
  214. package/server/src/Models/Payload.php +17 -1
  215. package/server/src/Models/ServiceArea.php +1 -1
  216. package/server/src/Models/ServiceRate.php +273 -1
  217. package/server/src/Models/ServiceRateFee.php +39 -9
  218. package/server/src/Models/TrackingNumber.php +8 -2
  219. package/server/src/Models/Vehicle.php +18 -2
  220. package/server/src/Models/VendorPersonnel.php +10 -1
  221. package/server/src/Models/Waypoint.php +2 -2
  222. package/server/src/Models/Zone.php +1 -1
  223. package/server/src/Notifications/LateDeparture.php +2 -2
  224. package/server/src/Notifications/ProlongedStoppage.php +2 -2
  225. package/server/src/Notifications/RouteDeviation.php +2 -2
  226. package/server/src/Observers/ContactObserver.php +2 -0
  227. package/server/src/Observers/PurchaseRateObserver.php +20 -0
  228. package/server/src/Observers/ServiceRateObserver.php +6 -6
  229. package/server/src/Observers/ZoneObserver.php +56 -0
  230. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
  231. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  232. package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
  233. package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
  234. package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
  235. package/server/src/Support/Analytics/RevenueTrend.php +0 -1
  236. package/server/src/Support/Analytics/TopDrivers.php +10 -2
  237. package/server/src/Support/CustomerAuth.php +98 -0
  238. package/server/src/Support/GettingStarted.php +144 -0
  239. package/server/src/Support/LabelPdf.php +52 -0
  240. package/server/src/Support/Metrics/AbstractMetric.php +4 -4
  241. package/server/src/Support/PlaceSearch.php +282 -0
  242. package/server/src/Support/ResolvesOrderServiceStops.php +522 -0
  243. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  244. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  245. package/server/src/Tracking/TrackingStop.php +13 -11
  246. package/server/src/routes.php +76 -10
  247. package/server/tests/AnalyticsRoutesTest.php +8 -0
  248. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  249. package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
  250. package/server/tests/CustomerEndpointTest.php +199 -0
  251. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  252. package/server/tests/GettingStartedStatusTest.php +21 -0
  253. package/server/tests/LabelPdfRenderingTest.php +23 -0
  254. package/server/tests/LiveControllerViewportTest.php +60 -0
  255. package/server/tests/MetricsRegistryTest.php +4 -8
  256. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  257. package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
  258. package/server/tests/OrderConfigEndpointTest.php +86 -0
  259. package/server/tests/OrderFileAttachmentTest.php +20 -0
  260. package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
  261. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  262. package/translations/ar-ae.yml +9 -0
  263. package/translations/bg-bg.yaml +9 -0
  264. package/translations/en-us.yaml +12 -0
  265. package/translations/es-pa.yaml +9 -0
  266. package/translations/fr-fr.yaml +9 -0
  267. package/translations/mn-mn.yaml +9 -0
  268. package/translations/pt-br.yaml +9 -0
  269. package/translations/ru-ru.yaml +9 -0
  270. package/translations/vi-vn.yaml +9 -0
  271. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -7,10 +7,9 @@ use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
7
  use Fleetbase\FleetOps\Http\Resources\v1\Place as PlaceResource;
8
8
  use Fleetbase\FleetOps\Imports\PlaceImport;
9
9
  use Fleetbase\FleetOps\Models\Place;
10
- use Fleetbase\FleetOps\Support\Geocoding;
10
+ use Fleetbase\FleetOps\Support\PlaceSearch;
11
11
  use Fleetbase\Http\Requests\ExportRequest;
12
12
  use Fleetbase\Http\Requests\ImportRequest;
13
- use Fleetbase\LaravelMysqlSpatial\Types\Point;
14
13
  use Illuminate\Http\Request;
15
14
  use Illuminate\Support\Str;
16
15
  use Maatwebsite\Excel\Facades\Excel;
@@ -50,50 +49,15 @@ class PlaceController extends FleetOpsController
50
49
 
51
50
  $query = Place::where('company_uuid', session('company'))
52
51
  ->whereNull('deleted_at')
53
- ->applyDirectivesForPermissions('fleet-ops list place')
54
- ->search($searchQuery);
55
-
56
- if ($latitude && $longitude) {
57
- $point = new Point($latitude, $longitude);
58
- $query->whereNotNull('location')->whereRaw('
59
- ST_Y(location) BETWEEN -90 AND 90
60
- AND ST_X(location) BETWEEN -180 AND 180
61
- AND NOT (ST_X(location) = 0 AND ST_Y(location) = 0)
62
- ');
63
- $query->orderByDistanceSphere('location', $point, 'asc');
64
- } else {
65
- $query->orderBy('name', 'desc');
66
- }
67
-
68
- if ($limit) {
69
- $query->limit($limit);
70
- }
52
+ ->applyDirectivesForPermissions('fleet-ops list place');
71
53
 
72
- $results = $query->get();
73
-
74
- if ($geo && Geocoding::canGoogleGeocode()) {
75
- if ($searchQuery) {
76
- try {
77
- $geocodingResults = Geocoding::query($searchQuery, $latitude, $longitude);
78
-
79
- foreach ($geocodingResults as $result) {
80
- $results->prepend($result);
81
- }
82
- } catch (\Throwable $e) {
83
- return response()->error($e->getMessage());
84
- }
85
- } elseif ($latitude && $longitude) {
86
- try {
87
- $geocodingResults = Geocoding::reverseFromCoordinates($latitude, $longitude, $searchQuery);
88
-
89
- foreach ($geocodingResults as $result) {
90
- $results->prepend($result);
91
- }
92
- } catch (\Throwable $e) {
93
- return response()->error($e->getMessage());
94
- }
95
- }
96
- }
54
+ $results = PlaceSearch::search($query, $searchQuery, [
55
+ 'limit' => $limit,
56
+ 'geo' => $geo,
57
+ 'latitude' => $latitude,
58
+ 'longitude' => $longitude,
59
+ 'no_query_order' => 'name_desc',
60
+ ]);
97
61
 
98
62
  return PlaceResource::collection($results);
99
63
  }
@@ -108,29 +72,7 @@ class PlaceController extends FleetOpsController
108
72
  $searchQuery = $request->searchQuery();
109
73
  $latitude = $request->input('latitude', false);
110
74
  $longitude = $request->input('longitude', false);
111
- $results = collect();
112
-
113
- if ($searchQuery && Geocoding::canGoogleGeocode()) {
114
- try {
115
- $geocodingResults = Geocoding::query($searchQuery, $latitude, $longitude);
116
-
117
- foreach ($geocodingResults as $result) {
118
- $results->push($result);
119
- }
120
- } catch (\Throwable $e) {
121
- return response()->error($e->getMessage());
122
- }
123
- } elseif ($latitude && $longitude) {
124
- try {
125
- $geocodingResults = Geocoding::reverseFromCoordinates($latitude, $longitude, $searchQuery);
126
-
127
- foreach ($geocodingResults as $result) {
128
- $results->push($result);
129
- }
130
- } catch (\Throwable $e) {
131
- return response()->error($e->getMessage());
132
- }
133
- }
75
+ $results = PlaceSearch::geocode($searchQuery, $latitude, $longitude);
134
76
 
135
77
  return response()->json($results)->withHeaders(['Cache-Control' => 'no-cache']);
136
78
  }
@@ -188,7 +188,7 @@ class ServiceQuoteController extends FleetOpsController
188
188
  $service = $request->input('service', 'all'); // the specific service rate to query - defaults to `all`
189
189
  $serviceType = $request->input('service_type'); // the specific type of service rate to query
190
190
  $isCashOnDelivery = $request->has('cod');
191
- $currency = $request->has('currency');
191
+ $currency = $request->input('currency');
192
192
  $totalDistance = $request->input('distance');
193
193
  $totalTime = $request->input('time');
194
194
  $pickup = $request->or(['payload.pickup', 'payload.pickup_uuid', 'pickup']);
@@ -523,7 +523,7 @@ class SettingController extends Controller
523
523
  $config = config('fleetops.tracking', []);
524
524
  $systemSettings = Setting::lookup('fleet-ops.tracking-settings', []);
525
525
 
526
- $defaults = array_merge($config, is_array($systemSettings) ? $systemSettings : []);
526
+ $defaults = array_merge($config, is_array($systemSettings) ? $systemSettings : []);
527
527
  $defaults['alerts'] = $this->normalizeTrackingAlertSettings(data_get($defaults, 'alerts', []));
528
528
 
529
529
  return $defaults;
@@ -541,7 +541,7 @@ class SettingController extends Controller
541
541
  'distance_threshold_meters' => 500,
542
542
  ],
543
543
  'prolonged_stoppages' => [
544
- 'enabled' => true,
544
+ 'enabled' => true,
545
545
  'duration_threshold_minutes' => 30,
546
546
  ],
547
547
  ];
@@ -5,10 +5,12 @@ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
5
5
  use Fleetbase\FleetOps\Exports\VehicleExport;
6
6
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
7
  use Fleetbase\FleetOps\Imports\VehicleImport;
8
+ use Fleetbase\FleetOps\Models\Driver;
8
9
  use Fleetbase\FleetOps\Models\Vehicle;
9
10
  use Fleetbase\Http\Requests\ExportRequest;
10
11
  use Fleetbase\Http\Requests\ImportRequest;
11
12
  use Illuminate\Http\Request;
13
+ use Illuminate\Support\Arr;
12
14
  use Illuminate\Support\Facades\DB;
13
15
  use Illuminate\Support\Str;
14
16
  use Maatwebsite\Excel\Facades\Excel;
@@ -25,14 +27,65 @@ class VehicleController extends FleetOpsController
25
27
  /**
26
28
  * Handle post save transactions.
27
29
  */
28
- public function afterSave(Request $request, Vehicle $vehicle)
30
+ public function afterSave(Request $request, Vehicle $vehicle, array $input = [])
29
31
  {
32
+ if ($this->hasDriverInput($request)) {
33
+ $this->syncDriverAssignment($vehicle, $this->driverIdentifierFromRequest($request));
34
+ }
35
+
30
36
  $customFieldValues = $request->array('vehicle.custom_field_values');
31
37
  if ($customFieldValues) {
32
38
  $vehicle->syncCustomFieldValues($customFieldValues);
33
39
  }
34
40
  }
35
41
 
42
+ protected function hasDriverInput(Request $request): bool
43
+ {
44
+ $payload = $request->all();
45
+
46
+ return Arr::has($payload, 'vehicle.driver_uuid')
47
+ || Arr::has($payload, 'vehicle.driver.uuid')
48
+ || Arr::has($payload, 'vehicle.driver.id')
49
+ || Arr::has($payload, 'driver_uuid')
50
+ || Arr::has($payload, 'driver');
51
+ }
52
+
53
+ protected function driverIdentifierFromRequest(Request $request): ?string
54
+ {
55
+ $payload = $request->all();
56
+ $driver = data_get($payload, 'vehicle.driver') ?? data_get($payload, 'driver');
57
+
58
+ return data_get($payload, 'vehicle.driver_uuid')
59
+ ?? data_get($payload, 'vehicle.driver.uuid')
60
+ ?? data_get($payload, 'vehicle.driver.id')
61
+ ?? (is_string($driver) ? $driver : null)
62
+ ?? data_get($payload, 'driver_uuid')
63
+ ?? data_get($payload, 'driver.uuid')
64
+ ?? data_get($payload, 'driver.id');
65
+ }
66
+
67
+ protected function syncDriverAssignment(Vehicle $vehicle, ?string $identifier): void
68
+ {
69
+ if (empty($identifier)) {
70
+ $vehicle->unassignDriver();
71
+
72
+ return;
73
+ }
74
+
75
+ $driver = Driver::withoutGlobalScopes()
76
+ ->whereNull('deleted_at')
77
+ ->where(function ($query) use ($identifier) {
78
+ $query->where('uuid', $identifier)->orWhere('public_id', $identifier);
79
+ })
80
+ ->when(session('company'), fn ($query, $company) => $query->where('company_uuid', $company))
81
+ ->first();
82
+
83
+ if ($driver) {
84
+ $vehicle->assignDriver($driver);
85
+ $vehicle->setRelation('driver', $driver);
86
+ }
87
+ }
88
+
36
89
  /**
37
90
  * Get all status options for an vehicle.
38
91
  *
@@ -4,9 +4,12 @@ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\FleetOps\Exports\VendorExport;
6
6
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
+ use Fleetbase\FleetOps\Http\Resources\v1\Contact as ContactResource;
7
8
  use Fleetbase\FleetOps\Imports\VendorImport;
9
+ use Fleetbase\FleetOps\Models\Contact;
8
10
  use Fleetbase\FleetOps\Models\Driver;
9
11
  use Fleetbase\FleetOps\Models\Vendor;
12
+ use Fleetbase\FleetOps\Models\VendorPersonnel;
10
13
  use Fleetbase\Http\Requests\ExportRequest;
11
14
  use Fleetbase\Http\Requests\ImportRequest;
12
15
  use Illuminate\Http\Request;
@@ -190,4 +193,90 @@ class VendorController extends FleetOpsController
190
193
  'status' => 'ok',
191
194
  ]);
192
195
  }
196
+
197
+ public function vendorPersonnels(string $vendorId)
198
+ {
199
+ $vendor = Vendor::findByIdOrFail($vendorId);
200
+
201
+ $personnels = VendorPersonnel::where('vendor_uuid', $vendor->uuid)
202
+ ->with('contact')
203
+ ->latest()
204
+ ->get()
205
+ ->map(fn (VendorPersonnel $personnel) => $this->vendorPersonnelPayload($personnel));
206
+
207
+ return response()->json(['personnels' => $personnels->values()]);
208
+ }
209
+
210
+ public function addVendorPersonnel(Request $request, string $vendorId)
211
+ {
212
+ $vendor = Vendor::findByIdOrFail($vendorId);
213
+ $contact = $this->resolveOrCreatePersonnelContact($request);
214
+
215
+ $personnel = VendorPersonnel::updateOrCreate(
216
+ ['vendor_uuid' => $vendor->uuid, 'contact_uuid' => $contact->uuid],
217
+ [
218
+ 'role' => $request->input('role', 'member'),
219
+ 'status' => $request->input('status', 'active'),
220
+ 'invited_by_uuid' => session('user'),
221
+ ]
222
+ );
223
+
224
+ if ($request->boolean('create_login', true)) {
225
+ $contact->createUser();
226
+ }
227
+
228
+ return response()->json([
229
+ 'personnel' => $this->vendorPersonnelPayload($personnel->load('contact')),
230
+ ]);
231
+ }
232
+
233
+ public function removeVendorPersonnel(string $vendorId, string $contactId)
234
+ {
235
+ $vendor = Vendor::findByIdOrFail($vendorId);
236
+ $contact = Contact::findByIdOrFail($contactId);
237
+
238
+ VendorPersonnel::where(['vendor_uuid' => $vendor->uuid, 'contact_uuid' => $contact->uuid])->delete();
239
+
240
+ return response()->json(['status' => 'ok']);
241
+ }
242
+
243
+ protected function resolveOrCreatePersonnelContact(Request $request): Contact
244
+ {
245
+ $contactId = $request->input('contact');
246
+ if ($contactId) {
247
+ return Contact::where('company_uuid', session('company'))
248
+ ->where(function ($query) use ($contactId) {
249
+ $query->where('uuid', $contactId)->orWhere('public_id', $contactId);
250
+ })
251
+ ->firstOrFail();
252
+ }
253
+
254
+ return Contact::create([
255
+ 'company_uuid' => session('company'),
256
+ 'name' => $request->input('name'),
257
+ 'email' => $request->input('email'),
258
+ 'phone' => $request->input('phone'),
259
+ 'type' => 'customer',
260
+ ]);
261
+ }
262
+
263
+ protected function vendorPersonnelPayload(VendorPersonnel $personnel): array
264
+ {
265
+ $contact = $personnel->contact;
266
+
267
+ return [
268
+ 'id' => $contact?->public_id,
269
+ 'uuid' => $contact?->uuid,
270
+ 'contact_uuid' => $contact?->uuid,
271
+ 'public_id' => $contact?->public_id,
272
+ 'name' => $contact?->name,
273
+ 'email' => $contact?->email,
274
+ 'phone' => $contact?->phone,
275
+ 'photo_url' => $contact?->photo_url,
276
+ 'role' => $personnel->role ?? 'member',
277
+ 'status' => $personnel->status ?? 'active',
278
+ 'invited_by_uuid' => $personnel->invited_by_uuid,
279
+ 'contact' => $contact ? (new ContactResource($contact))->resolve() : null,
280
+ ];
281
+ }
193
282
  }
@@ -0,0 +1,34 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Middleware;
4
+
5
+ use Fleetbase\FleetOps\Support\CustomerAuth;
6
+ use Illuminate\Http\Request;
7
+
8
+ /**
9
+ * Enforce that a Customer-Token header is present and resolves to a
10
+ * customer-type Contact that belongs to the same company as the request's
11
+ * API credential.
12
+ *
13
+ * Apply this middleware to authenticated `/v1/customers/...` routes. The
14
+ * public sign-up / login routes do NOT use it.
15
+ */
16
+ class AuthenticateCustomerToken
17
+ {
18
+ public function handle(Request $request, \Closure $next)
19
+ {
20
+ $customer = CustomerAuth::resolveFromHeader($request);
21
+ if (!$customer) {
22
+ return response()->apiError('Customer token is missing or invalid.', 401);
23
+ }
24
+
25
+ $sessionCompany = session('company');
26
+ if ($sessionCompany && $customer->company_uuid !== $sessionCompany) {
27
+ return response()->apiError('Customer does not belong to this company.', 403);
28
+ }
29
+
30
+ CustomerAuth::setCurrent($customer);
31
+
32
+ return $next($request);
33
+ }
34
+ }
@@ -0,0 +1,63 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+
7
+ /**
8
+ * Validates the body of `POST /v1/customers/orders`.
9
+ *
10
+ * Mirrors the canonical Fleet-Ops Order create shape used by the operator
11
+ * `POST /v1/orders` endpoint: `type` / `order_config`, `scheduled_at`,
12
+ * `notes`, `meta`, and either a nested `payload` or top-level
13
+ * `pickup` / `dropoff` / `return` / `waypoints` / `entities`. Place and
14
+ * Entity sub-objects accept the standard Fleet-Ops Place / Entity field
15
+ * shapes — no client-portal aliases are recognized.
16
+ *
17
+ * `customer` is intentionally not accepted: the controller forces
18
+ * `customer_uuid` from the authenticated Customer-Token. `status`,
19
+ * `driver`, `vehicle`, `facilitator`, `dispatch` and similar operator-tier
20
+ * fields are also out of scope for the customer surface.
21
+ */
22
+ class CreateCustomerOrderRequest extends FleetbaseRequest
23
+ {
24
+ public function authorize(): bool
25
+ {
26
+ return request()->session()->has('api_credential')
27
+ || request()->session()->has('is_sanctum_token');
28
+ }
29
+
30
+ public function rules(): array
31
+ {
32
+ return [
33
+ 'type' => 'nullable|string',
34
+ 'order_config' => 'nullable|string',
35
+ 'scheduled_at' => 'nullable|date',
36
+ 'notes' => 'nullable|string|max:2000',
37
+ 'internal_id' => 'nullable|string|max:191',
38
+ 'meta' => 'nullable|array',
39
+ // Optional ServiceQuote reference (uuid or `sqte_…` public_id).
40
+ // Resolved server-side via `ServiceQuote::resolveFromRequest` and
41
+ // consumed by `$order->purchaseServiceQuote()` after creation.
42
+ 'service_quote' => 'nullable|string',
43
+
44
+ // payload may be an object OR a payload public_id string
45
+ 'payload' => 'nullable',
46
+
47
+ // Top-level alternatives when `payload` is not provided. Each
48
+ // Place sub-object accepts the standard Place fillable shape.
49
+ 'pickup' => 'nullable',
50
+ 'dropoff' => 'nullable',
51
+ 'return' => 'nullable',
52
+ 'waypoints' => 'nullable|array',
53
+ 'entities' => 'nullable|array',
54
+ 'entities.*.name' => 'nullable|string',
55
+ 'entities.*.description' => 'nullable|string',
56
+ 'entities.*.weight' => 'nullable|numeric|min:0',
57
+ 'entities.*.weight_unit' => 'nullable|string',
58
+ 'entities.*.declared_value' => 'nullable|numeric|min:0',
59
+ 'entities.*.currency' => 'nullable|string|size:3',
60
+ 'entities.*.meta' => 'nullable|array',
61
+ ];
62
+ }
63
+ }
@@ -0,0 +1,48 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+ use Illuminate\Validation\Rule;
7
+
8
+ /**
9
+ * Validates the body of `POST /v1/customers` (final account creation step).
10
+ *
11
+ * Expects the verification code previously requested via
12
+ * `POST /v1/customers/request-creation-code`.
13
+ */
14
+ class CreateCustomerRequest extends FleetbaseRequest
15
+ {
16
+ public function authorize(): bool
17
+ {
18
+ return request()->session()->has('api_credential')
19
+ || request()->session()->has('is_sanctum_token');
20
+ }
21
+
22
+ public function rules(): array
23
+ {
24
+ return [
25
+ 'identity' => 'required|string',
26
+ 'code' => 'required|exists:verification_codes,code',
27
+ 'name' => 'required|string',
28
+ 'password' => 'required|string|min:8',
29
+ 'email' => [
30
+ 'email', 'nullable',
31
+ Rule::unique('contacts')->where(function ($query) {
32
+ $query->where('company_uuid', session('company'));
33
+
34
+ return $query->whereNull('deleted_at');
35
+ }),
36
+ ],
37
+ 'phone' => [
38
+ 'nullable', 'string',
39
+ Rule::unique('contacts')->where(function ($query) {
40
+ $query->where('company_uuid', session('company'));
41
+
42
+ return $query->whereNull('deleted_at');
43
+ }),
44
+ ],
45
+ 'meta' => 'nullable|array',
46
+ ];
47
+ }
48
+ }
@@ -35,7 +35,7 @@ class CreateDriverRequest extends FleetbaseRequest
35
35
  'country' => 'nullable|size:2',
36
36
  'city' => 'nullable|string',
37
37
  'vehicle' => 'nullable|string|starts_with:vehicle_|exists:vehicles,public_id',
38
- 'status' => 'nullable|string|in:active,inactive',
38
+ 'status' => 'nullable|string|in:active,available,inactive',
39
39
  'vendor' => 'nullable|exists:vendors,public_id',
40
40
  'job' => 'nullable|exists:orders,public_id',
41
41
  'location' => ['nullable', new ResolvablePoint()],
@@ -26,10 +26,14 @@ class CreateIssueRequest extends FleetbaseRequest
26
26
  return [
27
27
  'driver' => ['required'],
28
28
  'location' => ['required'],
29
+ 'order' => ['nullable', 'exists:orders,public_id'],
30
+ 'order_uuid' => ['nullable', 'exists:orders,uuid'],
29
31
  'report' => ['required'],
30
32
  'category' => ['nullable'],
31
33
  'type' => ['nullable'],
32
34
  'priority' => ['nullable'],
35
+ 'tags' => ['nullable', 'array'],
36
+ 'tags.*' => ['string'],
33
37
  ];
34
38
  }
35
39
  }
@@ -4,6 +4,7 @@ namespace Fleetbase\FleetOps\Http\Requests;
4
4
 
5
5
  use Fleetbase\FleetOps\Rules\ResolvablePoint;
6
6
  use Fleetbase\Http\Requests\FleetbaseRequest;
7
+ use Illuminate\Validation\Rule;
7
8
 
8
9
  class CreateVehicleRequest extends FleetbaseRequest
9
10
  {
@@ -25,7 +26,30 @@ class CreateVehicleRequest extends FleetbaseRequest
25
26
  public function rules()
26
27
  {
27
28
  return [
28
- 'status' => 'nullable|in:operational,maintenance,decommissioned',
29
+ 'status' => [
30
+ 'nullable',
31
+ Rule::in([
32
+ 'active',
33
+ 'available',
34
+ 'in_use',
35
+ 'maintenance',
36
+ 'out_of_service',
37
+ 'reserved',
38
+ 'retired',
39
+ 'staging',
40
+ 'on_route',
41
+ 'idle',
42
+ 'cleaning',
43
+ 'awaiting_parts',
44
+ 'inspection_due',
45
+ 'inspection_failed',
46
+ 'accident',
47
+ 'compliance_hold',
48
+ 'stolen',
49
+ 'operational',
50
+ 'decommissioned',
51
+ ]),
52
+ ],
29
53
  'vendor' => 'nullable|exists:vendors,public_id',
30
54
  'driver' => 'nullable|exists:drivers,public_id',
31
55
  'location' => ['nullable', new ResolvablePoint()],
@@ -51,7 +51,7 @@ class CreateDriverRequest extends CreateDriverApiRequest
51
51
  'country' => 'nullable|string|size:2',
52
52
  'city' => 'nullable|string|max:255',
53
53
  'vehicle' => ['nullable', new ResolvableVehicle()],
54
- 'status' => 'nullable|string|in:active,inactive',
54
+ 'status' => 'nullable|string|in:active,available,inactive',
55
55
  'vendor' => 'nullable|exists:vendors,public_id',
56
56
  'job' => 'nullable|exists:orders,public_id',
57
57
  'location' => ['nullable', new ResolvablePoint()],
@@ -13,9 +13,13 @@ class UpdateIssueRequest extends CreateIssueRequest
13
13
  {
14
14
  return [
15
15
  'report' => ['required'],
16
+ 'order' => ['nullable', 'exists:orders,public_id'],
17
+ 'order_uuid' => ['nullable', 'exists:orders,uuid'],
16
18
  'category' => ['nullable'],
17
19
  'type' => ['nullable'],
18
20
  'priority' => ['nullable'],
21
+ 'tags' => ['nullable', 'array'],
22
+ 'tags.*' => ['string'],
19
23
  ];
20
24
  }
21
25
  }
@@ -0,0 +1,29 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+
7
+ /**
8
+ * Validates the body of `POST /v1/customers/request-creation-code`.
9
+ */
10
+ class VerifyCreateCustomerRequest extends FleetbaseRequest
11
+ {
12
+ public function authorize(): bool
13
+ {
14
+ return request()->session()->has('api_credential')
15
+ || request()->session()->has('is_sanctum_token');
16
+ }
17
+
18
+ public function rules(): array
19
+ {
20
+ return [
21
+ 'mode' => 'required|in:email,sms',
22
+ 'identity' => 'required|string',
23
+ // Optional profile context — when provided, used to greet the
24
+ // customer in the verification email and pre-seed the User row.
25
+ 'name' => 'nullable|string|max:255',
26
+ 'phone' => 'nullable|string|max:32',
27
+ ];
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Resources\Internal\v1;
4
+
5
+ use Fleetbase\Http\Resources\FleetbaseResource;
6
+
7
+ class OrderConfig extends FleetbaseResource
8
+ {
9
+ /**
10
+ * Transform the resource into an array.
11
+ *
12
+ * @param \Illuminate\Http\Request $request
13
+ */
14
+ public function toArray($request): array
15
+ {
16
+ return [
17
+ 'id' => $this->id,
18
+ 'uuid' => $this->uuid,
19
+ 'public_id' => $this->public_id,
20
+ 'company_uuid' => $this->company_uuid,
21
+ 'author_uuid' => $this->author_uuid,
22
+ 'category_uuid' => $this->category_uuid,
23
+ 'icon_uuid' => $this->icon_uuid,
24
+ 'name' => $this->name,
25
+ 'namespace' => $this->namespace,
26
+ 'description' => $this->description,
27
+ 'key' => $this->key,
28
+ 'status' => $this->status,
29
+ 'version' => $this->version,
30
+ 'core_service' => (bool) $this->core_service,
31
+ 'flow' => $this->flow ?? [],
32
+ 'entities' => $this->entities ?? [],
33
+ 'tags' => $this->tags ?? [],
34
+ 'meta' => $this->meta ?? [],
35
+ 'created_at' => $this->created_at,
36
+ 'updated_at' => $this->updated_at,
37
+ 'deleted_at' => $this->deleted_at,
38
+ 'type' => 'order-config',
39
+ ];
40
+ }
41
+ }