// 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 n’a é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 l’extrait, 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 d’arrondi 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({}) }) })