@fleetbase/fleetops-engine 0.6.68 → 0.6.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/RELEASE.md +11 -9
  2. package/addon/components/contact/form.hbs +13 -2
  3. package/addon/components/customer/details.hbs +17 -17
  4. package/addon/components/customer/form.hbs +13 -2
  5. package/addon/components/customer/form.js +0 -56
  6. package/addon/components/driver/details.hbs +13 -19
  7. package/addon/components/driver/form.hbs +20 -35
  8. package/addon/components/driver/form.js +0 -58
  9. package/addon/components/modals/driver-form.hbs +2 -2
  10. package/addon/components/modals/reset-profile-credentials.hbs +23 -0
  11. package/addon/components/modals/reset-profile-credentials.js +70 -0
  12. package/addon/components/profile-identity-hint.hbs +13 -0
  13. package/addon/components/profile-identity-hint.js +49 -0
  14. package/addon/controllers/management/drivers/index.js +4 -0
  15. package/addon/extension.js +4 -2
  16. package/addon/services/contact-actions.js +50 -89
  17. package/addon/services/driver-actions.js +98 -0
  18. package/addon/utils/profile-login-actions.js +172 -0
  19. package/app/components/modals/{reset-customer-credentials.js → reset-profile-credentials.js} +1 -1
  20. package/app/components/profile-identity-hint.js +1 -0
  21. package/composer.json +1 -1
  22. package/extension.json +1 -1
  23. package/package.json +3 -3
  24. package/scripts/pest-bootstrap.php +12 -0
  25. package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
  26. package/server/resources/views/mail/driver-credentials.blade.php +21 -0
  27. package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
  28. package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
  29. package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
  30. package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
  31. package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
  32. package/server/src/Http/Controllers/Internal/v1/ContactController.php +5 -17
  33. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +40 -4
  34. package/server/src/Http/Controllers/Internal/v1/DriverController.php +182 -162
  35. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
  36. package/server/src/Http/Controllers/Internal/v1/OrderController.php +156 -27
  37. package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
  38. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
  39. package/server/src/Http/Resources/v1/Contact.php +3 -0
  40. package/server/src/Http/Resources/v1/Driver.php +3 -0
  41. package/server/src/Mail/DriverCredentialsMail.php +55 -0
  42. package/server/src/Models/Contact.php +55 -90
  43. package/server/src/Models/Sensor.php +3 -5
  44. package/server/src/Observers/ContactObserver.php +2 -22
  45. package/server/src/Observers/DriverObserver.php +5 -6
  46. package/server/src/Providers/FleetOpsServiceProvider.php +15 -0
  47. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
  48. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
  49. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
  50. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
  51. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
  52. package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
  53. package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
  54. package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
  55. package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
  56. package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
  57. package/server/src/Support/ProfileAccountManager.php +405 -0
  58. package/server/src/routes.php +5 -0
  59. package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
  60. package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
  61. package/server/tests/ApiDriverControllerContractsTest.php +102 -20
  62. package/server/tests/CommandContractsTest.php +4 -4
  63. package/server/tests/ControllerHelperContractsTest.php +3 -11
  64. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +5 -0
  65. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
  66. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
  67. package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
  68. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
  69. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
  70. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
  71. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
  72. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
  73. package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
  74. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
  75. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
  76. package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
  77. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
  78. package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
  79. package/server/tests/ModelAccessorContractsTest.php +82 -20
  80. package/server/tests/ObserverContractsTest.php +27 -2
  81. package/server/tests/RequestContractsTest.php +19 -11
  82. package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
  83. package/server/tests/TelematicControllerContractsTest.php +22 -4
  84. package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
  85. package/server/tests/Unit/Models/ContactTest.php +48 -5
  86. package/server/tests/Unit/Models/SensorTest.php +2 -2
  87. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
  88. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
  89. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
  90. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
  91. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
  92. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
  93. package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
  94. package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
  95. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
  96. package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
  97. package/translations/en-us.yaml +37 -2
  98. package/translations/uk-ua.yaml +3475 -0
  99. package/addon/components/modals/reset-customer-credentials.hbs +0 -18
  100. package/addon/components/modals/reset-customer-credentials.js +0 -55
