where('tenant_id', (int) $tenant->getKey()) ->where('role', 'owner') ->exists(); } public function userHasDuplicateMemberships(Tenant $tenant, User $user): bool { return TenantMembership::query() ->where('tenant_id', (int) $tenant->getKey()) ->where('user_id', (int) $user->getKey()) ->count() > 1; } public function mergeDuplicateMembershipsForUser(Tenant $tenant, User $actor, User $member): void { DB::transaction(function () use ($tenant, $actor, $member): void { $memberships = TenantMembership::query() ->where('tenant_id', (int) $tenant->getKey()) ->where('user_id', (int) $member->getKey()) ->orderBy('created_at') ->get(); if ($memberships->count() <= 1) { return; } $roles = $memberships->pluck('role')->all(); $roleToKeep = $this->highestRole($roles); $membershipToKeep = $memberships->firstWhere('role', $roleToKeep) ?? $memberships->first(); if (! $membershipToKeep instanceof TenantMembership) { return; } $idsToDelete = $memberships ->reject(fn (TenantMembership $m): bool => $m->getKey() === $membershipToKeep->getKey()) ->pluck($membershipToKeep->getKeyName()) ->all(); $membershipToKeep->forceFill([ 'role' => $roleToKeep, ])->save(); TenantMembership::query() ->whereIn($membershipToKeep->getKeyName(), $idsToDelete) ->delete(); $this->auditLogger->log( tenant: $tenant, action: AuditActionId::TenantMembershipDuplicatesMerged->value, context: [ 'metadata' => [ 'member_user_id' => (int) $member->getKey(), 'kept_membership_id' => (string) $membershipToKeep->getKey(), 'deleted_membership_ids' => array_values(array_map('strval', $idsToDelete)), 'result_role' => $roleToKeep, 'source_roles' => $roles, ], ], actorId: (int) $actor->getKey(), actorEmail: $actor->email, actorName: $actor->name, status: 'success', resourceType: 'tenant', resourceId: (string) $tenant->getKey(), ); }); } /** * @param array $roles */ private function highestRole(array $roles): string { $priority = [ 'owner' => 3, 'manager' => 2, 'readonly' => 1, ]; $bestRole = 'readonly'; $bestScore = 0; foreach ($roles as $role) { $score = $priority[$role] ?? 0; if ($score > $bestScore) { $bestScore = $score; $bestRole = (string) $role; } } return $bestRole; } }