Files
pdf_oralia_vibe/frontend/tests/totauxLocataire.test.js
Bertrand Benjamin 8c959f5d5f refactor: donne une source unique aux colonnes du compte rendu
L'ordre et les libellés des colonnes étaient retapés à six endroits : l'en-tête
du tableau, deux boucles de cellules, la ligne « Totaux », la table des libellés
et la liste des colonnes recoupées. Ces copies avaient déjà divergé — le solde
antérieur, rendu hors de la boucle, était comparé aux lignes sans que son écart
puisse s'afficher dans sa cellule. `COLONNES_CRG` devient la seule déclaration,
et tout le reste en dérive ; l'écart du solde antérieur s'affiche du coup là où
on le cherche.

`ecartsAvecLignes` rapporte maintenant l'extrait, le calculé et ce qui manque
entre les deux. Le composant refaisait cette soustraction pour son infobulle,
avec un `|| 0` là où l'utilitaire emploie `Number` et `Number.isFinite` : deux
règles de coercition pour un même calcul, libres de diverger.

Le contenu déplié d'un lot passe de `v-show` à `v-if`. Le tableau des lignes
compte une centaine de champs éditables ; les garder montés pour la vingtaine de
lots d'un document faisait re-rendre à chaque frappe deux mille champs que
personne ne regardait.

`setNestedValue` était recopié à l'identique dans trois composants et
`formatCurrency` redéfini dans le composant alors que `utils/format.js` existe
et dit lui-même que les nouveaux affichages passent par lui. Le premier part
dans `utils/chemin.js`, le second cède la place à `formatMontantPrecis`, dont le
formateur `Intl` est construit une fois pour toutes.

`EditableField` affirmait en dur que sa valeur venait du compte rendu. C'est vrai
des trois écrans qui l'emploient aujourd'hui, mais un formulaire de saisie
manuelle mentirait sans le savoir : l'origine devient une prop, avec cette
valeur par défaut.

Le type des lignes de report est défini des deux côtés de l'application sans
lien entre eux ; chacun renvoie désormais à l'autre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 14:18:58 +02:00

