validated(); // Tenant gate. New residents always belong to one LGU; the // ResolveTenant middleware sets it from the X-Tenant-Code header. $tenant = Tenancy::current(); if (! $tenant) { return $this->fail('Pick your LGU first.', ['tenant' => ['LGU code required']], 400); } $user = DB::transaction(function () use ($data, $tenant) { $user = User::create([ 'tenant_id' => $tenant->id, 'first_name' => $data['first_name'], 'middle_name' => $data['middle_name'] ?? null, 'last_name' => $data['last_name'], 'email' => $data['email'], 'phone' => $data['phone'], 'password' => Hash::make($data['password']), 'role' => $data['role'], 'status' => User::STATUS_PENDING, 'preferred_language' => $data['preferred_language'] ?? 'en', ]); $user->assignRole($data['role']); $profileClass = User::profileModelForRole($data['role']); if ($profileClass) { $profileClass::create(['user_id' => $user->id]); } NotificationPreference::create([ 'user_id' => $user->id, 'language' => $data['preferred_language'] ?? 'en', ]); return $user; }); $issue = $otp->issue( destination: $user->phone, purpose: OtpCode::PURPOSE_REGISTER, user: $user, ); // Fire-and-forget email verification link. If the mail backend is // misconfigured we don't block registration. try { $user->sendEmailVerificationNotification(); } catch (\Throwable $e) { \Log::warning('Failed to send verification email', [ 'user_id' => $user->id, 'error' => $e->getMessage(), ]); } return $this->created([ 'user' => new UserResource($user), 'otp_sent' => $issue->issued, 'otp_destination' => $user->phone, 'debug_code' => app()->environment('local', 'testing') ? $issue->plainCode : null, ], 'Registration successful. Verify the OTP sent to your phone.'); } }