@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
@@ -50,7 +50,7 @@ class DriverController extends Controller
50
50
  $input = $request->except(['name', 'password', 'email', 'phone', 'location', 'altitude', 'heading', 'speed', 'meta']);
51
51
 
52
52
  // Add default status
53
- $input['status'] = 'active';
53
+ $input['status'] = $request->input('status', 'available');
54
54
 
55
55
  // get user details for driver
56
56
  $userDetails = $request->only(['name', 'password', 'email', 'phone', 'timezone']);
@@ -30,6 +30,7 @@ class IssueController extends Controller
30
30
  'type',
31
31
  'report',
32
32
  'priority',
33
+ 'tags',
33
34
  'status',
34
35
  ]);
35
36
 
@@ -85,6 +86,7 @@ class IssueController extends Controller
85
86
  'type',
86
87
  'report',
87
88
  'priority',
89
+ 'tags',
88
90
  'status',
89
91
  ]);
90
92
 
@@ -0,0 +1,54 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
4
+
5
+ use Fleetbase\FleetOps\Http\Resources\v1\OrderConfig as OrderConfigResource;
6
+ use Fleetbase\FleetOps\Models\OrderConfig;
7
+ use Fleetbase\Http\Controllers\Controller;
8
+ use Illuminate\Database\Eloquent\ModelNotFoundException;
9
+ use Illuminate\Http\Request;
10
+
11
+ /**
12
+ * Public, read-only API surface for OrderConfigs.
13
+ *
14
+ * The internal admin console manages the full OrderConfig lifecycle through
15
+ * `/int/v1/order-configs`. This public endpoint exposes the projected,
16
+ * non-sensitive subset (flow activities + metadata) that consumers like a
17
+ * customer portal need to render status filter chips, activity labels, and
18
+ * other UI driven by the active flow.
19
+ */
20
+ class OrderConfigController extends Controller
21
+ {
22
+ /**
23
+ * List the OrderConfigs for the company resolved from the API credential.
24
+ *
25
+ * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
26
+ */
27
+ public function query(Request $request)
28
+ {
29
+ $results = OrderConfig::queryWithRequest($request);
30
+
31
+ return OrderConfigResource::collection($results);
32
+ }
33
+
34
+ /**
35
+ * Find a single OrderConfig. The `{id}` segment accepts any identifier
36
+ * supported by {@see OrderConfig::resolveFromIdentifier} — `uuid`,
37
+ * `public_id`, `namespace`, or short `key` (e.g. `transport`).
38
+ *
39
+ * @return OrderConfigResource|\Illuminate\Http\JsonResponse
40
+ */
41
+ public function find(string $id)
42
+ {
43
+ $orderConfig = OrderConfig::resolveFromIdentifier($id);
44
+ if (!$orderConfig) {
45
+ try {
46
+ $orderConfig = OrderConfig::findRecordOrFail($id);
47
+ } catch (ModelNotFoundException $e) {
48
+ return response()->apiError('Order config not found.', 404);
49
+ }
50
+ }
51
+
52
+ return new OrderConfigResource($orderConfig);
53
+ }
54
+ }
@@ -5,6 +5,7 @@ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
5
5
  use Fleetbase\FleetOps\Events\OrderDispatchFailed;
6
6
  use Fleetbase\FleetOps\Events\OrderReady;
7
7
  use Fleetbase\FleetOps\Events\OrderStarted;
8
+ use Fleetbase\FleetOps\Exceptions\CustomerUserConflictException;
8
9
  use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
9
10
  use Fleetbase\FleetOps\Flow\Activity;
10
11
  use Fleetbase\FleetOps\Http\Requests\CreateOrderRequest;
@@ -23,6 +24,7 @@ use Fleetbase\FleetOps\Models\Place;
23
24
  use Fleetbase\FleetOps\Models\Proof;
24
25
  use Fleetbase\FleetOps\Models\ServiceQuote;
25
26
  use Fleetbase\FleetOps\Models\Waypoint;
27
+ use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
26
28
  use Fleetbase\FleetOps\Support\Utils;
27
29
  use Fleetbase\Http\Controllers\Controller;
28
30
  use Fleetbase\Http\Resources\Comment as CommentResource;
@@ -43,6 +45,8 @@ use Illuminate\Validation\ValidationException;
43
45
 
