@fleetbase/fleetops-engine 0.6.48 → 0.6.50

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 (266) hide show
  1. package/addon/components/customer/create-order-form.hbs +2 -2
  2. package/addon/components/customer/form.hbs +2 -2
  3. package/addon/components/customer/order-form.hbs +2 -2
  4. package/addon/components/customer/orders.hbs +2 -2
  5. package/addon/components/display-place.hbs +2 -2
  6. package/addon/components/driver/details.hbs +5 -1
  7. package/addon/components/driver/form.hbs +11 -1
  8. package/addon/components/fleet-listing-panel.hbs +2 -2
  9. package/addon/components/fleet-listing-panel.js +10 -0
  10. package/addon/components/map/google-live-map.js +18 -75
  11. package/addon/components/map/leaflet-live-map.hbs +187 -37
  12. package/addon/components/map/leaflet-live-map.js +184 -20
  13. package/addon/components/modals/order-change-destination.hbs +46 -0
  14. package/addon/components/modals/order-change-destination.js +106 -0
  15. package/addon/components/modals/stop-activity.hbs +28 -0
  16. package/addon/components/modals/stop-activity.js +52 -0
  17. package/addon/components/modals/update-order-activity.hbs +45 -3
  18. package/addon/components/modals/update-order-activity.js +143 -5
  19. package/addon/components/orchestrator/phase-builder.hbs +21 -0
  20. package/addon/components/orchestrator/phase-builder.js +13 -0
  21. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  22. package/addon/components/orchestrator/resource-panel.js +2 -4
  23. package/addon/components/orchestrator-workbench.js +1 -0
  24. package/addon/components/order/activity-list.hbs +2 -2
  25. package/addon/components/order/activity-timeline.hbs +31 -29
  26. package/addon/components/order/details/activity.hbs +3 -3
  27. package/addon/components/order/details/activity.js +11 -0
  28. package/addon/components/order/details/documents.js +2 -0
  29. package/addon/components/order/details/purchase-rate.hbs +2 -2
  30. package/addon/components/order/details/route.hbs +11 -3
  31. package/addon/components/order/details/route.js +91 -14
  32. package/addon/components/order/details/tracking.hbs +101 -72
  33. package/addon/components/order/details/tracking.js +149 -2
  34. package/addon/components/order/form/details.hbs +3 -3
  35. package/addon/components/order/form/documents.js +11 -4
  36. package/addon/components/order/form/route.hbs +108 -104
  37. package/addon/components/order/form/route.js +37 -1
  38. package/addon/components/order/form/service-rate.hbs +2 -2
  39. package/addon/components/order/form.hbs +2 -2
  40. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  41. package/addon/components/order-config-manager/activity-flow.js +178 -12
  42. package/addon/components/place/address.js +1 -1
  43. package/addon/components/route-list.hbs +17 -17
  44. package/addon/components/route-list.js +42 -10
  45. package/addon/components/service-rate/details.hbs +41 -0
  46. package/addon/components/service-rate/form.hbs +118 -0
  47. package/addon/components/service-rate/form.js +55 -0
  48. package/addon/components/vehicle/form.js +1 -1
  49. package/addon/components/vehicle/pill.hbs +1 -1
  50. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
  51. package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
  52. package/addon/components/widget/fuel-efficiency.hbs +26 -0
  53. package/addon/components/widget/fuel-efficiency.js +43 -0
  54. package/addon/components/widget/geofence-violations.hbs +44 -0
  55. package/addon/components/widget/geofence-violations.js +55 -0
  56. package/addon/components/widget/issues-insights.hbs +43 -0
  57. package/addon/components/widget/issues-insights.js +62 -0
  58. package/addon/components/widget/kpi-active-orders.hbs +10 -0
  59. package/addon/components/widget/kpi-active-orders.js +5 -0
  60. package/addon/components/widget/kpi-aov.hbs +10 -0
  61. package/addon/components/widget/kpi-aov.js +5 -0
  62. package/addon/components/widget/kpi-distance.hbs +10 -0
  63. package/addon/components/widget/kpi-distance.js +5 -0
  64. package/addon/components/widget/kpi-drivers-online.hbs +10 -0
  65. package/addon/components/widget/kpi-drivers-online.js +5 -0
  66. package/addon/components/widget/kpi-earnings.hbs +10 -0
  67. package/addon/components/widget/kpi-earnings.js +5 -0
  68. package/addon/components/widget/kpi-open-issues.hbs +11 -0
  69. package/addon/components/widget/kpi-open-issues.js +5 -0
  70. package/addon/components/widget/kpi-tile.hbs +59 -0
  71. package/addon/components/widget/kpi-tile.js +168 -0
  72. package/addon/components/widget/live-fleet.hbs +122 -0
  73. package/addon/components/widget/live-fleet.js +365 -0
  74. package/addon/components/widget/maintenance-overview.hbs +45 -0
  75. package/addon/components/widget/maintenance-overview.js +40 -0
  76. package/addon/components/widget/on-time-delivery.hbs +27 -0
  77. package/addon/components/widget/on-time-delivery.js +73 -0
  78. package/addon/components/widget/operations-pulse.hbs +51 -0
  79. package/addon/components/widget/operations-pulse.js +173 -0
  80. package/addon/components/widget/orders-by-status.hbs +26 -0
  81. package/addon/components/widget/orders-by-status.js +54 -0
  82. package/addon/components/widget/revenue-trend.hbs +34 -0
  83. package/addon/components/widget/revenue-trend.js +67 -0
  84. package/addon/components/widget/top-drivers.hbs +44 -0
  85. package/addon/components/widget/top-drivers.js +54 -0
  86. package/addon/controllers/management/vehicles/index/new.js +1 -1
  87. package/addon/controllers/operations/orders/index/details.js +4 -4
  88. package/addon/controllers/operations/orders/index.js +14 -0
  89. package/addon/controllers/settings/routing.js +26 -0
  90. package/addon/extension.js +172 -3
  91. package/addon/helpers/place-address-html.js +2 -2
  92. package/addon/routes/operations/service-rates/index.js +1 -1
  93. package/addon/services/geofence.js +163 -19
  94. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  95. package/addon/services/map-adapter/google.js +13 -2
  96. package/addon/services/vehicle-actions.js +1 -1
  97. package/addon/styles/fleetops-engine.css +205 -88
  98. package/addon/templates/maintenance/equipment/index.hbs +1 -1
  99. package/addon/templates/maintenance/parts/index.hbs +1 -1
  100. package/addon/templates/maintenance/schedules/index.hbs +1 -1
  101. package/addon/templates/management/vehicles/index.hbs +1 -1
  102. package/addon/templates/settings/avatars.hbs +1 -1
  103. package/addon/templates/settings/custom-fields.hbs +1 -1
  104. package/addon/templates/settings/map.hbs +1 -1
  105. package/addon/templates/settings/navigator-app.hbs +1 -1
  106. package/addon/templates/settings/notifications.hbs +1 -1
  107. package/addon/templates/settings/orchestrator.hbs +1 -1
  108. package/addon/templates/settings/payments/index.hbs +1 -1
  109. package/addon/templates/settings/payments/onboard.hbs +1 -1
  110. package/addon/templates/settings/routing.hbs +22 -2
  111. package/addon/templates/settings/scheduling.hbs +1 -1
  112. package/addon/utils/fleet-ops-options.js +2 -1
  113. package/addon/utils/live-map-card-content.js +171 -0
  114. package/addon/utils/normalize-order-config-flow.js +41 -0
  115. package/addon/utils/place-address-html.js +3 -2
  116. package/addon/utils/route-visualization.js +54 -7
  117. package/app/components/modals/order-change-destination.js +1 -0
  118. package/app/components/modals/stop-activity.js +1 -0
  119. package/app/components/widget/fuel-efficiency.js +1 -0
  120. package/app/components/widget/geofence-violations.js +1 -0
  121. package/app/components/widget/issues-insights.js +1 -0
  122. package/app/components/widget/kpi-active-orders.js +1 -0
  123. package/app/components/widget/kpi-aov.js +1 -0
  124. package/app/components/widget/kpi-distance.js +1 -0
  125. package/app/components/widget/kpi-drivers-online.js +1 -0
  126. package/app/components/widget/kpi-earnings.js +1 -0
  127. package/app/components/widget/kpi-open-issues.js +1 -0
  128. package/app/components/widget/kpi-tile.js +1 -0
  129. package/app/components/widget/live-fleet.js +1 -0
  130. package/app/components/widget/maintenance-overview.js +1 -0
  131. package/app/components/widget/on-time-delivery.js +1 -0
  132. package/app/components/widget/operations-pulse.js +1 -0
  133. package/app/components/widget/orders-by-status.js +1 -0
  134. package/app/components/widget/revenue-trend.js +1 -0
  135. package/app/components/widget/top-drivers.js +1 -0
  136. package/app/utils/live-map-card-content.js +1 -0
  137. package/composer.json +1 -1
  138. package/extension.json +1 -1
  139. package/package.json +3 -3
  140. package/pnpm-workspace.yaml +1 -0
  141. package/server/config/telematics.php +111 -1
  142. package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
  143. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  144. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  145. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  146. package/server/resources/views/labels/default.php +57 -49
  147. package/server/resources/views/labels/waypoint-label.php +27 -20
  148. package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
  149. package/server/seeders/Testing/ConnectivitySeeder.php +213 -0
  150. package/server/seeders/Testing/FleetSeeder.php +341 -0
  151. package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
  152. package/server/seeders/Testing/NetworkSeeder.php +250 -0
  153. package/server/seeders/Testing/OrdersSeeder.php +198 -0
  154. package/server/seeders/Testing/TestingSeeder.php +40 -0
  155. package/server/seeders/{VroomOrchestrationSeeder.php → Testing/VroomOrchestrationSeeder.php} +29 -22
  156. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  157. package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
  158. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  159. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  160. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  161. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  162. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  163. package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
  164. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  165. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  166. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
  167. package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
  168. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  169. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
  170. package/server/src/Http/Controllers/Internal/v1/OrderController.php +441 -32
  171. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  172. package/server/src/Http/Controllers/Internal/v1/SettingController.php +46 -1
  173. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  174. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  175. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  176. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  177. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  178. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  179. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  180. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  181. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  182. package/server/src/Http/Resources/v1/Customer.php +79 -0
  183. package/server/src/Http/Resources/v1/Driver.php +2 -0
  184. package/server/src/Http/Resources/v1/Fleet.php +4 -0
  185. package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
  186. package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
  187. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  188. package/server/src/Http/Resources/v1/Payload.php +23 -20
  189. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  190. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  191. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  192. package/server/src/Models/Contact.php +92 -15
  193. package/server/src/Models/Customer.php +64 -0
  194. package/server/src/Models/Driver.php +14 -3
  195. package/server/src/Models/Entity.php +2 -2
  196. package/server/src/Models/Order.php +29 -4
  197. package/server/src/Models/OrderConfig.php +14 -11
  198. package/server/src/Models/Payload.php +17 -1
  199. package/server/src/Models/ServiceRate.php +273 -1
  200. package/server/src/Models/ServiceRateFee.php +39 -9
  201. package/server/src/Models/TrackingNumber.php +8 -2
  202. package/server/src/Models/Vehicle.php +18 -2
  203. package/server/src/Models/Waypoint.php +2 -2
  204. package/server/src/Notifications/LateDeparture.php +48 -0
  205. package/server/src/Notifications/ProlongedStoppage.php +48 -0
  206. package/server/src/Notifications/RouteDeviation.php +48 -0
  207. package/server/src/Observers/ContactObserver.php +2 -0
  208. package/server/src/Observers/ServiceRateObserver.php +6 -6
  209. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +29 -2
  210. package/server/src/Providers/FleetOpsServiceProvider.php +6 -0
  211. package/server/src/Providers/NotificationServiceProvider.php +3 -0
  212. package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
  213. package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
  214. package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
  215. package/server/src/Support/Analytics/IssuesInsights.php +67 -0
  216. package/server/src/Support/Analytics/LiveFleet.php +127 -0
  217. package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
  218. package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
  219. package/server/src/Support/Analytics/OperationsPulse.php +86 -0
  220. package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
  221. package/server/src/Support/Analytics/RevenueTrend.php +78 -0
  222. package/server/src/Support/Analytics/TopDrivers.php +92 -0
  223. package/server/src/Support/CustomerAuth.php +98 -0
  224. package/server/src/Support/LabelPdf.php +52 -0
  225. package/server/src/Support/Metrics/AbstractMetric.php +170 -0
  226. package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
  227. package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
  228. package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
  229. package/server/src/Support/Metrics/EarningsMetric.php +30 -0
  230. package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
  231. package/server/src/Support/Metrics/MoneyMetric.php +30 -0
  232. package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
  233. package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
  234. package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
  235. package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
  236. package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
  237. package/server/src/Support/Metrics/Registry.php +46 -0
  238. package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
  239. package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
  240. package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
  241. package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
  242. package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
  243. package/server/src/Support/Metrics.php +46 -249
  244. package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
  245. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
  246. package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
  247. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  248. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  249. package/server/src/Tracking/TrackingStop.php +13 -11
  250. package/server/src/routes.php +50 -0
  251. package/server/tests/AnalyticsRoutesTest.php +41 -0
  252. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  253. package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
  254. package/server/tests/CustomerEndpointTest.php +199 -0
  255. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  256. package/server/tests/LabelPdfRenderingTest.php +23 -0
  257. package/server/tests/LiveControllerViewportTest.php +60 -0
  258. package/server/tests/MetricsRegistryTest.php +84 -0
  259. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  260. package/server/tests/OrderConfigEndpointTest.php +86 -0
  261. package/server/tests/OrderFileAttachmentTest.php +20 -0
  262. package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
  263. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  264. package/server/tests/VroomOrchestrationEngineTest.php +91 -0
  265. package/translations/en-us.yaml +38 -0
  266. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -0,0 +1,74 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Resources\v1;
