Scope versement reste-à-verser to its own compte(s), not the entrée total

versementStatus() was reusing reconciliationByEntree()'s aggregate
resteAVerser/surVerse, which sums residuals across every compte the
entrée touches -- including comptes tied to *other* sorties linked to
the same entrée. A versement paid entirely through Maud could show
"reste à verser" driven by an unrelated Sandrine/Chloé shortfall on
the same entrée, or vice versa mask its own compte's sur-versement
behind an unrelated compte's surplus.

reconciliationByEntree() now also keeps a per-compte residual map
(parCompteResidual), and versementStatus() sums only the residuals for
the compte(s) this specific versement's own répartition touches.

Verified against a real case: entrée EPAU F2549-50-51 (répartition
across 8 comptes) with one linked "Versement Maud" of -10 000€ against
an entrée-side Maud share of 5 632,86€. The entrée's own badge still
correctly shows the aggregate ("reste 24 325,54 € · sur-versé
4 367,14 €"), but the versement row itself now shows "Sur-versé :
4 367,14 €" -- its actual Maud-only residual -- instead of the
previous "Reste à verser", which was purely an artifact of the other
7 comptes' unrelated shortfalls.
This commit is contained in:
2026-09-05 11:09:37 +02:00
parent 6bdf12b8fe
commit 9a70205c83
@@ -304,8 +304,15 @@
let resteAVerser = 0;
let surVerse = 0;
const detail = [];
// Kept per-compte (not just folded into the two totals above) --
// versementStatus() below needs to check a single sortie's own
// compte(s) against the entrée, not the entrée's overall
// reconciliation, which can span *other* comptes tied to other
// sorties linked to the same entrée.
const parCompteResidual = {};
for (const c of comptes) {
const residual = Math.round(((entreeRow.parCompte[c] || 0) + (versementsParCompte[c] || 0)) * 100) / 100;
parCompteResidual[c] = residual;
if (residual > 0.01) resteAVerser += residual;
else if (residual < -0.01) surVerse += -residual;
if (Math.abs(residual) > 0.01) detail.push(c + ' : ' + this.formatEur(residual));
@@ -315,6 +322,7 @@
resteAVerser: Math.round(resteAVerser * 100) / 100,
surVerse: Math.round(surVerse * 100) / 100,
detail: detail.join(', ') || 'Entièrement soldé',
parCompteResidual,
});
}
return map;
@@ -423,13 +431,16 @@
},
// Flags a "versement freelance" row that isn't (fully) backed by
// the entrée client it pays out against: either not linked at all,
// or linked but reconciliationByEntree still shows a residual on
// that entrée. The residual belongs to the entrée as a whole, not
// to any one sortie -- when several versements share an entrée,
// each shows the same aggregate residual, since there's no way to
// say which specific one is "the" shortfall. Null (no highlight)
// when the linked entrée isn't in the currently loaded window --
// same accepted trade-off as reconciliationByEntree itself.
// or linked but its own compte(s) still show a residual against
// that entrée. Deliberately scoped to just the compte(s) this
// versement's own répartition touches (reconciliationByEntree's
// parCompteResidual), not the entrée's overall resteAVerser/
// surVerse -- those can be driven entirely by a *different* compte
// tied to some other sortie linked to the same entrée, which says
// nothing about whether this versement's own répartition is
// settled. Null (no highlight) when the linked entrée isn't in the
// currently loaded window -- same accepted trade-off as
// reconciliationByEntree itself.
versementStatus(item) {
if (item.type !== 'versement') return null;
if (!item.entreeLieeId) {
@@ -437,11 +448,18 @@
}
const recon = this.reconciliationByEntree.get(item.entreeLieeId);
if (!recon) return null;
if (recon.resteAVerser > 0.01) {
return { kind: 'reste', detail: 'Reste à verser sur lentrée liée : ' + this.formatEur(recon.resteAVerser) };
let resteAVerser = 0;
let surVerse = 0;
for (const c of Object.keys(item.parCompte)) {
const residual = recon.parCompteResidual[c] || 0;
if (residual > 0.01) resteAVerser += residual;
else if (residual < -0.01) surVerse += -residual;
}
if (recon.surVerse > 0.01) {
return { kind: 'sur-verse', detail: 'Sur-versé sur lentrée liée : ' + this.formatEur(recon.surVerse) };
if (resteAVerser > 0.01) {
return { kind: 'reste', detail: 'Reste à verser (comptes de cette ligne) : ' + this.formatEur(Math.round(resteAVerser * 100) / 100) };
}
if (surVerse > 0.01) {
return { kind: 'sur-verse', detail: 'Sur-versé (comptes de cette ligne) : ' + this.formatEur(Math.round(surVerse * 100) / 100) };
}
return null;
},