44
46
  class OrderController extends Controller
45
47
  {
48
+ use ResolvesOrderServiceStops;
49
+
46
50
  /**
47
51
  * Creates a new Fleetbase Order resource.
48
52
  *
@@ -251,6 +255,13 @@ class OrderController extends Controller
251
255
  $customer = Arr::only($customer, ['internal_id', 'name', 'title', 'email', 'phone', 'meta']);
252
256
 
253
257
  try {
258
+ $customerCandidate = new Contact([
259
+ ...$customer,
260
+ 'company_uuid' => session('company'),
261
+ 'type' => 'customer',
262
+ ]);
263
+ $customerCandidate->assertCustomerIdentityIsAvailable();
264
+
254
265
  $customer = Contact::firstOrCreate(
255
266
  [
256
267
  'company_uuid' => session('company'),
@@ -263,19 +274,12 @@ class OrderController extends Controller
263
274
  'type' => 'customer',
264
275
  ]
265
276
  );
277
+ } catch (CustomerUserConflictException $e) {
278
+ return response()->apiError($e->getMessage());
279
+ } catch (UserAlreadyExistsException $e) {
280
+ return response()->apiError($e->getMessage());
266
281
  } catch (\Exception $e) {
267
282
  return response()->apiError('Failed to find or create customer for order.');
268
- } catch (UserAlreadyExistsException $e) {
269
- try {
270
- // If user already exist then assign user to this customer and the company
271
- $existingUser = $e->getUser();
272
- // Assign user to customer
273
- if ($existingUser && $customer) {
274
- $customer->assignUser($existingUser);
275
- }
276
- } catch (\Exception $e) {
277
- return response()->apiError('Failed to find or create customer for order.');
278
- }
279
283
  }
280
284
 
281
285
  if (Str::isUuid($customer)) {
@@ -1009,11 +1013,10 @@ class OrderController extends Controller
1009
1013
  $driver->current_job_uuid = $order->uuid;
1010
1014
  $driver->save();
1011
1015
 
1012
- /** @var \Fleetbase\LaravelMysqlSpatial\Types\Point */
1013
- $location = $order->getLastLocation();
1014
-
1015
- // set first destination for payload
1016
- $payload->setFirstWaypoint($activity, $location);
1016
+ // set first service stop destination for multi-stop routes
1017
+ if ($this->payloadUsesServiceStopActivity($payload)) {
1018
+ $this->ensurePayloadCurrentServiceStop($payload);
1019
+ }
1017
1020
  $order->setRelation('payload', $payload);
1018
1021
 
1019
1022
  // update order activity
@@ -1043,7 +1046,17 @@ class OrderController extends Controller
1043
1046
  // if string $id
1044
1047
  if (!$order) {
1045
1048
  try {
1046
- $order = Order::findRecordOrFail($id, ['driverAssigned', 'payload.entities', 'payload.currentWaypoint', 'payload.waypoints']);
1049
+ $order = Order::findRecordOrFail($id, [
1050
+ 'driverAssigned',
1051
+ 'payload.entities',
1052
+ 'payload.pickup',
1053
+ 'payload.dropoff',
1054
+ 'payload.return',
1055
+ 'payload.currentWaypoint',
1056
+ 'payload.waypoints',
1057
+ 'payload.waypointMarkers.place',
1058
+ 'payload.waypointMarkers.trackingNumber.status',
1059
+ ]);
1047
1060
  } catch (ModelNotFoundException $exception) {
1048
1061
  return response()->apiError('Order resource not found.', 404);
1049
1062
  }
@@ -1094,65 +1107,59 @@ class OrderController extends Controller
1094
1107
  /** @var \Fleetbase\LaravelMysqlSpatial\Types\Point */
1095
1108
  $location = $order->getLastLocation();
1096
1109
 
1097
- // Check if multiple waypoint order to update activity for
1098
- $isMultipleWaypointOrder = (bool) $order->payload->isMultipleDropOrder;
1099
-
1100
- // if is multi drop order and no current destination set it
1101
- if ($isMultipleWaypointOrder && !$order->payload->current_waypoint_uuid) {
1102
- $order->payload->setFirstWaypoint($activity, $location);
1103
- }
1110
+ $usesServiceStopActivity = $this->payloadUsesServiceStopActivity($order->payload);
1111
+ $isLifecycleActivity = Utils::isActivity($activity) && in_array($activity->code, ['created', 'dispatched', 'started'], true);
1104
1112
 
1105
- // Handle multiple dropoff waypoint activity
1106
- if (Utils::isActivity($activity) && $activity->completesOrder() && $isMultipleWaypointOrder) {
1107
- // Check if every waypoint is completed
1108
- $isCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
1109
- return $waypoint->complete;
1110
- });
1113
+ // Classic pickup/dropoff orders and static lifecycle statuses update the order.
1114
+ if (!$usesServiceStopActivity || $isLifecycleActivity) {
1115
+ // Update order activity
1116
+ $order->updateActivity($activity, $proof);
1111
1117
 
1112
- // only update activity for waypoint
1113
- if (!$isCompleted) {
1114
- $order->payload->updateWaypointActivity($activity, $location, $proof);
1115
- $order->payload->setNextWaypointDestination();
1116
- $order->payload->load('waypointMarkers');
1118
+ if (!$usesServiceStopActivity) {
1119
+ foreach ($order->payload->entities as $entity) {
1120
+ $entity->insertActivity($activity, $location, $proof);
1121
+ }
1122
+ }
1117
1123
 
1118
- // recheck if order is completed
1119
- $isFullyCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
1120
- return $waypoint->complete;
1121
- });
1124
+ if ($usesServiceStopActivity && $activity->is('started')) {
1125
+ $this->ensurePayloadCurrentServiceStop($order->payload);
1126
+ }
1122
1127
 
1123
- if (!$isFullyCompleted) {
1124
- return new OrderResource($order);
1125
- }
1128
+ // Handle order completion
1129
+ if (Utils::isActivity($activity) && $activity->completesOrder()) {
1130
+ $order->driverAssigned?->unassignCurrentJob();
1131
+ $order->complete($this->resolveProof($proof));
1126
1132
  }
1133
+
1134
+ return new OrderResource($order->refresh());
1127
1135
  }