4
+
5
+ use Fleetbase\Http\Resources\FleetbaseResource;
6
+ use Fleetbase\Support\Http;
7
+
8
+ /**
9
+ * Public, read-only projection of an OrderConfig.
10
+ *
11
+ * Exposes the activity `flow` so consumers can render status filter chips,
12
+ * activity labels, and progress UI without learning the internal config
13
+ * schema. Internal-only fields (entities JSON, version, namespace columns
14
+ * that aren't part of the public contract) are filtered out.
15
+ */
16
+ class OrderConfig extends FleetbaseResource
17
+ {
18
+ /**
19
+ * Transform the resource into an array.
20
+ *
21
+ * @param \Illuminate\Http\Request $request
22
+ */
23
+ public function toArray($request): array
24
+ {
25
+ return [
26
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
27
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
28
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
29
+ 'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
30
+ 'key' => $this->key,
31
+ 'name' => $this->name,
32
+ 'namespace' => $this->namespace,
33
+ 'description' => $this->description,
34
+ 'tags' => $this->tags,
35
+ 'status' => $this->status,
36
+ 'version' => $this->version,
37
+ 'flow' => $this->projectFlow(),
38
+ 'created_at' => $this->created_at,
39
+ 'updated_at' => $this->updated_at,
40
+ ];
41
+ }
42
+
43
+ /**
44
+ * Project the flow JSON into an ordered list of activities, keeping only
45
+ * the public-safe per-activity fields. `code` and `status` (the label)
46
+ * are the contract; `complete`, `color`, `details`, `pod_method`, and
47
+ * `require_pod` are useful UI hints that can ride along.
48
+ */
49
+ protected function projectFlow(): array
50
+ {
51
+ $flow = $this->flow;
52
+ if (!is_array($flow) || empty($flow)) {
53
+ return [];
54
+ }
55
+
56
+ $activities = [];
57
+ foreach ($flow as $activity) {
58
+ if (!is_array($activity)) {
59
+ continue;
60
+ }
61
+ $activities[] = [
62
+ 'code' => $activity['code'] ?? ($activity['key'] ?? null),
63
+ 'status' => $activity['status'] ?? null,
64
+ 'details' => $activity['details'] ?? null,
65
+ 'color' => $activity['color'] ?? null,
66
+ 'complete' => (bool) ($activity['complete'] ?? false),
67
+ 'pod_method' => $activity['pod_method'] ?? null,
68
+ 'require_pod' => (bool) ($activity['require_pod'] ?? false),
69
+ ];
70
+ }
71
+
72
+ return $activities;
73
+ }
74
+ }
@@ -22,26 +22,29 @@ class Payload extends FleetbaseResource
22
22
  $withRouteETA = $request->has('with_route_eta');
