Ajoute la TVA (%) et calcule automatiquement le Montant TTC

Architecture : Montant HT reste saisi à la main, un nouveau champ TVA
(%) le complète, et Montant TTC = round(HT * (1 + TVA/100), 2) devient
une valeur calculée plutôt que saisie -- champ readonly dans le
formulaire (aperçu live en JS), calcul faisant foi côté serveur dans
figli_compta_ledger_node_presave().

Migration (update hooks 8004/8005) : Montant TTC n'est **jamais**
modifié pour les données historiques -- seul un TVA effectif est
rétro-calculé depuis HT/TTC existants et ajouté en tant que nouvelle
métadonnée (1498 lignes renseignées, 58 laissées vides faute de TTC
source). Précision du champ TVA fixée à 4 décimales après vérification
empirique sur les 1556 lignes existantes : reconstruire TTC = HT * (1 +
TVA/100) avec un taux arrondi à 4 décimales ne s'écarte du TTC réel que
pour 9 lignes (probables factures multi-taux), contre 74 à 2 décimales.

Garde-fou supplémentaire dans le presave : le recalcul du TTC ne se
déclenche que si HT ou TVA ont réellement changé par rapport à la
révision précédente (comparaison à $node->original) -- sans ça,
rouvrir une ancienne ligne migrée pour corriger un simple libellé
aurait silencieusement dérivé son TTC historique de ±0,01€ à cause de
l'arrondi du taux rétro-calculé, ce qu'interdit la règle du projet de
ne jamais corriger les données historiques.

Réorganisation du formulaire : N° Facture rejoint Client sur une même
ligne, Montant HT/TVA/Montant TTC forment la ligne suivante -- les
poids de champs doivent rester des entiers (Drupal tronque silencieusement
tout poids fractionnaire lors de la sauvegarde de l'affichage).
This commit is contained in:
2026-09-07 11:36:53 +02:00
parent f00f680143
commit b7490eb76a
9 changed files with 354 additions and 29 deletions
@@ -0,0 +1,60 @@
/**
* @file
* Live preview of Montant TTC = Montant HT * (1 + TVA/100) on the ligne
* comptable add/edit form -- purely cosmetic, so the associate sees the
* computed amount before saving instead of a blank/stale readonly field.
* The authoritative computation is server-side, in
* figli_compta_ledger_node_presave() (figli_compta_ledger.module) --
* whatever ends up in this input on submit is discarded and recomputed
* there regardless of whether this script even ran.
*/
(function (Drupal, once) {
'use strict';
Drupal.behaviors.figliLedgerTvaCalc = {
attach: function (context) {
// once()'s selector match runs through context.querySelectorAll(),
// which -- like any querySelectorAll -- never matches the context
// node itself, only its descendants. The AJAX dialog opening this
// form passes the <form class="figli-ledger-form"> element itself
// as context, so a selector of '.figli-ledger-form' silently
// matched nothing and this behavior never ran. Selecting the HT
// input directly sidesteps that: it's always a genuine descendant
// of whatever context gets passed (document on a full page load,
// the form itself from the dialog, or anything in between).
once('figli-ledger-tva-calc', '[name="field_montant_ht[0][value]"]', context).forEach(function (ht) {
var form = ht.closest('form');
var tva = form.querySelector('[name="field_tva[0][value]"]');
var ttc = form.querySelector('[name="field_montant_ttc[0][value]"]');
if (!tva || !ttc) {
return;
}
function recompute() {
var htValue = parseFloat(ht.value);
if (isNaN(htValue)) {
return;
}
var tvaValue = parseFloat(tva.value);
if (isNaN(tvaValue)) {
tvaValue = 0;
}
ttc.value = (htValue * (1 + tvaValue / 100)).toFixed(2);
}
// Deliberately not called once up front on an existing (edit)
// line: the stored Montant TTC may predate this computed-field
// architecture and not be perfectly reproducible from HT/TVA to
// the centime (see figli_compta_ledger_update_8005()'s
// docblock) -- showing a recomputed number the instant the
// modal opens, before the associate has touched anything, would
// misrepresent what's actually saved. Only recompute once HT or
// TVA are actually edited, matching the same "only overwrite
// Montant TTC when HT/TVA actually changed" guard in
// figli_compta_ledger_node_presave().
ht.addEventListener('input', recompute);
tva.addEventListener('input', recompute);
});
}
};
})(Drupal, once);