Aller au contenu

Lot 1, séquence A6 — Certification

1er octobre 2026. Mise en œuvre de la séquence A6 du plan du Lot 1. Décisions : EC-024 à EC-027, EC-053 ; écart ouvert : EC-072.

Amendée le 02/10/2026 par le Lot 1 bis, partie B (portée du certificat) : le périmètre JSON libre est remplacé par la table certificat_portee.

Amendée le 02/10/2026 par le Lot 1 bis (référentiel global et états par référentiel) : la table referentiel de cette page s’appelle désormais organisation_referentiel ; les six états ne sont plus une liste figée, ils viennent du pack référentiel ; les raisons statut_suspendu, statut_retire, conventionnel et conversion_annee_1 deviennent statut_hors_qualite et etat_non_revendicable.

  • « Expiré » se calcule, il ne se stocke pas. certificat.statut ne vaut que valide, suspendu ou retire. Un certificat est en vigueur à la date D si son statut est valide et si date_debut ≤ D ≤ date_fin (bornes incluses). Une opération datée avant l’expiration est couverte.
  • Un seul endroit décide du verdict : la fonction pure certification.peutRevendiquer (packages/domain). La base ne fournit que des faits ; l’API les assemble et appelle le domaine. Le téléphone (Lot 3) l’appellera sur les données synchronisées. Le plan A6 prévoyait aussi une fonction SQL du même nom : elle n’existe pas, pour ne pas avoir deux implémentations d’une règle de revendication qui divergent.
  • Un fournisseur ne se crée jamais sans son certificat complet (EC-053), garanti par la base.
  • Aucune route REST en A6 : le contrat est gelé en A9, les écrans B3 demanderont leurs routes.
Objet Contenu
organisation.source_conformite Ajoutée par A6 (le modèle la supposait, le socle ne l’avait pas) : interne par défaut, ou externe. Une seule source active par organisation
app.referentiel Liste propre à l’organisation (UE, NOP…), code unique, libellés fr et en. Alimente le facteur dynamique referentiel (EC-065)
app.certificat Titulaire exactement un : opérateur (l’organisation elle-même), groupement ou fournisseur (EC-025) ; organisme et code sans contrôle de format (EC-027) ; numéro ; dates ; statut ; PDF en fichier, obligatoire pour un fournisseur
app.statut_conformite Ajout seul. Unité exactement une (producteur, parcelle, zone) ; six états ; début de conversion obligatoire pour les états de conversion ; date d’effet ; source ; référence de décision ; motif ; groupement_id repris de l’unité
  • Fournisseur sans certificat : un déclencheur de contrainte différé vérifie, au commit, qu’il existe un certificat du fournisseur. Les deux se créent dans la même transaction, dans n’importe quel ordre ; une fiche écrite seule fait échouer le commit. Le PDF est obligatoire pour ce certificat.
  • Révision d’un statut : la première ligne d’une unité pour un référentiel n’a pas besoin de motif ; toute révision en exige un (RG-150). Le statut en vigueur à une date est la ligne de date d’effet la plus récente jusqu’à elle (app.statut_conformite_a).
  • Source : la source d’une ligne doit égaler organisation.source_conformite.
  • « Retiré » et « conventionnel » sont deux états distincts, en base comme dans le verdict (EC-026).
  • Alertes : app.certificats_a_surveiller(date, jours) liste les certificats valide expirés ou qui expirent dans jours jours. Le seuil est un paramètre ; le niveau d’alerte est jugé par le domaine.

peutRevendiquer({ etat, certificats, date }) : une unité peut revendiquer un référentiel si son statut à la date est bio ou conversion_annee_2_et_plus, et si au moins un certificat est en vigueur chez son groupement ou chez l’opérateur. Un seul maillon est jugé ; la chaîne complète « chaque maillon détient un certificat valide » (AV1-03) se compose avec les ventes, au Lot 2.

Statut de l’unité Verdict Vendable sous
bio, conversion_annee_2_et_plus revendique si un certificat est en vigueur —
conversion_annee_1 refus conversion_annee_1 conventionnel (vendableSous)
conventionnel refus conventionnel conventionnel
suspendu, retire refus statut_suspendu, statut_retire rien
aucun statut refus statut_inconnu rien

Quand aucun certificat n’est en vigueur, la raison est la plus proche de la validité : certificat_expire, puis certificat_pas_commence, certificat_suspendu, certificat_retire, ou certificat_absent.

apps/api/src/certification/certification.service.ts : creerFournisseurAvecCertificat (une transaction, un certificat refusé annule la fiche), enregistrerStatut (source prise sur l’organisation ; sur un refus, la transaction est à annuler), verdictRevendication (lit statut et certificats, laisse le domaine conclure), certificatsASurveiller (seuil par défaut PARAMETRES_CERTIFICATION, 60 jours, constante nommée en attendant le mécanisme de paramètres du noyau, comme EC-049 et EC-069).

Nouveaux fichiers referentiels.json (UE, NOP) et statuts.json : 74 statuts, soit un statut UE par producteur (40 bio, 10 en conversion année 1, 8 en année 2 et plus, 1 suspendu, 1 retiré) et un statut NOP pour les producteurs bio du groupement dont le certificat NOP est expiré. Les fichiers existants sont inchangés. Le chargement est rejouable : les statuts étant en ajout seul, il n’insère que les lignes absentes, car le déclencheur « motif obligatoire » s’exécute avant la détection de conflit.

Test Ce qu’il vérifie
VT-CERT-001 à 040 Bornes de validité, suspendu et retiré, alertes à 60 jours, chaque état, critère de fin n° 3, opération antérieure à l’expiration, groupement ou opérateur, raisons de refus
db/tests/l1-a6.sql Unicités, un titulaire exactement, dates, expire refusé, PDF du fournisseur, fournisseur sans certificat refusé au contrôle différé, ajout seul, motif de révision, source, conversion, statut à une date, alertes avec deux seuils, isolation
apps/api/test/certification.test.ts Création atomique du fournisseur, refus au commit d’une fiche seule, historique du statut, verdict sur les dates du jeu de démonstration, SR-L1-11, alertes

Routes REST de la certification (écrans B3) ; chaîne complète des maillons d’une vente (Lot 2, AV1-03) ; date d’une suspension ou d’un retrait de certificat (EC-072) ; règles PowerSync des nouvelles tables (A9).