Lot 1, séquence B3 — Écrans des référentiels
1er octobre 2026. Mise en œuvre de la séquence B3 du plan du Lot 1, écran par écran. Les huit écrans sont livrés. ADR : 0041 (MapLibre), 0042 (react-jsonschema-form).
| Écran | État | Contrat d’API |
|---|---|---|
| Campagnes | Livré (lecture) | 1.2.0 |
| Produits et attributs | Livré (lecture, aperçu du formulaire) | 1.3.0 |
| Numérotation | Livré (aperçu en lecture) | 1.4.0 |
| Producteurs, groupements | Livré (liste, fiche, fusion) | 1.5.0 |
| Parcelles | Livré (liste, fiche, carte, import GeoJSON) | 1.6.0 |
| Certificats et statuts | Livré (lecture, synthèse, verdict) | 1.7.0 |
| Contrats | Livré (lecture : lignes, primes, réfaction lisible) | 1.8.0 |
| Capacités | Livré (lecture, double validation) | 1.9.0 |
Ce qui est en lecture seule, et pourquoi
Section intitulée « Ce qui est en lecture seule, et pourquoi »Trois écrans seulement écrivent : Producteurs (fusion, review.resolve), Parcelles (import de polygone, plot.survey) et Capacités (validation, capacity.approve). Ce sont les trois seuls cas où le catalogue des permissions (EC-038) a déjà le droit qu’il faut. Pour tout le reste (créer une campagne, éditer un produit, saisir un certificat ou réviser un statut, créer ou activer un contrat, proposer une capacité ou fixer un rendement), les services existent sans route, parce qu’aucune permission ne les couvre et qu’on n’en invente pas : EC-082, EC-083, EC-085, EC-086, EC-087. Il faut décider ces droits avant d’ouvrir les routes.
Campagnes
Section intitulée « Campagnes »GET /v1/campagnes rend désormais, pour chaque campagne, le pack figé à son ouverture : pack: { code, libelle, version } (contrat 1.2.0, ajout additif). L’écran liste code, nom, année, statut, période (réelle, sinon prévue), pack et version du modèle de numérotation.
Lecture seule : la création et l’ouverture n’ont ni service ni route (EC-082).
Produits et attributs
Section intitulée « Produits et attributs »GET /v1/produits (contrat 1.3.0) rend les produits de l’organisation avec leurs attributs typés, dans l’ordre du pack : code, noms fr et en, unité, matière première liée, pack d’origine, et pour chaque attribut son JSON Schema, son caractère obligatoire et son ordre. Accessible à tout membre actif.
L’écran liste les produits, montre la fiche et les attributs, puis un aperçu du formulaire généré par react-jsonschema-form depuis ces schémas (ADR 0042). Rien n’est enregistré : les valeurs d’attributs se saisissent sur les lots (Lot 2). Le formulaire et le serveur valident le même JSON Schema avec Ajv (test B3-01 : même verdict que validerAttributs). Le thème « Registre » (libellé au-dessus, erreur en clair) est dans apps/backoffice/src/formulaire/. L’écran est chargé à la demande.
Lecture seule : la création et la modification d’un produit n’ont ni service ni route (EC-083).
Numérotation
Section intitulée « Numérotation »GET /v1/numerotation/apercu?campagne=&produit=&nature= (contrat 1.4.0) rend le numéro que la prochaine attribution donnerait, son rang et le modèle figé de la campagne (segments). Il lit derniere_valeur + 1 de la séquence : rien n’avance, rien n’est verrouillé, donc l’aperçu est indicatif (test : deux appels donnent le même numéro, aucune ligne de séquence créée). Le format n’est calculé qu’à un endroit, numeroOfficiel du domaine ; attribuerNumero et l’aperçu partagent le même chargement (chargerContexte) et le même formatage.
Erreurs : campagne_introuvable, produit_introuvable (404), campagne_non_ouverte, sequence_hors_capacite, segment_manquant (409), parametre_invalide (400). Les messages fr et en sont dans les catalogues.
L’écran suit la campagne active du sélecteur : si elle n’est pas ouverte, il le dit. Les codes de nature (MP, REC, NET, PF) restent à confirmer (EC-068). Le design system §7 n’a pas d’entrée « Numérotation » : elle est placée sous Référentiels en attendant (EC-084).
Producteurs et groupements
Section intitulée « Producteurs et groupements »Quatre routes (contrat 1.5.0) :
| Route | Droit | Rend |
|---|---|---|
GET /v1/groupements |
membre actif | Groupements avec leur nombre de producteurs actifs |
GET /v1/producteurs |
membre actif | Liste filtrable (groupement, statut, q, limite ≤ 500, 200 par défaut), avec en_controle |
GET /v1/producteurs/{id} |
membre actif | Fiche, doublons de pièce, contrôles ouverts |
POST /v1/producteurs/{id}/fusionner |
review.resolve |
La fiche retenue, après fusion |
- Doublon signalé, jamais refusé (AV1-02) : la fiche montre les autres fiches actives qui portent la même pièce (comparaison normalisée) et les contrôles ouverts.
- Numéro de pièce masqué : seuls les trois derniers caractères sortent de l’API (
numero_piece_masque). Le numéro complet ne quitte pas le serveur ; aucun test ne le retrouve dans la réponse. - Fusion : le corps est
{ retenu_id, motif }et l’absorbée est dans le chemin. L’API appelle la fonction de la base (app.fusionner_producteurs), qui désactive la fiche absorbée, la renvoie vers la retenue et lève ses contrôles de doublon avec la décisionfusion(le même acte, pas deux). Rien n’est supprimé. Refus : motif vide ou même fiche (fusion_refusee, 400), fiche absente ou déjà fusionnée (fiche_introuvable, 404), auteur de la commande (levee_par_auteur, 403, SB-12). - Droit de la fusion :
review.resolve, comme la levée d’un contrôle, puisque la fusion en lève. À revoir avec EC-071. - Les lectures n’exigent que d’être membre actif : le catalogue des permissions n’a pas de droit de lecture des référentiels (EC-038). Les données personnelles (téléphone, village) sont donc lisibles de tout membre ; à revoir avec EC-071.
L’éligibilité au groupe (RG-024) n’est pas affichée : elle attend la route des surfaces mesurées (écran Parcelles).
Parcelles
Section intitulée « Parcelles »Trois routes (contrat 1.6.0) :
| Route | Droit | Rend |
|---|---|---|
GET /v1/parcelles |
membre actif | Liste filtrable (producteur, groupement, q, limite ≤ 500), avec la version en vigueur et en_controle |
GET /v1/parcelles/{id} |
membre actif | Fiche : versions de polygone, écart de surface, contrôles ouverts |
POST /v1/parcelles/{id}/polygone |
plot.survey |
La nouvelle version (201) |
- Écart de surface (GPS-04) : surface déclarée, surface mesurée par la base et incertitude (périmètre × précision médiane).
anomalieest vrai seulement si l’écart dépasse l’incertitude ; dans la marge, l’écran le dit en toutes lettres. Sans polygone valide,ecartest nul. Le calcul est celui du domaine (evaluerEcartDeLaParcelle). - Import : GeoJSON
Polygon,FeatureouFeatureCollectionà un seul polygone, sans trou (EC-048). Chaque import est une nouvelle version (ajout seul). L’API ne ferme ni ne corrige rien : un anneau non fermé est refusé (polygone_illisible), un polygone invalide est conservé et signalé par un contrôlepolygone_invalide, jamais réparé. Un chevauchement ouvre un contrôlechevauchement_parcelle(tolérance paramétrée, A5). - Droit :
plot.survey, le droit du relevé sur le terrain (parcelle.relever). - Carte (ADR 0041) : MapLibre, chargé à la demande (environ 1 Mo, un fichier séparé), fond uni (EC-081). Version en vigueur en trait plein, anciennes en pointillé, polygone invalide en couleur terre. Les contours sont dessinés en lignes : le découpage en tuiles de MapLibre écarte un polygone auto-intersecté, que le contour en ligne montre tel quel (constaté sur un nœud papillon, d’où ce choix). Exemple dans
/_galerieen développement. - Les tests de carte portent sur la préparation des données (B3-02, B3-03), pas sur le rendu WebGL.
Le client de l’API est maintenant créé à la première utilisation : la galerie s’ouvre sans configuration Keycloak.
Certificats et statuts
Section intitulée « Certificats et statuts »Trois routes de lecture (contrat 1.7.0), ouvertes à tout membre actif :
| Route | Rend |
|---|---|
GET /v1/certificats?referentiel=&date= |
Certificats par date de fin croissante, avec etat et alerte calculés à la date |
GET /v1/conformite/statuts?date= |
Par référentiel actif, le nombre d’unités par état de conformité à la date |
GET /v1/conformite/verdict?referentiel=&producteur=|parcelle=&date= |
Le verdict de revendication d’une unité |
- « Expiré » se calcule, il ne se stocke pas :
etatvient deetatCertificatdu domaine etalertedealerteCertificat, avec le seuil de 60 jours (RG-016, constantePARAMETRES_CERTIFICATION). Test : le même certificat est « en vigueur » au 01/10/2026 et « expiré » au 01/12/2026, sans que la base change. La date vaut aujourd’hui (UTC, horloge de l’API) par défaut ; une date inexistante est refusée (parametre_invalide). - Synthèse : pour chaque (unité, référentiel), la dernière ligne d’historique dont la date d’effet n’est pas postérieure à la date, la même règle que
app.statut_conformite_a. Une unité sans statut n’est pas comptée. - Verdict :
verdictRevendication(A6) puispeutRevendiquerdu domaine, un seul endroit qui décide. La réponse porte la qualité revendiquée (tampon de qualité), ou la raison du refus et la qualité sous laquelle l’unité reste vendable. - Aucune information par la couleur seule : chaque état de certificat porte un symbole et un libellé ; le vert ne sert qu’au tampon « bio ».
Lecture seule : la saisie d’un certificat, d’un fournisseur avec son certificat et la révision d’un statut de conformité (motif obligatoire, RG-150) existent en services mais n’ont pas de route : aucune permission du catalogue (EC-038) ne les couvre (EC-085).
Contrats
Section intitulée « Contrats »Deux routes de lecture (contrat 1.8.0), ouvertes à tout membre actif : GET /v1/contrats (filtres campagne, statut, type) et GET /v1/contrats/{id}.
- Valeur engagée : somme des lignes au prix de base, hors primes, par devise, avec un arrondi par ligne (
valeurLignedu domaine). Test d’intégration : 10 000 kg à 150 XOF/kg valent 1 500 000 XOF ; 2 000 kg à 2,50 EUR/kg valent 500 000 centimes d’euro ; les deux totaux ne se mélangent pas. - Formule de réfaction lisible : l’API rend la formule validée par son schéma (
FormuleRefaction), l’écran la met en phrase avec les pourcentages et le mode d’arrondi. Une formule stockée qui ne passe pas le schéma est rendue nulle et signalée « formule illisible » : elle n’est jamais devinée. - Primes : en montant (dans la devise de la ligne) ou en taux du prix de base. Aucun montant sans devise.
- Campagne : l’écran suit la campagne active du sélecteur, avec une case pour voir toutes les campagnes.
Lecture seule : la création d’un contrat en brouillon et son activation existent en services (enregistrerContrat, activerContrat) sans route, faute de permission dans le catalogue (EC-086).
Capacités
Section intitulée « Capacités »Deux routes (contrat 1.9.0) :
| Route | Droit | Rend |
|---|---|---|
GET /v1/capacites?campagne=&etat= |
membre actif | La version courante de chaque (cible, campagne), avec circuit de validation et solde |
POST /v1/capacites/{id}/validations |
capacity.approve |
La capacité après la décision |
- La base calcule, l’écran affiche.
capacite_g(surface mesurée × rendement) est la colonne générée de la base ; rien de ce qui compte n’est saisi. L’état (en_attente,validee,refusee) est celui deetat_validation_capacite, le seul endroit qui décide. Le motif du rendement (RG-023) est celui de la révision connue à la création de la capacité. - Double validation : la validation se fait sous un rôle (
directionoursci) que le validateur prouve par l’ensemble de permissions qu’il détient, en plus decapacity.approve(EC-075). La séparation des tâches est tenue par le service et par la base (SB-11) : l’auteur ne valide pas sa capacité (separation_des_taches), une personne ne valide pas sous deux rôles, un rôle ne valide qu’une fois (validation_refusee, 409). Un refus exige un commentaire (commentaire_requis, 400). Une décision refusée du service annule la transaction : aucune validation à moitié écrite. - Applicable (EC-012) :
applicableest vrai seulement si cette version est la capacité applicable de la cible. Test : après un nouveau relevé du polygone, la capacité restevalideemais n’est plusapplicable, et l’écran l’écrit. - Solde : consommé, reste (négatif en cas de dépassement, jamais masqué, RG-022), taux et niveau (
normal,seuil,depassement). Le niveau vient deevaluerCollectedu domaine sur une collecte nulle, avec le seuil de 80 % (constantePARAMETRES_CAPACITE). Il s’affiche avec la jauge du design system. Absent tant que la capacité n’est pas validée. - L’écran suit la campagne active du sélecteur.
Hors écran : la proposition d’une capacité et la saisie d’un rendement (motif obligatoire) existent en services (proposerCapacite, definirRendement) sans route, faute de permission dans le catalogue (EC-087).