Lot 1, séquence A7 — Contrats
1er octobre 2026. Mise en œuvre de la séquence A7 du plan du Lot 1. Décisions : EC-028, EC-030, EC-051, EC-052, EC-070 ; écart ouvert : EC-073.
- Un contrat porte une contrepartie, une campagne, une période et des lignes : un produit, une quantité en grammes, un prix par unité de prix, une tolérance, une formule de réfaction. Des primes s’y ajoutent.
- Aucun montant sans devise. La devise d’une prime est celle de sa ligne, garantie par une clé composée.
- Un contrat est modifiable en brouillon, puis figé à l’activation : un changement de prix est un nouveau contrat.
- La formule de réfaction vit sur la ligne et se lit à la réception :
calculerPeseeSurContratest le premier appelant depesee.calculerPesee. - EC-070 corrigé : le taux de change de la base est publié en unités majeures, et le domaine l’adapte aux unités mineures.
- Aucune route REST en A7 : le contrat est gelé en A9, les écrans B3 demanderont leurs routes.
EC-070 : du taux publié aux unités mineures
Section intitulée « EC-070 : du taux publié aux unités mineures »app.taux_change stocke EUR → XOF à 655957/1000 : le taux publié, 1 EUR = 655,957 XOF, en unités majeures. Les montants du domaine sont en unités mineures (centimes d’euro, francs CFA). Il manquait le facteur 10^(décimales de la devise cible − décimales de la devise source) : lu tel quel, le taux aurait multiplié par 100 un montant EUR → XOF.
La base garde le taux publié (c’est la parité officielle, auditable). Le domaine l’adapte :
| Élément | Rôle |
|---|---|
app.devise |
Devises connues et leurs décimales (XOF 0, EUR 2, USD 2). Globale, en lecture seule pour l’API. Cible des clés étrangères de montants |
devises.tauxUnitesMineures |
EUR → XOF donne 655957/100000, XOF → EUR donne 100000/655957, comme VT-ENT-015 |
devises.choisirTaux |
Le taux publié le plus récent au plus tard à la date, dans le sens demandé, sinon l’inverse de l’autre sens ; un taux futur n’est pas appliqué |
taux.service.ts |
Lit la base, adapte, rend un TauxChange prêt pour convertirMontant ; convertirALaDate convertit un montant |
L’éligibilité au groupe (A4) résout maintenant son taux elle-même quand le chiffre d’affaires d’un producteur n’est pas dans la devise du seuil : un test passe par le taux réel de la base.
| Table | Contenu |
|---|---|
app.contrat |
Code unique, type (achat, vente, prestation), une contrepartie exactement (producteur, groupement, fournisseur ou client) cohérente avec le type, campagne, période, statut, conditions de paiement, pièce jointe |
app.contrat_ligne |
Produit (un par contrat), quantite_engagee_g, prix_unite_mineure par unite_prix, devise, tolerance_cp, formule_refaction en JSON |
app.prime |
Type fermé (bio, equitable, qualite), mode montant (par unité de prix, devise de la ligne) ou taux (centièmes de point du prix de base) ; un type par ligne |
Cohérence contrepartie et type : un achat va à un producteur, un groupement ou un fournisseur ; une vente à un client ; une prestation à un client ou à un fournisseur (EC-052). Le prix a une unité : 150 XOF par kilo ; le stock reste en grammes (EC-030).
Cycle de vie : brouillon → actif → solde ou resilie, sans retour ; un brouillon peut être abandonné (resilie). L’activation exige au moins une ligne et une campagne non clôturée. Hors brouillon, seul le statut change, y compris pour le rôle des migrations.
Formule de réfaction (EC-051, RG-171)
Section intitulée « Formule de réfaction (EC-051, RG-171) »Quatre formes, validées par FormuleRefaction (Ajv) : aucune, excedent_lineaire, lineaire_simple, bilan_de_masse, avec leurs normes en centièmes de point (0 à 10 000, et sous 10 000 pour une humidité de norme). Le mode d’arrondi fait partie de la formule : à la saisie il est facultatif et le service injecte plus_proche (valeur de départ d’EC-051, à confirmer sur un premier contrat type) ; une formule stockée sans arrondi est signalée (formule_invalide), jamais devinée.
versFormuleDomaine rapproche le type des schémas de celui du domaine par un switch exhaustif : si l’un gagne une formule que l’autre ignore, la compilation échoue.
Exemple chiffré (1 000 kg nets, humidité 17 %, impuretés 3 %, normes d’exemple 15 % et 2 %) : excédent linéaire 30 000 g de réfaction, linéaire simple 49 400 g, bilan de masse 52 824 g.
Services de l’API
Section intitulée « Services de l’API »enregistrerContrat (forme validée avant toute écriture, formule complétée, devise de prime reprise de la ligne), activerContrat (sur un refus, la transaction est à annuler), calculerPeseeSurContrat, valeurEngagee (valeur au prix de base par devise, un arrondi par ligne, hors primes).
Jeu de démonstration
Section intitulée « Jeu de démonstration »contrats.json : 2 contrats d’achat chargés en brouillon avec leurs lignes et leurs primes, puis activés (150 et 145 XOF le kilo ; le premier sans réfaction, fruit frais, le second avec une formule d’exemple non calibrée). Les prix d’origine (15 000 et 14 500 sans unité) valaient plus de 20 EUR le kilo et ont été corrigés. Le chargement est rejouable : les lignes étant figées une fois le contrat actif, il n’insère que ce qui manque.
| Test | Ce qu’il vérifie |
|---|---|
VT-DEV-001 à 013, VT-CONT-001 à 008 |
Taux d’unités mineures dans les deux sens, choix du taux daté, valeur d’une ligne à un seul arrondi |
db/tests/l1-a7.sql |
Devises en lecture seule, contrepartie et type, devise et unité connues, formule en objet typé, primes sans montant sans devise, cycle de vie, contrat figé, isolation |
apps/api/test/contrats.test.ts |
Formules invalides refusées avant écriture, arrondi par défaut, calculerPesee sur les quatre formules, devise de prime, contrat figé, valeur engagée, conversion par le taux réel de la base, éligibilité sur ce taux |
packages/schemas/src/contrat.test.ts |
Forme des formules, des lignes et des primes |
Hors séance
Section intitulée « Hors séance »Solde engagé − livré et alerte de tolérance, vente sans contrat « spot », référentiels exigés d’une vente (Lot 2) ; formule de réfaction par défaut du produit (EC-073) ; suppression d’une ligne d’un brouillon (le rôle de l’API n’a aucun droit de suppression : on abandonne le brouillon et on en crée un autre) ; filtrage des contrats sur le téléphone, qui n’ont pas toujours de groupement (client, fournisseur) : à régler avec les règles PowerSync (A9) ; routes REST (écrans B3).