Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .ai/AI_STARTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,30 @@ Ce guide détaille les 9 contextes où un champ doit potentiellement être ajout
- [ ] Lister les index importants pour les performances
- [ ] Documenter les tables principales et leurs relations

### Organisation des Repositories

Les repositories Doctrine sont progressivement réorganisés pour séparer les responsabilités :

**Principe général :**
- Les méthodes de Repository doivent retourner des **entités** ou des collections d'entités
- Les méthodes qui retournent des **données agrégées** (comptages, moyennes, statistiques) doivent être déplacées dans des **Query Services** dans `src/Repository/Query/`

**Structure de `src/Repository/Query/` :**
```
src/Repository/Query/
├── Dashboard/ # Requêtes pour le tableau de bord
├── EmailAlert/ # Requêtes pour les alertes email
├── SignalementList/ # Requêtes pour les listes de signalements
└── Statistics/ # Requêtes pour les statistiques (nouveau)
```

**Pattern des Query Services :**
- Namespace : `App\Repository\Query\{Domain}\`
- Injection : `EntityManagerInterface` (pas de Repository)
- Méthodes : retournent des données brutes (int, array, float, etc.)
- Nommage : suffixe `Query` (ex: `StatisticsCountQuery`)
```

## Commandes utiles

### Développement local
Expand Down Expand Up @@ -253,6 +277,34 @@ npm run build # Build production

## Pièges courants et bonnes pratiques

### Workflow de développement recommandé

**IMPORTANT : Toujours vérifier la qualité du code après les modifications**

Après avoir effectué des modifications de code (création, modification, suppression de fichiers PHP), **toujours** exécuter dans l'ordre :

1. **`make cs-fix`** - Corriger automatiquement le formatage du code (PHP-CS-Fixer)
2. **`make stan`** - Vérifier l'analyse statique (PHPStan niveau 6)

Ces commandes permettent de :
- Respecter les conventions de code PSR-12
- Détecter les erreurs de typage et les problèmes potentiels
- Maintenir la qualité du code du projet

**Exemple de workflow complet :**
```bash
# 1. Faire les modifications de code
# 2. Corriger le formatage
make cs-fix

# 3. Vérifier l'analyse statique
make stan

# 4. Si tout est OK, commiter
git add .
git commit -m "Description des modifications"
```

**TODO :**
- [ ] Lister les erreurs fréquentes (ex: oublier de flush() Doctrine)
- [ ] Documenter les problèmes de cache à éviter
Expand Down
4 changes: 3 additions & 1 deletion src/Controller/Back/BackCommuneController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Repository\AutoAffectationRuleRepository;
use App\Repository\CommuneRepository;
use App\Repository\PartnerRepository;
use App\Repository\Query\Commune\CommuneStatisticsQuery;
use App\Repository\SignalementRepository;
use App\Repository\TerritoryRepository;
use App\Service\Gouv\Ban\AddressService;
Expand Down Expand Up @@ -69,14 +70,15 @@ public function edit(
Commune $commune,
Request $request,
SignalementRepository $signalementRepository,
CommuneStatisticsQuery $communeStatisticsQuery,
AutoAffectationRuleRepository $autoAffectationRuleRepository,
PartnerRepository $partnerRepository,
TerritoryRepository $territoryRepository,
EntityManagerInterface $em,
AddressService $addressService,
): Response {
$poiCommune = $addressService->getMunicipalityByCityCode($commune->getNom(), $commune->getCodeInsee());
$countSignalementsWithCommune = $signalementRepository->countForCommune($commune);
$countSignalementsWithCommune = $communeStatisticsQuery->countForCommune($commune);
$inconsistentSignalements = $signalementRepository->findWithInconsistentCommuneName($commune);
$form = $this->createForm(CommuneType::class, $commune);
$originalNom = $commune->getNom();
Expand Down
9 changes: 6 additions & 3 deletions src/Repository/AffectationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Entity\JobEvent;
use App\Entity\Signalement;
use App\Entity\Territory;
use App\Repository\Query\Statistics\FilteredStatisticsQuery;
use App\Service\Interconnection\Esabora\EsaboraSCHSService;
use App\Service\Interconnection\Esabora\EsaboraSISHService;
use App\Service\ListFilters\SearchAffectationWithoutSubscription;
Expand All @@ -30,8 +31,10 @@ class AffectationRepository extends ServiceEntityRepository
{
private const int DELAY_VISITE_AFTER_AFFECTATION = 15;

public function __construct(ManagerRegistry $registry)
{
public function __construct(
ManagerRegistry $registry,
private readonly FilteredStatisticsQuery $filteredStatisticsQuery,
) {
parent::__construct($registry, Affectation::class);
}

Expand All @@ -47,7 +50,7 @@ public function countByPartenaireFiltered(StatisticsFilters $statisticsFilters):
$qb->leftJoin('a.partner', 'partner');
}

$qb = SignalementRepository::addFiltersToQueryBuilder($qb, $statisticsFilters);
$qb = $this->filteredStatisticsQuery->addFiltersToQueryBuilder($qb, $statisticsFilters);

return $qb->getQuery()
->getResult();
Expand Down
26 changes: 26 additions & 0 deletions src/Repository/Query/Commune/CommuneStatisticsQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Repository\Query\Commune;

use App\Entity\Commune;
use App\Entity\Signalement;
use Doctrine\ORM\EntityManagerInterface;

class CommuneStatisticsQuery
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
) {
}

public function countForCommune(Commune $commune): int
{
$qb = $this->entityManager->createQueryBuilder()
->from(Signalement::class, 's')
->select('COUNT(s.id)')
->where('s.inseeOccupant = :insee')
->setParameter('insee', $commune->getCodeInsee());

return (int) $qb->getQuery()->getSingleScalarResult();
}
}
73 changes: 73 additions & 0 deletions src/Repository/Query/Dashboard/NouveauxDossiersKpiQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace App\Repository\Query\Dashboard;

