@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.
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/form.hbs +2 -2
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +2 -2
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +184 -20
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +45 -3
- package/addon/components/modals/update-order-activity.js +143 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +11 -0
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -17
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +41 -0
- package/addon/components/service-rate/form.hbs +118 -0
- package/addon/components/service-rate/form.js +55 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/operations/orders/index/details.js +4 -4
- package/addon/controllers/operations/orders/index.js +14 -0
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +91 -1
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +429 -26
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Models/Contact.php +92 -15
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Order.php +29 -4
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +1 -1
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +43 -9
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/en-us.yaml +1 -0
- 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 = [
|
|
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'
|
|
44
|
-
'max'
|
|
45
|
-
'fee'
|
|
46
|
-
'distance'
|
|
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']
|
|
66
|
-
$row['distance']
|
|
67
|
-
$row['min']
|
|
68
|
-
$row['max']
|
|
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' => '
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
17
|
+
public static string $package = 'fleet-ops';
|
|
18
18
|
|
|
19
19
|
public function __construct(public Order $order, public array $context = [])
|
|
20
20
|
{
|
|
@@ -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'
|
|
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'
|
|
44
|
-
'14d'
|
|
45
|
-
'30d'
|
|
46
|
-
'90d'
|
|
47
|
-
'180d'
|
|
48
|
-
'365d'
|
|
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(
|
|
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(
|
|
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
|
|
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();
|
|
@@ -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
|
|
18
|
-
protected ?\DateTimeInterface $end
|
|
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
|
|
22
|
-
protected string $sparklineUnit
|
|
21
|
+
protected int $sparklineBuckets = 0;
|
|
22
|
+
protected string $sparklineUnit = 'day';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Period boundaries for the currently-executing aggregate call.
|