1128
1136
 
1129
- // also update for each order entities if not multiple drop order
1130
- // all entities will share the same activity status as is one drop order
1131
- if (!$isMultipleWaypointOrder) {
1132
- // Update order activity
1133
- $order->updateActivity($activity, $proof);
1137
+ if (!$order->started && in_array($order->status, ['created', 'dispatched'], true)) {
1138
+ return response()->apiError('Order must be started before waypoint activity can be updated.', 422);
1139
+ }
1134
1140
 
1135
- // Only update entities belonging to the waypoint
1136
- foreach ($order->payload->entities as $entity) {
1137
- $entity->insertActivity($activity, $location, $proof);
1138
- }
1139
- } else {
1140
- // Update parent order when status is `dispatched` or `started`
1141
- if (in_array($activity->code, ['started', 'dispatched'])) {
1142
- $order->updateActivity($activity, $proof);
1143
- }
1141
+ $currentStop = $this->ensurePayloadCurrentServiceStop($order->payload);
1142
+ $isCompletingCurrentStop = Utils::isActivity($activity) && $activity->complete();
1144
1143
 
1145
- $order->payload->updateWaypointActivity($activity, $location);
1144
+ $this->updateCurrentServiceStopActivity($order, $activity, $location, $proof);
1145
+
1146
+ if (Utils::isActivity($activity) && $activity->is('canceled')) {
1147
+ $order->driverAssigned?->unassignCurrentJob();
1148
+ $order->cancel();
1149
+
1150
+ return new OrderResource($order->refresh());
1146
1151
  }
1147
1152
 
1148
- // Handle order completion
1149
1153
  if (Utils::isActivity($activity) && $activity->completesOrder()) {
1150
- // unset from driver current job
1151
- $order->driverAssigned->unassignCurrentJob();
1152
- $order->complete();
1154
+ $nextStop = $isCompletingCurrentStop ? $this->advanceCurrentServiceStopDestination($order, $order->payload) : null;
1155
+
1156
+ if (!$nextStop || !$currentStop) {
1157
+ $order->driverAssigned?->unassignCurrentJob();
1158
+ $order->complete($this->resolveProof($proof));
1159
+ }
1153
1160
  }
1154
1161
 
1155
- return new OrderResource($order);
1162
+ return new OrderResource($order->refresh());
1156
1163
  }