184 lines
6.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Le recoupement des totaux avec les lignes sert d'alerte sur les extractions
// incomplètes : il est éprouvé sur les configurations des comptes rendus réels.
import { describe, expect, it } from 'vitest'
import { ecartsAvecLignes, totauxCalcules } from '../src/utils/totauxLocataire.js'
// Lot 11 CHARLOT ANDREE : un loyer avec provision, réglé.
const ligneLoyer = {
type: 'loyer',
periode: { debut: '2026-06-01', fin: '2026-06-30' },
loyers: 809.64,
taxes: 0,
provisions: 69,
divers: { montant: 0, libelle: null },
total: 878.64,
regles: 878.64,
impayes: 0,
}
describe('totauxCalcules', () => {
it('agrège colonne par colonne, comme lœil sur le tableau', () => {
expect(totauxCalcules([ligneLoyer])).toEqual({
solde_anterieur: 0,
loyers: 809.64,
taxes: 0,
provisions: 69,
divers: 0,
total: 878.64,
regles: 878.64,
impayes: 0,
})
})
it('somme la colonne « total » telle quelle, sans la déduire des autres', () => {
// Le compte rendu ne remplit cette colonne que sur une ligne par bloc : les
// blocs se recomposent à l'échelle du lot, c'est ce total-là qui fait foi.
const totaux = totauxCalcules([
{ type: 'loyer', loyers: 997.02, provisions: 64, total: 0 },
{ type: 'loyer', loyers: 500, total: 1561.02 },
])
expect(totaux.total).toBe(1561.02)
expect(totaux.loyers).toBe(1497.02)
})
it('somme la colonne « impayé » plutôt que de la recalculer', () => {
const totaux = totauxCalcules([
{ type: 'loyer', loyers: 500, total: 500, regles: 400, impayes: 100 },
])
expect(totaux.impayes).toBe(100)
})
it('range le report de solde à part sans le confondre avec les loyers', () => {
// Lot 03 du compte rendu de février : un report de 0,63 et un loyer réglé.
const totaux = totauxCalcules([
{ type: 'solde_anterieur', loyers: 0.63, total: 0.63, regles: 0, impayes: 0.63 },
{ type: 'loyer', loyers: 640, provisions: 31, total: 671, regles: 671, impayes: 0 },
])
expect(totaux.solde_anterieur).toBe(0.63)
expect(totaux.loyers).toBe(640)
expect(totaux.total).toBe(671.63)
expect(totaux.impayes).toBe(0.63)
})
it('additionne les lignes de types différents', () => {
// Lot 09 TERRIER ADILE : régularisation de sortie, montants négatifs.
const totaux = totauxCalcules([
{ type: 'loyer', provisions: -6, total: 0 },
{ type: 'rappel_loyer', loyers: -138.98, total: 0 },
{ type: 'divers', divers: { montant: -455, libelle: 'Rembt dépot de garantie' }, total: 0 },
{ type: 'divers', divers: { montant: 23.02 }, total: -268.2, regles: -268.2 },
])
expect(totaux.loyers).toBe(-138.98)
expect(totaux.provisions).toBe(-6)
expect(totaux.divers).toBe(-431.98)
expect(totaux.total).toBe(-268.2)
expect(totaux.regles).toBe(-268.2)
})
it('traite les champs absents comme des zéros', () => {
const totaux = totauxCalcules([{ type: 'loyer', loyers: 500 }])
expect(totaux.loyers).toBe(500)
expect(totaux.total).toBe(0)
expect(totauxCalcules([]).total).toBe(0)
expect(totauxCalcules(undefined).total).toBe(0)
})
it('arrondit au centime plutôt que de traîner les flottants', () => {
const totaux = totauxCalcules([
{ type: 'loyer', loyers: 0.1 },
{ type: 'loyer', loyers: 0.2 },
])
expect(totaux.loyers).toBe(0.3)
})
})
describe('ecartsAvecLignes', () => {
const extraitsCharlot = {
solde_anterieur: 0,
loyers: 809.64,
taxes: 0,
provisions: 69,
divers: 0,
total: 878.64,
regles: 878.64,
impayes: 0,
}
it('ne signale rien quand les lignes recoupent la ligne « Totaux »', () => {
const calcules = totauxCalcules([ligneLoyer])
expect(ecartsAvecLignes(extraitsCharlot, calcules)).toEqual({})
})
it('signale une colonne Total qui ne somme pas, et le règlement qui manque avec', () => {
// Lot 07 de S_33680000_2025-11-25 : la dernière ligne a perdu ses colonnes
// Total et Regles à l'extraction, le compte rendu porte 707,29 € pour les deux.
const calcules = totauxCalcules([
{ type: 'loyer', loyers: -265.29, provisions: -50, total: 0, regles: 0 },
{ type: 'divers', divers: { montant: -87.85 }, total: -403.14, regles: -403.14 },
{ type: 'loyer', loyers: 265.29, provisions: 50, total: 0, regles: 0 },
{ type: 'divers', divers: { montant: 87.85 }, total: 403.14, regles: 403.14 },
{ type: 'loyer', loyers: 657.29, provisions: 50, total: 0, regles: 0 },
])
// Les colonnes de détail se recoupent, seules Total et Regles décrochent.
expect(calcules.loyers).toBe(657.29)
expect(calcules.provisions).toBe(50)
expect(calcules.divers).toBe(0)
const ecarts = ecartsAvecLignes(
{
solde_anterieur: 0,
loyers: 657.29,
taxes: 0,
provisions: 50,
divers: 0,
total: 707.29,
regles: 707.29,
impayes: 0,
},
calcules
)
expect(ecarts).toEqual({
total: { extrait: 707.29, calcule: 0, manquant: 707.29 },
regles: { extrait: 707.29, calcule: 0, manquant: 707.29 },
})
})
it('signale un divers absent des lignes extraites', () => {
const calcules = totauxCalcules([
{ type: 'divers', divers: { montant: -455 } },
{ type: 'divers', divers: { montant: 23.02 } },
])
expect(ecartsAvecLignes({ divers: -123.22 }, calcules)).toEqual({
divers: { extrait: -123.22, calcule: -431.98, manquant: 308.76 },
})
})
it('signale un lot dont aucune ligne na été extraite', () => {
// Lot 08 de S_33680000_2025-08-26 : 100 € réglés, aucune ligne.
const ecarts = ecartsAvecLignes({ loyers: 0, regles: 100 }, totauxCalcules([]))
expect(ecarts).toEqual({ regles: { extrait: 100, calcule: 0, manquant: 100 } })
})
it('rapporte lextrait, le calculé et ce qui manque entre les deux', () => {
const calcules = totauxCalcules([{ type: 'loyer', loyers: 500 }])
expect(ecartsAvecLignes({ loyers: 800 }, calcules)).toEqual({
loyers: { extrait: 800, calcule: 500, manquant: 300 },
})
})
it('tolère un écart darrondi sous le centime', () => {
const calcules = totauxCalcules([{ type: 'loyer', loyers: 100 }])
expect(ecartsAvecLignes({ loyers: 100.004 }, calcules)).toEqual({})
})
it('ignore une colonne que le compte rendu ne renseigne pas', () => {
const calcules = totauxCalcules([{ type: 'loyer', loyers: 100 }])
expect(ecartsAvecLignes({ loyers: 100, taxes: null }, calcules)).toEqual({})
})
it('ne compare rien sans totaux extraits', () => {
expect(ecartsAvecLignes(null, totauxCalcules([ligneLoyer]))).toEqual({})
})
})