@@ -3,6 +3,7 @@
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\Exceptions\FleetbaseRequestValidationException;
6
+ use Fleetbase\FleetOps\Exceptions\ProfileIdentityConflictException;
6
7
  use Fleetbase\FleetOps\Exports\DriverExport;
7
8
  use Fleetbase\FleetOps\Http\Controllers\Api\v1\DriverController as ApiDriverController;
8
9
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
@@ -13,11 +14,11 @@ use Fleetbase\FleetOps\Imports\DriverImport;
13
14
  use Fleetbase\FleetOps\Models\Driver;
14
15
  use Fleetbase\FleetOps\Models\Order;
15
16
  use Fleetbase\FleetOps\Models\Vehicle;
17
+ use Fleetbase\FleetOps\Support\ProfileAccountManager;
16
18
  use Fleetbase\FleetOps\Support\Utils;
17
19
  use Fleetbase\Http\Requests\ExportRequest;
18
20
  use Fleetbase\Http\Requests\ImportRequest;
19
21
  use Fleetbase\LaravelMysqlSpatial\Types\Point;
20
- use Fleetbase\Models\Invite;
21
22
  use Fleetbase\Models\User;
22
23
  use Fleetbase\Models\VerificationCode;
23
24
  use Fleetbase\Support\Auth;
@@ -58,72 +59,6 @@ class DriverController extends FleetOpsController
58
59
  $validator = Validator::make($input, $rules);
59
60
 
60
61
  if ($validator->fails()) {
61
- // here if the user exists already
62
- // within organization: offer to create driver record
63
- // outside organization: invite to join organization AS DRIVER
64
- if ($validator->errors()->hasAny(['phone', 'email'])) {
65
- // get existing user
66
- $existingUser = null;
67
-
68
- // if values provided for user lookup
69
- if (!empty($input['phone']) || !empty($input['email'])) {
70
- $existingUserQuery = User::query();
71
-
72
- if (!empty($input['phone']) && is_string($input['phone'])) {
73
- $existingUserQuery->orWhere(function ($q) use ($input) {
74
- $q->where('phone', $input['phone'])->whereNotNull('phone');
75
- });
76
- }
77
-
78
- if (!empty($input['email']) && is_string($input['email'])) {
79
- $existingUserQuery->orWhere(function ($q) use ($input) {
80
- $q->where('email', $input['email'])->whereNotNull('email');
81
- });
82
- }
83
-
84
- $existingUser = $existingUserQuery->first();
85
- }
86
-
87
- if ($existingUser) {
88
- // if exists in organization create driver profile for user
89
- $isOrganizationMember = $existingUser->companies()->where('companies.uuid', session('company'))->exists();
90
-
91
- // Check if driver profile also already exists
92
- $existingDriverProfile = Driver::where(['company_uuid' => session('company'), 'user_uuid' => $existingUser->uuid])->first();
93
- if ($existingDriverProfile) {
94
- return ['driver' => new $this->resource($existingDriverProfile)];
95
- }
96
-
97
- // create driver profile for user
98
- $input = collect($input)
99
- ->except(['name', 'password', 'email', 'phone', 'meta', 'avatar_uuid', 'photo_uuid', 'status'])
100
- ->filter()
101
- ->toArray();
102
-
103
- // Get current session company
104
- $company = Auth::getCompany();
105
- $input['company_uuid'] = session('company', $company->uuid);
106
- $input['user_uuid'] = $existingUser->uuid;
107
- $input['slug'] = $existingUser->slug;
108
-
109
- // If no location provided set
110
- if (empty($input['location'])) {
111
- $input['location'] = new Point(0, 0);
112
- }
113
-
114
- // create the profile
115
- $driverProfile = Driver::create($input);
116
-
117
- // If not already a member of the company assign them to the company and send the user an invite
118
- if (!$isOrganizationMember && $company) {
119
- $existingUser->assignCompany($company);
120
- }
121
-
122
- return ['driver' => new $this->resource($driverProfile)];
123
- }
124
- }
125
-
126
- // check from validator object if phone or email is not unique
127
62
  return $createDriverRequest->responseWithErrors($validator);
128
63
  }
129
64
 
@@ -134,104 +69,36 @@ class DriverController extends FleetOpsController
134
69
  $input = collect($input);