23
23
 
24
24
  return [
25
- 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
26
- 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
27
- 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
28
- 'current_waypoint_uuid' => $this->when(Http::isInternalRequest(), $this->current_waypoint_uuid),
29
- 'pickup_uuid' => $this->when(Http::isInternalRequest(), $this->pickup_uuid),
30
- 'dropoff_uuid' => $this->when(Http::isInternalRequest(), $this->dropoff_uuid),
31
- 'return_uuid' => $this->when(Http::isInternalRequest(), $this->return_uuid),
32
- 'current_waypoint' => $this->when(Http::isPublicRequest() && $this->currentWaypoint, data_get($this, 'currentWaypoint.public_id')),
33
- 'pickup' => $this->getPlace($this->pickup),
34
- 'dropoff' => $this->getPlace($this->dropoff),
35
- 'return' => new Place($this->return),
36
- 'waypoints' => Waypoint::collection($this->getWaypoints($withRouteETA)),
37
- 'entities' => Entity::collection($this->entities),
38
- 'cod_amount' => $this->cod_amount ?? null,
39
- 'cod_currency' => $this->cod_currency ?? null,
40
- 'cod_payment_method' => $this->cod_payment_method ?? null,
41
- 'payment_method' => $this->payment_method ?? null,
42
- 'meta' => data_get($this, 'meta', Utils::createObject()),
43
- 'updated_at' => $this->updated_at,
44
- 'created_at' => $this->created_at,
25
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
26
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
27
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
28
+ 'current_waypoint_uuid' => $this->when(Http::isInternalRequest(), $this->current_waypoint_uuid),
29
+ 'pickup_uuid' => $this->when(Http::isInternalRequest(), $this->pickup_uuid),
30
+ 'pickup_tracking_number_uuid' => $this->when(Http::isInternalRequest(), $this->pickup_tracking_number_uuid),
31
+ 'dropoff_uuid' => $this->when(Http::isInternalRequest(), $this->dropoff_uuid),
32
+ 'dropoff_tracking_number_uuid' => $this->when(Http::isInternalRequest(), $this->dropoff_tracking_number_uuid),
33
+ 'return_uuid' => $this->when(Http::isInternalRequest(), $this->return_uuid),
34
+ 'return_tracking_number_uuid' => $this->when(Http::isInternalRequest(), $this->return_tracking_number_uuid),
35
+ 'current_waypoint' => $this->when(Http::isPublicRequest() && $this->currentWaypoint, data_get($this, 'currentWaypoint.public_id')),
36
+ 'pickup' => $this->getPlace($this->pickup),
37
+ 'dropoff' => $this->getPlace($this->dropoff),
38
+ 'return' => new Place($this->return),
39
+ 'waypoints' => Waypoint::collection($this->getWaypoints($withRouteETA)),
40
+ 'entities' => Entity::collection($this->entities),
41
+ 'cod_amount' => $this->cod_amount ?? null,
42
+ 'cod_currency' => $this->cod_currency ?? null,
43
+ 'cod_payment_method' => $this->cod_payment_method ?? null,
44
+ 'payment_method' => $this->payment_method ?? null,
45
+ 'meta' => data_get($this, 'meta', Utils::createObject()),
46
+ 'updated_at' => $this->updated_at,
47
+ 'created_at' => $this->created_at,
45
48
  ];
