Skip to main content

Conventions Techniques

Ce document decrit les conventions de code et les regles techniques a respecter lors du developpement sur Bemba.

UUID - Ramsey UUID

Toutes les entites utilisent Ramsey\Uuid\UuidInterface pour les identifiants.

:::danger Ne jamais utiliser Symfony\Component\Uid\Uuid :::

Implementation correcte

use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;

#[ORM\Entity]
class MonEntite
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
private UuidInterface $id;

public function __construct()
{
$this->id = Uuid::uuid4(); // PAS Uuid::v4()
}

public function getId(): UuidInterface // PAS Uuid
{
return $this->id;
}
}

HashRouter - URLs Frontend

Le frontend utilise HashRouter de React Router. Toutes les URLs generees par le backend doivent inclure /#/.

URLs correctes

// CORRECT
$url = $this->frontendUrl . '/#/auth/magic-link/' . $token;
$url = $this->frontendUrl . '/#/reset-password/' . $token;

// INCORRECT - ne fonctionne pas avec HashRouter
$url = $this->frontendUrl . '/auth/magic-link/' . $token;

RefreshToken

L'entite RefreshToken utilise des noms de methodes specifiques.

MethodeDescriptionAlternative incorrecte
setUsername(string)Email de l'utilisateursetUser()
setRefreshToken(string)Token aleatoiresetToken()
getRefreshToken()Recuperer le tokengetToken()
setValid(DateTimeImmutable)Date d'expirationsetExpiresAt()

Exemple

$refreshToken = new RefreshToken();
$refreshToken->setUsername($user->getEmail());
$refreshToken->setRefreshToken(bin2hex(random_bytes(32)));
$refreshToken->setValid(new \DateTimeImmutable('+7 days'));

User - Methodes de Securite

MethodeDescription
resetFailedAttempts()Reset tentatives echouees
getFailedLoginAttempts()Nombre de tentatives echouees
setFailedLoginAttempts(int)Definir le compteur

:::warning Attention resetFailedLoginAttempts() n'existe pas. Utiliser resetFailedAttempts(). :::


API URL en Developpement

Les appels fetch() natifs doivent utiliser REACT_APP_API_URL.

// CORRECT
const apiUrl = process.env.REACT_APP_API_URL || '/api';
const response = await fetch(`${apiUrl}/endpoint`);

// INCORRECT - pointe sur port 3000 en dev
const response = await fetch('/api/endpoint');

Configuration

# .env.development.local
REACT_APP_API_URL=http://localhost:8000/api

# .env.production
REACT_APP_API_URL=/api

React Query + Axios

L'API retourne { success: true, data: { ... } }. Avec Axios et React Query :

// L'API retourne: { success: true, data: { dashboard: {...} } }
// Axios extrait: response.data = { success: true, data: { dashboard: {...} } }
// React Query: { data: queryData } = useQuery(...)

const { data: queryData } = useQuery({ queryFn: () => settingsApi.get() });

// CORRECT - 2 niveaux de .data
queryData?.data?.dashboard

// INCORRECT - 3 niveaux = undefined
queryData?.data?.data?.dashboard // BUG!

Regle: queryData.data = l'objet retourne par l'API dans le champ data.


Entites Principales

Menage

Fichier: backend/src/Entity/Menage.php

Champs numeriques (DECIMAL)

Ces champs acceptent ?string, pas float :

  • latitude
  • longitude
  • altitude
  • precisionGps

Champs entiers

ChampTypeContrainte
tailleMenageint@Assert\Positive (> 0)
nbEnfants04intDefault 0
nbEnfants514intDefault 0
nbAdultes1564intDefault 0
nbPersonnes65PlusintDefault 0
nbHandicapesintDefault 0
nbActifsOccupesintDefault 0

Relations

RelationTypeEntite cible
chefMenageOneToOneChefMenage
logementOneToOneLogement
regionManyToOneRegion
secteurManyToOneSecteur
localiteManyToOneLocalite
campagneManyToOneCampagne
enqueteurManyToOneEnqueteur

ChefMenage

Fichier: backend/src/Entity/ChefMenage.php

Champ correctAlternative incorrecte
niveauEducationniveauEtude
occupationPrincipaleactivitePrincipale
statutMatrimonialsituationMatrimoniale

Logement

Fichier: backend/src/Entity/Logement.php

Champ correctAlternative incorrecte
nbPiecesnombrePieces
materiauMurnatureMurs
materiauToitnatureToit
materiauSolnatureSol
typeToilettetypeToilettes
combustibleCuisinesourceEnergieCuisson
evacuationDechetsevacuationOrdures

Gestion des Chaines Vides

Lors de la mise a jour des entites, convertir les chaines vides :

Type de champChaine vide '' devient
DECIMAL (GPS)null
INT nullablenull
INT non-nullable0 (sauf tailleMenage > 0)

Exemple

// GPS coordinates
$val = $data['latitude'];
$menage->setLatitude($val !== '' && $val !== null ? (string) $val : null);

// Nullable int
$chefMenage->setAge($data['age'] !== '' && $data['age'] !== null ? (int) $data['age'] : null);

// Non-nullable int with default 0
$menage->setNbEnfants04($val !== '' && $val !== null ? (int) $val : 0);

Routes API Custom

Menages

MethodeRouteDescription
POST/api/menages/{id}/updateMise a jour (evite conflit API Platform)
GET/api/menages/{id}/membresListe des membres

Cycles

MethodeRouteDescription
GET/api/cycles/{id}/documentsListe des documents
POST/api/cycles/{id}/documentsUpload document
DELETE/api/cycles/{id}/documents/{docId}Supprimer document

Campagnes

MethodeRouteDescription
GET/api/campagnesListe paginee
GET/api/campagnes/dropdownListe pour select
GET/api/campagnes/{id}/statsStatistiques globales
GET/api/campagnes/{id}/stats/enqueteursStats par enqueteur
GET/api/campagnes/{id}/stats/zonesStats par zone

Permissions

Format : module.action

Exemples

registre.menages_view
registre.menages_edit
registre.menages_validate
transferts.cycles_view
transferts.cycles_create
transferts.cycles_validate
rapports.export
campagnes.view
campagnes.create
campagnes.stats
enqueteurs.view
enqueteurs.create

Verification cote backend

// Dans un controleur
#[IsGranted('ROLE_ADMIN')]
public function create(): Response { ... }

// Ou via le voter
$this->denyAccessUnlessGranted('campagnes.create');

Verification cote frontend

import { usePermissions } from '../hooks/usePermissions';

const { can } = usePermissions();

{can('campagnes.create') && (
<Button onClick={handleCreate}>Nouvelle campagne</Button>
)}

Header UI - MainLayout

Le header global (profil, notifications, recherche, statut serveur) est gere par MainLayout.tsx.

:::warning Important Les pages individuelles (Dashboard, etc.) ne doivent PAS dupliquer ces elements. :::


Service Worker

Fichier: frontend/public/service-worker.js

Pour forcer une mise a jour du cache apres deploiement :

  1. Incrementer la version :

    const CACHE_NAME = 'pch-sig-cache-v5'; // Incrementer
  2. Informer les utilisateurs de faire Ctrl+Shift+R


Hierarchie Geographique (Guinee-Bissau)

Region (ex: Oio, Bafata)
└── Secteur (ex: Farim, Contuboel)
└── Localite / Tabanca (ex: Farim Centro, Canquelifa)

:::info Note La hierarchie geographique en Guinee-Bissau comprend 3 niveaux. Le Secteur depend de la Region, et la Localite depend du Secteur. :::


Prochaines Etapes