Beseitigt die Doppelung Admin-Login vs. Mitarbeiter — jeder ist ein Employee
mit optionalem Login/Rechtegruppe (Voraussetzung für Mitarbeiter-Zeiterfassung).
- Employee implementiert UserInterface/PasswordAuthenticated (loginEmail unique,
password, roles); User-Entität entfernt; Security-Provider → Employee.loginEmail
- Plattform = Reseller mit isPlatform + Org-Firma; Reseller haben Org-Firma
(Company.isResellerOrg) für ihr Personal → alles = Reseller→Firma→Mitarbeiter
- TenantContext leitet Reseller/Company aus dem Mitarbeiter ab (Reseller-/
Plattform-Admin = reseller-weit)
- UserAdminController: Login pro Mitarbeiter vergeben/entziehen
(POST/DELETE /api/employees/{id}/login), /api/users = Logins-Übersicht
- Provisioning/Seed auf das neue Modell; Migrationen zu einer Baseline gesquasht
- Frontend: EmployeesView Login-Block + UsersView (Logins-Übersicht)
Verifiziert: Login, /me, Mandantenscoping, delegierter Grant (Eskalation→403),
öffentliches Profil, SPA-Flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
2.3 KiB
PHP
65 lines
2.3 KiB
PHP
<?php
|
||
|
||
namespace App\Repository;
|
||
|
||
use App\Entity\Employee;
|
||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||
use Doctrine\Persistence\ManagerRegistry;
|
||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||
|
||
/**
|
||
* @extends ServiceEntityRepository<Employee>
|
||
*/
|
||
class EmployeeRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
||
{
|
||
public function __construct(ManagerRegistry $registry)
|
||
{
|
||
parent::__construct($registry, Employee::class);
|
||
}
|
||
|
||
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||
{
|
||
if (!$user instanceof Employee) {
|
||
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
|
||
}
|
||
$user->setPassword($newHashedPassword);
|
||
$this->getEntityManager()->persist($user);
|
||
$this->getEntityManager()->flush();
|
||
}
|
||
|
||
/**
|
||
* Lädt ein öffentlich sichtbares (aktives) Profil anhand Firmen- und
|
||
* Mitarbeiter-Slug. Nicht mandantengefiltert – diese Seiten sind öffentlich.
|
||
*/
|
||
public function findPublic(string $companySlug, string $slug): ?Employee
|
||
{
|
||
return $this->createQueryBuilder('e')
|
||
->join('e.company', 'c')
|
||
->andWhere('c.slug = :companySlug')
|
||
->andWhere('e.slug = :slug')
|
||
->andWhere('e.status = :status')
|
||
->setParameter('companySlug', $companySlug)
|
||
->setParameter('slug', $slug)
|
||
->setParameter('status', 'active')
|
||
->getQuery()
|
||
->getOneOrNullResult();
|
||
}
|
||
|
||
/** Aktives Profil über den stabilen NFC/QR-Kurz-Code (für /t/{code}). */
|
||
public function findByShortCode(string $shortCode): ?Employee
|
||
{
|
||
return $this->findOneBy(['shortCode' => $shortCode, 'status' => 'active']);
|
||
}
|
||
|
||
/** @return Employee[] Mitarbeiter ohne Kurz-Code (für Backfill). */
|
||
public function findWithoutShortCode(): array
|
||
{
|
||
return $this->createQueryBuilder('e')
|
||
->andWhere('e.shortCode IS NULL')
|
||
->getQuery()
|
||
->getResult();
|
||
}
|
||
}
|