46
49
  }
47
50
 
@@ -17,17 +17,24 @@ class ServiceArea extends FleetbaseResource
17
17
  public function toArray($request)
18
18
  {
19
19
  return $this->withCustomFields([
20
- 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
21
- 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
22
- 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
23
- 'name' => $this->name,
24
- 'type' => $this->type,
25
- 'center' => $this->location,
26
- 'border' => $this->border,
27
- 'zones' => $this->whenLoaded('zones', fn () => Zone::collection($this->zones)),
28
- 'status' => $this->status,
29
- 'updated_at' => $this->updated_at,
30
- 'created_at' => $this->created_at,
20
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, $this->public_id),
21
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
22
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
23
+ 'name' => $this->name,
24
+ 'type' => $this->type,
25
+ 'center' => $this->location,
26
+ 'border' => $this->border,
27
+ 'zones' => $this->whenLoaded('zones', fn () => Zone::collection($this->zones)),
28
+ 'color' => $this->color,
29
+ 'stroke_color' => $this->stroke_color,
30
+ 'trigger_on_entry' => $this->trigger_on_entry,
31
+ 'trigger_on_exit' => $this->trigger_on_exit,
32
+ 'dwell_threshold_minutes' => $this->dwell_threshold_minutes,
33
+ 'speed_limit_kmh' => $this->speed_limit_kmh,
34
+ 'country' => $this->country,
35
+ 'status' => $this->status,
36
+ 'updated_at' => $this->updated_at,
37
+ 'created_at' => $this->created_at,
31
38
  ]);
