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>
94 lines
3.3 KiB
PHP
94 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Entity\Company;
|
|
use App\Entity\Employee;
|
|
use App\Entity\PlatformPlan;
|
|
use App\Entity\Reseller;
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
|
|
/**
|
|
* Legt einen Reseller an: Reseller + Org-Firma (für sein Personal) + optional
|
|
* einen Admin-Mitarbeiter mit Login. Nur für Plattform-Admins.
|
|
*/
|
|
#[IsGranted('ROLE_PLATFORM_ADMIN')]
|
|
final class ResellerProvisioningController
|
|
{
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $em,
|
|
private readonly UserPasswordHasherInterface $hasher,
|
|
) {
|
|
}
|
|
|
|
#[Route('/api/platform/provision-reseller', name: 'platform_provision_reseller', methods: ['POST'])]
|
|
public function __invoke(Request $request): JsonResponse
|
|
{
|
|
$d = json_decode($request->getContent(), true) ?? [];
|
|
|
|
$name = trim((string) ($d['name'] ?? ''));
|
|
$slug = trim((string) ($d['slug'] ?? ''));
|
|
if ('' === $name || '' === $slug) {
|
|
return new JsonResponse(['error' => 'name und slug sind erforderlich.'], 422);
|
|
}
|
|
|
|
$reseller = (new Reseller())->setName($name)->setSlug($slug);
|
|
if (!empty($d['primaryDomain'])) {
|
|
$reseller->setPrimaryDomain((string) $d['primaryDomain']);
|
|
}
|
|
if (!empty($d['planId'])) {
|
|
$plan = $this->em->getRepository(PlatformPlan::class)->find($d['planId']);
|
|
if ($plan instanceof PlatformPlan) {
|
|
$reseller->setPlatformPlan($plan);
|
|
}
|
|
}
|
|
|
|
// Org-Firma des Resellers (beherbergt dessen Personal)
|
|
$orgCompany = (new Company())
|
|
->setName($name)
|
|
->setSlug($slug.'-team')
|
|
->setReseller($reseller)
|
|
->setIsResellerOrg(true);
|
|
|
|
$this->em->persist($reseller);
|
|
$this->em->persist($orgCompany);
|
|
|
|
$adminEmail = trim((string) ($d['adminEmail'] ?? ''));
|
|
$adminPassword = (string) ($d['adminPassword'] ?? '');
|
|
$admin = null;
|
|
if ('' !== $adminEmail && '' !== $adminPassword) {
|
|
$admin = (new Employee())
|
|
->setFirstName((string) ($d['adminFirstName'] ?? 'Admin'))
|
|
->setLastName((string) ($d['adminLastName'] ?? $name))
|
|
->setSlug('admin')
|
|
->setCompany($orgCompany)
|
|
->setLoginEmail($adminEmail)
|
|
->setRoles([Employee::ROLE_RESELLER_ADMIN]);
|
|
$admin->setEmail($adminEmail);
|
|
$admin->setPassword($this->hasher->hashPassword($admin, $adminPassword));
|
|
}
|
|
|
|
try {
|
|
if ($admin) {
|
|
$this->em->persist($admin);
|
|
}
|
|
$this->em->flush();
|
|
} catch (UniqueConstraintViolationException) {
|
|
return new JsonResponse(['error' => 'Slug oder Admin-E-Mail bereits vergeben.'], 422);
|
|
}
|
|
|
|
return new JsonResponse([
|
|
'id' => (string) $reseller->getId(),
|
|
'name' => $reseller->getName(),
|
|
'slug' => $reseller->getSlug(),
|
|
'adminCreated' => null !== $admin,
|
|
], 201);
|
|
}
|
|
}
|