@fleetbase/fleetops-engine 0.6.49 → 0.6.51
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/README.md +185 -67
- package/addon/components/contact/panel-header.hbs +11 -48
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/details.hbs +1 -2
- package/addon/components/customer/form.hbs +62 -50
- package/addon/components/customer/form.js +27 -1
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +50 -2
- package/addon/components/customer/orders.js +67 -5
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/home/getting-started-guidance.hbs +113 -0
- package/addon/components/home/getting-started-guidance.js +107 -0
- package/addon/components/issue/details/comments.hbs +3 -0
- package/addon/components/issue/details/comments.js +3 -0
- package/addon/components/issue/details/correspondence.hbs +9 -0
- package/addon/components/issue/details/correspondence.js +83 -0
- package/addon/components/issue/details/documents.hbs +69 -0
- package/addon/components/issue/details/documents.js +85 -0
- package/addon/components/issue/details.hbs +220 -59
- package/addon/components/issue/details.js +245 -0
- package/addon/components/issue/form.hbs +19 -1
- package/addon/components/issue/panel-header.hbs +43 -0
- package/addon/components/issue/panel-header.js +22 -0
- package/addon/components/issue/timeline.hbs +40 -0
- package/addon/components/issue/timeline.js +45 -0
- package/addon/components/leaflet-tracking-marker.js +3 -0
- package/addon/components/map/drawer/position-listing.js +3 -0
- 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 +201 -20
- package/addon/components/map/order-list-overlay.hbs +4 -4
- package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
- package/addon/components/modals/convert-customer-to-vendor.js +55 -0
- package/addon/components/modals/issue-assign.hbs +20 -0
- package/addon/components/modals/issue-assign.js +49 -0
- package/addon/components/modals/issue-close.hbs +30 -0
- package/addon/components/modals/issue-close.js +73 -0
- package/addon/components/modals/issue-status.hbs +20 -0
- package/addon/components/modals/issue-status.js +43 -0
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/reset-customer-credentials.js +12 -6
- 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 +78 -2
- package/addon/components/modals/update-order-activity.js +197 -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 +13 -2
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/proof.hbs +40 -38
- package/addon/components/order/details/proof.js +31 -4
- 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/details.hbs +5 -5
- package/addon/components/order/details.js +19 -1
- 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/header.hbs +1 -1
- 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 -29
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +42 -1
- package/addon/components/service-rate/form.hbs +119 -1
- package/addon/components/service-rate/form.js +59 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/vendor/personnel-manager.hbs +166 -0
- package/addon/components/vendor/personnel-manager.js +143 -0
- package/addon/components/widget/live-fleet.hbs +1 -0
- package/addon/components/widget/live-fleet.js +2 -7
- package/addon/components/widget/revenue-trend.hbs +1 -1
- package/addon/components/widget/revenue-trend.js +75 -2
- package/addon/controllers/management/contacts/customers/details.js +31 -13
- package/addon/controllers/management/contacts/customers.js +16 -5
- package/addon/controllers/management/contacts/index/details.js +31 -13
- package/addon/controllers/management/contacts/index.js +7 -0
- package/addon/controllers/management/issues/index/details.js +52 -6
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/management/vendors/index/details.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +15 -7
- package/addon/controllers/operations/orders/index.js +15 -1
- package/addon/extension.js +19 -12
- package/addon/helpers/format-duration.js +1 -38
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/initializers/load-leaflet-assets.js +5 -6
- package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
- package/addon/routes/management/issues/index/details.js +1 -1
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +231 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/getting-started.js +44 -0
- package/addon/services/issue-actions.js +71 -0
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/leaflet-map-manager.js +2 -0
- package/addon/services/map-adapter/leaflet.js +19 -0
- package/addon/services/service-areas.js +3 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +1127 -1
- package/addon/templates/management/contacts/customers/details.hbs +1 -1
- package/addon/templates/management/contacts/index/details.hbs +1 -1
- package/addon/templates/management/contacts.hbs +2 -2
- package/addon/templates/management/issues/index/details/index.hbs +3 -1
- package/addon/templates/management/issues/index/details.hbs +3 -2
- package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
- package/addon/templates/operations/orders/index/details/index.hbs +2 -2
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
- package/addon/templates/operations/service-rates/index/new.hbs +1 -0
- package/addon/templates/settings/payments/onboard.hbs +8 -8
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
- package/addon/utils/leaflet-plugin-loader.js +219 -0
- 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/addon/utils/setup-customer-portal.js +4 -64
- package/app/components/home/getting-started-guidance.js +1 -0
- package/app/components/issue/details/comments.js +1 -0
- package/app/components/issue/details/correspondence.js +1 -0
- package/app/components/issue/details/documents.js +1 -0
- package/app/components/issue/panel-header.js +1 -0
- package/app/components/modals/convert-customer-to-vendor.js +1 -0
- package/app/components/modals/issue-assign.js +1 -0
- package/app/components/modals/issue-close.js +1 -0
- package/app/components/modals/issue-status.js +1 -0
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/vendor/personnel-manager.js +1 -0
- package/app/helpers/format-duration.js +1 -1
- package/app/services/getting-started.js +1 -0
- package/app/utils/leaflet-draw-namespace-guard.js +1 -0
- package/app/utils/leaflet-plugin-loader.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +5 -5
- 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/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
- package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
- package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
- package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
- package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/resources/views/mail/customer-credentials.blade.php +16 -13
- 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/Expansions/UserFilterExpansion.php +28 -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/IssueController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
- 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 +154 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
- package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
- 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/Controllers/Internal/v1/VendorController.php +89 -0
- 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/CreateIssueRequest.php +4 -0
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
- 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/Index/Order.php +12 -9
- package/server/src/Http/Resources/v1/Issue.php +25 -0
- package/server/src/Http/Resources/v1/Order.php +20 -18
- 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/Mail/CustomerCredentialsMail.php +5 -6
- package/server/src/Models/Contact.php +122 -66
- 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/Issue.php +47 -6
- package/server/src/Models/Order.php +62 -10
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceArea.php +1 -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/VendorPersonnel.php +10 -1
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Models/Zone.php +1 -1
- 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/PurchaseRateObserver.php +20 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Observers/ZoneObserver.php +56 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -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 +10 -2
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/GettingStarted.php +144 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/PlaceSearch.php +282 -0
- package/server/src/Support/ResolvesOrderServiceStops.php +522 -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 +76 -10
- package/server/tests/AnalyticsRoutesTest.php +8 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/GettingStartedStatusTest.php +21 -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/OrderActivityFlowRegressionTest.php +116 -0
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/ar-ae.yml +9 -0
- package/translations/bg-bg.yaml +9 -0
- package/translations/en-us.yaml +12 -0
- package/translations/es-pa.yaml +9 -0
- package/translations/fr-fr.yaml +9 -0
- package/translations/mn-mn.yaml +9 -0
- package/translations/pt-br.yaml +9 -0
- package/translations/ru-ru.yaml +9 -0
- package/translations/vi-vn.yaml +9 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\FleetOps\Exceptions\CustomerUserConflictException;
|
|
6
7
|
use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
|
|
7
8
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
9
|
use Fleetbase\Models\CompanyUser;
|
|
9
|
-
use Fleetbase\Models\Invite;
|
|
10
10
|
use Fleetbase\Models\Model;
|
|
11
11
|
use Fleetbase\Models\User;
|
|
12
|
-
use Fleetbase\Notifications\UserInvited;
|
|
13
12
|
use Fleetbase\Traits\HasApiModelBehavior;
|
|
14
13
|
use Fleetbase\Traits\HasApiModelCache;
|
|
15
14
|
use Fleetbase\Traits\HasCustomFields;
|
|
@@ -300,15 +299,15 @@ class Contact extends Model
|
|
|
300
299
|
}
|
|
301
300
|
|
|
302
301
|
/**
|
|
303
|
-
* Creates a new user from the given contact
|
|
302
|
+
* Creates a new user from the given contact without sending an organization invitation.
|
|
304
303
|
*
|
|
305
304
|
* This method first checks if a user with the same email or phone number as the contact already exists.
|
|
306
305
|
* If such a user exists, it throws a UserAlreadyExistsException. Otherwise, it proceeds to create a new
|
|
307
306
|
* user record using the contact's details, assigns the user to the contact's company, and links the
|
|
308
|
-
* user to the contact.
|
|
307
|
+
* user to the contact.
|
|
309
308
|
*
|
|
310
309
|
* @param Contact $contact the contact instance from which the user should be created
|
|
311
|
-
* @param bool $sendInvite
|
|
310
|
+
* @param bool $sendInvite Deprecated and ignored. Contact and customer users do not receive organization invitations.
|
|
312
311
|
*
|
|
313
312
|
* @return User the newly created user instance
|
|
314
313
|
*
|
|
@@ -319,7 +318,12 @@ class Contact extends Model
|
|
|
319
318
|
// Check if user already exist with email or phone number
|
|
320
319
|
$existingUser = null;
|
|
321
320
|
if ($contact->email || $contact->phone) {
|
|
322
|
-
$existingUser = User::where(
|
|
321
|
+
$existingUser = User::where(function ($query) use ($contact) {
|
|
322
|
+
$query->where('company_uuid', $contact->company_uuid)
|
|
323
|
+
->orWhereHas('companyUsers', function ($query) use ($contact) {
|
|
324
|
+
$query->where('company_uuid', $contact->company_uuid);
|
|
325
|
+
});
|
|
326
|
+
})
|
|
323
327
|
->where(function ($query) use ($contact) {
|
|
324
328
|
if ($contact->email) {
|
|
325
329
|
$query->where('email', $contact->email);
|
|
@@ -335,15 +339,15 @@ class Contact extends Model
|
|
|
335
339
|
}
|
|
336
340
|
|
|
337
341
|
if ($existingUser) {
|
|
342
|
+
if ($contact->isCustomer()) {
|
|
343
|
+
$contact->assertCustomerUserCanBeAssigned($existingUser);
|
|
344
|
+
}
|
|
345
|
+
|
|
338
346
|
// Check if existing user belongs to another contact
|
|
339
347
|
$existingUserContact = Contact::where(['user_uuid' => $existingUser->uuid, 'company_uuid' => $contact->company_uuid])->whereHas('user')->first();
|
|
340
348
|
if ($existingUserContact) {
|
|
341
349
|
throw new UserAlreadyExistsException('User already exists, try to assigning the user to this contact.', $existingUser);
|
|
342
350
|
} else {
|
|
343
|
-
if ($contact->isCustomer()) {
|
|
344
|
-
$contact->normalizeCustomerUser($existingUser);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
351
|
// Assign the user to this contact instead
|
|
348
352
|
$contact->setAttribute('user_uuid', $existingUser->uuid);
|
|
349
353
|
if ($update) {
|
|
@@ -373,8 +377,8 @@ class Contact extends Model
|
|
|
373
377
|
// Set user type
|
|
374
378
|
$user->setType($contact->type);
|
|
375
379
|
|
|
376
|
-
//
|
|
377
|
-
|
|
380
|
+
// Assign to company without triggering core organization invitations.
|
|
381
|
+
static::assignUserToContactCompany($contact, $user);
|
|
378
382
|
|
|
379
383
|
// Assign customer role
|
|
380
384
|
if ($contact->isCustomer()) {
|
|
@@ -387,23 +391,6 @@ class Contact extends Model
|
|
|
387
391
|
$contact->update(['user_uuid' => $user->uuid]);
|
|
388
392
|
}
|
|
389
393
|
|
|
390
|
-
// Optionally, send invite
|
|
391
|
-
if ($sendInvite) {
|
|
392
|
-
// send invitation to user
|
|
393
|
-
$invitation = Invite::create([
|
|
394
|
-
'company_uuid' => $user->company_uuid,
|
|
395
|
-
'created_by_uuid' => session('user'),
|
|
396
|
-
'subject_uuid' => $user->company_uuid,
|
|
397
|
-
'subject_type' => Utils::getMutationType('company'),
|
|
398
|
-
'protocol' => 'email',
|
|
399
|
-
'recipients' => [$user->email],
|
|
400
|
-
'reason' => 'join_company',
|
|
401
|
-
]);
|
|
402
|
-
|
|
403
|
-
// notify user
|
|
404
|
-
$user->notify(new UserInvited($invitation));
|
|
405
|
-
}
|
|
406
|
-
|
|
407
394
|
$contact->setRelation('user', $user);
|
|
408
395
|
|
|
409
396
|
return $user;
|
|
@@ -423,13 +410,7 @@ class Contact extends Model
|
|
|
423
410
|
return null;
|
|
424
411
|
}
|
|
425
412
|
|
|
426
|
-
|
|
427
|
-
if ($quiet) {
|
|
428
|
-
$user->forceFill(['type' => 'customer'])->saveQuietly();
|
|
429
|
-
} else {
|
|
430
|
-
$user->setType('customer');
|
|
431
|
-
}
|
|
432
|
-
}
|
|
413
|
+
$this->assertCustomerUserCanBeAssigned($user);
|
|
433
414
|
|
|
434
415
|
$this->loadMissing('company');
|
|
435
416
|
if ($this->company) {
|
|
@@ -463,7 +444,7 @@ class Contact extends Model
|
|
|
463
444
|
public function repairCustomerTypeInvariant(bool $quiet = false): ?User
|
|
464
445
|
{
|
|
465
446
|
if (!$this->isCustomer()) {
|
|
466
|
-
$this->
|
|
447
|
+
return $this->getUser();
|
|
467
448
|
}
|
|
468
449
|
|
|
469
450
|
$user = $this->getUser();
|
|
@@ -491,33 +472,41 @@ class Contact extends Model
|
|
|
491
472
|
}
|
|
492
473
|
|
|
493
474
|
/**
|
|
494
|
-
* Assigns a user to the company
|
|
475
|
+
* Assigns a user to the contact's company without sending an organization invitation.
|
|
495
476
|
*
|
|
496
477
|
* This method performs the following actions:
|
|
497
478
|
* 1. Associates the provided user with the current company, assigning them a role based on the
|
|
498
479
|
* company's type (e.g., 'Fleet-Ops Customer' or 'Fleet-Ops Contact').
|
|
499
480
|
* 2. If the user is of type 'customer', assigns the 'Fleet-Ops Customer' role specifically.
|
|
500
481
|
* 3. Updates the current entity's `user_uuid` to reference the assigned user.
|
|
501
|
-
* 4. Optionally sends an invitation email to the user, inviting them to join the company.
|
|
502
482
|
*
|
|
503
483
|
* @param User $user the user to be assigned to the company
|
|
504
|
-
* @param bool $sendInvite
|
|
484
|
+
* @param bool $sendInvite Deprecated and ignored. Contact and customer users do not receive organization invitations.
|
|
505
485
|
*
|
|
506
486
|
* @return self returns the current instance to allow method chaining
|
|
507
487
|
*
|
|
508
|
-
* @throws \Exception if an error occurs during user assignment
|
|
488
|
+
* @throws \Exception if an error occurs during user assignment
|
|
509
489
|
*/
|
|
510
490
|
public function assignUser(User $user, bool $sendInvite = false): self
|
|
511
491
|
{
|
|
512
492
|
// Load company
|
|
513
493
|
$this->loadMissing('company');
|
|
514
494
|
|
|
515
|
-
if ($this->isCustomer()
|
|
516
|
-
$
|
|
495
|
+
if ($this->isCustomer()) {
|
|
496
|
+
$this->assertCustomerUserCanBeAssigned($user);
|
|
497
|
+
|
|
498
|
+
$existingUserContact = static::where(['user_uuid' => $user->uuid, 'company_uuid' => $this->company_uuid])
|
|
499
|
+
->whereNot('uuid', $this->uuid)
|
|
500
|
+
->whereHas('user')
|
|
501
|
+
->first();
|
|
502
|
+
|
|
503
|
+
if ($existingUserContact) {
|
|
504
|
+
throw new UserAlreadyExistsException('User already exists, try to assigning the user to this contact.', $user);
|
|
505
|
+
}
|
|
517
506
|
}
|
|
518
507
|
|
|
519
|
-
//
|
|
520
|
-
|
|
508
|
+
// Assign to company without triggering core organization invitations.
|
|
509
|
+
static::assignUserToContactCompany($this, $user);
|
|
521
510
|
|
|
522
511
|
// Get the company user instance
|
|
523
512
|
$companyUser = $user->getCompanyUser($this->company);
|
|
@@ -529,28 +518,79 @@ class Contact extends Model
|
|
|
529
518
|
// Set user to contact
|
|
530
519
|
$this->update(['user_uuid' => $user->uuid]);
|
|
531
520
|
|
|
532
|
-
// Optionally, send invite
|
|
533
|
-
if ($sendInvite) {
|
|
534
|
-
// send invitation to user
|
|
535
|
-
$invitation = Invite::create([
|
|
536
|
-
'company_uuid' => $user->company_uuid,
|
|
537
|
-
'created_by_uuid' => session('user'),
|
|
538
|
-
'subject_uuid' => $user->company_uuid,
|
|
539
|
-
'subject_type' => Utils::getMutationType('company'),
|
|
540
|
-
'protocol' => 'email',
|
|
541
|
-
'recipients' => [$user->email],
|
|
542
|
-
'reason' => 'join_company',
|
|
543
|
-
]);
|
|
544
|
-
|
|
545
|
-
// notify user
|
|
546
|
-
$user->notify(new UserInvited($invitation));
|
|
547
|
-
}
|
|
548
|
-
|
|
549
521
|
$this->setRelation('user', $user);
|
|
550
522
|
|
|
551
523
|
return $this;
|
|
552
524
|
}
|
|
553
525
|
|
|
526
|
+
public function assertCustomerIdentityIsAvailable(): void
|
|
527
|
+
{
|
|
528
|
+
if (!$this->isCustomer()) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
$assignedUser = $this->getAssignedUserWithoutTypeScope();
|
|
533
|
+
if ($assignedUser) {
|
|
534
|
+
$this->assertCustomerUserCanBeAssigned($assignedUser);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
$matchedUser = $this->findExistingUserByIdentity();
|
|
538
|
+
if ($matchedUser && $matchedUser->uuid !== $this->user_uuid) {
|
|
539
|
+
$this->assertCustomerUserCanBeAssigned($matchedUser);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
public function assertCustomerUserCanBeAssigned(User $user): void
|
|
544
|
+
{
|
|
545
|
+
if ($user->type === 'customer') {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
throw new CustomerUserConflictException(static::customerUserConflictMessage($user), $user);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
public static function customerUserConflictMessage(?User $user = null): string
|
|
553
|
+
{
|
|
554
|
+
$field = $user?->email ? 'email' : ($user?->phone ? 'phone number' : 'user account');
|
|
555
|
+
|
|
556
|
+
return 'This ' . $field . ' belongs to an existing staff user and cannot be used for a customer account.';
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private function findExistingUserByIdentity(): ?User
|
|
560
|
+
{
|
|
561
|
+
if (!$this->email && !$this->phone) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return User::where(function ($query) {
|
|
566
|
+
$query->where('company_uuid', $this->company_uuid)
|
|
567
|
+
->orWhereHas('companyUsers', function ($query) {
|
|
568
|
+
$query->where('company_uuid', $this->company_uuid);
|
|
569
|
+
});
|
|
570
|
+
})
|
|
571
|
+
->where(function ($query) {
|
|
572
|
+
if ($this->email) {
|
|
573
|
+
$query->where('email', $this->email);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if ($this->phone) {
|
|
577
|
+
$method = $this->email ? 'orWhere' : 'where';
|
|
578
|
+
$query->{$method}('phone', Utils::formatPhoneNumber($this->phone));
|
|
579
|
+
}
|
|
580
|
+
})
|
|
581
|
+
->whereNull('deleted_at')
|
|
582
|
+
->first();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private function getAssignedUserWithoutTypeScope(): ?User
|
|
586
|
+
{
|
|
587
|
+
if (!Str::isUuid($this->user_uuid)) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return User::where('uuid', $this->user_uuid)->first();
|
|
592
|
+
}
|
|
593
|
+
|
|
554
594
|
public function syncWithUser(): bool
|
|
555
595
|
{
|
|
556
596
|
$updates = [];
|
|
@@ -580,12 +620,11 @@ class Contact extends Model
|
|
|
580
620
|
}
|
|
581
621
|
|
|
582
622
|
/**
|
|
583
|
-
* Creates a new user from the current contact instance
|
|
623
|
+
* Creates a new user from the current contact instance without sending an organization invitation.
|
|
584
624
|
*
|
|
585
|
-
* This method is a wrapper around the createUserFromContact method.
|
|
586
|
-
* from a contact instance and, optionally, sending an invitation to the newly created user.
|
|
625
|
+
* This method is a wrapper around the createUserFromContact method.
|
|
587
626
|
*
|
|
588
|
-
* @param bool $sendInvite
|
|
627
|
+
* @param bool $sendInvite Deprecated and ignored. Contact and customer users do not receive organization invitations.
|
|
589
628
|
*
|
|
590
629
|
* @return User the newly created user instance
|
|
591
630
|
*/
|
|
@@ -632,4 +671,21 @@ class Contact extends Model
|
|
|
632
671
|
{
|
|
633
672
|
return !$this->hasUser();
|
|
634
673
|
}
|
|
674
|
+
|
|
675
|
+
private static function assignUserToContactCompany(Contact $contact, User $user): ?CompanyUser
|
|
676
|
+
{
|
|
677
|
+
$contact->loadMissing('company');
|
|
678
|
+
|
|
679
|
+
if (!$contact->company) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
$role = $contact->isCustomer() ? 'Fleet-Ops Customer' : 'Fleet-Ops Contact';
|
|
684
|
+
$companyUser = $contact->company->addUser($user, $role);
|
|
685
|
+
|
|
686
|
+
$user->forceFill(['company_uuid' => $contact->company->uuid])->saveQuietly();
|
|
687
|
+
$user->setRelation('companyUser', $companyUser);
|
|
688
|
+
|
|
689
|
+
return $companyUser;
|
|
690
|
+
}
|
|
635
691
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Models;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Database\Eloquent\Builder;
|
|
6
|
+
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
7
|
+
use Illuminate\Support\Str;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fleet-Ops customer.
|
|
11
|
+
*
|
|
12
|
+
* A thin specialization of {@see Contact} that is scoped to rows with
|
|
13
|
+
* `type='customer'` and forces that type on create. This is what the public
|
|
14
|
+
* customer API surface (`/v1/customers/...`) operates on.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors {@see \Fleetbase\Storefront\Models\Customer} but lives directly in
|
|
17
|
+
* FleetOps so that customer auth + customer-scoped orders work without the
|
|
18
|
+
* Storefront publishable-key + Store/Network gating.
|
|
19
|
+
*/
|
|
20
|
+
class Customer extends Contact
|
|
21
|
+
{
|
|
22
|
+
/**
|
|
23
|
+
* The key to use in the payload responses.
|
|
24
|
+
*/
|
|
25
|
+
protected string $payloadKey = 'customer';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Boot the model.
|
|
29
|
+
*/
|
|
30
|
+
protected static function boot()
|
|
31
|
+
{
|
|
32
|
+
parent::boot();
|
|
33
|
+
|
|
34
|
+
static::creating(function ($model) {
|
|
35
|
+
$model->type = 'customer';
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
static::addGlobalScope('type', function (Builder $builder) {
|
|
39
|
+
$builder->where('type', 'customer');
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Orders owned by this customer.
|
|
45
|
+
*/
|
|
46
|
+
public function orders(): HasMany
|
|
47
|
+
{
|
|
48
|
+
return $this->hasMany(Order::class, 'customer_uuid')
|
|
49
|
+
->whereNull('deleted_at')
|
|
50
|
+
->withoutGlobalScopes();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Find a customer by either a `customer_xxx` or `contact_xxx` public id.
|
|
55
|
+
*/
|
|
56
|
+
public static function findFromCustomerId(string $publicId): ?self
|
|
57
|
+
{
|
|
58
|
+
if (Str::startsWith($publicId, 'customer')) {
|
|
59
|
+
$publicId = Str::replaceFirst('customer', 'contact', $publicId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return static::where('public_id', $publicId)->first();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -119,6 +119,15 @@ class Driver extends Model
|
|
|
119
119
|
'time_window_end',
|
|
120
120
|
];
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Set attributes and defaults.
|
|
124
|
+
*
|
|
125
|
+
* @var array
|
|
126
|
+
*/
|
|
127
|
+
protected $attributes = [
|
|
128
|
+
'status' => 'available',
|
|
129
|
+
];
|
|
130
|
+
|
|
122
131
|
/**
|
|
123
132
|
* The attributes that are guarded and not mass assignable.
|
|
124
133
|
*
|
|
@@ -595,9 +604,9 @@ class Driver extends Model
|
|
|
595
604
|
/**
|
|
596
605
|
* Set the driver status attribute.
|
|
597
606
|
*/
|
|
598
|
-
public function setStatusAttribute(?string $status = '
|
|
607
|
+
public function setStatusAttribute(?string $status = 'available'): void
|
|
599
608
|
{
|
|
600
|
-
$this->attributes['status'] = $status;
|
|
609
|
+
$this->attributes['status'] = $status === null || $status === 'active' ? 'available' : $status;
|
|
601
610
|
}
|
|
602
611
|
|
|
603
612
|
/**
|
|
@@ -854,7 +863,7 @@ class Driver extends Model
|
|
|
854
863
|
'user_uuid' => $user->uuid,
|
|
855
864
|
'drivers_license_number' => $driversLicenseNumber,
|
|
856
865
|
'country' => $country,
|
|
857
|
-
'status' => '
|
|
866
|
+
'status' => 'available',
|
|
858
867
|
'location' => Utils::parsePointToWkt(new Point(0, 0)),
|
|
859
868
|
]);
|
|
860
869
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Models;
|
|
4
4
|
|
|
5
|
-
use Barryvdh\DomPDF\Facade\Pdf;
|
|
6
5
|
use Fleetbase\Casts\Json;
|
|
7
6
|
use Fleetbase\Casts\PolymorphicType;
|
|
7
|
+
use Fleetbase\FleetOps\Support\LabelPdf;
|
|
8
8
|
use Fleetbase\FleetOps\Support\Utils;
|
|
9
9
|
use Fleetbase\FleetOps\Traits\HasTrackingNumber;
|
|
10
10
|
use Fleetbase\FleetOps\Traits\PayloadAccessors;
|
|
@@ -142,7 +142,7 @@ class Entity extends Model
|
|
|
142
142
|
*/
|
|
143
143
|
public function pdfLabel()
|
|
144
144
|
{
|
|
145
|
-
return
|
|
145
|
+
return LabelPdf::fromHtml($this->label());
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
@@ -14,7 +14,11 @@ use Fleetbase\Traits\HasCustomFields;
|
|
|
14
14
|
use Fleetbase\Traits\HasPublicId;
|
|
15
15
|
use Fleetbase\Traits\HasUuid;
|
|
16
16
|
use Fleetbase\Traits\TracksApiCredential;
|
|
17
|
+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
18
|
+
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
17
19
|
use Illuminate\Support\Str;
|
|
20
|
+
use Spatie\Activitylog\LogOptions;
|
|
21
|
+
use Spatie\Activitylog\Traits\LogsActivity;
|
|
18
22
|
|
|
19
23
|
class Issue extends Model
|
|
20
24
|
{
|
|
@@ -24,6 +28,7 @@ class Issue extends Model
|
|
|
24
28
|
use SpatialTrait;
|
|
25
29
|
use HasApiModelBehavior;
|
|
26
30
|
use HasApiModelCache;
|
|
31
|
+
use LogsActivity;
|
|
27
32
|
use HasCustomFields;
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -60,6 +65,7 @@ class Issue extends Model
|
|
|
60
65
|
'assigned_to_uuid',
|
|
61
66
|
'vehicle_uuid',
|
|
62
67
|
'driver_uuid',
|
|
68
|
+
'order_uuid',
|
|
63
69
|
'issue_id',
|
|
64
70
|
'location',
|
|
65
71
|
'category',
|
|
@@ -106,6 +112,31 @@ class Issue extends Model
|
|
|
106
112
|
*/
|
|
107
113
|
protected $appends = ['driver_name', 'vehicle_name', 'assignee_name', 'reporter_name'];
|
|
108
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Get the activity log options for the model.
|
|
117
|
+
*/
|
|
118
|
+
public function getActivitylogOptions(): LogOptions
|
|
119
|
+
{
|
|
120
|
+
return LogOptions::defaults()
|
|
121
|
+
->logOnly([
|
|
122
|
+
'title',
|
|
123
|
+
'report',
|
|
124
|
+
'status',
|
|
125
|
+
'priority',
|
|
126
|
+
'assigned_to_uuid',
|
|
127
|
+
'vehicle_uuid',
|
|
128
|
+
'driver_uuid',
|
|
129
|
+
'order_uuid',
|
|
130
|
+
'type',
|
|
131
|
+
'category',
|
|
132
|
+
'tags',
|
|
133
|
+
'resolved_at',
|
|
134
|
+
'meta',
|
|
135
|
+
])
|
|
136
|
+
->logOnlyDirty()
|
|
137
|
+
->dontSubmitEmptyLogs();
|
|
138
|
+
}
|
|
139
|
+
|
|
109
140
|
/**
|
|
110
141
|
* The attributes excluded from the model's JSON form.
|
|
111
142
|
*
|
|
@@ -114,7 +145,7 @@ class Issue extends Model
|
|
|
114
145
|
protected $hidden = [];
|
|
115
146
|
|
|
116
147
|
/**
|
|
117
|
-
* @return
|
|
148
|
+
* @return BelongsTo
|
|
118
149
|
*/
|
|
119
150
|
public function reportedBy()
|
|
120
151
|
{
|
|
@@ -122,7 +153,7 @@ class Issue extends Model
|
|
|
122
153
|
}
|
|
123
154
|
|
|
124
155
|
/**
|
|
125
|
-
* @return
|
|
156
|
+
* @return BelongsTo
|
|
126
157
|
*/
|
|
127
158
|
public function reporter()
|
|
128
159
|
{
|
|
@@ -130,7 +161,7 @@ class Issue extends Model
|
|
|
130
161
|
}
|
|
131
162
|
|
|
132
163
|
/**
|
|
133
|
-
* @return
|
|
164
|
+
* @return BelongsTo
|
|
134
165
|
*/
|
|
135
166
|
public function assignedTo()
|
|
136
167
|
{
|
|
@@ -138,7 +169,7 @@ class Issue extends Model
|
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
/**
|
|
141
|
-
* @return
|
|
172
|
+
* @return BelongsTo
|
|
142
173
|
*/
|
|
143
174
|
public function assignee()
|
|
144
175
|
{
|
|
@@ -146,7 +177,7 @@ class Issue extends Model
|
|
|
146
177
|
}
|
|
147
178
|
|
|
148
179
|
/**
|
|
149
|
-
* @return
|
|
180
|
+
* @return BelongsTo
|
|
150
181
|
*/
|
|
151
182
|
public function vehicle()
|
|
152
183
|
{
|
|
@@ -154,13 +185,23 @@ class Issue extends Model
|
|
|
154
185
|
}
|
|
155
186
|
|
|
156
187
|
/**
|
|
157
|
-
* @return
|
|
188
|
+
* @return BelongsTo
|
|
158
189
|
*/
|
|
159
190
|
public function driver()
|
|
160
191
|
{
|
|
161
192
|
return $this->belongsTo(Driver::class);
|
|
162
193
|
}
|
|
163
194
|
|
|
195
|
+
public function order(): BelongsTo
|
|
196
|
+
{
|
|
197
|
+
return $this->belongsTo(Order::class, 'order_uuid', 'uuid')->without(['comments', 'files']);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public function files(): HasMany
|
|
201
|
+
{
|
|
202
|
+
return $this->hasMany(\Fleetbase\Models\File::class, 'subject_uuid', 'uuid')->latest();
|
|
203
|
+
}
|
|
204
|
+
|
|
164
205
|
/**
|
|
165
206
|
* Set the title attribute for the Issue model.
|
|
166
207
|
*
|