32
39
  }
33
40
 
@@ -39,14 +46,21 @@ class ServiceArea extends FleetbaseResource
39
46
  public function toWebhookPayload()
40
47
  {
41
48
  return [
42
- 'id' => $this->public_id,
43
- 'name' => $this->name,
44
- 'type' => $this->type,
45
- 'center' => $this->location,
46
- 'border' => $this->border,
47
- 'status' => $this->status,
48
- 'updated_at' => $this->updated_at,
49
- 'created_at' => $this->created_at,
49
+ 'id' => $this->public_id,
50
+ 'name' => $this->name,
51
+ 'type' => $this->type,
52
+ 'center' => $this->location,
53
+ 'border' => $this->border,
54
+ 'color' => $this->color,
55
+ 'stroke_color' => $this->stroke_color,
56
+ 'trigger_on_entry' => $this->trigger_on_entry,
57
+ 'trigger_on_exit' => $this->trigger_on_exit,
58
+ 'dwell_threshold_minutes' => $this->dwell_threshold_minutes,
59
+ 'speed_limit_kmh' => $this->speed_limit_kmh,
60
+ 'country' => $this->country,
61
+ 'status' => $this->status,
62
+ 'updated_at' => $this->updated_at,
63
+ 'created_at' => $this->created_at,
50
64
  ];
51
65
  }
52
66
  }
@@ -20,6 +20,13 @@ class ServiceRateFee extends FleetbaseResource
20
20
  'id' => $this->when(Http::isInternalRequest(), $this->id),
21
21
  'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
22
22
  'service_rate_uuid' => $this->when(Http::isInternalRequest(), $this->service_rate_uuid),
23
+ 'service_area_uuid' => $this->when(Http::isInternalRequest(), $this->service_area_uuid),
24
+ 'zone_uuid' => $this->when(Http::isInternalRequest(), $this->zone_uuid),
25
+ 'service_area' => $this->whenLoaded('serviceArea', fn () => new ServiceArea($this->serviceArea)),
26
+ 'zone' => $this->whenLoaded('zone', fn () => new Zone($this->zone)),
27
+ 'label' => $this->label,
28
+ 'priority' => $this->priority,
29
+ 'is_fallback' => (bool) $this->is_fallback,
23
30
  'fee' => $this->fee,
24
31
  'currency' => $this->currency,
25
32
  'min' => $this->min,
@@ -69,11 +69,11 @@ class HandleOrderDispatched implements ShouldQueue
69
69
  return;
70
70
  }
71
71
 
