La carte d'un lot n'affichait que quatre des huit colonnes du compte rendu, la colonne Total absente, et le bandeau replié montrait un total figé : corriger un règlement ne le changeait pas. Pire, les quatre agrégats modifiables ne sont jamais enregistrés — seules les lignes partent en base (`service.py`) — pendant que les colonnes réellement écrites, dont Regles et Impayé, n'étaient éditables nulle part. On corrigeait donc un champ sans effet, et pas celui qu'il fallait. Le détail des lignes reprend maintenant le tableau du compte rendu, colonne pour colonne, la ligne « Totaux » comprise en pied. Les dix champs d'une ligne et les huit de la ligne Totaux sont modifiables, sans exception : cette page sert à vérifier une extraction avant de l'enregistrer, l'utilisateur y a le dernier mot. Rien n'est plus recalculé à l'affichage ni reporté d'un champ sur un autre. Corriger une colonne ne déclenche que ce qui a été demandé — un total réécrit d'office effacerait sans le dire ce que le compte rendu porte. Un seul contrôle subsiste, en signalement pur : chaque colonne de la ligne « Totaux » est confrontée à la somme de cette même colonne sur les lignes. Le recoupement est celui que fait l'œil sur le tableau. Déduire le total des autres colonnes laissait passer le cas le plus parlant — une colonne Total qui ne somme visiblement pas, faute d'avoir extrait la valeur d'une ligne. Sur les 388 lots des documents extraits, ce contrôle signale quatre lots, tous de vraies extractions incomplètes : un règlement de 707,29 € qu'aucune ligne ne porte, un « divers » de 308,76 € sauté à un changement de page, deux lots réglés sans ligne. La colonne fautive passe en surbrillance et affiche la valeur calculée sous le montant extrait, sans jamais s'y substituer. Le solde antérieur quitte la colonne Loyers pour la colonne Période, où le compte rendu l'imprime. Compté à part sans être affiché à part, il donnait une colonne Loyers qui semblait ne pas sommer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
4.9 KiB
JavaScript
146 lines
4.9 KiB
JavaScript
// La carte locataire est la vue où l'on vérifie une extraction avant de
|
||
// l'enregistrer : elle ne doit afficher que des champs extraits, tous modifiables,
|
||
// et ne jamais substituer un calcul à ce que le compte rendu porte.
|
||
import { describe, expect, it } from 'vitest'
|
||
import { createSSRApp, h } from 'vue'
|
||
import { renderToString } from 'vue/server-renderer'
|
||
|
||
import LocataireCard from '../src/components/LocataireCard.vue'
|
||
|
||
// Lot 11 de M_33670000_2025-09-22, avec le règlement mal extrait observé sur un
|
||
// compte rendu ultérieur : le PDF annonce 7 878,00 € réglés pour 867,45 € dus.
|
||
function lotCharlot(surcharges = {}) {
|
||
return {
|
||
lot: { numero: '11', type: 'Appartement T3' },
|
||
locataire: { nom: 'CHARLOT ANDREE' },
|
||
lignes: [
|
||
{
|
||
type: 'loyer',
|
||
periode: { debut: '2025-09-01', fin: '2025-09-30' },
|
||
loyers: 798.45,
|
||
taxes: 0,
|
||
provisions: 69,
|
||
divers: { montant: 0, libelle: null },
|
||
total: 867.45,
|
||
regles: 867.45,
|
||
impayes: 0,
|
||
},
|
||
],
|
||
totaux: {
|
||
solde_anterieur: 0,
|
||
loyers: 798.45,
|
||
taxes: 0,
|
||
provisions: 69,
|
||
divers: 0,
|
||
total: 867.45,
|
||
regles: 867.45,
|
||
impayes: 0,
|
||
},
|
||
...surcharges,
|
||
}
|
||
}
|
||
|
||
// Les montants sont formatés avec des espaces insécables (séparateur de milliers,
|
||
// espace avant €) et le template en insère aux sauts de ligne : tous les blancs
|
||
// sont ramenés à un espace simple pour pouvoir chercher un montant.
|
||
async function rendre(locataire) {
|
||
const app = createSSRApp({
|
||
render: () => h(LocataireCard, { locataire }),
|
||
})
|
||
const html = await renderToString(app)
|
||
return html.replace(/\s+/g, ' ')
|
||
}
|
||
|
||
/** Les lignes seules, sans la ligne « Totaux » qui porte les valeurs déduites. */
|
||
function corpsDuTableau(html) {
|
||
return html.match(/<tbody>(.*)<\/tbody>/)[1]
|
||
}
|
||
|
||
describe('LocataireCard', () => {
|
||
it('reprend les colonnes du compte rendu, ligne « Totaux » comprise', async () => {
|
||
const html = await rendre(lotCharlot())
|
||
for (const colonne of ['Loyers', 'Taxes', 'Provisions', 'Divers', 'Total', 'Regles', 'Impaye']) {
|
||
expect(html).toContain(colonne)
|
||
}
|
||
expect(html).toContain('Totaux')
|
||
expect(html).toContain('Solde ant.')
|
||
})
|
||
|
||
it('laisse la ligne porter le total du compte rendu, sans le recalculer', async () => {
|
||
// Lot 13 de M_33670000_2025-08-26 : colonne « total » vide sur cette ligne,
|
||
// le compte rendu la porte sur une autre ligne du même bloc.
|
||
const lot = lotCharlot()
|
||
lot.lignes[0].loyers = 997.02
|
||
lot.lignes[0].provisions = 64
|
||
lot.lignes[0].total = 0
|
||
|
||
const lignes = corpsDuTableau(await rendre(lot))
|
||
expect(lignes).toContain('997,02 €')
|
||
expect(lignes).toContain('64,00 €')
|
||
// La somme des colonnes n'a pas à apparaître : la ligne n'est pas déduite.
|
||
expect(lignes).not.toContain('1 061,02 €')
|
||
})
|
||
|
||
it('n’affiche aucune valeur déduite quand l’extraction se recoupe', async () => {
|
||
const html = await rendre(lotCharlot())
|
||
expect(html).not.toContain('calc.')
|
||
expect(html).not.toContain('A verifier')
|
||
})
|
||
|
||
it('signale la colonne qui ne somme pas, sans toucher au montant extrait', async () => {
|
||
// Cas du lot 07 de S_33680000_2025-11-25 : la colonne Total des lignes reste
|
||
// à 0 alors que le compte rendu annonce 707,29 € pour le lot.
|
||
const lot = lotCharlot()
|
||
lot.lignes[0].total = 0
|
||
lot.totaux.total = 707.29
|
||
|
||
const html = await rendre(lot)
|
||
expect(html).toContain('A verifier')
|
||
expect(html).toContain('calculé : 0,00 €')
|
||
// Le montant du compte rendu reste affiché tel quel.
|
||
expect(html).toContain('707,29 €')
|
||
})
|
||
|
||
it('met la colonne en défaut en évidence', async () => {
|
||
const lot = lotCharlot()
|
||
lot.totaux.regles = 707.29
|
||
|
||
const html = await rendre(lot)
|
||
expect(html).toContain('bg-amber-500/10')
|
||
})
|
||
|
||
it('dit au survol ce qui est extrait et ce qui est calculé', async () => {
|
||
const lot = lotCharlot()
|
||
lot.totaux.regles = 707.29
|
||
|
||
const html = await rendre(lot)
|
||
expect(html).toContain('Valeur extraite du compte rendu — cliquer pour la modifier')
|
||
expect(html).toContain('Extrait du compte rendu : 707,29 €')
|
||
expect(html).toContain('Calculé sur les 1 ligne(s) : 867,45 €')
|
||
})
|
||
|
||
it('signale un lot dont aucune ligne n’a été extraite', async () => {
|
||
const lot = lotCharlot({ lignes: [] })
|
||
|
||
const html = await rendre(lot)
|
||
expect(html).toContain('Aucune ligne extraite')
|
||
expect(html).toContain('A verifier')
|
||
})
|
||
|
||
it('montre l’impayé du compte rendu dans le bandeau replié', async () => {
|
||
const lot = lotCharlot()
|
||
lot.totaux.impayes = 67.45
|
||
|
||
const html = await rendre(lot)
|
||
expect(html).toContain('Impayes: 67,45 €')
|
||
})
|
||
|
||
it('distingue un trop-perçu d’un impayé', async () => {
|
||
const lot = lotCharlot()
|
||
lot.totaux.impayes = -32.55
|
||
|
||
const html = await rendre(lot)
|
||
expect(html).toContain('Trop-percu: 32,55 €')
|
||
})
|
||
})
|