@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,170 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\Models\Company;
6
+ use Illuminate\Support\Carbon;
7
+
8
+ /**
9
+ * Base class for FleetOps scalar metrics.
10
+ *
11
+ * Subclasses implement query() + aggregate() and declare slug() + format(). The base
12
+ * handles period selection, optional period-over-period delta, and optional sparkline.
13
+ */
14
+ abstract class AbstractMetric
15
+ {
16
+ protected Company $company;
17
+ protected ?\DateTimeInterface $start = null;
18
+ protected ?\DateTimeInterface $end = null;
19
+ protected ?\DateTimeInterface $compareStart = null;
20
+ protected ?\DateTimeInterface $compareEnd = null;
21
+ protected int $sparklineBuckets = 0;
22
+ protected string $sparklineUnit = 'day';
23
+
24
+ /**
25
+ * Period boundaries for the currently-executing aggregate call.
26
+ * Set by value()/delta()/sparkline() before invoking aggregate(); subclasses
27
+ * that need to issue a second parallel query (e.g. AvgOrderValueMetric) read
28
+ * these instead of re-introspecting the query's where clauses.
29
+ */
30
+ protected ?\DateTimeInterface $currentStart = null;
31
+ protected ?\DateTimeInterface $currentEnd = null;
32
+
33
+ abstract public static function slug(): string;
34
+
35
+ abstract public function format(): string;
36
+
37
+ /**
38
+ * Build the query for the given period. The base class invokes this with the
39
+ * current period, the comparison period (for delta), and one sub-range per
40
+ * sparkline bucket — so it must be cheap and idempotent.
41
+ */
42
+ abstract protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end);
43
+
44
+ /**
45
+ * Reduce a query to a single numeric value. Override for sums (`->sum('amount')`),
46
+ * counts (`->count()`), or computed expressions.
47
+ */
48
+ abstract protected function aggregate($query): float|int;
49
+
50
+ public function currency(): ?string
51
+ {
52
+ return null;
53
+ }
54
+
55
+ public static function forCompany(Company $company): static
56
+ {
57
+ return (new static())->setCompany($company);
58
+ }
59
+
60
+ public function between(?\DateTimeInterface $start, ?\DateTimeInterface $end): static
61
+ {
62
+ $this->start = $start;
63
+ $this->end = $end;
64
+
65
+ return $this;
66
+ }
67
+
68
+ public function compareTo(\DateTimeInterface $start, \DateTimeInterface $end): static
69
+ {
70
+ $this->compareStart = $start;
71
+ $this->compareEnd = $end;
72
+
73
+ return $this;
74
+ }
75
+
76
+ public function withSparkline(int $buckets = 14, string $unit = 'day'): static
77
+ {
78
+ $this->sparklineBuckets = max(0, $buckets);
79
+ $this->sparklineUnit = $unit;
80
+
81
+ return $this;
82
+ }
83
+
84
+ public function value(): float|int
85
+ {
86
+ return $this->runAggregate($this->start, $this->end);
87
+ }
88
+
89
+ public function delta(): ?float
90
+ {
91
+ if (!$this->compareStart || !$this->compareEnd) {
92
+ return null;
93
+ }
94
+
95
+ $current = $this->value();
96
+ $previous = $this->runAggregate($this->compareStart, $this->compareEnd);
97
+
98
+ if ((float) $previous === 0.0) {
99
+ return $current > 0 ? 100.0 : 0.0;
100
+ }
101
+
102
+ return round((($current - $previous) / $previous) * 100, 1);
103
+ }
104
+
105
+ public function sparkline(): ?array
106
+ {
107
+ if ($this->sparklineBuckets === 0 || $this->end === null) {
108
+ return null;
109
+ }
110
+
111
+ $end = Carbon::instance($this->end);
112
+ $cursor = $end->copy()->sub($this->sparklineUnit, $this->sparklineBuckets);
113
+ $labels = [];
114
+ $data = [];
115
+
116
+ for ($i = 0; $i < $this->sparklineBuckets; $i++) {
117
+ $next = $cursor->copy()->add($this->sparklineUnit, 1);
118
+ $labels[] = $cursor->format('Y-m-d');
119
+ $data[] = $this->runAggregate($cursor->toDateTime(), $next->toDateTime());
120
+ $cursor = $next;
121
+ }
122
+
123
+ return ['labels' => $labels, 'data' => $data];
124
+ }
125
+
126
+ /**
127
+ * Run a single aggregation for a sub-period. Sets $currentStart/$currentEnd
128
+ * so subclasses that need to issue a parallel query can read the bucket range.
129
+ */
130
+ protected function runAggregate(?\DateTimeInterface $start, ?\DateTimeInterface $end): float|int
131
+ {
132
+ $this->currentStart = $start;
133
+ $this->currentEnd = $end;
134
+
135
+ return $this->aggregate($this->query($start, $end));
136
+ }
137
+
138
+ public function get(): array
139
+ {
140
+ $payload = [
141
+ 'slug' => static::slug(),
142
+ 'value' => $this->value(),
143
+ 'format' => $this->format(),
144
+ ];
145
+
146
+ $currency = $this->currency();
147
+ if ($currency !== null) {
148
+ $payload['currency'] = $currency;
149
+ }
150
+
151
+ $delta = $this->delta();
152
+ if ($delta !== null) {
153
+ $payload['delta_pct'] = $delta;
154
+ }
155
+
156
+ $sparkline = $this->sparkline();
157
+ if ($sparkline !== null) {
158
+ $payload['sparkline'] = $sparkline;
159
+ }
160
+
161
+ return $payload;
162
+ }
163
+
164
+ protected function setCompany(Company $company): static
165
+ {
166
+ $this->company = $company;
167
+
168
+ return $this;
169
+ }
170
+ }
@@ -0,0 +1,28 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Support\LiveOrderQuery;
6
+
7
+ class ActiveLiveOrdersMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'active_live_orders';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ return LiveOrderQuery::make($this->company->uuid, ['active' => true]);
22
+ }
23
+
24
+ protected function aggregate($query): int
25
+ {
26
+ return (int) $query->count();
27
+ }
28
+ }
@@ -0,0 +1,53 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Fleetbase\Models\Transaction;
7
+
8
+ /**
9
+ * Average revenue per completed order in the period. Computed as
10
+ * sum(transactions.amount, primary_currency) / count(completed orders).
11
+ *
12
+ * Uses $this->currentStart/$currentEnd (set by the base class) so the metric
13
+ * cooperates with the sparkline machinery — each bucket recomputes both
14
+ * numerator and denominator over its own sub-range.
15
+ */
16
+ class AvgOrderValueMetric extends MoneyMetric
17
+ {
18
+ public static function slug(): string
19
+ {
20
+ return 'avg_order_value';
21
+ }
22
+
23
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
24
+ {
25
+ $query = Order::where('company_uuid', $this->company->uuid)
26
+ ->where('status', 'completed');
27
+
28
+ if ($start && $end) {
29
+ $query->whereBetween('created_at', [$start, $end]);
30
+ }
31
+
32
+ return $query;
33
+ }
34
+
35
+ protected function aggregate($query): float
36
+ {
37
+ $orderCount = (int) $query->count();
38
+ if ($orderCount === 0) {
39
+ return 0.0;
40
+ }
41
+
42
+ $revenueQuery = Transaction::where('company_uuid', $this->company->uuid)
43
+ ->where('currency', $this->currency());
44
+
45
+ if ($this->currentStart && $this->currentEnd) {
46
+ $revenueQuery->whereBetween('created_at', [$this->currentStart, $this->currentEnd]);
47
+ }
48
+
49
+ $totalRevenue = (float) $revenueQuery->sum('amount');
50
+
51
+ return round($totalRevenue / $orderCount, 2);
52
+ }
53
+ }
@@ -0,0 +1,30 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Driver;
6
+
7
+ class DriversOnlineMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'drivers_online';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ return Driver::where('company_uuid', $this->company->uuid)
22
+ ->where('online', true)
23
+ ->whereNotNull('current_job_uuid');
24
+ }
25
+
26
+ protected function aggregate($query): int
27
+ {
28
+ return (int) $query->count();
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\Models\Transaction;
6
+
7
+ class EarningsMetric extends MoneyMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'earnings';
12
+ }
13
+
14
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
15
+ {
16
+ $query = Transaction::where('company_uuid', $this->company->uuid)
17
+ ->where('currency', $this->currency());
18
+
19
+ if ($start && $end) {
20
+ $query->whereBetween('created_at', [$start, $end]);
21
+ }
22
+
23
+ return $query;
24
+ }
25
+
26
+ protected function aggregate($query): float
27
+ {
28
+ return (float) $query->sum('amount');
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\FuelReport;
6
+
7
+ class FuelCostsMetric extends MoneyMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'fuel_costs';
12
+ }
13
+
14
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
15
+ {
16
+ $query = FuelReport::where('company_uuid', $this->company->uuid)
17
+ ->where('currency', $this->currency());
18
+
19
+ if ($start && $end) {
20
+ $query->whereBetween('created_at', [$start, $end]);
21
+ }
22
+
23
+ return $query;
24
+ }
25
+
26
+ protected function aggregate($query): float
27
+ {
28
+ return (float) $query->sum('amount');
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ /**
6
+ * Base for money-valued metrics. Enforces currency awareness so we never repeat
7
+ * the legacy bug of summing mixed-currency rows.
8
+ */
9
+ abstract class MoneyMetric extends AbstractMetric
10
+ {
11
+ protected ?string $currencyOverride = null;
12
+
13
+ public function inCurrency(?string $currency): static
14
+ {
15
+ $this->currencyOverride = $currency;
16
+
17
+ return $this;
18
+ }
19
+
20
+ public function format(): string
21
+ {
22
+ return 'money';
23
+ }
24
+
25
+ public function currency(): string
26
+ {
27
+ return $this->currencyOverride
28
+ ?? ($this->company->currency ?? 'USD');
29
+ }
30
+ }
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Issue;
6
+
7
+ class OpenIssuesMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'open_issues';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ $query = Issue::where('company_uuid', $this->company->uuid)
22
+ ->where('status', 'pending');
23
+
24
+ if ($start && $end) {
25
+ $query->whereBetween('created_at', [$start, $end]);
26
+ }
27
+
28
+ return $query;
29
+ }
30
+
31
+ protected function aggregate($query): int
32
+ {
33
+ return (int) $query->count();
34
+ }
35
+ }
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+
7
+ class OrdersCanceledMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'orders_canceled';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ $query = Order::where('company_uuid', $this->company->uuid)
22
+ ->where('status', 'canceled');
23
+
24
+ if ($start && $end) {
25
+ $query->whereBetween('created_at', [$start, $end]);
26
+ }
27
+
28
+ return $query;
29
+ }
30
+
31
+ protected function aggregate($query): int
32
+ {
33
+ return (int) $query->count();
34
+ }
35
+ }
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+
7
+ class OrdersCompletedMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'orders_completed';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ $query = Order::where('company_uuid', $this->company->uuid)
22
+ ->where('status', 'completed');
23
+
24
+ if ($start && $end) {
25
+ $query->whereBetween('created_at', [$start, $end]);
26
+ }
27
+
28
+ return $query;
29
+ }
30
+
31
+ protected function aggregate($query): int
32
+ {
33
+ return (int) $query->count();
34
+ }
35
+ }
@@ -0,0 +1,51 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+
7
+ /**
8
+ * Counts orders currently in flight. Uses an explicit allowlist of "active" statuses
9
+ * rather than the legacy exclusion list, which was brittle against new statuses.
10
+ */
11
+ class OrdersInProgressMetric extends AbstractMetric
12
+ {
13
+ public const IN_PROGRESS_STATUSES = [
14
+ 'dispatched',
15
+ 'assigned',
16
+ 'started',
17
+ 'enroute',
18
+ 'in_progress',
19
+ 'driver_enroute',
20
+ 'picking_up',
21
+ 'picked_up',
22
+ 'dropping_off',
23
+ ];
24
+
25
+ public static function slug(): string
26
+ {
27
+ return 'orders_in_progress';
28
+ }
29
+
30
+ public function format(): string
31
+ {
32
+ return 'count';
33
+ }
34
+
35
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
36
+ {
37
+ $query = Order::where('company_uuid', $this->company->uuid)
38
+ ->whereIn('status', self::IN_PROGRESS_STATUSES);
39
+
40
+ if ($start && $end) {
41
+ $query->whereBetween('created_at', [$start, $end]);
42
+ }
43
+
44
+ return $query;
45
+ }
46
+
47
+ protected function aggregate($query): int
48
+ {
49
+ return (int) $query->count();
50
+ }
51
+ }
@@ -0,0 +1,37 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Illuminate\Support\Carbon;
7
+
8
+ class OrdersScheduledMetric extends AbstractMetric
9
+ {
10
+ public static function slug(): string
11
+ {
12
+ return 'orders_scheduled';
13
+ }
14
+
15
+ public function format(): string
16
+ {
17
+ return 'count';
18
+ }
19
+
20
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
21
+ {
22
+ $query = Order::where('company_uuid', $this->company->uuid)
23
+ ->where('status', 'created')
24
+ ->whereDate('scheduled_at', '>', Carbon::now());
25
+
26
+ if ($start && $end) {
27
+ $query->whereBetween('created_at', [$start, $end]);
28
+ }
29
+
30
+ return $query;
31
+ }
32
+
33
+ protected function aggregate($query): int
34
+ {
35
+ return (int) $query->count();
36
+ }
37
+ }
@@ -0,0 +1,46 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ /**
6
+ * Explicit slug → metric-class map. Replaces the legacy
7
+ * `array_slice(get_class_methods, 9)` reflection which broke whenever a
8
+ * private helper was added to the wrong position in Metrics.php.
9
+ */
10
+ class Registry
11
+ {
12
+ /** @var array<string, class-string<AbstractMetric>> */
13
+ public const METRICS = [
14
+ 'earnings' => EarningsMetric::class,
15
+ 'fuel_costs' => FuelCostsMetric::class,
16
+ 'total_distance_traveled' => TotalDistanceTraveledMetric::class,
17
+ 'total_time_traveled' => TotalTimeTraveledMetric::class,
18
+ 'orders_completed' => OrdersCompletedMetric::class,
19
+ 'orders_canceled' => OrdersCanceledMetric::class,
20
+ 'orders_in_progress' => OrdersInProgressMetric::class,
21
+ 'orders_scheduled' => OrdersScheduledMetric::class,
22
+ 'active_live_orders' => ActiveLiveOrdersMetric::class,
23
+ 'drivers_online' => DriversOnlineMetric::class,
24
+ 'total_drivers' => TotalDriversMetric::class,
25
+ 'total_customers' => TotalCustomersMetric::class,
26
+ 'open_issues' => OpenIssuesMetric::class,
27
+ 'resolved_issues' => ResolvedIssuesMetric::class,
28
+ 'avg_order_value' => AvgOrderValueMetric::class,
29
+ ];
30
+
31
+ public static function resolve(string $slug): ?string
32
+ {
33
+ return self::METRICS[$slug] ?? null;
34
+ }
35
+
36
+ /** @return array<string, class-string<AbstractMetric>> */
37
+ public static function all(): array
38
+ {
39
+ return self::METRICS;
40
+ }
41
+
42
+ public static function slugs(): array
43
+ {
44
+ return array_keys(self::METRICS);
45
+ }
46
+ }
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Issue;
6
+
7
+ class ResolvedIssuesMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'resolved_issues';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ $query = Issue::where('company_uuid', $this->company->uuid)
22
+ ->whereNotNull('resolved_at');
23
+
24
+ if ($start && $end) {
25
+ $query->whereBetween('resolved_at', [$start, $end]);
26
+ }
27
+
28
+ return $query;
29
+ }
30
+
31
+ protected function aggregate($query): int
32
+ {
33
+ return (int) $query->count();
34
+ }
35
+ }
@@ -0,0 +1,29 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Metrics;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+
7
+ class TotalCustomersMetric extends AbstractMetric
8
+ {
9
+ public static function slug(): string
10
+ {
11
+ return 'total_customers';
12
+ }
13
+
14
+ public function format(): string
15
+ {
16
+ return 'count';
17
+ }
18
+
19
+ protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
20
+ {
21
+ return Contact::where('company_uuid', $this->company->uuid)
22
+ ->where('type', 'customer');
23
+ }
24
+
25
+ protected function aggregate($query): int
26
+ {
27
+ return (int) $query->count();
28
+ }
29
+ }