*/ class DoctrineWeighingTicketRepository extends ServiceEntityRepository implements WeighingTicketRepositoryInterface { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, WeighingTicket::class); } public function findById(int $id): ?WeighingTicket { return $this->find($id); } public function save(WeighingTicket $ticket): void { $this->getEntityManager()->persist($ticket); $this->getEntityManager()->flush(); } public function createListQueryBuilder(?string $search = null): QueryBuilder { // Left-join des contreparties pour la recherche par nom (sans cartesien // dangereux : ManyToOne). Le cloisonnement par site courant est ajoute // par le SiteScopedQueryExtension (§ 2.3). Tri par defaut number DESC. $qb = $this->createQueryBuilder('wt') ->leftJoin('wt.client', 'c') ->leftJoin('wt.supplier', 's') ->andWhere('wt.deletedAt IS NULL') ->orderBy('wt.number', 'DESC') ; $this->applySearch($qb, $search); return $qb; } /** * Recherche fuzzy insensible a la casse sur le numero, le nom du client / * fournisseur, le libelle « Autre » et l'immatriculation (§ 4.1). * Metacaracteres LIKE (%, _, \) echappes pour rester litteraux. */ private function applySearch(QueryBuilder $qb, ?string $search): void { if (null === $search || '' === trim($search)) { return; } $escaped = str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], trim($search)); $pattern = '%'.mb_strtolower($escaped, 'UTF-8').'%'; $qb->andWhere( 'LOWER(wt.number) LIKE :search ' .'OR LOWER(c.companyName) LIKE :search ' .'OR LOWER(s.companyName) LIKE :search ' .'OR LOWER(wt.otherLabel) LIKE :search ' .'OR LOWER(wt.immatriculation) LIKE :search', )->setParameter('search', $pattern); } }