@fleetbase/fleetops-engine 0.6.49 → 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 (142) 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/map/google-live-map.js +6 -2
  7. package/addon/components/map/leaflet-live-map.hbs +48 -14
  8. package/addon/components/map/leaflet-live-map.js +184 -20
  9. package/addon/components/modals/order-change-destination.hbs +46 -0
  10. package/addon/components/modals/order-change-destination.js +106 -0
  11. package/addon/components/modals/stop-activity.hbs +28 -0
  12. package/addon/components/modals/stop-activity.js +52 -0
  13. package/addon/components/modals/update-order-activity.hbs +45 -3
  14. package/addon/components/modals/update-order-activity.js +143 -5
  15. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  16. package/addon/components/orchestrator/resource-panel.js +2 -4
  17. package/addon/components/order/activity-list.hbs +2 -2
  18. package/addon/components/order/activity-timeline.hbs +31 -29
  19. package/addon/components/order/details/activity.hbs +3 -3
  20. package/addon/components/order/details/activity.js +11 -0
  21. package/addon/components/order/details/documents.js +2 -0
  22. package/addon/components/order/details/purchase-rate.hbs +2 -2
  23. package/addon/components/order/details/route.hbs +11 -3
  24. package/addon/components/order/details/route.js +91 -14
  25. package/addon/components/order/details/tracking.hbs +101 -72
  26. package/addon/components/order/details/tracking.js +149 -2
  27. package/addon/components/order/form/details.hbs +3 -3
  28. package/addon/components/order/form/documents.js +11 -4
  29. package/addon/components/order/form/route.hbs +108 -104
  30. package/addon/components/order/form/route.js +37 -1
  31. package/addon/components/order/form/service-rate.hbs +2 -2
  32. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  33. package/addon/components/order-config-manager/activity-flow.js +178 -12
  34. package/addon/components/place/address.js +1 -1
  35. package/addon/components/route-list.hbs +17 -17
  36. package/addon/components/route-list.js +42 -10
  37. package/addon/components/service-rate/details.hbs +41 -0
  38. package/addon/components/service-rate/form.hbs +118 -0
  39. package/addon/components/service-rate/form.js +55 -0
  40. package/addon/components/vehicle/form.js +1 -1
  41. package/addon/components/vehicle/pill.hbs +1 -1
  42. package/addon/controllers/management/vehicles/index/new.js +1 -1
  43. package/addon/controllers/operations/orders/index/details.js +4 -4
  44. package/addon/controllers/operations/orders/index.js +14 -0
  45. package/addon/helpers/place-address-html.js +2 -2
  46. package/addon/routes/operations/service-rates/index.js +1 -1
  47. package/addon/services/geofence.js +163 -19
  48. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  49. package/addon/services/vehicle-actions.js +1 -1
  50. package/addon/styles/fleetops-engine.css +91 -1
  51. package/addon/utils/fleet-ops-options.js +2 -1
  52. package/addon/utils/normalize-order-config-flow.js +41 -0
  53. package/addon/utils/place-address-html.js +3 -2
  54. package/addon/utils/route-visualization.js +54 -7
  55. package/app/components/modals/order-change-destination.js +1 -0
  56. package/app/components/modals/stop-activity.js +1 -0
  57. package/composer.json +1 -1
  58. package/extension.json +1 -1
  59. package/package.json +3 -3
  60. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  61. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  62. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  63. package/server/resources/views/labels/default.php +57 -49
  64. package/server/resources/views/labels/waypoint-label.php +27 -20
  65. package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
  66. package/server/seeders/Testing/FleetSeeder.php +22 -22
  67. package/server/seeders/Testing/OrdersSeeder.php +6 -22
  68. package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
  69. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  70. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  71. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  72. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  73. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  74. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  75. package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
  76. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  77. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  78. package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
  79. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  80. package/server/src/Http/Controllers/Internal/v1/OrderController.php +429 -26
  81. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  82. package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
  83. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  84. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  85. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  86. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  87. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  88. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  89. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  90. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  91. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  92. package/server/src/Http/Resources/v1/Customer.php +79 -0
  93. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  94. package/server/src/Http/Resources/v1/Payload.php +23 -20
  95. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  96. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  97. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  98. package/server/src/Models/Contact.php +92 -15
  99. package/server/src/Models/Customer.php +64 -0
  100. package/server/src/Models/Driver.php +12 -3
  101. package/server/src/Models/Entity.php +2 -2
  102. package/server/src/Models/Order.php +29 -4
  103. package/server/src/Models/Payload.php +17 -1
  104. package/server/src/Models/ServiceRate.php +273 -1
  105. package/server/src/Models/ServiceRateFee.php +39 -9
  106. package/server/src/Models/TrackingNumber.php +8 -2
  107. package/server/src/Models/Vehicle.php +18 -2
  108. package/server/src/Models/Waypoint.php +2 -2
  109. package/server/src/Notifications/LateDeparture.php +2 -2
  110. package/server/src/Notifications/ProlongedStoppage.php +2 -2
  111. package/server/src/Notifications/RouteDeviation.php +2 -2
  112. package/server/src/Observers/ContactObserver.php +2 -0
  113. package/server/src/Observers/ServiceRateObserver.php +6 -6
  114. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
  115. package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
  116. package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
  117. package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
  118. package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
  119. package/server/src/Support/Analytics/RevenueTrend.php +0 -1
  120. package/server/src/Support/Analytics/TopDrivers.php +1 -1
  121. package/server/src/Support/CustomerAuth.php +98 -0
  122. package/server/src/Support/LabelPdf.php +52 -0
  123. package/server/src/Support/Metrics/AbstractMetric.php +4 -4
  124. package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
  125. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  126. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  127. package/server/src/Tracking/TrackingStop.php +13 -11
  128. package/server/src/routes.php +43 -9
  129. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  130. package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
  131. package/server/tests/CustomerEndpointTest.php +199 -0
  132. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  133. package/server/tests/LabelPdfRenderingTest.php +23 -0
  134. package/server/tests/LiveControllerViewportTest.php +60 -0
  135. package/server/tests/MetricsRegistryTest.php +4 -8
  136. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  137. package/server/tests/OrderConfigEndpointTest.php +86 -0
  138. package/server/tests/OrderFileAttachmentTest.php +20 -0
  139. package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
  140. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  141. package/translations/en-us.yaml +1 -0
  142. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -7,6 +7,7 @@ use Fleetbase\FleetOps\Support\Utils;
