@fleetbase/fleetops-engine 0.6.47 → 0.6.49
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/admin/routing-settings.hbs +71 -0
- package/addon/components/admin/routing-settings.js +124 -0
- package/addon/components/customer/orders.js +1 -1
- package/addon/components/display-place.hbs +1 -1
- package/addon/components/driver/details.hbs +5 -1
- package/addon/components/driver/form.hbs +11 -1
- package/addon/components/fleet-listing-panel.hbs +2 -2
- package/addon/components/fleet-listing-panel.js +10 -0
- package/addon/components/map/google-live-map.js +12 -73
- package/addon/components/map/leaflet-live-map.hbs +149 -33
- package/addon/components/map/order-list-overlay/order.hbs +18 -3
- package/addon/components/map/toolbar.hbs +2 -2
- package/addon/components/map/toolbar.js +10 -2
- package/addon/components/orchestrator/phase-builder.hbs +49 -5
- package/addon/components/orchestrator/phase-builder.js +34 -1
- package/addon/components/orchestrator/resource-panel.hbs +19 -1
- package/addon/components/orchestrator/resource-panel.js +4 -0
- package/addon/components/orchestrator-workbench.js +4 -1
- package/addon/components/order/details/purchase-rate.hbs +19 -22
- package/addon/components/order/details/route.hbs +26 -1
- package/addon/components/order/details/route.js +50 -0
- package/addon/components/order/details/tracking.hbs +171 -27
- package/addon/components/order/details/tracking.js +457 -0
- package/addon/components/order/form.hbs +2 -2
- package/addon/components/order/kanban-card.hbs +18 -3
- package/addon/components/order-list-overlay/order.hbs +18 -3
- package/addon/components/order-progress-card.hbs +21 -6
- package/addon/components/order-tracking-lookup.hbs +7 -7
- package/addon/components/order-tracking-lookup.js +1 -1
- package/addon/components/route-list.hbs +78 -8
- package/addon/components/route-list.js +48 -6
- package/addon/components/tracking-stop-progress.hbs +45 -0
- package/addon/components/tracking-stop-progress.js +79 -0
- package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
- package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
- package/addon/components/widget/fuel-efficiency.hbs +26 -0
- package/addon/components/widget/fuel-efficiency.js +43 -0
- package/addon/components/widget/geofence-violations.hbs +44 -0
- package/addon/components/widget/geofence-violations.js +55 -0
- package/addon/components/widget/issues-insights.hbs +43 -0
- package/addon/components/widget/issues-insights.js +62 -0
- package/addon/components/widget/kpi-active-orders.hbs +10 -0
- package/addon/components/widget/kpi-active-orders.js +5 -0
- package/addon/components/widget/kpi-aov.hbs +10 -0
- package/addon/components/widget/kpi-aov.js +5 -0
- package/addon/components/widget/kpi-distance.hbs +10 -0
- package/addon/components/widget/kpi-distance.js +5 -0
- package/addon/components/widget/kpi-drivers-online.hbs +10 -0
- package/addon/components/widget/kpi-drivers-online.js +5 -0
- package/addon/components/widget/kpi-earnings.hbs +10 -0
- package/addon/components/widget/kpi-earnings.js +5 -0
- package/addon/components/widget/kpi-open-issues.hbs +11 -0
- package/addon/components/widget/kpi-open-issues.js +5 -0
- package/addon/components/widget/kpi-tile.hbs +59 -0
- package/addon/components/widget/kpi-tile.js +168 -0
- package/addon/components/widget/live-fleet.hbs +122 -0
- package/addon/components/widget/live-fleet.js +365 -0
- package/addon/components/widget/maintenance-overview.hbs +45 -0
- package/addon/components/widget/maintenance-overview.js +40 -0
- package/addon/components/widget/on-time-delivery.hbs +27 -0
- package/addon/components/widget/on-time-delivery.js +73 -0
- package/addon/components/widget/operations-pulse.hbs +51 -0
- package/addon/components/widget/operations-pulse.js +173 -0
- package/addon/components/widget/orders-by-status.hbs +26 -0
- package/addon/components/widget/orders-by-status.js +54 -0
- package/addon/components/widget/revenue-trend.hbs +34 -0
- package/addon/components/widget/revenue-trend.js +67 -0
- package/addon/components/widget/top-drivers.hbs +44 -0
- package/addon/components/widget/top-drivers.js +54 -0
- package/addon/controllers/settings/routing.js +124 -1
- package/addon/extension.js +172 -3
- package/addon/helpers/format-duration.js +38 -0
- package/addon/routes/application.js +6 -0
- package/addon/services/map-adapter/google.js +13 -2
- package/addon/services/order-list-overlay.js +2 -2
- package/addon/styles/fleetops-engine.css +806 -46
- package/addon/templates/maintenance/equipment/index.hbs +1 -1
- package/addon/templates/maintenance/parts/index.hbs +1 -1
- package/addon/templates/maintenance/schedules/index.hbs +1 -1
- package/addon/templates/management/vehicles/index.hbs +1 -1
- package/addon/templates/settings/avatars.hbs +1 -1
- package/addon/templates/settings/custom-fields.hbs +1 -1
- package/addon/templates/settings/map.hbs +1 -1
- package/addon/templates/settings/navigator-app.hbs +1 -1
- package/addon/templates/settings/notifications.hbs +1 -1
- package/addon/templates/settings/orchestrator.hbs +1 -1
- package/addon/templates/settings/payments/index.hbs +1 -1
- package/addon/templates/settings/payments/onboard.hbs +1 -1
- package/addon/templates/settings/routing.hbs +82 -4
- package/addon/templates/settings/scheduling.hbs +1 -1
- package/addon/utils/live-map-card-content.js +171 -0
- package/app/components/tracking-stop-progress.js +1 -0
- package/app/components/widget/fuel-efficiency.js +1 -0
- package/app/components/widget/geofence-violations.js +1 -0
- package/app/components/widget/issues-insights.js +1 -0
- package/app/components/widget/kpi-active-orders.js +1 -0
- package/app/components/widget/kpi-aov.js +1 -0
- package/app/components/widget/kpi-distance.js +1 -0
- package/app/components/widget/kpi-drivers-online.js +1 -0
- package/app/components/widget/kpi-earnings.js +1 -0
- package/app/components/widget/kpi-open-issues.js +1 -0
- package/app/components/widget/kpi-tile.js +1 -0
- package/app/components/widget/live-fleet.js +1 -0
- package/app/components/widget/maintenance-overview.js +1 -0
- package/app/components/widget/on-time-delivery.js +1 -0
- package/app/components/widget/operations-pulse.js +1 -0
- package/app/components/widget/orders-by-status.js +1 -0
- package/app/components/widget/revenue-trend.js +1 -0
- package/app/components/widget/top-drivers.js +1 -0
- package/app/helpers/format-duration.js +1 -0
- package/app/utils/live-map-card-content.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +1 -0
- package/server/config/fleetops.php +32 -11
- package/server/config/telematics.php +111 -1
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
- package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
- package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
- package/server/seeders/Testing/ConnectivitySeeder.php +212 -0
- package/server/seeders/Testing/FleetSeeder.php +341 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
- package/server/seeders/Testing/NetworkSeeder.php +250 -0
- package/server/seeders/Testing/OrdersSeeder.php +214 -0
- package/server/seeders/Testing/TestingSeeder.php +40 -0
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +409 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +52 -21
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +164 -12
- package/server/src/Http/Resources/v1/Driver.php +2 -0
- package/server/src/Http/Resources/v1/Fleet.php +4 -0
- package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
- package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
- package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
- package/server/src/Models/Contact.php +122 -13
- package/server/src/Models/Driver.php +2 -0
- package/server/src/Models/Order.php +1 -1
- package/server/src/Models/OrderConfig.php +14 -11
- package/server/src/Models/Payload.php +8 -8
- package/server/src/Models/Place.php +0 -3
- package/server/src/Models/ServiceRate.php +1 -1
- package/server/src/Notifications/LateDeparture.php +48 -0
- package/server/src/Notifications/ProlongedStoppage.php +48 -0
- package/server/src/Notifications/RouteDeviation.php +48 -0
- package/server/src/Observers/ContactObserver.php +8 -0
- package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +357 -38
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
- package/server/src/Providers/FleetOpsServiceProvider.php +32 -0
- package/server/src/Providers/NotificationServiceProvider.php +3 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
- package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
- package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
- package/server/src/Support/Analytics/IssuesInsights.php +67 -0
- package/server/src/Support/Analytics/LiveFleet.php +127 -0
- package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
- package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
- package/server/src/Support/Analytics/OperationsPulse.php +86 -0
- package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
- package/server/src/Support/Analytics/RevenueTrend.php +79 -0
- package/server/src/Support/Analytics/TopDrivers.php +92 -0
- package/server/src/Support/LiveOrderQuery.php +75 -0
- package/server/src/Support/Metrics/AbstractMetric.php +170 -0
- package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
- package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
- package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
- package/server/src/Support/Metrics/EarningsMetric.php +30 -0
- package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
- package/server/src/Support/Metrics/MoneyMetric.php +30 -0
- package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
- package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
- package/server/src/Support/Metrics/Registry.php +46 -0
- package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
- package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
- package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
- package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
- package/server/src/Support/Metrics.php +46 -234
- package/server/src/Support/OSRM.php +11 -6
- package/server/src/Support/OrderTracker.php +7 -582
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
- package/server/src/Support/Utils.php +1 -1
- package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
- package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
- package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
- package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
- package/server/src/Tracking/README.md +25 -0
- package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
- package/server/src/Tracking/TrackingContext.php +63 -0
- package/server/src/Tracking/TrackingContextBuilder.php +204 -0
- package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
- package/server/src/Tracking/TrackingOptions.php +49 -0
- package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
- package/server/src/Tracking/TrackingProviderManager.php +89 -0
- package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
- package/server/src/Tracking/TrackingProviderResult.php +20 -0
- package/server/src/Tracking/TrackingStop.php +59 -0
- package/server/src/routes.php +27 -0
- package/server/tests/AnalyticsRoutesTest.php +41 -0
- package/server/tests/CapacityAllocationEngineTest.php +108 -0
- package/server/tests/LiveOrderQueryTest.php +34 -0
- package/server/tests/MetricsRegistryTest.php +88 -0
- package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
- package/server/tests/OrchestratorConsumableApiTest.php +74 -0
- package/server/tests/PingDriverEndpointTest.php +19 -0
- package/server/tests/TrackingIntelligenceTest.php +176 -0
- package/server/tests/VroomOrchestrationEngineTest.php +250 -0
- package/translations/en-us.yaml +41 -0
|
@@ -5,6 +5,7 @@ namespace Fleetbase\FleetOps\Models;
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
6
|
use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
|
|
7
7
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
|
+
use Fleetbase\Models\CompanyUser;
|
|
8
9
|
use Fleetbase\Models\Invite;
|
|
9
10
|
use Fleetbase\Models\Model;
|
|
10
11
|
use Fleetbase\Models\User;
|
|
@@ -261,6 +262,14 @@ class Contact extends Model
|
|
|
261
262
|
return $this->type === 'customer';
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Determines if this contact is a Fleet-Ops customer.
|
|
267
|
+
*/
|
|
268
|
+
public function isCustomer(): bool
|
|
269
|
+
{
|
|
270
|
+
return $this->type === 'customer';
|
|
271
|
+
}
|
|
272
|
+
|
|
264
273
|
/**
|
|
265
274
|
* Creates a new contact from an import row.
|
|
266
275
|
*/
|
|
@@ -308,19 +317,33 @@ class Contact extends Model
|
|
|
308
317
|
public static function createUserFromContact(Contact $contact, bool $sendInvite = false, bool $update = false): User
|
|
309
318
|
{
|
|
310
319
|
// Check if user already exist with email or phone number
|
|
311
|
-
$existingUser =
|
|
312
|
-
|
|
313
|
-
$
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
320
|
+
$existingUser = null;
|
|
321
|
+
if ($contact->email || $contact->phone) {
|
|
322
|
+
$existingUser = User::where('company_uuid', $contact->company_uuid)
|
|
323
|
+
->where(function ($query) use ($contact) {
|
|
324
|
+
if ($contact->email) {
|
|
325
|
+
$query->where('email', $contact->email);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if ($contact->phone) {
|
|
329
|
+
$method = $contact->email ? 'orWhere' : 'where';
|
|
330
|
+
$query->{$method}('phone', Utils::formatPhoneNumber($contact->phone));
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
->whereNull('deleted_at')
|
|
334
|
+
->first();
|
|
335
|
+
}
|
|
336
|
+
|
|
318
337
|
if ($existingUser) {
|
|
319
338
|
// Check if existing user belongs to another contact
|
|
320
339
|
$existingUserContact = Contact::where(['user_uuid' => $existingUser->uuid, 'company_uuid' => $contact->company_uuid])->whereHas('user')->first();
|
|
321
340
|
if ($existingUserContact) {
|
|
322
341
|
throw new UserAlreadyExistsException('User already exists, try to assigning the user to this contact.', $existingUser);
|
|
323
342
|
} else {
|
|
343
|
+
if ($contact->isCustomer()) {
|
|
344
|
+
$contact->normalizeCustomerUser($existingUser);
|
|
345
|
+
}
|
|
346
|
+
|
|
324
347
|
// Assign the user to this contact instead
|
|
325
348
|
$contact->setAttribute('user_uuid', $existingUser->uuid);
|
|
326
349
|
if ($update) {
|
|
@@ -351,10 +374,10 @@ class Contact extends Model
|
|
|
351
374
|
$user->setType($contact->type);
|
|
352
375
|
|
|
353
376
|
// Assing to company
|
|
354
|
-
$user->assignCompany($contact->company, $
|
|
377
|
+
$user->assignCompany($contact->company, $contact->isCustomer() ? 'Fleet-Ops Customer' : 'Fleet-Ops Contact');
|
|
355
378
|
|
|
356
379
|
// Assign customer role
|
|
357
|
-
if ($
|
|
380
|
+
if ($contact->isCustomer()) {
|
|
358
381
|
$user->assignSingleRole('Fleet-Ops Customer');
|
|
359
382
|
}
|
|
360
383
|
|
|
@@ -386,6 +409,87 @@ class Contact extends Model
|
|
|
386
409
|
return $user;
|
|
387
410
|
}
|
|
388
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Normalize the linked user for a customer contact.
|
|
414
|
+
*/
|
|
415
|
+
public function normalizeCustomerUser(?User $user = null, bool $quiet = false): ?User
|
|
416
|
+
{
|
|
417
|
+
if (!$this->isCustomer()) {
|
|
418
|
+
return $user;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
$user ??= $this->getUser();
|
|
422
|
+
if (!$user) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if ($user->type !== 'customer') {
|
|
427
|
+
if ($quiet) {
|
|
428
|
+
$user->forceFill(['type' => 'customer'])->saveQuietly();
|
|
429
|
+
} else {
|
|
430
|
+
$user->setType('customer');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
$this->loadMissing('company');
|
|
435
|
+
if ($this->company) {
|
|
436
|
+
$companyUser = CompanyUser::firstOrCreate(
|
|
437
|
+
[
|
|
438
|
+
'company_uuid' => $this->company->uuid,
|
|
439
|
+
'user_uuid' => $user->uuid,
|
|
440
|
+
],
|
|
441
|
+
[
|
|
442
|
+
'company_uuid' => $this->company->uuid,
|
|
443
|
+
'user_uuid' => $user->uuid,
|
|
444
|
+
'status' => $user->status ?? 'active',
|
|
445
|
+
]
|
|
446
|
+
);
|
|
447
|
+
$user->forceFill(['company_uuid' => $this->company->uuid])->saveQuietly();
|
|
448
|
+
$user->setRelation('companyUser', $companyUser);
|
|
449
|
+
|
|
450
|
+
if ($companyUser) {
|
|
451
|
+
$companyUser->assignSingleRole('Fleet-Ops Customer');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
$this->setRelation('user', $user);
|
|
456
|
+
|
|
457
|
+
return $user;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Repair a historical customer contact and its linked user.
|
|
462
|
+
*/
|
|
463
|
+
public function repairCustomerTypeInvariant(bool $quiet = false): ?User
|
|
464
|
+
{
|
|
465
|
+
if (!$this->isCustomer()) {
|
|
466
|
+
$this->forceFill(['type' => 'customer'])->saveQuietly();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
$user = $this->getUser();
|
|
470
|
+
if (!$user && ($this->email || $this->phone)) {
|
|
471
|
+
if ($quiet) {
|
|
472
|
+
$this->loadMissing('company');
|
|
473
|
+
$user = User::create([
|
|
474
|
+
'company_uuid' => $this->company_uuid,
|
|
475
|
+
'name' => $this->name,
|
|
476
|
+
'email' => $this->email,
|
|
477
|
+
'phone' => $this->phone ? Utils::formatPhoneNumber($this->phone) : null,
|
|
478
|
+
'username' => Str::slug($this->name . '_' . Str::random(4), '_'),
|
|
479
|
+
'password' => Str::random(),
|
|
480
|
+
'timezone' => $this->company->timezone ?? date_default_timezone_get(),
|
|
481
|
+
'status' => 'pending',
|
|
482
|
+
]);
|
|
483
|
+
$user->forceFill(['type' => 'customer'])->saveQuietly();
|
|
484
|
+
$this->forceFill(['user_uuid' => $user->uuid])->saveQuietly();
|
|
485
|
+
} else {
|
|
486
|
+
$user = $this->createUser();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return $this->normalizeCustomerUser($user, $quiet);
|
|
491
|
+
}
|
|
492
|
+
|
|
389
493
|
/**
|
|
390
494
|
* Assigns a user to the company and optionally sends an invitation email.
|
|
391
495
|
*
|
|
@@ -406,16 +510,21 @@ class Contact extends Model
|
|
|
406
510
|
public function assignUser(User $user, bool $sendInvite = false): self
|
|
407
511
|
{
|
|
408
512
|
// Load company
|
|
409
|
-
$this->loadMissing('
|
|
513
|
+
$this->loadMissing('company');
|
|
514
|
+
|
|
515
|
+
if ($this->isCustomer() && $user->type !== 'customer') {
|
|
516
|
+
$user->setType('customer');
|
|
517
|
+
}
|
|
410
518
|
|
|
411
519
|
// Assing to company
|
|
412
|
-
$user->assignCompany($this->company, $this->
|
|
520
|
+
$user->assignCompany($this->company, $this->isCustomer() ? 'Fleet-Ops Customer' : 'Fleet-Ops Contact');
|
|
413
521
|
|
|
414
522
|
// Get the company user instance
|
|
415
523
|
$companyUser = $user->getCompanyUser($this->company);
|
|
416
524
|
|
|
417
|
-
|
|
418
|
-
|
|
525
|
+
if ($companyUser) {
|
|
526
|
+
$companyUser->assignSingleRole($this->isCustomer() ? 'Fleet-Ops Customer' : 'Fleet-Ops Contact');
|
|
527
|
+
}
|
|
419
528
|
|
|
420
529
|
// Set user to contact
|
|
421
530
|
$this->update(['user_uuid' => $user->uuid]);
|
|
@@ -97,6 +97,7 @@ class Driver extends Model
|
|
|
97
97
|
'signup_token_used',
|
|
98
98
|
'avatar_url',
|
|
99
99
|
'drivers_license_number',
|
|
100
|
+
'license_expiry',
|
|
100
101
|
'location',
|
|
101
102
|
'heading',
|
|
102
103
|
'bearing',
|
|
@@ -141,6 +142,7 @@ class Driver extends Model
|
|
|
141
142
|
'location' => Point::class,
|
|
142
143
|
'online' => 'boolean',
|
|
143
144
|
'meta' => Json::class,
|
|
145
|
+
'license_expiry' => 'date',
|
|
144
146
|
// Orchestrator
|
|
145
147
|
'skills' => Json::class,
|
|
146
148
|
'max_travel_time' => 'integer',
|
|
@@ -1739,7 +1739,7 @@ class Order extends Model
|
|
|
1739
1739
|
}
|
|
1740
1740
|
}
|
|
1741
1741
|
|
|
1742
|
-
$company
|
|
1742
|
+
$company = $this->relationLoaded('company') ? $this->company : $this->company()->first();
|
|
1743
1743
|
$orderConfig = OrderConfig::defaultOrCreate($company);
|
|
1744
1744
|
if ($orderConfig instanceof OrderConfig) {
|
|
1745
1745
|
$orderConfig->setOrderContext($this);
|
|
@@ -458,28 +458,31 @@ class OrderConfig extends Model
|
|
|
458
458
|
/**
|
|
459
459
|
* Resolves an OrderConfig from a given identifier or an array of identifiers within an optional company context.
|
|
460
460
|
*
|
|
461
|
-
* @param string|array $orderConfigIdentifier the identifier(s) for the OrderConfig (namespace, public_id, or key)
|
|
461
|
+
* @param string|array $orderConfigIdentifier the identifier(s) for the OrderConfig (uuid, namespace, public_id, or key)
|
|
462
462
|
* @param Company|null $company the company context, if any, to narrow down the search
|
|
463
463
|
*
|
|
464
464
|
* @return OrderConfig|null the found OrderConfig or null if none found
|
|
465
465
|
*/
|
|
466
466
|
public static function resolveFromIdentifier($orderConfigIdentifier, ?Company $company = null): ?OrderConfig
|
|
467
467
|
{
|
|
468
|
-
$
|
|
468
|
+
$companyUuid = $company instanceof Company ? $company->uuid : session('company');
|
|
469
|
+
$identifiers = is_array($orderConfigIdentifier) ? $orderConfigIdentifier : [$orderConfigIdentifier];
|
|
470
|
+
$identifiers = collect($identifiers)->filter(fn ($identifier) => filled($identifier))->values();
|
|
471
|
+
|
|
472
|
+
if ($identifiers->isEmpty()) {
|
|
473
|
+
return static::default($company);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
foreach ($identifiers as $identifier) {
|
|
477
|
+
$query = static::query();
|
|
469
478
|
|
|
470
|
-
if ($company instanceof Company) {
|
|
471
|
-
$query->where('company_uuid', $company->uuid);
|
|
472
|
-
} else {
|
|
473
|
-
$companyUuid = session('company');
|
|
474
479
|
if ($companyUuid) {
|
|
475
480
|
$query->where('company_uuid', $companyUuid);
|
|
476
481
|
}
|
|
477
|
-
}
|
|
478
482
|
|
|
479
|
-
$identifiers = is_array($orderConfigIdentifier) ? $orderConfigIdentifier : [$orderConfigIdentifier];
|
|
480
|
-
foreach ($identifiers as $identifier) {
|
|
481
483
|
$orderConfig = $query->where(function ($query) use ($identifier) {
|
|
482
|
-
$query->where('
|
|
484
|
+
$query->where('uuid', $identifier)
|
|
485
|
+
->orWhere('namespace', $identifier)
|
|
483
486
|
->orWhere('public_id', $identifier)
|
|
484
487
|
->orWhere('key', $identifier);
|
|
485
488
|
})->first();
|
|
@@ -489,7 +492,7 @@ class OrderConfig extends Model
|
|
|
489
492
|
}
|
|
490
493
|
}
|
|
491
494
|
|
|
492
|
-
return
|
|
495
|
+
return null;
|
|
493
496
|
}
|
|
494
497
|
|
|
495
498
|
/**
|
|
@@ -555,15 +555,15 @@ class Payload extends Model
|
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
$this->loadMissing('waypointMarkers');
|
|
558
|
-
$keptWaypointIds
|
|
558
|
+
$keptWaypointIds = [];
|
|
559
559
|
$availableWaypointMarkers = $this->waypointMarkers()->get();
|
|
560
560
|
|
|
561
561
|
foreach ($waypoints as $index => $attributes) {
|
|
562
|
-
$raw
|
|
563
|
-
$type
|
|
564
|
-
$customerUuidIn
|
|
562
|
+
$raw = $attributes;
|
|
563
|
+
$type = data_get($raw, 'type', 'dropoff');
|
|
564
|
+
$customerUuidIn = data_get($raw, 'customer_uuid');
|
|
565
565
|
$customerPubIdIn = data_get($raw, 'customer_id');
|
|
566
|
-
$customerType
|
|
566
|
+
$customerType = data_get($raw, 'customer_type', 'fleetops:contact');
|
|
567
567
|
|
|
568
568
|
if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) {
|
|
569
569
|
$attributes = Utils::get($attributes, 'place');
|
|
@@ -599,12 +599,12 @@ class Payload extends Model
|
|
|
599
599
|
$placeUuid = $place->uuid;
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
-
$customerUuid
|
|
602
|
+
$customerUuid = null;
|
|
603
603
|
$customerTypeNamespace = null;
|
|
604
604
|
|
|
605
605
|
if ($customerType) {
|
|
606
606
|
$customerTypeNamespace = Utils::getMutationType($customerType);
|
|
607
|
-
$customerModel
|
|
607
|
+
$customerModel = app($customerTypeNamespace);
|
|
608
608
|
|
|
609
609
|
if ($customerUuidIn && $customerModel->where('uuid', $customerUuidIn)->exists()) {
|
|
610
610
|
$customerUuid = $customerUuidIn;
|
|
@@ -642,7 +642,7 @@ class Payload extends Model
|
|
|
642
642
|
continue;
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
-
$waypointRecord
|
|
645
|
+
$waypointRecord = Waypoint::create($values);
|
|
646
646
|
$keptWaypointIds[] = $waypointRecord->uuid;
|
|
647
647
|
}
|
|
648
648
|
|
|
@@ -21,7 +21,6 @@ use Fleetbase\Traits\Searchable;
|
|
|
21
21
|
use Fleetbase\Traits\SendsWebhooks;
|
|
22
22
|
use Fleetbase\Traits\TracksApiCredential;
|
|
23
23
|
use Illuminate\Support\Carbon;
|
|
24
|
-
use Illuminate\Support\Facades\DB;
|
|
25
24
|
use Illuminate\Support\Str;
|
|
26
25
|
|
|
27
26
|
class Place extends Model
|
|
@@ -383,8 +382,6 @@ class Place extends Model
|
|
|
383
382
|
|
|
384
383
|
/**
|
|
385
384
|
* Create a new Place instance from a geocoding lookup.
|
|
386
|
-
*
|
|
387
|
-
* @return array
|
|
388
385
|
*/
|
|
389
386
|
public static function getValuesFromGeocodingLookup(string $address): array
|
|
390
387
|
{
|
|
@@ -740,7 +740,7 @@ class ServiceRate extends Model
|
|
|
740
740
|
|
|
741
741
|
if ($this->isAlgorithm()) {
|
|
742
742
|
$resolvedEndpointCount = $endpointCount ?? $this->inferEndpointCountFromStops($waypoints);
|
|
743
|
-
$rateFee
|
|
743
|
+
$rateFee = $this->normalizeCalculatedMoney(Algo::exec(
|
|
744
744
|
$this->algorithm,
|
|
745
745
|
$this->buildAlgorithmVariables(
|
|
746
746
|
$entities,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Notifications;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Notifications\Messages\MailMessage;
|
|
9
|
+
use Illuminate\Notifications\Notification;
|
|
10
|
+
|
|
11
|
+
class LateDeparture extends Notification implements ShouldQueue
|
|
12
|
+
{
|
|
13
|
+
use Queueable;
|
|
14
|
+
|
|
15
|
+
public static string $name = 'Late Departure';
|
|
16
|
+
public static string $description = 'Notify when an order has not departed after the configured grace period.';
|
|
17
|
+
public static string $package = 'fleet-ops';
|
|
18
|
+
|
|
19
|
+
public function __construct(public Order $order, public array $context = [])
|
|
20
|
+
{
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function via($notifiable): array
|
|
24
|
+
{
|
|
25
|
+
return ['mail', 'database'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public function toMail($notifiable): MailMessage
|
|
29
|
+
{
|
|
30
|
+
$orderId = $this->order->tracking ?? $this->order->public_id;
|
|
31
|
+
|
|
32
|
+
return (new MailMessage())
|
|
33
|
+
->subject("Late departure detected for {$orderId}")
|
|
34
|
+
->line("Order {$orderId} has not departed within the configured grace period.")
|
|
35
|
+
->line('Please review the order and dispatch status.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function toArray($notifiable): array
|
|
39
|
+
{
|
|
40
|
+
return [
|
|
41
|
+
'event' => 'order.late_departure',
|
|
42
|
+
'order_id' => $this->order->public_id,
|
|
43
|
+
'order_uuid' => $this->order->uuid,
|
|
44
|
+
'context' => $this->context,
|
|
45
|
+
'message' => sprintf('Order %s has not departed within the configured grace period.', $this->order->tracking ?? $this->order->public_id),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Notifications;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Notifications\Messages\MailMessage;
|
|
9
|
+
use Illuminate\Notifications\Notification;
|
|
10
|
+
|
|
11
|
+
class ProlongedStoppage extends Notification implements ShouldQueue
|
|
12
|
+
{
|
|
13
|
+
use Queueable;
|
|
14
|
+
|
|
15
|
+
public static string $name = 'Prolonged Stoppage';
|
|
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';
|
|
18
|
+
|
|
19
|
+
public function __construct(public Order $order, public array $context = [])
|
|
20
|
+
{
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function via($notifiable): array
|
|
24
|
+
{
|
|
25
|
+
return ['mail', 'database'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public function toMail($notifiable): MailMessage
|
|
29
|
+
{
|
|
30
|
+
$orderId = $this->order->tracking ?? $this->order->public_id;
|
|
31
|
+
|
|
32
|
+
return (new MailMessage())
|
|
33
|
+
->subject("Prolonged stoppage detected for {$orderId}")
|
|
34
|
+
->line("The vehicle assigned to order {$orderId} has remained stopped beyond the configured threshold.")
|
|
35
|
+
->line('Please review the active trip and vehicle location.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function toArray($notifiable): array
|
|
39
|
+
{
|
|
40
|
+
return [
|
|
41
|
+
'event' => 'order.prolonged_stoppage',
|
|
42
|
+
'order_id' => $this->order->public_id,
|
|
43
|
+
'order_uuid' => $this->order->uuid,
|
|
44
|
+
'context' => $this->context,
|
|
45
|
+
'message' => sprintf('The vehicle assigned to order %s has remained stopped beyond the configured threshold.', $this->order->tracking ?? $this->order->public_id),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Notifications;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Bus\Queueable;
|
|
7
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
8
|
+
use Illuminate\Notifications\Messages\MailMessage;
|
|
9
|
+
use Illuminate\Notifications\Notification;
|
|
10
|
+
|
|
11
|
+
class RouteDeviation extends Notification implements ShouldQueue
|
|
12
|
+
{
|
|
13
|
+
use Queueable;
|
|
14
|
+
|
|
15
|
+
public static string $name = 'Route Deviation';
|
|
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';
|
|
18
|
+
|
|
19
|
+
public function __construct(public Order $order, public array $context = [])
|
|
20
|
+
{
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function via($notifiable): array
|
|
24
|
+
{
|
|
25
|
+
return ['mail', 'database'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public function toMail($notifiable): MailMessage
|
|
29
|
+
{
|
|
30
|
+
$orderId = $this->order->tracking ?? $this->order->public_id;
|
|
31
|
+
|
|
32
|
+
return (new MailMessage())
|
|
33
|
+
->subject("Route deviation detected for {$orderId}")
|
|
34
|
+
->line("The vehicle assigned to order {$orderId} has deviated from the active route beyond the configured threshold.")
|
|
35
|
+
->line('Please review the route and current vehicle position.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function toArray($notifiable): array
|
|
39
|
+
{
|
|
40
|
+
return [
|
|
41
|
+
'event' => 'order.route_deviation',
|
|
42
|
+
'order_id' => $this->order->public_id,
|
|
43
|
+
'order_uuid' => $this->order->uuid,
|
|
44
|
+
'context' => $this->context,
|
|
45
|
+
'message' => sprintf('The vehicle assigned to order %s has deviated from the active route beyond the configured threshold.', $this->order->tracking ?? $this->order->public_id),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -27,11 +27,19 @@ class ContactObserver
|
|
|
27
27
|
*/
|
|
28
28
|
public function saving(Contact $contact)
|
|
29
29
|
{
|
|
30
|
+
if ($contact->exists && $contact->getOriginal('type') === 'customer' && $contact->isDirty('type') && $contact->type !== 'customer') {
|
|
31
|
+
throw new \Exception('Customer contact type cannot be changed.');
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
// Get the contacts assosciated user
|
|
31
35
|
if ($contact->doesntHaveUser()) {
|
|
32
36
|
$contact->createUser();
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
if ($contact->isCustomer()) {
|
|
40
|
+
$contact->normalizeCustomerUser();
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
// Validate email is available to user
|
|
36
44
|
if (!empty($contact->email) && $contact->wasChanged('email') && $this->isEmailUnavailable($contact)) {
|
|
37
45
|
throw new \Exception('Email attempting to update for ' . $contact->type . ' is not available.');
|