135
70
 
136
71
  // Get current session company
137
- $company = Auth::getCompany();
72
+ $company = Auth::getCompany();
138
73
  if (!$company) {
139
74
  throw new \Exception('Unable to create driver.');
140
75
  }
141
76
 
142
- if ($input->has('user_uuid')) {
143
- $user = User::where('uuid', $input->get('user_uuid'))->first();
144
-
145
- // Check if a driver profile already exists for this user in the current company
146
- if ($user) {
147
- $existingDriver = Driver::where(['user_uuid' => $user->uuid, 'company_uuid' => session('company')])->first();
148
- if ($existingDriver) {
149
- throw new \Exception('This user account already belongs to a driver.');
150
- }
151
- }
152
-
153
- // If user doesn't exist with provided UUID, create new user
154
- if (!$user) {
155
- $userInput = $input
156
- ->only(['name', 'password', 'email', 'phone', 'status', 'avatar_uuid'])
157
- ->filter()
158
- ->toArray();
159
-
160
- // handle `photo_uuid`
161
- if (isset($input['photo_uuid']) && Str::isUuid($input['photo_uuid'])) {
162
- $userInput['avatar_uuid'] = $input['photo_uuid'];
163
- }
164
-
165
- // Make sure password is set
166
- if (empty($userInput['password'])) {
167
- $userInput['password'] = Str::random(14);
168
- }
169
-
170
- // Set user company
171
- $userInput['company_uuid'] = session('company', $company->uuid);
172
-
173
- // Apply user infos
174
- $userInput = User::applyUserInfoFromRequest($request, $userInput);
175
-
176
- // Create user account
177
- $user = User::create($userInput);
178
-
179
- // Set the user type to driver
180
- $user->setType('driver');
181
- } elseif ($input->has('photo_uuid')) {
182
- // Update existing user's avatar if photo provided
183
- $user->update(['avatar_uuid' => $input->get('photo_uuid')]);
184
- }
185
- } else {
186
- $userInput = $input
187
- ->only(['name', 'password', 'email', 'phone', 'status', 'avatar_uuid'])
188
- ->filter()
189
- ->toArray();
190
-
191
- // handle `photo_uuid`
192
- if (isset($input['photo_uuid']) && Str::isUuid($input['photo_uuid'])) {
193
- $userInput['avatar_uuid'] = $input['photo_uuid'];
194
- }
195
-
196
- // Make sure password is set
197
- if (empty($userInput['password'])) {
198
- $userInput['password'] = Str::random(14);
199
- }
200
-
201
- // Set user company
202
- $userInput['company_uuid'] = session('company', $company->uuid);
203
-
204
- // Apply user infos
205
- $userInput = User::applyUserInfoFromRequest($request, $userInput);
206
-
207
- // Create user account
208
- $user = User::create($userInput);
209
-
210
- // Set the user type to driver
211
- $user->setType('driver');
212
- }
213
-
214
- // if exists in organization create driver profile for user
215
- $isOrganizationMember = $user->companies()->where('companies.uuid', session('company'))->exists();
77
+ // The login account is managed by the driver profile: a team member
78
+ // with the email/phone is linked, otherwise a driver account is created.
79
+ $avatarUuid = Str::isUuid($input->get('photo_uuid')) ? $input->get('photo_uuid') : $input->get('avatar_uuid');
80
+ $user = ProfileAccountManager::resolveForProfile(
81
+ $company->uuid,
82
+ 'driver',
83
+ $input->get('name'),
84
+ $input->get('email'),
85
+ $input->get('phone'),
86
+ User::applyUserInfoFromRequest($request, array_filter([
87
+ 'password' => $input->get('password'),
88
+ 'status' => 'active',
89
+ 'avatar_uuid' => $avatarUuid,
90
+ ]))
91
+ );
216
92
 
217
93
  // Prepare input
218
94
  $input = $input
219
- ->except(['name', 'password', 'email', 'phone', 'meta', 'avatar_uuid', 'photo_uuid', 'status'])
95
+ ->except(['name', 'password', 'email', 'phone', 'meta', 'avatar_uuid', 'photo_uuid', 'status', 'user_uuid', 'user'])
220
96
  ->filter()