use App\Entity\Enum\AffectationStatus;
use App\Entity\Enum\SignalementStatus;
use App\Entity\Signalement;
use App\Entity\User;
use App\Entity\UserSignalementSubscription;
use App\Service\DashboardTabPanel\Kpi\CountNouveauxDossiers;
use Doctrine\ORM\EntityManagerInterface;

class NouveauxDossiersKpiQuery
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
) {
}

/**
* @param array<int, \App\Entity\Territory> $territories
*/
public function countNouveauxDossiersKpi(array $territories = [], ?User $user = null): CountNouveauxDossiers
{
$select = sprintf(
'NEW %s(
%s, -- countFormulaireUsager
%s, -- countFormulairePro
%s, -- countSansAffectation
%s, -- countNouveauxDossiers
%s -- countNoAgentDossiers
)',
CountNouveauxDossiers::class,
$user ? 0 : 'COALESCE(SUM(CASE WHEN s.statut = :statut_validation AND s.createdBy IS NULL THEN 1 ELSE 0 END), 0)',
$user ? 0 : 'COALESCE(SUM(CASE WHEN s.statut = :statut_validation AND s.createdBy IS NOT NULL THEN 1 ELSE 0 END), 0)',
$user ? 0 : 'COALESCE(SUM(CASE WHEN s.statut = :statut_active AND a.id IS NULL THEN 1 ELSE 0 END), 0)',
$user ? 'COALESCE(SUM(CASE WHEN a.partner IN (:partners) AND a.statut = :affectation_wait THEN 1 ELSE 0 END), 0)' : 0,
$user ? 'COALESCE(SUM(CASE WHEN a.partner IN (:partners) AND a.statut = :affectation_accepted AND NOT EXISTS(
SELECT 1 FROM '.UserSignalementSubscription::class.' uss
WHERE uss.signalement = s
AND EXISTS(
SELECT 1 FROM '.User::class.' u2
JOIN u2.userPartners up2
WHERE uss.user = u2
AND up2.partner IN (:partners)
)
) THEN 1 ELSE 0 END), 0)' : 0,
);

$qb = $this->entityManager->createQueryBuilder()
->from(Signalement::class, 's')
->select($select)
->leftJoin('s.affectations', 'a');

if (null === $user) {
$qb->setParameter('statut_active', SignalementStatus::ACTIVE);
$qb->setParameter('statut_validation', SignalementStatus::NEED_VALIDATION);
}

if (!empty($territories)) {
$qb->andWhere('s.territory IN (:territories)')
->setParameter('territories', $territories);
}

if ($user?->isUserPartner() || $user?->isPartnerAdmin()) {
$qb->setParameter('partners', $user->getPartners())
->setParameter('affectation_wait', AffectationStatus::WAIT)
->setParameter('affectation_accepted', AffectationStatus::ACCEPTED);
}

return $qb->getQuery()->getSingleResult();
}
}
Loading
Loading