feat(directory) : add Prospect entity with conversion to Client (back)
LST-58 (2.4), part 2 — Prospect (new entity). Completes the Directory backend.
- ProspectStatus enum (new/contacted/qualified/won/lost) + Prospect entity
(name, company, email, phone, address, status, source, notes,
convertedClient -> ClientInterface) with Timestampable/Blamable + #[Auditable].
- API: GetCollection/Get (ROLE_USER), Post/Patch/Delete (ROLE_ADMIN),
custom POST /prospects/{id}/convert (ConvertProspectProcessor: creates a
Client from the prospect, links convertedClient, sets status=Won; idempotent).
SearchFilter on status.
- Repository interface + Doctrine impl (bound); 6 MCP tools (list/get/create/
update/delete/convert-prospect); Serializer::prospect(). Module perms
directory.prospects.view/manage. Demo fixtures (3 prospects, one converted).
- Additive migration: CREATE TABLE prospect + FKs ON DELETE SET NULL + COMMENT.
163 tests green (incl. conversion test), mapping valid, cs-fixer clean.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Directory\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Module\Directory\Domain\Entity\Client;
|
||||
use App\Module\Directory\Domain\Enum\ProspectStatus;
|
||||
use App\Module\Directory\Domain\Repository\ProspectRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
#[McpTool(name: 'convert-prospect', description: 'Convert a prospect into a client (admin). Idempotent: returns the prospect unchanged if already converted.')]
|
||||
class ConvertProspectTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProspectRepositoryInterface $prospectRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
public function __invoke(int $id): string
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
|
||||
}
|
||||
|
||||
$prospect = $this->prospectRepository->findById($id);
|
||||
if (null === $prospect) {
|
||||
throw new InvalidArgumentException(sprintf('Prospect with ID %d not found.', $id));
|
||||
}
|
||||
|
||||
if (null === $prospect->getConvertedClient()) {
|
||||
$client = new Client();
|
||||
$client->setName($prospect->getCompany() ?: (string) $prospect->getName());
|
||||
$client->setEmail($prospect->getEmail());
|
||||
$client->setPhone($prospect->getPhone());
|
||||
$client->setStreet($prospect->getStreet());
|
||||
$client->setCity($prospect->getCity());
|
||||
$client->setPostalCode($prospect->getPostalCode());
|
||||
|
||||
$this->entityManager->persist($client);
|
||||
|
||||
$prospect->setConvertedClient($client);
|
||||
$prospect->setStatus(ProspectStatus::Won);
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return json_encode(Serializer::prospect($prospect));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user