7
7
  use Fleetbase\Models\Model;
8
8
  use Fleetbase\Traits\HasUuid;
9
9
  use Fleetbase\Traits\TracksApiCredential;
10
+ use Illuminate\Database\Eloquent\Relations\BelongsTo;
10
11
 
11
12
  class ServiceRateFee extends Model
12
13
  {
@@ -32,7 +33,22 @@ class ServiceRateFee extends Model
32
33
  *
33
34
  * @var array
34
35
  */
35
- protected $fillable = ['_key', 'service_rate_uuid', 'distance', 'distance_unit', 'min', 'max', 'unit', 'fee', 'currency'];
36
+ protected $fillable = [
37
+ '_key',
38
+ 'service_rate_uuid',
39
+ 'service_area_uuid',
40
+ 'zone_uuid',
41
+ 'label',
42
+ 'priority',
43
+ 'is_fallback',
44
+ 'distance',
45
+ 'distance_unit',
46
+ 'min',
47
+ 'max',
48
+ 'unit',
49
+ 'fee',
50
+ 'currency',
51
+ ];
36
52
 
37
53
  /**
38
54
  * The attributes that should be cast to native types.
@@ -40,10 +56,12 @@ class ServiceRateFee extends Model
40
56
  * @var array
41
57
  */
42
58
  protected $casts = [
43
- 'min' => 'integer',
44
- 'max' => 'integer',
45
- 'fee' => Money::class,
46
- 'distance' => 'integer',
59
+ 'min' => 'integer',
60
+ 'max' => 'integer',
61
+ 'fee' => Money::class,
62
+ 'distance' => 'integer',
63
+ 'priority' => 'integer',
64
+ 'is_fallback' => 'boolean',
47
65
  ];
48
66
 
49
67
  /**
@@ -62,14 +80,26 @@ class ServiceRateFee extends Model
62
80
 
63
81
  public static function onRowInsert($row)
64
82
  {
65
- $row['fee'] = Money::apply($row['fee'] ?? 0);
66
- $row['distance'] = Utils::numbersOnly($row['distance'] ?? null);
67
- $row['min'] = Utils::numbersOnly($row['min'] ?? null);
68
- $row['max'] = Utils::numbersOnly($row['max'] ?? null);
83
+ $row['fee'] = Money::apply($row['fee'] ?? 0);
84
+ $row['distance'] = Utils::numbersOnly($row['distance'] ?? null);
85
+ $row['min'] = Utils::numbersOnly($row['min'] ?? null);
86
+ $row['max'] = Utils::numbersOnly($row['max'] ?? null);
87
+ $row['priority'] = Utils::numbersOnly($row['priority'] ?? 0);
88
+ $row['is_fallback'] = Utils::castBoolean($row['is_fallback'] ?? false);
69
89
 
70
90
  return $row;
71
91
  }
72
92
 
93
+ public function serviceArea(): BelongsTo
94
+ {
95
+ return $this->belongsTo(ServiceArea::class, 'service_area_uuid', 'uuid');
96
+ }
97
+
98
+ public function zone(): BelongsTo
99
+ {
100
+ return $this->belongsTo(Zone::class, 'zone_uuid', 'uuid');
101
+ }
102
+
73
103
  public function setDistanceAttribute($value)
74
104
  {
75
105
  $this->attributes['distance'] = Utils::numbersOnly($value);
@@ -14,6 +14,7 @@ use Fleetbase\Traits\SendsWebhooks;
14
14
  use Fleetbase\Traits\TracksApiCredential;
15
15
  use Illuminate\Support\Carbon;
16
16
  use Illuminate\Support\Facades\DB;
17
+ use Illuminate\Support\Facades\Schema;
17
18
  use Illuminate\Support\Str;
18
19
  use Milon\Barcode\Facades\DNS2DFacade as DNS2D;
19
20
 
@@ -242,7 +243,7 @@ class TrackingNumber extends Model
242
243
  $status = strtolower($trackingStatus->code);
243
244
  $owner = $this->load(['owner'])->getRelationValue('owner');
244
245
 
245
- if ($owner && $owner->isFillable('status') && $owner->status !== $status) {
246
+ if ($owner && static::ownerHasStatusColumn($owner) && $owner->isFillable('status') && $owner->status !== $status) {
246
247
  $this->owner->status = $status;
247
248
 
248
249
  $this->owner->save();
@@ -303,7 +304,7 @@ class TrackingNumber extends Model
303
304
  TrackingNumber::where('uuid', $uuid)->update(['status_uuid' => $trackingStatusId]);
304
305
 
305
306
  // update owner status
306
- if ($owner && $owner instanceof Model && $owner->isFillable('status')) {
307
+ if ($owner && $owner instanceof Model && static::ownerHasStatusColumn($owner) && $owner->isFillable('status')) {
307
308
  // runs event cycles
308
309
  // $model->update([ 'status' => 'created' ]);
309
310
 
@@ -313,4 +314,9 @@ class TrackingNumber extends Model
313
314
 
314
315
  return $uuid;
315
316
  }
317
+
318
+ protected static function ownerHasStatusColumn(Model $owner): bool
319
+ {
320
+ return Schema::hasColumn($owner->getTable(), 'status');
321
+ }
316
322
  }
@@ -223,6 +223,7 @@ class Vehicle extends Model
223
223
  */
224
224
  protected $attributes = [
225
225
  'avatar_url' => 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg',
226
+ 'status' => 'available',
226
227
  ];
227
228
 
228
229
  /**
@@ -324,6 +325,14 @@ class Vehicle extends Model
324
325
  return $this->hasOne(Driver::class, 'vehicle_uuid', 'uuid')->without(['vehicle']);
325
326
  }
326
327
 
328
+ /**
329
+ * Set the vehicle status attribute.
330
+ */
331
+ public function setStatusAttribute(?string $status = 'available'): void
332
+ {
333
+ $this->attributes['status'] = $status === null || $status === 'active' ? 'available' : $status;
334
+ }
335
+
327
336
  public function category(): BelongsTo
328
337
  {
329
338
  return $this->belongsTo(Category::class, 'category_uuid', 'uuid');
@@ -597,6 +606,14 @@ class Vehicle extends Model
597
606
  return $this;
598
607
  }
599
608
 
609
+ public function unassignDriver(): self
610
+ {
611
+ Driver::where('vehicle_uuid', $this->uuid)->update(['vehicle_uuid' => null]);
612
+ $this->unsetRelation('driver');
613
+
614
+ return $this;
615
+ }
616
+
600
617
  /**
601
618
  * Retrieves the last known position of the driver within the current company context.
602
619
  *
@@ -727,9 +744,8 @@ class Vehicle extends Model
727
744
  'plate_number' => $plateNumber,
728
745
  'vin' => $vin,
729
746
  'type' => $type,
730
- 'status' => 'active',
747
+ 'status' => 'available',
731
748
  'online' => 0,
732
- 'status' => 'active',
733
749
  ]);
734
750
 
735
751
  // If driver was resolved assign driver to vehicle
@@ -2,8 +2,8 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
- use Barryvdh\DomPDF\Facade\Pdf;
6
5
  use Fleetbase\Casts\PolymorphicType;
6
+ use Fleetbase\FleetOps\Support\LabelPdf;
7
7
  use Fleetbase\FleetOps\Support\Utils;
8
8
  use Fleetbase\FleetOps\Traits\HasTrackingNumber;
9
9
  use Fleetbase\FleetOps\Traits\PayloadAccessors;
@@ -94,7 +94,7 @@ class Waypoint extends Model
94
94
  */
95
95
  public function pdfLabel()
96
96
  {
97
- return Pdf::loadHTML($this->label());
97
+ return LabelPdf::fromHtml($this->label());
98
98
  }
99
99
 
100
100
  /**
@@ -12,9 +12,9 @@ class LateDeparture extends Notification implements ShouldQueue
12
12
  {
13
13
  use Queueable;
14
14
 
15
- public static string $name = 'Late Departure';
15
+ public static string $name = 'Late Departure';
16
16
  public static string $description = 'Notify when an order has not departed after the configured grace period.';
17
- public static string $package = 'fleet-ops';
17
+ public static string $package = 'fleet-ops';
18
18
 
19
19
  public function __construct(public Order $order, public array $context = [])
20
20
  {
@@ -12,9 +12,9 @@ class ProlongedStoppage extends Notification implements ShouldQueue
12
12
  {
13
13
  use Queueable;
14
14
 
15
- public static string $name = 'Prolonged Stoppage';
15
+ public static string $name = 'Prolonged Stoppage';
16
16
  public static string $description = 'Notify when a vehicle remains stopped during an active trip beyond the configured threshold.';
17
- public static string $package = 'fleet-ops';
17
+ public static string $package = 'fleet-ops';
18
18
 
19
19
  public function __construct(public Order $order, public array $context = [])
20
20
  {
@@ -12,9 +12,9 @@ class RouteDeviation extends Notification implements ShouldQueue
12
12
  {
13
13
  use Queueable;
14
14
 
15
- public static string $name = 'Route Deviation';
15
+ public static string $name = 'Route Deviation';
16
16
  public static string $description = 'Notify when tracked vehicle location deviates from the active route beyond the configured threshold.';
17
- public static string $package = 'fleet-ops';
17
+ public static string $package = 'fleet-ops';
18
18
 
19
19
  public function __construct(public Order $order, public array $context = [])
20
20
  {
@@ -31,6 +31,8 @@ class ContactObserver
31
31
  throw new \Exception('Customer contact type cannot be changed.');
32
32
  }
33
33
 
34
+ $contact->assertCustomerIdentityIsAvailable();
35
+
34
36
  // Get the contacts assosciated user
35
37
  if ($contact->doesntHaveUser()) {
36
38
  $contact->createUser();
@@ -14,10 +14,10 @@ class ServiceRateObserver
14
14
  */
15
15
  public function created(ServiceRate $serviceRate)
16
16
  {
17
- $serviceRateFees = request()->input('serviceRate.rate_fees');
18
- $serviceRateParcelFees = request()->input('serviceRate.parcel_fees');
17
+ $serviceRateFees = request()->input('serviceRate.rate_fees', request()->input('service_rate.rate_fees'));
18
+ $serviceRateParcelFees = request()->input('serviceRate.parcel_fees', request()->input('service_rate.parcel_fees'));
19
19
 
20
- if ($serviceRate->isFixedMeter() || $serviceRate->isPerDrop()) {
20
+ if ($serviceRate->isFixedMeter() || $serviceRate->isPerDrop() || $serviceRate->isMultiZoneDistance()) {
21
21
  $serviceRate->setServiceRateFees($serviceRateFees);
22
22
  }
23
23
 
@@ -33,10 +33,10 @@ class ServiceRateObserver
33
33
  */
34
34
  public function updated(ServiceRate $serviceRate)
35
35
  {
36
- $serviceRateFees = request()->input('serviceRate.rate_fees');
37
- $serviceRateParcelFees = request()->input('serviceRate.parcel_fees');
36
+ $serviceRateFees = request()->input('serviceRate.rate_fees', request()->input('service_rate.rate_fees'));
37
+ $serviceRateParcelFees = request()->input('serviceRate.parcel_fees', request()->input('service_rate.parcel_fees'));
38
38
 
39
- if ($serviceRate->isFixedMeter() || $serviceRate->isPerDrop()) {
39
+ if ($serviceRate->isFixedMeter() || $serviceRate->isPerDrop() || $serviceRate->isMultiZoneDistance()) {
40
40
  $serviceRate->setServiceRateFees($serviceRateFees);
41
41
  }
42
42
 
@@ -85,7 +85,7 @@ class VroomOrchestrationEngine implements OrchestrationEngineInterface
85
85
  $vroomPayload = [
86
86
  'jobs' => [],
87
87
  'shipments' => [],
88
- 'vehicles' => array_map(function (array $v) use ($profile) {
88
+ 'vehicles' => array_map(function (array $v) use ($profile) {
89
89
  $vehicle = [
90
90
  'id' => crc32($v['id']), // VROOM requires integer IDs
91
91
  'description' => json_encode(['vehicle_id' => $v['id'], 'driver_id' => $v['driver_id']]),
@@ -56,6 +56,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
56
56
  \Fleetbase\FleetOps\Console\Commands\FixInvalidPolymorphicRelationTypeNamespaces::class,
57
57
  \Fleetbase\FleetOps\Console\Commands\AssignDriverRoles::class,
58
58
  \Fleetbase\FleetOps\Console\Commands\AssignCustomerRoles::class,
59
+ \Fleetbase\FleetOps\Console\Commands\AuditCustomerUserConflicts::class,
59
60
  \Fleetbase\FleetOps\Console\Commands\SimulateOrderRouteNavigation::class,
60
61
  \Fleetbase\FleetOps\Console\Commands\DebugOrderTracker::class,
61
62
  \Fleetbase\FleetOps\Console\Commands\PurgeUnpurchasedServiceQuotes::class,
@@ -40,12 +40,12 @@ abstract class AbstractAnalytics
40
40
  {
41
41
  if ($period !== null && $period !== '') {
42
42
  $days = match ($period) {
43
- '7d' => 7,
44
- '14d' => 14,
45
- '30d' => 30,
46
- '90d' => 90,
47
- '180d' => 180,
48
- '365d' => 365,
43
+ '7d' => 7,
44
+ '14d' => 14,
45
+ '30d' => 30,
46
+ '90d' => 90,
47
+ '180d' => 180,
48
+ '365d' => 365,
49
49
  default => null,
50
50
  };
51
51
 
@@ -22,7 +22,7 @@ class FuelEfficiency extends AbstractAnalytics
22
22
  $fuelByWeek = FuelReport::where('company_uuid', $this->company->uuid)
23
23
  ->where('currency', $currency)
24
24
  ->whereBetween('created_at', [$start, $end])
25
- ->selectRaw("YEARWEEK(created_at, 1) as wk, MIN(DATE(created_at)) as wk_start, SUM(amount) as total_cost")
25
+ ->selectRaw('YEARWEEK(created_at, 1) as wk, MIN(DATE(created_at)) as wk_start, SUM(amount) as total_cost')
26
26
  ->groupBy('wk')
27
27
  ->orderBy('wk')
28
28
  ->get();
@@ -30,7 +30,7 @@ class FuelEfficiency extends AbstractAnalytics
30
30
  $distanceByWeek = Order::where('company_uuid', $this->company->uuid)
31
31
  ->where('status', 'completed')
32
32
  ->whereBetween('updated_at', [$start, $end])
33
- ->selectRaw("YEARWEEK(updated_at, 1) as wk, SUM(distance) as total_distance")
33
+ ->selectRaw('YEARWEEK(updated_at, 1) as wk, SUM(distance) as total_distance')
34
34
  ->groupBy('wk')
35
35
  ->orderBy('wk')
36
36
  ->pluck('total_distance', 'wk');
@@ -41,7 +41,7 @@ class OrdersByStatus extends AbstractAnalytics
41
41
  while ($cursor <= $endDay) {
42
42
  $labels[] = $cursor->format('M j');
43
43
  foreach (array_keys(self::STATUS_PALETTE) as $status) {
44
- $match = $rows->first(fn ($r) => $r->bucket === $cursor->format('Y-m-d') && $r->status === $status);
44
+ $match = $rows->first(fn ($r) => $r->bucket === $cursor->format('Y-m-d') && $r->status === $status);
45
45
  $byStatus[$status][] = $match ? (int) $match->total : 0;
46
46
  }
47
47
  $cursor->addDay();
@@ -4,7 +4,6 @@ namespace Fleetbase\FleetOps\Support\Analytics;
4
4
 
5
5
  use Fleetbase\Models\Transaction;
6
6
  use Illuminate\Support\Carbon;
7
- use Illuminate\Support\Facades\DB;
8
7
 
9
8
  /**
10
9
  * Time-series of revenue (sum of transactions) per bucket, filtered to the
@@ -12,7 +12,7 @@ use Illuminate\Support\Carbon;
12
12
  */
13
13
  class TopDrivers extends AbstractAnalytics
14
14
  {
15
- protected int $limit = 10;
15
+ protected int $limit = 10;
16
16
  protected string $sortBy = 'orders_completed';
17
17
 
18
18
  public function limit(int $limit): self
@@ -0,0 +1,98 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Illuminate\Http\Request;
7
+ use Illuminate\Support\Str;
8
+ use Laravel\Sanctum\PersonalAccessToken;
9
+
10
+ /**
11
+ * Helper for resolving the authenticated customer from a request.
12
+ *
13
+ * The customer token is issued by `CustomerController` as a Sanctum
14
+ * `PersonalAccessToken` whose `name` column carries the owning Contact's UUID.
15
+ * Mirrors {@see \Fleetbase\Storefront\Support\Storefront::getCustomerFromToken}
16
+ * but with no Storefront-specific session.
17
+ */
18
+ class CustomerAuth
19
+ {
20
+ public const HEADER = 'Customer-Token';
21
+
22
+ public const APP_BINDING = 'currentCustomer';
23
+
24
+ /**
25
+ * Resolve the Contact represented by the `Customer-Token` header.
26
+ *
27
+ * Preference order when a User has more than one customer-type Contact:
28
+ * 1. Contact whose UUID matches the token's `name` column (Storefront convention).
29
+ * 2. Contact in the same company as the resolved API credential (`session('company')`).
30
+ * 3. The first remaining customer-type Contact for the token's User.
31
+ */
32
+ public static function resolveFromHeader(?Request $request = null): ?Contact
33
+ {
34
+ $request = $request ?? request();
35
+ $token = $request->header(self::HEADER);
36
+ if (!$token) {
37
+ return null;
38
+ }
39
+
40
+ $accessToken = PersonalAccessToken::findToken($token);
41
+ if (!$accessToken) {
42
+ return null;
43
+ }
44
+
45
+ // Storefront convention: token name = Contact UUID.
46
+ if (is_string($accessToken->name) && Str::isUuid($accessToken->name)) {
47
+ $contact = Contact::where('uuid', $accessToken->name)
48
+ ->where('type', 'customer')
49
+ ->first();
50
+ if ($contact) {
51
+ return $contact;
52
+ }
53
+ }
54
+
55
+ // Fallback: pick the customer Contact bound to the token's user, preferring
56
+ // the same company as the API credential when available.
57
+ $userId = $accessToken->tokenable_id ?? null;
58
+ $userIdentifier = $accessToken->tokenable->uuid ?? $userId;
59
+ if (!$userIdentifier) {
60
+ return null;
61
+ }
62
+
63
+ $contactQuery = Contact::where('user_uuid', $userIdentifier)
64
+ ->where('type', 'customer');
65
+
66
+ $sessionCompany = session('company');
67
+ if ($sessionCompany) {
68
+ $companyPreferred = (clone $contactQuery)
69
+ ->where('company_uuid', $sessionCompany)
70
+ ->first();
71
+ if ($companyPreferred) {
72
+ return $companyPreferred;
73
+ }
74
+ }
75
+
76
+ return $contactQuery->first();
77
+ }
78
+
79
+ /**
80
+ * Return the currently-bound customer (set by AuthenticateCustomerToken middleware).
81
+ */
82
+ public static function current(): ?Contact
83
+ {
84
+ return app()->bound(self::APP_BINDING) ? app(self::APP_BINDING) : null;
85
+ }
86
+
87
+ /**
88
+ * Bind the resolved customer for the current request lifecycle.
89
+ */
90
+ public static function setCurrent(Contact $contact): void
91
+ {
92
+ app()->instance(self::APP_BINDING, $contact);
93
+ session([
94
+ 'customer_id' => $contact->uuid,
95
+ 'contact_id' => $contact->uuid,
96
+ ]);
97
+ }
98
+ }
@@ -0,0 +1,52 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Barryvdh\DomPDF\Facade\Pdf;
6
+
7
+ class LabelPdf
8
+ {
9
+ /**
10
+ * Create a PDF instance for FleetOps labels with UTF-8 and embedded-font
11
+ * options applied consistently across order, waypoint, and entity labels.
12
+ */
13
+ public static function fromHtml(string $html)
14
+ {
15
+ $pdf = Pdf::loadHTML($html, 'UTF-8');
16
+
17
+ if (method_exists($pdf, 'setOptions')) {
18
+ $pdf->setOptions(static::options());
19
+ }
20
+
21
+ if (method_exists($pdf, 'getDomPDF')) {
22
+ $dompdf = $pdf->getDomPDF();
23
+ foreach (static::dompdfOptions() as $key => $value) {
24
+ if (method_exists($dompdf, 'set_option')) {
25
+ $dompdf->set_option($key, $value);
26
+ }
27
+ }
28
+ }
29
+
30
+ return $pdf;
31
+ }
32
+
33
+ public static function options(): array
34
+ {
35
+ return [
36
+ 'defaultFont' => 'DejaVu Sans',
37
+ 'isHtml5ParserEnabled' => true,
38
+ 'isFontSubsettingEnabled' => true,
39
+ 'isRemoteEnabled' => false,
40
+ ];
41
+ }
42
+
43
+ public static function dompdfOptions(): array
44
+ {
45
+ return [
46
+ 'defaultFont' => 'DejaVu Sans',
47
+ 'isHtml5ParserEnabled' => true,
48
+ 'isFontSubsettingEnabled' => true,
49
+ 'isRemoteEnabled' => false,
50
+ ];
51
+ }
52
+ }
@@ -14,12 +14,12 @@ use Illuminate\Support\Carbon;
14
14
  abstract class AbstractMetric
15
15
  {
16
16
  protected Company $company;
17
- protected ?\DateTimeInterface $start = null;
18
- protected ?\DateTimeInterface $end = null;
17
+ protected ?\DateTimeInterface $start = null;
18
+ protected ?\DateTimeInterface $end = null;
19
19
  protected ?\DateTimeInterface $compareStart = null;
20
20
  protected ?\DateTimeInterface $compareEnd = null;
21
- protected int $sparklineBuckets = 0;
22
- protected string $sparklineUnit = 'day';
21
+ protected int $sparklineBuckets = 0;
22
+ protected string $sparklineUnit = 'day';
23
23
 
24
24
  /**
25
25
  * Period boundaries for the currently-executing aggregate call.