1157
1164
 
1158
1165
  /**
@@ -1165,7 +1172,14 @@ class OrderController extends Controller
1165
1172
  $waypointId = $request->input('waypoint');
1166
1173
 
1167
1174
  try {
1168
- $order = Order::findRecordOrFail($id, ['payload']);
1175
+ $order = Order::findRecordOrFail($id, [
1176
+ 'payload.pickup',
1177
+ 'payload.dropoff',
1178
+ 'payload.return',
1179
+ 'payload.waypoints',
1180
+ 'payload.waypointMarkers.place',
1181
+ 'payload.waypointMarkers.trackingNumber.status',
1182
+ ]);
1169
1183
  } catch (ModelNotFoundException $exception) {
1170
1184
  return response()->json(
1171
1185
  [
@@ -1175,19 +1189,20 @@ class OrderController extends Controller
1175
1189
  );
1176
1190
  }
1177
1191
 
1178
- // Get waypoint record if available
1179
- $waypoint = null;
1180
- if ($waypointId) {
1181
- $waypoint = Waypoint::where('payload_uuid', $order->payload_uuid)->whereHas('place', function ($query) use ($waypointId) {
1182
- $query->where('public_id', $waypointId);
1183
- })->first();
1192
+ $canUpdateServiceStopActivity = $this->payloadUsesServiceStopActivity($order->payload)
1193
+ && ($order->started || $order->started_at || !in_array($order->status, ['created', 'dispatched'], true));
1194
+ $stop = null;
1195
+ if ($canUpdateServiceStopActivity) {
1196
+ $stop = $waypointId
1197
+ ? $this->resolveServiceStopFromKey($order->payload, $waypointId)
1198
+ : $this->payloadCurrentServiceStop($order->payload);
1184
1199
  }
1185
1200
 
1186
1201
  $orderConfig = $order->ensureOrderConfig();
1187
1202
  if (!$orderConfig) {
1188
1203
  return response()->apiError('No order config found for order.');
1189
1204
  }
1190
- $activities = $orderConfig->nextActivity($waypoint);
1205
+ $activities = $stop ? $this->nextActivitiesForServiceStop($order, $order->payload, $stop) : $orderConfig->nextActivity();
1191
1206
 
1192
1207
  // If activity is to complete order add proof of delivery properties if required
1193
1208
  // This is a temporary fix until activity is updated to handle POD on it's own
@@ -1282,7 +1297,18 @@ class OrderController extends Controller
1282
1297
  public function setDestination(string $id, string $placeId)
1283
1298
  {
1284
1299
  try {
1285
- $order = Order::findRecordOrFail($id, ['payload.waypoints', 'payload.pickup', 'payload.dropoff', 'driverAssigned', 'vehicleAssigned', 'customer', 'facilitator']);
1300
+ $order = Order::findRecordOrFail($id, [
1301
+ 'payload.pickup',
1302
+ 'payload.dropoff',
1303
+ 'payload.return',
1304
+ 'payload.waypoints',
1305
+ 'payload.waypointMarkers.place',
1306
+ 'payload.waypointMarkers.trackingNumber.status',
1307
+ 'driverAssigned',
1308
+ 'vehicleAssigned',
1309
+ 'customer',
1310
+ 'facilitator',
1311
+ ]);
1286
1312
  } catch (ModelNotFoundException $exception) {
1287
1313
  return response()->apiError('Order resource not found.', 404);
1288
1314
  }
@@ -1290,17 +1316,13 @@ class OrderController extends Controller
1290
1316
  // Get the order payload
1291
1317
  $payload = $order->payload;
1292
1318
 
1293
- // Resolve the place
1294
- $place = $payload->waypoints->firstWhere('public_id', $placeId)
1295
- ?? (($payload->pickup && $payload->pickup->public_id === $placeId) ? $payload->pickup : null)
1296
- ?? (($payload->dropoff && $payload->dropoff->public_id === $placeId) ? $payload->dropoff : null);
1297
-
1298
- if (!$place) {
1319
+ $stop = $this->resolveServiceStopFromKey($payload, $placeId);
1320
+ if (!$stop) {
1299
1321
  return response()->apiError('Place resource is not a valid destination.', 422);
1300
1322
  }
1301
1323
 
1302
1324
  // Persist destination choice
1303
- $payload->setCurrentWaypoint($place);
1325
+ $this->setPayloadCurrentServiceStop($payload, $stop);
1304
1326
 
1305
1327
  return new OrderResource($order->refresh());
1306
1328
  }
@@ -1385,7 +1407,7 @@ class OrderController extends Controller
1385
1407
 
1386
1408
  // Load Order
1387
1409
  try {
1388
- $order = Order::findRecordOrFail($id);
1410
+ $order = Order::findRecordOrFail($id, ['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints', 'payload.waypointMarkers.place']);
1389
1411
  } catch (ModelNotFoundException $e) {
1390
1412
  return response()->apiError('Order resource not found.', 404);
1391
1413
  }
@@ -1580,7 +1602,7 @@ class OrderController extends Controller
1580
1602
 
1581
1603
  // Load Order
1582
1604
  try {
1583
- $order = Order::findRecordOrFail($id);
1605
+ $order = Order::findRecordOrFail($id, ['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints', 'payload.waypointMarkers.place']);
1584
1606
  } catch (ModelNotFoundException $e) {
1585
1607
  return response()->apiError('Order resource not found.', 404);
1586
1608
  }
@@ -1678,6 +1700,13 @@ class OrderController extends Controller
1678
1700
  return $order;
1679
1701
  }
1680
1702
 
1703
+ if (in_array($type, ['place', 'waypoint'], true)) {
1704
+ $stop = $this->resolveServiceStopFromKey($order->payload, $subjectId);
1705
+ if ($stop) {
1706
+ return ($stop['waypoint'] ?? null) instanceof Waypoint ? $stop['waypoint'] : ($stop['place'] ?? $order);
1707
+ }
1708
+ }
1709
+
1681
1710
  return match ($type) {
1682
1711
  'place', 'waypoint' => Waypoint::withoutGlobalScopes()
1683
1712
  ->where('payload_uuid', $order->payload_uuid)
@@ -7,15 +7,11 @@ use Fleetbase\FleetOps\Http\Requests\UpdatePlaceRequest;
7
7
  use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
8
8
  use Fleetbase\FleetOps\Http\Resources\v1\Place as PlaceResource;
9
9
  use Fleetbase\FleetOps\Models\Place;
10
+ use Fleetbase\FleetOps\Support\PlaceSearch;
10
11
  use Fleetbase\FleetOps\Support\Utils;
11
12
  use Fleetbase\Http\Controllers\Controller;
12
13
  use Fleetbase\LaravelMysqlSpatial\Types\Point;
13
- use Geocoder\Laravel\Facades\Geocoder;
14
- use Geocoder\Provider\GoogleMapsPlaces\GoogleMapsPlaces;
15
- use Geocoder\Query\GeocodeQuery;
16
- use Http\Adapter\Guzzle7\Client;
17
14
  use Illuminate\Http\Request;
18
- use Illuminate\Support\Facades\DB;
19
15
  use Illuminate\Support\Str;
20
16
 
21
17
  class PlaceController extends Controller
@@ -283,63 +279,19 @@ class PlaceController extends Controller
283
279
  {
284
280
  $searchQuery = strtolower($request->input('query'));
285
281
  $limit = $request->input('limit', 10);
286
- $geo = $request->input('geo', false);
282
+ $geo = $request->boolean('geo');
287
283
  $latitude = $request->input('latitude', false);
288
284
  $longitude = $request->input('longitude', false);
289
285
 
290
- $results = DB::table('places')
291
- ->where('company_uuid', session('company'))
292
- ->whereNull('deleted_at')
293
- ->where(function ($q) use ($searchQuery) {
294
- if (Utils::notEmpty($searchQuery)) {
295
- $q->orWhere(DB::raw('lower(name)'), 'like', '%' . $searchQuery . '%');
296
- $q->orWhere(DB::raw('lower(street1)'), 'like', '%' . $searchQuery . '%');
297
- $q->orWhere(DB::raw('lower(postal_code)'), 'like', '%' . $searchQuery . '%');
298
- }
299
- })
300
- ->limit($limit)
301
- ->orderBy('name', 'desc')
302
- ->get()
303
- ->map(function ($place) {
304
- $place = (array) $place;
305
- $place['location'] = Utils::mysqlPointAsGeometry($place['location']);
306
- $place = new Place($place);
307
- $place->address = $place->toAddressString();
308
-
309
- return $place;
310
- })
311
- ->values();
312
-
313
- if ($geo && Utils::notEmpty($searchQuery)) {
314
- $httpClient = new Client();
315
- $provider = new \Geocoder\Provider\GoogleMaps\GoogleMaps($httpClient, null, env('GOOGLE_MAPS_API_KEY'));
316
- $geocoder = new \Geocoder\StatefulGeocoder($provider, 'en');
317
-
318
- if ($latitude && $longitude) {
319
- $geoResults = $geocoder->geocodeQuery(
320
- GeocodeQuery::create($searchQuery)
321
- ->withData('mode', GoogleMapsPlaces::GEOCODE_MODE_SEARCH)
322
- ->withData('location', "$latitude, $longitude")
323
- );
324
-
325
- $geoResults = collect($geoResults->all());
326
- } else {
327
- $geoResults = Geocoder::geocode($searchQuery)->get();
328
- }
286
+ $query = Place::where('company_uuid', session('company'))->whereNull('deleted_at');
329
287
 
330
- $geoResults = $geoResults
331
- ->map(function ($googleAddress) {
332
- return Place::createFromGoogleAddress($googleAddress);
333
- })
334
- ->values();
335
-
336
- $results = $results->merge($geoResults);
337
- }
338
-
339
- $results = $results
340
- ->sortBy('name')
341
- ->values()
342
- ->toArray();
288
+ $results = PlaceSearch::search($query, $searchQuery, [
289
+ 'limit' => $limit,
290
+ 'geo' => $geo,
291
+ 'latitude' => $latitude,
292
+ 'longitude' => $longitude,
293
+ 'no_query_order' => 'name_desc',
294
+ ]);
343
295
 
344
296
  return PlaceResource::collection($results);
345
297
  }
@@ -24,7 +24,7 @@ class ServiceAreaController extends Controller
24
24
  public function create(CreateServiceAreaRequest $request)
25
25
  {
26
26
  // get request input
27
- $input = $request->only(['name', 'type', 'status', 'country', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
27
+ $input = $request->only(['name', 'type', 'status', 'country', 'border', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
28
28
 
29
29
  // get radius for creating service area border - default to 500 meters
30
30
  $radius = (int) $request->input('radius', 500);
@@ -99,7 +99,7 @@ class ServiceAreaController extends Controller
99
99
  }
100
100
 
101
101
  // get request input
102
- $input = $request->only(['name', 'type', 'status', 'country', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
102
+ $input = $request->only(['name', 'type', 'status', 'country', 'border', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
103
103
 
104
104
  // get radius for creating service area border - default to 500 meters
105
105
  $radius = $request->input('radius', 500);
@@ -67,7 +67,7 @@ class VehicleController extends Controller
67
67
  $vehicle = Vehicle::create($input);
68
68
 
69
69
  // driver assignment
70
- if ($request->has('driver')) {
70
+ if ($request->exists('driver') && !empty($request->input('driver'))) {
71
71
  // set this vehicle to the driver
72
72
  try {
73
73
  $driver = Driver::findRecordOrFail($request->input('driver'));
@@ -80,8 +80,7 @@ class VehicleController extends Controller
80
80
  );
81
81
  }
82
82
 
83
- $driver->vehicle_uuid = $vehicle->uuid;
84
- $driver->save();
83
+ $vehicle->assignDriver($driver);
85
84
  }
86
85
 
87
86
  // response the driver resource
@@ -150,6 +149,24 @@ class VehicleController extends Controller
150
149
  // save the update
151
150
  $vehicle->save();
152
151
 
152
+ if ($request->exists('driver')) {
153
+ if (empty($request->input('driver'))) {
154
+ $vehicle->unassignDriver();
155
+ } else {
156
+ try {
157
+ $driver = Driver::findRecordOrFail($request->input('driver'));
158
+ $vehicle->assignDriver($driver);
159
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
160
+ return response()->json(
161
+ [
162
+ 'error' => 'The driver attempted to assign this vehicle was not found.',
163
+ ],
164
+ 404
165
+ );
166
+ }
167
+ }
168
+ }
169
+
153
170
  // get udpated vehicle
154
171
  $vehicle = $vehicle->refresh();
155
172