221
97
  ->toArray();
222
98
 
223
- // Assign user to company and send invite
224
- if (!$isOrganizationMember && $company) {
225
- $user->assignCompany($company);
226
- }
227
-
228
- // Set user type as driver and set role to driver
229
- if ($user->type === 'driver') {
230
- $user->assignSingleRole('Driver');
231
- }
232
-
233
- $input['user_uuid'] = $user->uuid;
234
- $input['slug'] = $user->slug;
99
+ $input['company_uuid'] = $company->uuid;
100
+ $input['user_uuid'] = $user->uuid;
101
+ $input['slug'] = $user->slug;
235
102
 
236
103
  // If no location provided set
237
104
  if (empty($input['location'])) {
@@ -248,6 +115,8 @@ class DriverController extends FleetOpsController
248
115
  );
249
116
 
250
117
  return ['driver' => new $this->resource($record)];
118
+ } catch (ProfileIdentityConflictException $e) {
119
+ return response()->error($e->getMessage(), 422, ['field' => $e->getField()]);
251
120
  } catch (QueryException $e) {
252
121
  return response()->error(env('DEBUG') ? $e->getMessage() : 'Error occurred while trying to create a ' . $this->resourceSingularlName);
253
122
  } catch (FleetbaseRequestValidationException $e) {
@@ -287,18 +156,15 @@ class DriverController extends FleetOpsController
287
156
  function (&$request, &$driver, &$input) {
288
157
  $driver->load(['user'])->guard(['user_uuid']);
289
158
  $input = collect($input);
290
- $userInput = $input->only(['name', 'password', 'email', 'phone', 'avatar_uuid'])->reject(fn ($value) => $value === null)->toArray();
159
+ $userInput = $input->only(['name', 'password', 'email', 'phone', 'avatar_uuid'])->reject(fn ($value, $key) => $value === null && !in_array($key, ['email', 'phone'], true))->toArray();
291
160
  // handle `photo_uuid`
292
161
  if (isset($input['photo_uuid']) && Str::isUuid($input['photo_uuid'])) {
293
162
  $userInput['avatar_uuid'] = $input['photo_uuid'];
294
163
  }
295
- $input = $input->except(['name', 'password', 'email', 'phone', 'meta', 'avatar_uuid', 'photo_uuid'])->toArray();
164
+ $input = $input->except(['name', 'password', 'email', 'phone', 'meta', 'avatar_uuid', 'photo_uuid', 'user_uuid', 'user'])->toArray();
296
165
 
297
- // Update driver user details
298
- $driverUser = $driver->getUser();
299
- if ($driverUser && !empty($userInput)) {
300
- $driverUser->update($userInput);
301
- }
166
+ // Update the driver's login account through its proxy fields
167
+ ProfileAccountManager::syncProxyFields($driver->getUser(), $userInput);
302
168
 
303
169
  // Flush cache
304
170
  $driver->flushAttributesCache();
@@ -318,6 +184,8 @@ class DriverController extends FleetOpsController
318
184
  );
319
185
 
320
186
  return ['driver' => new $this->resource($record)];
187
+ } catch (ProfileIdentityConflictException $e) {
188
+ return response()->error($e->getMessage(), 422, ['field' => $e->getField()]);
321
189
  } catch (QueryException $e) {
322
190
  return response()->error(env('DEBUG') ? $e->getMessage() : 'Error occurred while trying to update a ' . $this->resourceSingularlName);
323
191
  } catch (FleetbaseRequestValidationException $e) {
@@ -327,6 +195,150 @@ class DriverController extends FleetOpsController
327
195
  }
328
196
  }
329
197
 
