feat(mcp) : outils MCP Directory pour prestataires, contacts, adresses et rapports
Pull Request — Quality gate / Frontend (build) (pull_request) Successful in 41s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 59s

Ajoute 20 nouveaux outils MCP pour permettre à Claude (ou tout client MCP) de
remplir un dossier client / prospect / prestataire complet — onglets
Information, Contact, Adresse et Rapport — sans passer par l'UI.

Entités couvertes (CRUD complet, 5 outils chacune) :
- Prestataire : create / update / get / list / delete
- Contact : create / update / get / list / delete
- Address : create / update / get / list / delete
- CommercialReport : create / update / get / list / delete

Détails :
- Contact / Address / CommercialReport doivent être rattachés à exactement
  un parent parmi clientId, prospectId, prestataireId (validation côté tool).
- get-client, get-prospect et get-prestataire renvoient désormais un payload
  enrichi avec la liste de leurs contacts, adresses et rapports liés : un
  seul appel pour reconstruire l'onglet entier.
- Pour CommercialReport, le type (note / call / meeting / email) et la date
  occurredAt sont validés ; l'auteur est rempli automatiquement par le
  listener existant.
- Sécurité : ROLE_ADMIN aligné sur les autres outils MCP de Directory (pas
  de migration vers les permissions RBAC fines pour rester cohérent).

Plumbing :
- Repositories Contact / Address / CommercialReport : ajout de findBy() sur
  les interfaces (l'implémentation Doctrine l'a déjà via ServiceEntityRepository).
- Bindings interface -> implémentation Doctrine ajoutés dans services.yaml
  pour Prestataire / Contact / Address / CommercialReport.
- Sérialiseur partagé étendu : prestataire / contact / address /
  commercialReport / reportDocument.

Vérification : 86 outils MCP exposés au total (66 avant + 20 ajoutés), test
end-to-end via le transport HTTP (create-prestataire + create-contact +
create-address + create-commercial-report + get-prestataire renvoyant le
dossier complet). Suite PHPUnit verte.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 20:36:46 +02:00
parent 94e6abcbaa
commit 99626b89da
23 changed files with 1276 additions and 4 deletions
@@ -8,8 +8,13 @@ use App\Module\Absence\Domain\Entity\AbsenceBalance;
use App\Module\Absence\Domain\Entity\AbsencePolicy;
use App\Module\Absence\Domain\Entity\AbsenceRequest;
use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Entity\Address;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\Directory\Domain\Entity\CommercialReport;
use App\Module\Directory\Domain\Entity\Contact;
use App\Module\Directory\Domain\Entity\Prestataire;
use App\Module\Directory\Domain\Entity\Prospect;
use App\Module\Directory\Domain\Entity\ReportDocument;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
@@ -374,6 +379,98 @@ final class Serializer
];
}
/**
* @return array<string, mixed>
*/
public static function prestataire(Prestataire $p): array
{
return [
'id' => $p->getId(),
'name' => $p->getName(),
'email' => $p->getEmail(),
'phone' => $p->getPhone(),
'website' => $p->getWebsite(),
];
}
/**
* @return array<string, mixed>
*/
public static function contact(Contact $c): array
{
return [
'id' => $c->getId(),
'firstName' => $c->getFirstName(),
'lastName' => $c->getLastName(),
'jobTitle' => $c->getJobTitle(),
'email' => $c->getEmail(),
'phonePrimary' => $c->getPhonePrimary(),
'phoneSecondary' => $c->getPhoneSecondary(),
'clientId' => $c->getClient()?->getId(),
'prospectId' => $c->getProspect()?->getId(),
'prestataireId' => $c->getPrestataire()?->getId(),
];
}
/**
* @return array<string, mixed>
*/
public static function address(Address $a): array
{
return [
'id' => $a->getId(),
'label' => $a->getLabel(),
'street' => $a->getStreet(),
'streetComplement' => $a->getStreetComplement(),
'postalCode' => $a->getPostalCode(),
'city' => $a->getCity(),
'country' => $a->getCountry(),
'clientId' => $a->getClient()?->getId(),
'prospectId' => $a->getProspect()?->getId(),
'prestataireId' => $a->getPrestataire()?->getId(),
];
}
/**
* @return array<string, mixed>
*/
public static function reportDocument(ReportDocument $d): array
{
return [
'id' => $d->getId(),
'originalName' => $d->getOriginalName(),
'mimeType' => $d->getMimeType(),
'size' => $d->getSize(),
'createdAt' => $d->getCreatedAt()?->format('c'),
'uploadedBy' => self::user($d->getUploadedBy()),
];
}
/**
* @return array<string, mixed>
*/
public static function commercialReport(CommercialReport $r): array
{
return [
'id' => $r->getId(),
'subject' => $r->getSubject(),
'body' => $r->getBody(),
'occurredAt' => $r->getOccurredAt()?->format('Y-m-d'),
'type' => $r->getType()->value,
'typeLabel' => $r->getType()->label(),
'author' => self::user($r->getAuthor()),
'clientId' => $r->getClient()?->getId(),
'prospectId' => $r->getProspect()?->getId(),
'prestataireId' => $r->getPrestataire()?->getId(),
'documents' => array_map(
fn (ReportDocument $d) => self::reportDocument($d),
$r->getDocuments()->toArray()
),
'createdAt' => $r->getCreatedAt()?->format('c'),
'updatedAt' => $r->getUpdatedAt()?->format('c'),
];
}
/**
* @return array<string, mixed>
*/