72
- $drivers = Driver::where(['status' => 'active', 'online' => 1])
72
+ $drivers = Driver::where(['status' => 'available', 'online' => 1])
73
73
  ->whereHas('company', function ($q) {
74
74
  $q->whereHas('users', function ($q) {
75
75
  $q->whereHas('driver', function ($q) {
76
- $q->where(['status' => 'active', 'online' => 1]);
76
+ $q->where(['status' => 'available', 'online' => 1]);
77
77
  $q->whereNull('deleted_at');
78
78
  });
79
79
  });
@@ -3,6 +3,7 @@
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Exceptions\CustomerUserConflictException;
6
7
  use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
7
8
  use Fleetbase\FleetOps\Support\Utils;
8
9
  use Fleetbase\Models\CompanyUser;
@@ -319,7 +320,12 @@ class Contact extends Model
319
320
  // Check if user already exist with email or phone number
320
321
  $existingUser = null;
321
322
  if ($contact->email || $contact->phone) {
322
- $existingUser = User::where('company_uuid', $contact->company_uuid)
323
+ $existingUser = User::where(function ($query) use ($contact) {
324
+ $query->where('company_uuid', $contact->company_uuid)
325
+ ->orWhereHas('companyUsers', function ($query) use ($contact) {
326
+ $query->where('company_uuid', $contact->company_uuid);
327
+ });
328
+ })
323
329
  ->where(function ($query) use ($contact) {
324
330
  if ($contact->email) {
325
331
  $query->where('email', $contact->email);
@@ -335,15 +341,15 @@ class Contact extends Model
335
341
  }
336
342
 
337
343
  if ($existingUser) {
344
+ if ($contact->isCustomer()) {
345
+ $contact->assertCustomerUserCanBeAssigned($existingUser);
346
+ }
347
+
338
348
  // Check if existing user belongs to another contact
339
349
  $existingUserContact = Contact::where(['user_uuid' => $existingUser->uuid, 'company_uuid' => $contact->company_uuid])->whereHas('user')->first();
340
350
  if ($existingUserContact) {
341
351
  throw new UserAlreadyExistsException('User already exists, try to assigning the user to this contact.', $existingUser);
342
352
  } else {
343
- if ($contact->isCustomer()) {
344
- $contact->normalizeCustomerUser($existingUser);
345
- }
346
-
347
353
  // Assign the user to this contact instead
348
354
  $contact->setAttribute('user_uuid', $existingUser->uuid);
349
355
  if ($update) {
@@ -423,13 +429,7 @@ class Contact extends Model
423
429
  return null;
424
430
  }
425
431
 
426
- if ($user->type !== 'customer') {
427
- if ($quiet) {
428
- $user->forceFill(['type' => 'customer'])->saveQuietly();
429
- } else {
430
- $user->setType('customer');
431
- }
432
- }
432
+ $this->assertCustomerUserCanBeAssigned($user);
433
433
 
434
434
  $this->loadMissing('company');
435
435
  if ($this->company) {
@@ -463,7 +463,7 @@ class Contact extends Model
463
463
  public function repairCustomerTypeInvariant(bool $quiet = false): ?User
464
464
  {
465
465
  if (!$this->isCustomer()) {
466
- $this->forceFill(['type' => 'customer'])->saveQuietly();
466
+ return $this->getUser();
467
467
  }
468
468
 
469
469
  $user = $this->getUser();
@@ -512,8 +512,17 @@ class Contact extends Model
512
512
  // Load company
513
513
  $this->loadMissing('company');
514
514
 
515
- if ($this->isCustomer() && $user->type !== 'customer') {
516
- $user->setType('customer');
515
+ if ($this->isCustomer()) {
516
+ $this->assertCustomerUserCanBeAssigned($user);
517
+
518
+ $existingUserContact = static::where(['user_uuid' => $user->uuid, 'company_uuid' => $this->company_uuid])
519
+ ->whereNot('uuid', $this->uuid)
520
+ ->whereHas('user')
521
+ ->first();
522
+
523
+ if ($existingUserContact) {
524
+ throw new UserAlreadyExistsException('User already exists, try to assigning the user to this contact.', $user);
525
+ }
517
526
  }
518
527
 
519
528
  // Assing to company
@@ -551,6 +560,74 @@ class Contact extends Model
551
560
  return $this;
552
561
  }
553
562
 
563
+ public function assertCustomerIdentityIsAvailable(): void
564
+ {
565
+ if (!$this->isCustomer()) {
566
+ return;
567
+ }
568
+
569
+ $assignedUser = $this->getAssignedUserWithoutTypeScope();
570
+ if ($assignedUser) {
571
+ $this->assertCustomerUserCanBeAssigned($assignedUser);
572
+ }
573
+
574
+ $matchedUser = $this->findExistingUserByIdentity();
575
+ if ($matchedUser && $matchedUser->uuid !== $this->user_uuid) {
576
+ $this->assertCustomerUserCanBeAssigned($matchedUser);
577
+ }
578
+ }
579
+
580
+ public function assertCustomerUserCanBeAssigned(User $user): void
581
+ {
582
+ if ($user->type === 'customer') {
583
+ return;
584
+ }
585
+
586
+ throw new CustomerUserConflictException(static::customerUserConflictMessage($user), $user);
587
+ }
588
+
589
+ public static function customerUserConflictMessage(?User $user = null): string
590
+ {
591
+ $field = $user?->email ? 'email' : ($user?->phone ? 'phone number' : 'user account');
592
+
593
+ return 'This ' . $field . ' belongs to an existing staff user and cannot be used for a customer account.';
594
+ }
595
+
596
+ private function findExistingUserByIdentity(): ?User
597
+ {
598
+ if (!$this->email && !$this->phone) {
599
+ return null;
600
+ }
601
+
602
+ return User::where(function ($query) {
603
+ $query->where('company_uuid', $this->company_uuid)
604
+ ->orWhereHas('companyUsers', function ($query) {
605
+ $query->where('company_uuid', $this->company_uuid);
606
+ });
607
+ })
608
+ ->where(function ($query) {
609
+ if ($this->email) {
610
+ $query->where('email', $this->email);
611
+ }
612
+
613
+ if ($this->phone) {
614
+ $method = $this->email ? 'orWhere' : 'where';
615
+ $query->{$method}('phone', Utils::formatPhoneNumber($this->phone));
616
+ }
617
+ })
618
+ ->whereNull('deleted_at')
619
+ ->first();
620
+ }
621
+
622
+ private function getAssignedUserWithoutTypeScope(): ?User
623
+ {
624
+ if (!Str::isUuid($this->user_uuid)) {
625
+ return null;
626
+ }
627
+
628
+ return User::where('uuid', $this->user_uuid)->first();
629
+ }
630
+
554
631
  public function syncWithUser(): bool
555
632
  {
556
633
  $updates = [];
@@ -0,0 +1,64 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Models;
4
+
5
+ use Illuminate\Database\Eloquent\Builder;
6
+ use Illuminate\Database\Eloquent\Relations\HasMany;
7
+ use Illuminate\Support\Str;
8
+
9
+ /**
10
+ * Fleet-Ops customer.
11
+ *
12
+ * A thin specialization of {@see Contact} that is scoped to rows with
13
+ * `type='customer'` and forces that type on create. This is what the public
14
+ * customer API surface (`/v1/customers/...`) operates on.
15
+ *
16
+ * Mirrors {@see \Fleetbase\Storefront\Models\Customer} but lives directly in
17
+ * FleetOps so that customer auth + customer-scoped orders work without the
18
+ * Storefront publishable-key + Store/Network gating.
19
+ */
20
+ class Customer extends Contact
21
+ {
22
+ /**
23
+ * The key to use in the payload responses.
24
+ */
25
+ protected string $payloadKey = 'customer';
26
+
27
+ /**
28
+ * Boot the model.
29
+ */
30
+ protected static function boot()
31
+ {
32
+ parent::boot();
33
+
34
+ static::creating(function ($model) {
35
+ $model->type = 'customer';
36
+ });
37
+
38
+ static::addGlobalScope('type', function (Builder $builder) {
39
+ $builder->where('type', 'customer');
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Orders owned by this customer.
45
+ */
46
+ public function orders(): HasMany
47
+ {
48
+ return $this->hasMany(Order::class, 'customer_uuid')
49
+ ->whereNull('deleted_at')
50
+ ->withoutGlobalScopes();
51
+ }
52
+
53
+ /**
54
+ * Find a customer by either a `customer_xxx` or `contact_xxx` public id.
55
+ */
56
+ public static function findFromCustomerId(string $publicId): ?self
57
+ {
58
+ if (Str::startsWith($publicId, 'customer')) {
59
+ $publicId = Str::replaceFirst('customer', 'contact', $publicId);
60
+ }
61
+
62
+ return static::where('public_id', $publicId)->first();
63
+ }
64
+ }
@@ -97,6 +97,7 @@ class Driver extends Model
97
97
  'signup_token_used',
98
98
  'avatar_url',
99
99
  'drivers_license_number',
100
+ 'license_expiry',
100
101
  'location',
101
102
  'heading',
102
103
  'bearing',
@@ -118,6 +119,15 @@ class Driver extends Model
118
119
  'time_window_end',
119
120
  ];
120
121
 
122
+ /**
123
+ * Set attributes and defaults.
124
+ *
125
+ * @var array
126
+ */
127
+ protected $attributes = [
128
+ 'status' => 'available',
129
+ ];
130
+
121
131
  /**
122
132
  * The attributes that are guarded and not mass assignable.
123
133
  *
@@ -141,6 +151,7 @@ class Driver extends Model
141
151
  'location' => Point::class,
142
152
  'online' => 'boolean',
143
153
  'meta' => Json::class,
154
+ 'license_expiry' => 'date',
144
155
  // Orchestrator
145
156
  'skills' => Json::class,
146
157
  'max_travel_time' => 'integer',
@@ -593,9 +604,9 @@ class Driver extends Model
593
604
  /**
594
605
  * Set the driver status attribute.
595
606
  */
596
- public function setStatusAttribute(?string $status = 'active'): void
607
+ public function setStatusAttribute(?string $status = 'available'): void
597
608
  {
598
- $this->attributes['status'] = $status;
609
+ $this->attributes['status'] = $status === null || $status === 'active' ? 'available' : $status;
599
610
  }
600
611
 
601
612
  /**
@@ -852,7 +863,7 @@ class Driver extends Model
852
863
  'user_uuid' => $user->uuid,
853
864
  'drivers_license_number' => $driversLicenseNumber,
854
865
  'country' => $country,
855
- 'status' => 'active',
866
+ 'status' => 'available',
856
867
  'location' => Utils::parsePointToWkt(new Point(0, 0)),
857
868
  ]);
858
869
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
- use Barryvdh\DomPDF\Facade\Pdf;
6
5
  use Fleetbase\Casts\Json;
7
6
  use Fleetbase\Casts\PolymorphicType;
7
+ use Fleetbase\FleetOps\Support\LabelPdf;
8
8
  use Fleetbase\FleetOps\Support\Utils;
9
9
  use Fleetbase\FleetOps\Traits\HasTrackingNumber;
10
10
  use Fleetbase\FleetOps\Traits\PayloadAccessors;
@@ -142,7 +142,7 @@ class Entity extends Model
142
142
  */
143
143
  public function pdfLabel()
144
144
  {
145
- return Pdf::loadHTML($this->label());
145
+ return LabelPdf::fromHtml($this->label());
146
146
  }
147
147
 
148
148
  /**
@@ -2,7 +2,6 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
- use Barryvdh\DomPDF\Facade\Pdf;
6
5
  use Fleetbase\Casts\Json;
7
6
  use Fleetbase\Casts\PolymorphicType;
8
7
  use Fleetbase\FleetOps\Events\OrderCanceled;
@@ -10,6 +9,7 @@ use Fleetbase\FleetOps\Events\OrderCompleted;
10
9
  use Fleetbase\FleetOps\Events\OrderDispatched;
11
10
  use Fleetbase\FleetOps\Events\OrderDriverAssigned;
12
11
  use Fleetbase\FleetOps\Flow\Activity;
12
+ use Fleetbase\FleetOps\Support\LabelPdf;
13
13
  use Fleetbase\FleetOps\Support\OrderTracker;
14
14
  use Fleetbase\FleetOps\Support\Utils;
15
15
  use Fleetbase\FleetOps\Traits\HasTrackingNumber;
@@ -275,7 +275,32 @@ class Order extends Model
275
275
  */
276
276
  public function pdfLabel()
277
277
  {
278
- return Pdf::loadHTML($this->label());
278
+ return LabelPdf::fromHtml($this->label());
279
+ }
280
+
281
+ public function attachFiles($uploads): self
282
+ {
283
+ $ids = collect($uploads)
284
+ ->map(function ($upload) {
285
+ if (is_string($upload)) {
286
+ return $upload;
287
+ }
288
+
289
+ return data_get($upload, 'uuid') ?? data_get($upload, 'id');
290
+ })
291
+ ->filter()
292
+ ->unique()
293
+ ->values();
294
+
295
+ if ($ids->isEmpty()) {
296
+ return $this;
297
+ }
298
+
299
+ \Fleetbase\Models\File::whereIn('uuid', $ids)->get()->each(function ($file) {
300
+ $file->setKey($this);
301
+ });
302
+
303
+ return $this;
279
304
  }
280
305
 
281
306
  /**
@@ -1991,11 +2016,11 @@ class Order extends Model
1991
2016
  return collect();
1992
2017
  }
1993
2018
 
1994
- $drivers = Driver::where(['status' => 'active', 'online' => 1])
2019
+ $drivers = Driver::where(['status' => 'available', 'online' => 1])
1995
2020
  ->whereHas('company', function ($q) {
1996
2021
  $q->whereHas('users', function ($q) {
1997
2022
  $q->whereHas('driver', function ($q) {
1998
- $q->where(['status' => 'active', 'online' => 1]);
2023
+ $q->where(['status' => 'available', 'online' => 1]);
1999
2024
  $q->whereNull('deleted_at');
2000
2025
  });
2001
2026
  });
@@ -458,28 +458,31 @@ class OrderConfig extends Model
458
458
  /**
459
459
  * Resolves an OrderConfig from a given identifier or an array of identifiers within an optional company context.
460
460
  *
461
- * @param string|array $orderConfigIdentifier the identifier(s) for the OrderConfig (namespace, public_id, or key)
461
+ * @param string|array $orderConfigIdentifier the identifier(s) for the OrderConfig (uuid, namespace, public_id, or key)
462
462
  * @param Company|null $company the company context, if any, to narrow down the search
463
463
  *
464
464
  * @return OrderConfig|null the found OrderConfig or null if none found
465
465
  */
466
466
  public static function resolveFromIdentifier($orderConfigIdentifier, ?Company $company = null): ?OrderConfig
467
467
  {
468
- $query = static::query();
468
+ $companyUuid = $company instanceof Company ? $company->uuid : session('company');
469
+ $identifiers = is_array($orderConfigIdentifier) ? $orderConfigIdentifier : [$orderConfigIdentifier];
470
+ $identifiers = collect($identifiers)->filter(fn ($identifier) => filled($identifier))->values();
471
+
472
+ if ($identifiers->isEmpty()) {
473
+ return static::default($company);
474
+ }
475
+
476
+ foreach ($identifiers as $identifier) {
477
+ $query = static::query();
469
478
 
470
- if ($company instanceof Company) {
471
- $query->where('company_uuid', $company->uuid);
472
- } else {
473
- $companyUuid = session('company');
474
479
  if ($companyUuid) {
475
480
  $query->where('company_uuid', $companyUuid);
476
481
  }
477
- }
478
482
 
479
- $identifiers = is_array($orderConfigIdentifier) ? $orderConfigIdentifier : [$orderConfigIdentifier];
480
- foreach ($identifiers as $identifier) {
481
483
  $orderConfig = $query->where(function ($query) use ($identifier) {
482
- $query->where('namespace', $identifier)
484
+ $query->where('uuid', $identifier)
485
+ ->orWhere('namespace', $identifier)
483
486
  ->orWhere('public_id', $identifier)
484
487
  ->orWhere('key', $identifier);
485
488
  })->first();
@@ -489,7 +492,7 @@ class OrderConfig extends Model
489
492
  }
490
493
  }
491
494
 
492
- return static::default($company);
495
+ return null;
493
496
  }
494
497
 
495
498
  /**
@@ -61,7 +61,23 @@ class Payload extends Model
61
61
  *
62
62
  * @var array
63
63
  */
64
- protected $fillable = ['_key', 'company_uuid', 'pickup_uuid', 'dropoff_uuid', 'return_uuid', 'current_waypoint_uuid', 'meta', 'payment_method', 'cod_amount', 'cod_currency', 'cod_payment_method', 'type'];
64
+ protected $fillable = [
65
+ '_key',
66
+ 'company_uuid',
67
+ 'pickup_uuid',
68
+ 'pickup_tracking_number_uuid',
69
+ 'dropoff_uuid',
70
+ 'dropoff_tracking_number_uuid',
71
+ 'return_uuid',
72
+ 'return_tracking_number_uuid',
73
+ 'current_waypoint_uuid',
74
+ 'meta',
75
+ 'payment_method',
76
+ 'cod_amount',
77
+ 'cod_currency',
78
+ 'cod_payment_method',
79
+ 'type',
80
+ ];
65
81
 
66
82
  /**
67
83
  * The attributes that should be cast to native types.