198
+ /**
199
+ * Generate a new password for the driver's login and send it to them.
200
+ *
201
+ * @return JsonResponse
202
+ */
203
+ public function sendCredentials(string $id)
204
+ {
205
+ [$driver, $user, $error] = $this->resolveDriverLogin($id);
206
+ if ($error) {
207
+ return $error;
208
+ }
209
+
210
+ try {
211
+ $sentVia = ProfileAccountManager::sendCredentials($driver, $user);
212
+ } catch (\Exception $e) {
213
+ return response()->error($e->getMessage());
214
+ }
215
+
216
+ return response()->json(['status' => 'ok', 'sent_via' => $sentVia, 'driver' => $this->driverLoginPayload($driver)]);
217
+ }
218
+
219
+ /**
220
+ * Set a new password for the driver's login, optionally sending it to them.
221
+ *
222
+ * @return JsonResponse
223
+ */
224
+ public function resetCredentials(Request $request, string $id)
225
+ {
226
+ $password = $request->input('password');
227
+ if (!is_string($password) || strlen($password) < 8) {
228
+ return response()->error('Password must be at least 8 characters.');
229
+ }
230
+
231
+ if ($password !== $request->input('password_confirmation')) {
232
+ return response()->error('Passwords do not match.');
233
+ }
234
+
235
+ [$driver, $user, $error] = $this->resolveDriverLogin($id);
236
+ if ($error) {
237
+ return $error;
238
+ }
239
+
240
+ $user->changePassword($password);
241
+
242
+ try {
243
+ if ($request->boolean('send_credentials')) {
244
+ ProfileAccountManager::deliverCredentials($driver, $user, $password);
245
+ }
246
+ } catch (\Exception $e) {
247
+ return response()->error($e->getMessage());
248
+ }
249
+
250
+ return response()->json(['status' => 'ok', 'driver' => $this->driverLoginPayload($driver)]);
251
+ }
252
+
253
+ /**
254
+ * Stop the driver from signing in to the driver app.
255
+ *
256
+ * @return JsonResponse
257
+ */
258
+ public function deactivateLogin(string $id)
259
+ {
260
+ [$driver, $user, $error] = $this->resolveDriverLogin($id);
261
+ if ($error) {
262
+ return $error;
263
+ }
264
+
265
+ $user->deactivate();
266
+
267
+ // Sign the driver out of the driver app
268
+ $user->tokens()->delete();
269
+
270
+ return response()->json(['status' => 'ok', 'driver' => $this->driverLoginPayload($driver)]);
271
+ }
272
+
273
+ /**
274
+ * Allow the driver to sign in to the driver app again.
275
+ *
276
+ * @return JsonResponse
277
+ */
278
+ public function reactivateLogin(string $id)
279
+ {
280
+ [$driver, $user, $error] = $this->resolveDriverLogin($id);
281
+ if ($error) {
282
+ return $error;
283
+ }
284
+
285
+ $user->activate();
286
+
287
+ return response()->json(['status' => 'ok', 'driver' => $this->driverLoginPayload($driver)]);
288
+ }
289
+
290
+ /**
291
+ * Find the driver and its managed login account. A driver linked to a team
292
+ * member's account has its login managed in IAM.
293
+ *
294
+ * @return array{0: ?Driver, 1: ?User, 2: ?JsonResponse}
295
+ */
296
+ protected function resolveDriverLogin(string $id): array
297
+ {
298
+ $driver = Driver::where('company_uuid', session('company'))
299
+ ->where(function ($query) use ($id) {
300
+ $query->where('uuid', $id)->orWhere('public_id', $id);
301
+ })
302
+ ->first();
303
+
304
+ if (!$driver) {
305
+ return [null, null, response()->error('Driver not found.', 404)];
306
+ }
307
+
308
+ $user = $driver->user_uuid ? User::where('uuid', $driver->user_uuid)->first() : null;
309
+ if (!$user) {
310
+ return [$driver, null, response()->error('This driver has no login account.')];
311
+ }
312
+
313
+ if (ProfileAccountManager::isStaffAccount($user)) {
314
+ return [$driver, $user, response()->error('This driver signs in with a team member account. Manage this login in IAM.', 422)];
315
+ }
316
+
317
+ return [$driver, $user, null];
318
+ }
319
+
320
+ protected function driverLoginPayload(Driver $driver): array
321
+ {
322
+ $user = User::where('uuid', $driver->user_uuid)->first();
323
+
324
+ return [
325
+ 'id' => $driver->public_id,
326
+ 'uuid' => $driver->uuid,
327
+ 'user_uuid' => $driver->user_uuid,
328
+ 'is_staff_linked' => ProfileAccountManager::isStaffAccount($user),
329
+ 'login_status' => $user?->status,
330
+ 'user' => $user ? [
331
+ 'id' => $user->public_id,
332
+ 'uuid' => $user->uuid,
333
+ 'name' => $user->name,
334
+ 'email' => $user->email,
335
+ 'phone' => $user->phone,
336
+ 'status' => $user->status,
337
+ 'session_status' => $user->session_status,
338
+ ] : null,
339
+ ];
340
+ }
341
+
330
342
  /**
331
343
  * Get all status options for an driver.
332
344
  *
@@ -635,6 +647,10 @@ class DriverController extends FleetOpsController
635
647
  return response()->error('No driver with this phone # found.');
636
648
  }
637
649
 
650
+ if (ApiDriverController::isLoginDeactivated($user)) {
651
+ return response()->error(ApiDriverController::DEACTIVATED_LOGIN_MESSAGE, 403);
652
+ }
653
+
638
654
  // Generate verification token
639
655
  static::generateDriverLoginVerification($user);
640
656
 
@@ -663,6 +679,10 @@ class DriverController extends FleetOpsController
663
679
  return response()->error('Unable to verify code.');
664
680
  }
665
681
 
682
+ if (ApiDriverController::isLoginDeactivated($user)) {
683
+ return response()->error(ApiDriverController::DEACTIVATED_LOGIN_MESSAGE, 403);
684
+ }
685
+
666
686
  // Find and verify code
667
687
  $verificationCode = static::verificationCodeExists($user, $code, $for);
668
688
  if (!$verificationCode && !ApiDriverController::verificationBypassMatches($identity, $code)) {
@@ -3,16 +3,63 @@
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Driver;
6
7
  use Fleetbase\FleetOps\Models\IntegratedVendor;
7
8
  use Fleetbase\FleetOps\Models\Vendor;
9
+ use Fleetbase\FleetOps\Support\ProfileAccountManager;
8
10
  use Fleetbase\FleetOps\Support\Utils;
9
11
  use Fleetbase\Http\Controllers\Controller;
12
+ use Fleetbase\Models\User;
10
13
  use Illuminate\Http\Request;
11
14
  use Illuminate\Support\Arr;
12
15
  use Illuminate\Support\Str;
13
16
 
14
17
  class FleetOpsLookupController extends Controller
15
18
  {
19
+ /**
20
+ * Describes what an email/phone entered on a driver, contact or customer
21
+ * form resolves to: the team member the profile would be linked to, or why
22
+ * the email/phone can't be used.
23
+ *
24
+ * @return \Illuminate\Http\JsonResponse
25
+ */
26
+ public function profileIdentity(Request $request)
27
+ {
28
+ $type = $request->input('type', 'driver');
29
+ if (!in_array($type, ProfileAccountManager::MANAGED_TYPES, true)) {
30
+ return response()->error('Invalid profile type.', 422);
31
+ }
32
+
33
+ $currentUser = $this->currentProfileUser($type, $request->input('ignore'));
34
+ $lookup = ProfileAccountManager::lookup(session('company'), $type, $request->input('email'), $request->input('phone'), $currentUser);
35
+ $staff = $lookup['staff'];
36
+
37
+ return response()->json([
38
+ 'staff' => $staff ? ['uuid' => $staff->uuid, 'name' => $staff->name, 'email' => $staff->email, 'phone' => $staff->phone] : null,
39
+ 'conflict' => $lookup['conflict'],
40
+ ]);
41
+ }
42
+
43
+ /**
44
+ * The account of the profile being edited, so its own email/phone isn't
45
+ * reported as taken.
46
+ */
47
+ private function currentProfileUser(string $type, ?string $id): ?User
48
+ {
49
+ if (!$id) {
50
+ return null;
51
+ }
52
+
53
+ $model = $type === 'driver' ? Driver::withoutGlobalScopes() : Contact::query();
54
+ $profile = $model->where('company_uuid', session('company'))
55
+ ->where(function ($query) use ($id) {
56
+ $query->where('uuid', $id)->orWhere('public_id', $id);
57
+ })
58
+ ->first();
59
+
60
+ return $profile?->user_uuid ? User::where('uuid', $profile->user_uuid)->first() : null;
61
+ }
62
+
16
63
  /**
17
64
  * Returns a collection of polymorphic resources as JSON.
18
65
  *