@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
|
@@ -8,6 +8,8 @@ use Illuminate\Support\Facades\Cache;
|
|
|
8
8
|
|
|
9
9
|
class TrackingIntelligenceService
|
|
10
10
|
{
|
|
11
|
+
protected const TERMINAL_STATUSES = ['completed', 'canceled'];
|
|
12
|
+
|
|
11
13
|
public function __construct(
|
|
12
14
|
protected TrackingContextBuilder $contextBuilder,
|
|
13
15
|
protected TrackingProviderManager $providerManager,
|
|
@@ -34,19 +36,28 @@ class TrackingIntelligenceService
|
|
|
34
36
|
'completion_seconds' => data_get($tracker, 'eta.completion_seconds'),
|
|
35
37
|
'active_stop_at' => data_get($tracker, 'eta.active_stop_at'),
|
|
36
38
|
'completion_at' => data_get($tracker, 'eta.completion_at'),
|
|
39
|
+
'start_seconds' => data_get($tracker, 'eta.start_seconds'),
|
|
40
|
+
'start_at' => data_get($tracker, 'eta.start_at'),
|
|
37
41
|
'provider' => data_get($tracker, 'provider'),
|
|
38
42
|
'confidence' => data_get($tracker, 'confidence'),
|
|
43
|
+
'lifecycle' => data_get($tracker, 'lifecycle'),
|
|
39
44
|
'warnings' => data_get($tracker, 'warnings', []),
|
|
40
45
|
];
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
protected function buildResult(TrackingContext $context, TrackingProviderResult $providerResult, TrackingOptions $options): array
|
|
44
49
|
{
|
|
45
|
-
$now
|
|
46
|
-
$
|
|
47
|
-
$
|
|
48
|
-
$
|
|
49
|
-
$
|
|
50
|
+
$now = now();
|
|
51
|
+
$rawCompletionSeconds = $providerResult->durationInTrafficSeconds ?? $providerResult->durationSeconds;
|
|
52
|
+
$rawActiveStopSeconds = data_get($providerResult->legs, '0.duration_in_traffic_s', data_get($providerResult->legs, '0.duration_s', $rawCompletionSeconds));
|
|
53
|
+
$lifecycle = $this->lifecycle($context, $rawActiveStopSeconds, $now);
|
|
54
|
+
$showsLiveEta = (bool) data_get($lifecycle, 'show_live_eta');
|
|
55
|
+
$showsStartEta = (bool) data_get($lifecycle, 'show_start_eta');
|
|
56
|
+
$completionSeconds = $showsLiveEta ? $rawCompletionSeconds : null;
|
|
57
|
+
$activeStopSeconds = $showsLiveEta ? $rawActiveStopSeconds : null;
|
|
58
|
+
$startSeconds = $showsStartEta ? $rawActiveStopSeconds : null;
|
|
59
|
+
$progress = $this->progress($context, $providerResult);
|
|
60
|
+
$warnings = array_values(array_unique([...$context->warnings, ...$providerResult->warnings]));
|
|
50
61
|
|
|
51
62
|
if ($providerResult->confidence === 'low') {
|
|
52
63
|
$warnings[] = 'low_confidence_eta';
|
|
@@ -64,6 +75,7 @@ class TrackingIntelligenceService
|
|
|
64
75
|
'online' => (bool) data_get($context->driver, 'online', false),
|
|
65
76
|
],
|
|
66
77
|
'progress' => $progress,
|
|
78
|
+
'lifecycle' => $lifecycle,
|
|
67
79
|
'stops' => $context->stops->map(fn (TrackingStop $stop) => $stop->toArray())->values()->all(),
|
|
68
80
|
'active_stop' => $context->activeStop?->toArray(),
|
|
69
81
|
'next_stop' => $context->nextStop?->toArray(),
|
|
@@ -73,13 +85,15 @@ class TrackingIntelligenceService
|
|
|
73
85
|
'duration_in_traffic_s' => $providerResult->durationInTrafficSeconds,
|
|
74
86
|
'polyline' => $providerResult->polyline,
|
|
75
87
|
'coordinates' => $providerResult->coordinates,
|
|
76
|
-
'legs' => $this->legs($context, $providerResult, $now),
|
|
88
|
+
'legs' => $this->legs($context, $providerResult, $now, $showsLiveEta),
|
|
77
89
|
],
|
|
78
90
|
'eta' => [
|
|
79
91
|
'active_stop_seconds' => $activeStopSeconds,
|
|
80
92
|
'completion_seconds' => $completionSeconds,
|
|
81
93
|
'active_stop_at' => $this->addSeconds($now, $activeStopSeconds),
|
|
82
94
|
'completion_at' => $this->addSeconds($now, $completionSeconds),
|
|
95
|
+
'start_seconds' => $startSeconds,
|
|
96
|
+
'start_at' => $this->addSeconds($now, $startSeconds),
|
|
83
97
|
],
|
|
84
98
|
'insights' => [
|
|
85
99
|
'is_delayed' => false,
|
|
@@ -107,17 +121,17 @@ class TrackingIntelligenceService
|
|
|
107
121
|
];
|
|
108
122
|
}
|
|
109
123
|
|
|
110
|
-
protected function legs(TrackingContext $context, TrackingProviderResult $providerResult, Carbon $now): array
|
|
124
|
+
protected function legs(TrackingContext $context, TrackingProviderResult $providerResult, Carbon $now, bool $showsLiveEta = true): array
|
|
111
125
|
{
|
|
112
126
|
$elapsedSeconds = 0;
|
|
113
127
|
|
|
114
|
-
return collect($providerResult->legs)->map(function ($leg, $index) use ($context, $now, &$elapsedSeconds) {
|
|
128
|
+
return collect($providerResult->legs)->map(function ($leg, $index) use ($context, $now, $showsLiveEta, &$elapsedSeconds) {
|
|
115
129
|
$stop = $context->remainingStops->values()->get($index);
|
|
116
130
|
$legSeconds = data_get($leg, 'duration_in_traffic_s', data_get($leg, 'duration_s'));
|
|
117
131
|
$etaSeconds = null;
|
|
118
132
|
$etaAt = null;
|
|
119
133
|
|
|
120
|
-
if ($legSeconds !== null) {
|
|
134
|
+
if ($showsLiveEta && $stop instanceof TrackingStop && $legSeconds !== null) {
|
|
121
135
|
$elapsedSeconds += (float) $legSeconds;
|
|
122
136
|
$etaSeconds = $elapsedSeconds;
|
|
123
137
|
$etaAt = $this->addSeconds($now, $etaSeconds);
|
|
@@ -131,6 +145,92 @@ class TrackingIntelligenceService
|
|
|
131
145
|
})->all();
|
|
132
146
|
}
|
|
133
147
|
|
|
148
|
+
protected function lifecycle(TrackingContext $context, ?float $startSeconds, Carbon $now): array
|
|
149
|
+
{
|
|
150
|
+
$order = $context->order;
|
|
151
|
+
$status = strtolower((string) ($order->status ?? 'created'));
|
|
152
|
+
$hasAssignedDriver = !empty($order->driver_assigned_uuid);
|
|
153
|
+
$hasStarted = $this->hasOrderStarted($order);
|
|
154
|
+
$isTerminal = in_array($status, self::TERMINAL_STATUSES, true);
|
|
155
|
+
$isDispatched = $status === 'dispatched' || (bool) $order->dispatched;
|
|
156
|
+
|
|
157
|
+
if ($isTerminal) {
|
|
158
|
+
return [
|
|
159
|
+
'status' => $status,
|
|
160
|
+
'mode' => $status,
|
|
161
|
+
'message' => $status === 'completed' ? 'Order has been completed.' : 'Order has been canceled.',
|
|
162
|
+
'is_terminal' => true,
|
|
163
|
+
'has_started' => $hasStarted,
|
|
164
|
+
'show_live_eta' => false,
|
|
165
|
+
'show_start_eta' => false,
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!$hasAssignedDriver) {
|
|
170
|
+
return [
|
|
171
|
+
'status' => $status,
|
|
172
|
+
'mode' => 'unassigned',
|
|
173
|
+
'message' => 'Assign a driver to start live tracking and improve ETA accuracy.',
|
|
174
|
+
'is_terminal' => false,
|
|
175
|
+
'has_started' => $hasStarted,
|
|
176
|
+
'show_live_eta' => false,
|
|
177
|
+
'show_start_eta' => false,
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!$hasStarted && $isDispatched) {
|
|
182
|
+
return [
|
|
183
|
+
'status' => $status,
|
|
184
|
+
'mode' => 'dispatched',
|
|
185
|
+
'message' => 'Order has been dispatched. Estimated start is based on the assigned driver route to the first stop.',
|
|
186
|
+
'is_terminal' => false,
|
|
187
|
+
'has_started' => false,
|
|
188
|
+
'show_live_eta' => false,
|
|
189
|
+
'show_start_eta' => $startSeconds !== null,
|
|
190
|
+
'start_at' => $this->addSeconds($now, $startSeconds),
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!$hasStarted) {
|
|
195
|
+
return [
|
|
196
|
+
'status' => $status,
|
|
197
|
+
'mode' => 'pre_start',
|
|
198
|
+
'message' => 'Live ETA will begin once the order is started.',
|
|
199
|
+
'is_terminal' => false,
|
|
200
|
+
'has_started' => false,
|
|
201
|
+
'show_live_eta' => false,
|
|
202
|
+
'show_start_eta' => false,
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return [
|
|
207
|
+
'status' => $status,
|
|
208
|
+
'mode' => 'active',
|
|
209
|
+
'message' => null,
|
|
210
|
+
'is_terminal' => false,
|
|
211
|
+
'has_started' => true,
|
|
212
|
+
'show_live_eta' => true,
|
|
213
|
+
'show_start_eta' => false,
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
protected function hasOrderStarted(Order $order): bool
|
|
218
|
+
{
|
|
219
|
+
if ((bool) $order->started || $order->started_at || strtolower((string) $order->status) === 'started') {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
if ($order->relationLoaded('trackingStatuses')) {
|
|
225
|
+
return $order->trackingStatuses->contains(fn ($status) => strtolower((string) data_get($status, 'code')) === 'started');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return $order->trackingStatuses()->whereRaw('LOWER(code) = ?', ['started'])->exists();
|
|
229
|
+
} catch (\Throwable) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
134
234
|
protected function capabilitiesFor(string $provider): array
|
|
135
235
|
{
|
|
136
236
|
$registry = app(TrackingProviderRegistry::class);
|
|
@@ -18,6 +18,7 @@ class TrackingStop implements \JsonSerializable
|
|
|
18
18
|
public ?Waypoint $waypoint = null,
|
|
19
19
|
public bool $completed = false,
|
|
20
20
|
public ?int $sequence = null,
|
|
21
|
+
public ?string $trackingNumberUuid = null,
|
|
21
22
|
) {
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -35,20 +36,21 @@ class TrackingStop implements \JsonSerializable
|
|
|
35
36
|
$point = $this->point();
|
|
36
37
|
|
|
37
38
|
return [
|
|
38
|
-
'uuid'
|
|
39
|
-
'public_id'
|
|
40
|
-
'type'
|
|
41
|
-
'status'
|
|
42
|
-
'completed'
|
|
43
|
-
'sequence'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
39
|
+
'uuid' => $this->uuid,
|
|
40
|
+
'public_id' => $this->publicId,
|
|
41
|
+
'type' => $this->type,
|
|
42
|
+
'status' => $this->status,
|
|
43
|
+
'completed' => $this->completed,
|
|
44
|
+
'sequence' => $this->sequence,
|
|
45
|
+
'tracking_number_uuid' => $this->trackingNumberUuid,
|
|
46
|
+
'address' => data_get($this->place, 'address'),
|
|
47
|
+
'name' => data_get($this->place, 'name'),
|
|
48
|
+
'location' => $point ? [
|
|
47
49
|
'type' => 'Point',
|
|
48
50
|
'coordinates' => [$point->getLng(), $point->getLat()],
|
|
49
51
|
] : null,
|
|
50
|
-
'latitude'
|
|
51
|
-
'longitude'
|
|
52
|
+
'latitude' => $point ? $point->getLat() : null,
|
|
53
|
+
'longitude' => $point ? $point->getLng() : null,
|
|
52
54
|
];
|
|
53
55
|
}
|
|
54
56
|
|
package/server/src/routes.php
CHANGED
|
@@ -12,7 +12,7 @@ use Illuminate\Support\Facades\Route;
|
|
|
12
12
|
| is assigned the "api" middleware group. Enjoy building your API!
|
|
13
13
|
|
|
|
14
14
|
*/
|
|
15
|
-
Route::prefix(config('fleetops.api.routing.prefix'
|
|
15
|
+
Route::prefix(config('fleetops.api.routing.prefix'))->namespace('Fleetbase\FleetOps\Http\Controllers')->group(
|
|
16
16
|
function ($router) {
|
|
17
17
|
/*
|
|
18
18
|
|--------------------------------------------------------------------------
|
|
@@ -49,6 +49,38 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
49
49
|
$router->put('{id}', 'ContactController@update');
|
|
50
50
|
$router->delete('{id}', 'ContactController@delete');
|
|
51
51
|
});
|
|
52
|
+
// order-configs — read-only public projection of the OrderConfig flow.
|
|
53
|
+
$router->group(['prefix' => 'order-configs'], function () use ($router) {
|
|
54
|
+
$router->get('/', 'OrderConfigController@query');
|
|
55
|
+
$router->get('{id}', 'OrderConfigController@find');
|
|
56
|
+
});
|
|
57
|
+
// customers routes — public B2C customer auth + customer-scoped orders.
|
|
58
|
+
// - Public endpoints: API key only (resolves company from credential)
|
|
59
|
+
// - Authenticated endpoints: API key + `Customer-Token` header (Sanctum)
|
|
60
|
+
$router->group(['prefix' => 'customers', 'middleware' => []], function () use ($router) {
|
|
61
|
+
// Public auth flows
|
|
62
|
+
$router->post('request-creation-code', 'CustomerController@requestCreationCode');
|
|
63
|
+
$router->post('/', 'CustomerController@create');
|
|
64
|
+
$router->post('login', 'CustomerController@login');
|
|
65
|
+
$router->post('login-with-sms', 'CustomerController@loginWithPhone');
|
|
66
|
+
$router->post('verify-code', 'CustomerController@verifyCode');
|
|
67
|
+
$router->post('forgot-password', 'CustomerController@forgotPassword');
|
|
68
|
+
$router->post('reset-password', 'CustomerController@resetPassword');
|
|
69
|
+
|
|
70
|
+
// Authenticated (require Customer-Token)
|
|
71
|
+
$router->group(['middleware' => [Fleetbase\FleetOps\Http\Middleware\AuthenticateCustomerToken::class]], function () use ($router) {
|
|
72
|
+
$router->get('me', 'CustomerController@me');
|
|
73
|
+
$router->put('me', 'CustomerController@updateMe');
|
|
74
|
+
$router->match(['post', 'patch'], 'me', 'CustomerController@updateMe');
|
|
75
|
+
$router->post('logout', 'CustomerController@logout');
|
|
76
|
+
$router->post('logout-all', 'CustomerController@logoutAll');
|
|
77
|
+
$router->post('register-device', 'CustomerController@registerDevice');
|
|
78
|
+
$router->get('places', 'CustomerController@places');
|
|
79
|
+
$router->get('orders', 'CustomerController@orders');
|
|
80
|
+
$router->post('orders', 'CustomerController@createOrder');
|
|
81
|
+
$router->get('orders/{id}', 'CustomerController@findOrder');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
52
84
|
// vendors routes
|
|
53
85
|
$router->group(['prefix' => 'vendors'], function () use ($router) {
|
|
54
86
|
$router->post('/', 'VendorController@create');
|
|
@@ -280,6 +312,7 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
280
312
|
$router->post('import', $controller('import'));
|
|
281
313
|
$router->get('facilitators/{id}', $controller('getAsFacilitator'));
|
|
282
314
|
$router->get('customers/{id}', $controller('getAsCustomer'));
|
|
315
|
+
$router->post('{id}/convert-to-vendor', $controller('convertToVendor'));
|
|
283
316
|
$router->delete('bulk-delete', $controller('bulkDelete'));
|
|
284
317
|
}
|
|
285
318
|
);
|
|
@@ -319,6 +352,7 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
319
352
|
$router->delete('bulk-delete', $controller('bulkDelete'));
|
|
320
353
|
}
|
|
321
354
|
);
|
|
355
|
+
$router->get('issues/{id}/timeline', 'IssueController@timeline');
|
|
322
356
|
$router->fleetbaseRoutes(
|
|
323
357
|
'issues',
|
|
324
358
|
function ($router, $controller) {
|
|
@@ -346,8 +380,10 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
346
380
|
$router->get('{id}/tracker', 'OrderController@trackerInfo');
|
|
347
381
|
$router->get('{id}/eta', 'OrderController@waypointEtas');
|
|
348
382
|
$router->post('{id}/ping-driver', $controller('pingDriver'));
|
|
383
|
+
$router->post('{id}/capture-photo/{subjectId?}', $controller('capturePhoto'));
|
|
349
384
|
$router->post('process-imports', $controller('importFromFiles'));
|
|
350
385
|
$router->patch('route/{id}', $controller('editOrderRoute'));
|
|
386
|
+
$router->patch('set-destination/{id}/{placeId}', $controller('setDestination'));
|
|
351
387
|
$router->patch('update-activity/{id}', $controller('updateActivity'));
|
|
352
388
|
$router->get('{id}/proofs/{subjectId?}', $controller('proofs'));
|
|
353
389
|
$router->patch('bulk-assign-driver', $controller('bulkAssignDriver'));
|
|
@@ -436,6 +472,9 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
436
472
|
$router->match(['get', 'post'], 'export', $controller('export'));
|
|
437
473
|
$router->get('facilitators/{id}', $controller('getAsFacilitator'));
|
|
438
474
|
$router->get('customers/{id}', $controller('getAsCustomer'));
|
|
475
|
+
$router->get('{id}/personnels', $controller('vendorPersonnels'));
|
|
476
|
+
$router->post('{id}/personnels', $controller('addVendorPersonnel'));
|
|
477
|
+
$router->delete('{id}/personnels/{contact}', $controller('removeVendorPersonnel'));
|
|
439
478
|
$router->post('{id}/assign-driver', $controller('assignDriver'));
|
|
440
479
|
$router->post('{id}/remove-driver', $controller('removeDriver'));
|
|
441
480
|
$router->delete('bulk-delete', $controller('bulkDelete'));
|
|
@@ -489,7 +528,11 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
489
528
|
['prefix' => 'customers'],
|
|
490
529
|
function () use ($router) {
|
|
491
530
|
$router->get('/', 'MorphController@queryCustomers');
|
|
531
|
+
$router->post('create-portal-login', 'CustomerController@createPortalLogin');
|
|
492
532
|
$router->post('reset-credentials', 'CustomerController@resetCredentials');
|
|
533
|
+
$router->post('send-credentials', 'CustomerController@sendCredentials');
|
|
534
|
+
$router->post('deactivate-portal-login', 'CustomerController@deactivatePortalLogin');
|
|
535
|
+
$router->post('reactivate-portal-login', 'CustomerController@reactivatePortalLogin');
|
|
493
536
|
}
|
|
494
537
|
);
|
|
495
538
|
$router->group(
|
|
@@ -579,16 +622,22 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
579
622
|
$router->group(
|
|
580
623
|
['prefix' => 'analytics'],
|
|
581
624
|
function ($router) {
|
|
582
|
-
$router->get('operations-pulse',
|
|
583
|
-
$router->get('revenue-trend',
|
|
584
|
-
$router->get('orders-by-status',
|
|
585
|
-
$router->get('on-time-delivery',
|
|
586
|
-
$router->get('top-drivers',
|
|
587
|
-
$router->get('fuel-efficiency',
|
|
588
|
-
$router->get('issues-insights',
|
|
625
|
+
$router->get('operations-pulse', 'AnalyticsController@operationsPulse');
|
|
626
|
+
$router->get('revenue-trend', 'AnalyticsController@revenueTrend');
|
|
627
|
+
$router->get('orders-by-status', 'AnalyticsController@ordersByStatus');
|
|
628
|
+
$router->get('on-time-delivery', 'AnalyticsController@onTimeDelivery');
|
|
629
|
+
$router->get('top-drivers', 'AnalyticsController@topDrivers');
|
|
630
|
+
$router->get('fuel-efficiency', 'AnalyticsController@fuelEfficiency');
|
|
631
|
+
$router->get('issues-insights', 'AnalyticsController@issuesInsights');
|
|
589
632
|
$router->get('maintenance-overview', 'AnalyticsController@maintenanceOverview');
|
|
590
|
-
$router->get('geofence-violations',
|
|
591
|
-
$router->get('live-fleet',
|
|
633
|
+
$router->get('geofence-violations', 'AnalyticsController@geofenceViolations');
|
|
634
|
+
$router->get('live-fleet', 'AnalyticsController@liveFleet');
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
$router->group(
|
|
638
|
+
['prefix' => 'getting-started'],
|
|
639
|
+
function ($router) {
|
|
640
|
+
$router->get('status', 'GettingStartedController@status');
|
|
592
641
|
}
|
|
593
642
|
);
|
|
594
643
|
$router->group(
|
|
@@ -627,3 +676,20 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
|
|
|
627
676
|
);
|
|
628
677
|
}
|
|
629
678
|
);
|
|
679
|
+
|
|
680
|
+
if (filled(config('fleetops.api.routing.prefix'))) {
|
|
681
|
+
Route::prefix(config('fleetops.api.routing.internal_prefix', 'int') . '/v1')
|
|
682
|
+
->namespace('Fleetbase\FleetOps\Http\Controllers\Internal\v1')
|
|
683
|
+
->middleware([
|
|
684
|
+
'fleetbase.protected',
|
|
685
|
+
Fleetbase\FleetOps\Http\Middleware\TransformLocationMiddleware::class,
|
|
686
|
+
Fleetbase\FleetOps\Http\Middleware\SetupDriverSession::class,
|
|
687
|
+
])
|
|
688
|
+
->group(function ($router) {
|
|
689
|
+
$router->get('issues/{id}/timeline', 'IssueController@timeline');
|
|
690
|
+
$router->get('vendors/{id}/personnels', 'VendorController@vendorPersonnels');
|
|
691
|
+
$router->post('vendors/{id}/personnels', 'VendorController@addVendorPersonnel');
|
|
692
|
+
$router->delete('vendors/{id}/personnels/{contact}', 'VendorController@removeVendorPersonnel');
|
|
693
|
+
$router->post('contacts/{id}/convert-to-vendor', 'ContactController@convertToVendor');
|
|
694
|
+
});
|
|
695
|
+
}
|
|
@@ -39,3 +39,11 @@ test('analytics controller has one method per widget', function () {
|
|
|
39
39
|
expect($controller)->toContain("public function {$method}(");
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
test('top drivers on-time sorting uses a sql aggregate expression', function () {
|
|
44
|
+
$widget = file_get_contents(dirname(__DIR__) . '/src/Support/Analytics/TopDrivers.php');
|
|
45
|
+
|
|
46
|
+
expect($widget)->toContain("'on_time' => \"CASE");
|
|
47
|
+
expect($widget)->toContain('TIMESTAMPDIFF(SECOND, orders.scheduled_at, orders.updated_at) <= 1800');
|
|
48
|
+
expect($widget)->not->toContain("'on_time' => 'on_time_pct'");
|
|
49
|
+
});
|
|
@@ -16,8 +16,8 @@ function capacityTestOrder(string $publicId, float $weightKg, float $volumeLitre
|
|
|
16
16
|
public float $weight;
|
|
17
17
|
public string $weight_unit = 'kg';
|
|
18
18
|
public float $length;
|
|
19
|
-
public float $width
|
|
20
|
-
public float $height
|
|
19
|
+
public float $width = 1;
|
|
20
|
+
public float $height = 1;
|
|
21
21
|
public string $dimensions_unit = 'm';
|
|
22
22
|
|
|
23
23
|
public function __construct(float $weightKg, float $volumeLitres)
|
|
@@ -30,8 +30,8 @@ function capacityTestOrder(string $publicId, float $weightKg, float $volumeLitre
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
$order
|
|
34
|
-
$order->public_id
|
|
33
|
+
$order = new Order();
|
|
34
|
+
$order->public_id = $publicId;
|
|
35
35
|
$order->required_skills = $skills;
|
|
36
36
|
$order->setRelation('payload', $payload);
|
|
37
37
|
|
|
@@ -40,14 +40,14 @@ function capacityTestOrder(string $publicId, float $weightKg, float $volumeLitre
|
|
|
40
40
|
|
|
41
41
|
function capacityTestVehicle(string $publicId, float $weightKg, float $volumeM3 = 0, int $maxTasks = 0, array $skills = []): Vehicle
|
|
42
42
|
{
|
|
43
|
-
$vehicle
|
|
44
|
-
$vehicle->public_id
|
|
45
|
-
$vehicle->payload_capacity
|
|
46
|
-
$vehicle->payload_capacity_volume
|
|
43
|
+
$vehicle = new Vehicle();
|
|
44
|
+
$vehicle->public_id = $publicId;
|
|
45
|
+
$vehicle->payload_capacity = $weightKg;
|
|
46
|
+
$vehicle->payload_capacity_volume = $volumeM3;
|
|
47
47
|
$vehicle->payload_capacity_pallets = 0;
|
|
48
48
|
$vehicle->payload_capacity_parcels = 100;
|
|
49
|
-
$vehicle->max_tasks
|
|
50
|
-
$vehicle->skills
|
|
49
|
+
$vehicle->max_tasks = $maxTasks;
|
|
50
|
+
$vehicle->skills = $skills;
|
|
51
51
|
$vehicle->setRelation('driver', null);
|
|
52
52
|
|
|
53
53
|
return $vehicle;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
test('customer contacts reject existing non-customer users by identity', function () {
|
|
4
|
+
$contactModel = file_get_contents(__DIR__ . '/../src/Models/Contact.php');
|
|
5
|
+
$conflictClass = file_get_contents(__DIR__ . '/../src/Exceptions/CustomerUserConflictException.php');
|
|
6
|
+
$observer = file_get_contents(__DIR__ . '/../src/Observers/ContactObserver.php');
|
|
7
|
+
|
|
8
|
+
expect($contactModel)
|
|
9
|
+
->toContain('assertCustomerIdentityIsAvailable')
|
|
10
|
+
->toContain('assertCustomerUserCanBeAssigned')
|
|
11
|
+
->toContain("orWhereHas('companyUsers'")
|
|
12
|
+
->toContain("\$user->type === 'customer'")
|
|
13
|
+
->toContain('cannot be used for a customer account');
|
|
14
|
+
|
|
15
|
+
expect($conflictClass)->toContain('class CustomerUserConflictException extends UserAlreadyExistsException');
|
|
16
|
+
|
|
17
|
+
expect($observer)
|
|
18
|
+
->toContain('$contact->assertCustomerIdentityIsAvailable();')
|
|
19
|
+
->toContain('$contact->normalizeCustomerUser();');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('customer user assignment no longer promotes staff users', function () {
|
|
23
|
+
$contactModel = file_get_contents(__DIR__ . '/../src/Models/Contact.php');
|
|
24
|
+
|
|
25
|
+
expect($contactModel)
|
|
26
|
+
->toContain('public function assignUser(User $user, bool $sendInvite = false): self')
|
|
27
|
+
->toContain('$this->assertCustomerUserCanBeAssigned($user);')
|
|
28
|
+
->not->toContain("if (\$this->isCustomer() && \$user->type !== 'customer') {\n \$user->setType('customer');");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('customer creation entrypoints run the customer identity guard', function () {
|
|
32
|
+
$apiContact = file_get_contents(__DIR__ . '/../src/Http/Controllers/Api/v1/ContactController.php');
|
|
33
|
+
$internalContact = file_get_contents(__DIR__ . '/../src/Http/Controllers/Internal/v1/ContactController.php');
|
|
34
|
+
$orderController = file_get_contents(__DIR__ . '/../src/Http/Controllers/Api/v1/OrderController.php');
|
|
35
|
+
$customerForm = file_get_contents(__DIR__ . '/../../addon/components/customer/form.hbs');
|
|
36
|
+
|
|
37
|
+
expect($apiContact)->toContain('$contactCandidate->assertCustomerIdentityIsAvailable();');
|
|
38
|
+
expect($internalContact)->toContain('$this->assertCustomerIdentityIsAvailable($input');
|
|
39
|
+
expect($orderController)->toContain('$customerCandidate->assertCustomerIdentityIsAvailable();');
|
|
40
|
+
expect($customerForm)->toContain('is_customer=true');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('customer conflict audit command is registered and read only', function () {
|
|
44
|
+
$provider = file_get_contents(__DIR__ . '/../src/Providers/FleetOpsServiceProvider.php');
|
|
45
|
+
$command = file_get_contents(__DIR__ . '/../src/Console/Commands/AuditCustomerUserConflicts.php');
|
|
46
|
+
|
|
47
|
+
expect($provider)->toContain('AuditCustomerUserConflicts::class');
|
|
48
|
+
expect($command)
|
|
49
|
+
->toContain('fleetops:audit-customer-user-conflicts')
|
|
50
|
+
->toContain('Reports customer contacts linked to users')
|
|
51
|
+
->not->toContain('->save(')
|
|
52
|
+
->not->toContain('->update(')
|
|
53
|
+
->not->toContain('forceFill');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('customer portal welcome email is opt in and does not create organization invites', function () {
|
|
57
|
+
$internalContact = file_get_contents(__DIR__ . '/../src/Http/Controllers/Internal/v1/ContactController.php');
|
|
58
|
+
$internalCustomer = file_get_contents(__DIR__ . '/../src/Http/Controllers/Internal/v1/CustomerController.php');
|
|
59
|
+
$contactModel = file_get_contents(__DIR__ . '/../src/Models/Contact.php');
|
|
60
|
+
$customerCredentials = file_get_contents(__DIR__ . '/../src/Mail/CustomerCredentialsMail.php');
|
|
61
|
+
$customerEmail = file_get_contents(__DIR__ . '/../resources/views/mail/customer-credentials.blade.php');
|
|
62
|
+
$customerForm = file_get_contents(__DIR__ . '/../../addon/components/customer/form.hbs');
|
|
63
|
+
|
|
64
|
+
expect($internalContact)
|
|
65
|
+
->toContain('meta.customer_portal.send_welcome_email')
|
|
66
|
+
->toContain('assertCustomerPortalCanSendWelcomeEmail')
|
|
67
|
+
->toContain('sendCustomerPortalWelcomeEmail')
|
|
68
|
+
->toContain('Contact::createUserFromContact($contact, false, true)')
|
|
69
|
+
->toContain('Mail::to($user)->send(new CustomerCredentialsMail($password, $contact))')
|
|
70
|
+
->toContain("data_forget(\$meta, 'customer_portal.send_welcome_email')")
|
|
71
|
+
->toContain('fleetbase/customer-portal-api')
|
|
72
|
+
->not->toContain('UserInvited')
|
|
73
|
+
->not->toContain('Invite::create');
|
|
74
|
+
|
|
75
|
+
expect($internalCustomer)
|
|
76
|
+
->toContain('Contact::createUserFromContact($customer, false, true)')
|
|
77
|
+
->not->toContain('send_invite');
|
|
78
|
+
|
|
79
|
+
expect($customerCredentials)->toContain('customer portal access is ready')
|
|
80
|
+
->and($customerCredentials)->toContain("Utils::consoleUrl(\$accessUrlSlug ?: 'customer-portal')")
|
|
81
|
+
->and($customerEmail)->toContain('Your customer portal access is ready')
|
|
82
|
+
->and($customerEmail)->toContain('Sign in to customer portal')
|
|
83
|
+
->and($customerEmail)->toContain('Temporary password:')
|
|
84
|
+
->and($customerEmail)->toContain('You can change your password after signing in.')
|
|
85
|
+
->and($customerForm)->toContain('this.showWelcomeEmailOption')
|
|
86
|
+
->and($customerForm)->toContain('this.toggleWelcomeEmail');
|
|
87
|
+
|
|
88
|
+
expect($contactModel)
|
|
89
|
+
->toContain('assignUserToContactCompany')
|
|
90
|
+
->toContain('$contact->company->addUser($user, $role)')
|
|
91
|
+
->toContain('Deprecated and ignored. Contact and customer users do not receive organization invitations.')
|
|
92
|
+
->not->toContain('UserInvited')
|
|
93
|
+
->not->toContain('Invite::create')
|
|
94
|
+
->not->toContain('->assignCompany(');
|
|
95
|
+
});
|