feat: annonce les reecritures que l'enregistrement imposera
Refuser une modification, c'est reenvoyer la valeur actuelle du document. Mais
l'enregistrement ramene les donnees a une forme canonique (aujourd'hui les
numeros de lot) : sur ces champs, le refus n'est pas conserve. L'interface le
laissait croire.
Plutot que de traiter le cas des lots en dur, le serveur devient seul juge de ce
qu'il stocke et l'annonce : `canonicalize_extraction` regroupe les reecritures
faites a la persistance, `save_document` et l'apercu passent par elle, et
/api/documents/{id}/re-extract renvoie `previous_canonical` a cote de
`previous_data`. Le front compare ces deux jeux et signale, sous chaque case
decochee, la valeur que l'enregistrement reecrira — sans connaitre aucune regle.
Une transformation ajoutee plus tard remontera donc toute seule dans l'interface.
Toutes les differences restent visibles et toutes les cases actives : voir ce
qui change et pouvoir se prononcer prime, quitte a etre prevenu que ce champ-la
reviendra reecrit.
Supprime au passage frontend/src/utils/lots.js, qui dupliquait la regle de
normalisation cote client.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
<div v-if="diff.metadata.length">
|
||||
<div class="font-semibold text-amber-300 mb-1">Métadonnées</div>
|
||||
<div class="space-y-0.5">
|
||||
<div v-for="c in diff.metadata" :key="c.id" class="flex items-baseline gap-2">
|
||||
<div v-for="c in diff.metadata" :key="c.id">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<component :is="selectable ? 'label' : 'span'" class="flex items-baseline gap-2 flex-1 min-w-0" :class="selectable && 'cursor-pointer'">
|
||||
<input
|
||||
v-if="selectable"
|
||||
@@ -28,6 +29,8 @@
|
||||
@input="edit(c, $event.target.value)"
|
||||
class="flex-1 min-w-0 px-1.5 py-0.5 rounded border border-gray-600 bg-gray-900 text-green-400 font-medium disabled:opacity-40 disabled:line-through focus:ring-1 focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="overrideLabel(c.id)" class="pl-6 text-orange-700">⚠ {{ overrideLabel(c.id) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,19 +53,19 @@
|
||||
</span>
|
||||
<span class="font-medium text-gray-200">{{ loc.title }}</span>
|
||||
</div>
|
||||
<p v-if="loc.id && overrideLabel(loc.id)" class="pl-5 text-orange-700 mb-0.5">⚠ {{ overrideLabel(loc.id) }}</p>
|
||||
|
||||
<div v-for="c in loc.changes" :key="c.id">
|
||||
<!-- Champ identitaire : cochable et modifiable individuellement -->
|
||||
<div v-if="c.kind === 'champ'" class="flex items-baseline gap-2 pl-2">
|
||||
<component :is="selectable && !c.forced ? 'label' : 'span'" class="flex items-baseline gap-2 flex-1 min-w-0" :class="selectable && !c.forced && 'cursor-pointer'">
|
||||
<div v-if="c.kind === 'champ'" class="pl-2">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<component :is="selectable ? 'label' : 'span'" class="flex items-baseline gap-2 flex-1 min-w-0" :class="selectable && 'cursor-pointer'">
|
||||
<input
|
||||
v-if="selectable"
|
||||
type="checkbox"
|
||||
:checked="accepted(c.id)"
|
||||
:disabled="c.forced"
|
||||
:title="c.hint"
|
||||
@change="toggle(c.id, $event.target.checked)"
|
||||
class="w-3.5 h-3.5 rounded flex-shrink-0 self-center disabled:opacity-50"
|
||||
class="w-3.5 h-3.5 rounded flex-shrink-0 self-center"
|
||||
/>
|
||||
<span class="text-gray-500 w-24 flex-shrink-0">{{ c.label }}</span>
|
||||
<span class="text-red-400 break-all" :class="accepted(c.id) && 'line-through'">{{ c.before }}</span>
|
||||
@@ -79,7 +82,8 @@
|
||||
@input="edit(c, $event.target.value)"
|
||||
class="flex-1 min-w-0 px-1.5 py-0.5 rounded border border-gray-600 bg-gray-900 text-green-400 font-medium disabled:opacity-40 disabled:line-through focus:ring-1 focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
|
||||
/>
|
||||
<span v-if="selectable && c.forced" class="text-gray-400 italic flex-shrink-0">{{ c.hint }}</span>
|
||||
</div>
|
||||
<p v-if="overrideLabel(c.id)" class="pl-6 text-orange-700">⚠ {{ overrideLabel(c.id) }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Montants et lignes : un seul bloc, pour que les sommes restent justes -->
|
||||
@@ -103,6 +107,7 @@
|
||||
<span class="text-green-400 font-medium break-all" :class="!accepted(c.id) && 'line-through'">{{ f.after }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="overrideLabel(c.id)" class="pl-5 text-orange-700">⚠ {{ overrideLabel(c.id) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,6 +136,7 @@
|
||||
<div class="text-red-400 break-all" :class="accepted(op.id) && 'line-through'">{{ op.before }}</div>
|
||||
<div class="text-green-400 font-medium break-all" :class="!accepted(op.id) && 'line-through'">{{ op.after }}</div>
|
||||
</div>
|
||||
<p v-if="overrideLabel(op.id)" class="pl-2 text-orange-700">⚠ {{ overrideLabel(op.id) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,6 +146,8 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { fmtValue } from '../utils/diffExtraction.js'
|
||||
|
||||
const props = defineProps({
|
||||
diff: {
|
||||
type: Object,
|
||||
@@ -151,12 +159,27 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
// Changements que l'enregistrement réécrira quoi qu'on décide, par
|
||||
// identifiant → valeur imposée (cf. computeServerOverrides).
|
||||
serverOverrides: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:selection'])
|
||||
|
||||
const selectable = computed(() => props.selection !== null)
|
||||
|
||||
// Texte d'alerte quand refuser un changement ne servirait à rien.
|
||||
function overrideLabel(id) {
|
||||
if (!selectable.value || accepted(id)) return null
|
||||
if (!(id in props.serverOverrides)) return null
|
||||
const value = props.serverOverrides[id]
|
||||
const shown = value != null && typeof value === 'object' ? '' : ` « ${fmtValue(value)} »`
|
||||
return `L’enregistrement réécrira cette valeur${shown}`
|
||||
}
|
||||
|
||||
function accepted(id) {
|
||||
if (!selectable.value) return true
|
||||
const state = props.selection[id]
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
<ExtractionDiffDetails
|
||||
:diff="item.diff"
|
||||
:selection="item.selection"
|
||||
:server-overrides="item.serverOverrides"
|
||||
@update:selection="updateSelection(item, $event)"
|
||||
/>
|
||||
|
||||
@@ -321,7 +322,11 @@
|
||||
import { computed, onMounted, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import ExtractionDiffDetails from '../components/ExtractionDiffDetails.vue'
|
||||
import TagAutocomplete from '../components/TagAutocomplete.vue'
|
||||
import { computeExtractionDiff, listChangeIds } from '../utils/diffExtraction.js'
|
||||
import {
|
||||
computeExtractionDiff,
|
||||
computeServerOverrides,
|
||||
listChangeIds,
|
||||
} from '../utils/diffExtraction.js'
|
||||
import { countAccepted, mergeExtraction } from '../utils/mergeExtraction.js'
|
||||
import { fillPredictions, remapTags, toDepensesTags } from '../utils/tagRemap.js'
|
||||
|
||||
@@ -432,6 +437,8 @@ function resetItems() {
|
||||
newData: null,
|
||||
// État des cases du détail, par identifiant de changement.
|
||||
selection: {},
|
||||
// Changements que l'enregistrement réécrira, quel que soit le choix.
|
||||
serverOverrides: {},
|
||||
changeIds: [],
|
||||
counts: { accepted: 0, total: 0 },
|
||||
tags: {},
|
||||
@@ -492,6 +499,11 @@ async function scanOne(item) {
|
||||
item.changeIds = listChangeIds(diff)
|
||||
item.selection = {}
|
||||
item.counts = countAccepted(item.changeIds, {})
|
||||
item.serverOverrides = computeServerOverrides(
|
||||
diff,
|
||||
result.previous_data,
|
||||
result.previous_canonical
|
||||
)
|
||||
|
||||
await prepareTags(item, result)
|
||||
item.status = 'modifie'
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
// (nom, numéro de lot, métadonnées), un bloc pour les montants et lignes d'un
|
||||
// locataire — les retenir séparément produirait des totaux qui ne somment plus.
|
||||
|
||||
import { sameLot } from './lots.js'
|
||||
|
||||
const EPS = 0.005
|
||||
|
||||
function numEq(a, b) {
|
||||
@@ -116,10 +114,6 @@ function diffLocataire(index, before, after) {
|
||||
const bv = get(before, path)
|
||||
const av = get(after, path)
|
||||
if (!valEq(bv, av)) {
|
||||
// Une réécriture de forme du numéro de lot ("0001" → "01") est imposée par
|
||||
// le backend, qui normalise tout enregistrement : inutile de proposer de
|
||||
// la refuser, la valeur reviendrait normalisée.
|
||||
const forced = path === 'lot.numero' && sameLot(bv, av)
|
||||
changes.push({
|
||||
id: `loc:${index}:${path}`,
|
||||
kind: 'champ',
|
||||
@@ -129,9 +123,7 @@ function diffLocataire(index, before, after) {
|
||||
after: fmtValue(av),
|
||||
beforeValue: bv,
|
||||
afterValue: av,
|
||||
editable: !forced,
|
||||
forced,
|
||||
hint: forced ? 'Numéro normalisé automatiquement à l’enregistrement' : undefined,
|
||||
editable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -308,6 +300,68 @@ export function computeExtractionDiff(before, after) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repère les changements que le serveur imposera même si on les refuse.
|
||||
*
|
||||
* Refuser un changement, c'est réenvoyer la valeur actuelle du document. Or
|
||||
* l'enregistrement réécrit certaines valeurs sous une forme canonique : si la
|
||||
* valeur actuelle n'est pas déjà canonique, la refuser ne changera rien.
|
||||
*
|
||||
* On le déduit en comparant les données actuelles à leur forme canonique, toutes
|
||||
* deux fournies par le serveur : aucune règle de réécriture n'est connue ici, et
|
||||
* une transformation ajoutée plus tard remonte sans modification de ce code.
|
||||
*
|
||||
* @returns {Object} identifiant de changement → valeur que le serveur écrira
|
||||
*/
|
||||
export function computeServerOverrides(diff, previous, canonical) {
|
||||
const overrides = {}
|
||||
if (!previous || !canonical) return overrides
|
||||
|
||||
const differs = (a, b) => JSON.stringify(a ?? null) !== JSON.stringify(b ?? null)
|
||||
|
||||
for (const change of diff.metadata) {
|
||||
const actuel = get(previous.metadata, change.path)
|
||||
const impose = get(canonical.metadata, change.path)
|
||||
if (differs(actuel, impose)) overrides[change.id] = impose
|
||||
}
|
||||
|
||||
const prevLoc = previous.situation_locataires || []
|
||||
const canonLoc = canonical.situation_locataires || []
|
||||
for (const loc of diff.locataires) {
|
||||
const actuel = prevLoc[loc.index]
|
||||
const impose = canonLoc[loc.index]
|
||||
if (actuel === undefined) continue // locataire ajouté : rien à conserver
|
||||
|
||||
if (loc.kind !== 'modified') {
|
||||
// Refuser, c'est conserver le locataire entier tel qu'il est en base.
|
||||
if (differs(actuel, impose)) overrides[loc.id] = impose
|
||||
continue
|
||||
}
|
||||
for (const change of loc.changes) {
|
||||
if (change.kind === 'montants') {
|
||||
if (differs(actuel?.totaux, impose?.totaux) || differs(actuel?.lignes, impose?.lignes)) {
|
||||
overrides[change.id] = { totaux: impose?.totaux, lignes: impose?.lignes }
|
||||
}
|
||||
} else {
|
||||
const a = get(actuel, change.path)
|
||||
const b = get(impose, change.path)
|
||||
if (differs(a, b)) overrides[change.id] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prevOps = previous.recapitulatif_operations || []
|
||||
const canonOps = canonical.recapitulatif_operations || []
|
||||
for (const op of diff.operations) {
|
||||
if (op.kind !== 'removed') continue // un ajout refusé n'est pas enregistré
|
||||
if (differs(prevOps[op.oldIndex], canonOps[op.oldIndex])) {
|
||||
overrides[op.id] = canonOps[op.oldIndex]
|
||||
}
|
||||
}
|
||||
|
||||
return overrides
|
||||
}
|
||||
|
||||
// Identifiants de tous les changements sélectionnables, dans l'ordre d'affichage.
|
||||
// Un locataire ajouté ou supprimé compte pour un ; un locataire modifié expose
|
||||
// une entrée par champ identitaire, plus une pour ses montants.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Miroir de la normalisation des numéros de lot appliquée par le backend
|
||||
// (src/plesna_gerance/utils/lots.py) : tout enregistrement y passe, quelle que
|
||||
// soit la valeur envoyée. Le front en a besoin pour ne pas proposer de refuser
|
||||
// un changement que le serveur réappliquera de toute façon.
|
||||
|
||||
const LOT_NUMERO_WIDTH = 2
|
||||
const LOT_NUMERO_INCONNU = '00'
|
||||
|
||||
export function normalizeLotNumero(value) {
|
||||
if (value == null) return null
|
||||
const digits = String(value).replace(/\D/g, '')
|
||||
if (!digits) return null
|
||||
const significant = digits.replace(/^0+/, '')
|
||||
if (!significant) return LOT_NUMERO_INCONNU
|
||||
return significant.padStart(LOT_NUMERO_WIDTH, '0')
|
||||
}
|
||||
|
||||
// Vrai quand deux écritures désignent le même lot ("0001" et "01").
|
||||
export function sameLot(a, b) {
|
||||
return normalizeLotNumero(a) === normalizeLotNumero(b)
|
||||
}
|
||||
@@ -8,6 +8,12 @@
|
||||
// `selection` associe l'identifiant d'un changement (cf. diffExtraction.js) à
|
||||
// { accepted, value } ; une entrée absente vaut « retenu », pour que le cas
|
||||
// courant (tout accepter) ne demande aucune initialisation.
|
||||
//
|
||||
// Limite connue : le backend normalise les numéros de lot de tout enregistrement
|
||||
// (save_document → normalize_extraction_lots). Refuser une réécriture de forme
|
||||
// « 0001 » → « 01 » produit bien « 0001 » ici, mais la base portera « 01 ».
|
||||
// Choix assumé : mieux vaut voir toutes les différences et pouvoir se prononcer,
|
||||
// même si ce champ-là revient normalisé.
|
||||
|
||||
function clone(value) {
|
||||
return value == null ? value : JSON.parse(JSON.stringify(value))
|
||||
@@ -18,13 +24,6 @@ function isAccepted(selection, id) {
|
||||
return state ? state.accepted !== false : true
|
||||
}
|
||||
|
||||
// Un changement imposé (normalisation appliquée par le backend) est appliqué
|
||||
// quoi qu'il arrive : le refuser produirait un écart entre ce qu'affiche
|
||||
// l'interface et ce qui finit en base.
|
||||
function isApplied(selection, change) {
|
||||
return change.forced === true || isAccepted(selection, change.id)
|
||||
}
|
||||
|
||||
// Valeur à écrire pour un changement retenu : celle saisie à la main si elle
|
||||
// existe, sinon celle proposée par la nouvelle extraction.
|
||||
function chosenValue(selection, change) {
|
||||
@@ -58,7 +57,7 @@ export function mergeExtraction(previous, next, diff, selection = {}) {
|
||||
// --- Métadonnées : un champ retenu écrase le champ correspondant.
|
||||
if (!merged.metadata) merged.metadata = {}
|
||||
for (const change of diff.metadata) {
|
||||
if (isApplied(selection, change)) {
|
||||
if (isAccepted(selection, change.id)) {
|
||||
setPath(merged.metadata, change.path, chosenValue(selection, change))
|
||||
}
|
||||
}
|
||||
@@ -90,7 +89,7 @@ export function mergeExtraction(previous, next, diff, selection = {}) {
|
||||
|
||||
const kept = clone(previousLoc[i])
|
||||
for (const change of entry.changes) {
|
||||
if (!isApplied(selection, change)) continue
|
||||
if (!isAccepted(selection, change.id)) continue
|
||||
if (change.kind === 'montants') {
|
||||
// Les chiffres d'un locataire viennent en bloc du même parser.
|
||||
kept.totaux = clone(nextLoc[i]?.totaux)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// La fusion décide de ce qui est écrit en base : elle est testée pièce par pièce.
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { computeExtractionDiff, listChangeIds } from '../src/utils/diffExtraction.js'
|
||||
import {
|
||||
computeExtractionDiff,
|
||||
computeServerOverrides,
|
||||
listChangeIds,
|
||||
} from '../src/utils/diffExtraction.js'
|
||||
import { countAccepted, mergeExtraction } from '../src/utils/mergeExtraction.js'
|
||||
|
||||
// Données en base, inspirées d'un compte rendu réel.
|
||||
@@ -13,13 +17,13 @@ const previous = {
|
||||
},
|
||||
situation_locataires: [
|
||||
{
|
||||
lot: { numero: '0007', type: 'Appartement' },
|
||||
lot: { numero: '07', type: 'Appartement' },
|
||||
locataire: { nom: 'LATAPY NINA' },
|
||||
totaux: { total: 1200, regles: 1200, impayes: 0 },
|
||||
lignes: [{ type: 'loyer', loyers: 500, total: 500 }],
|
||||
},
|
||||
{
|
||||
lot: { numero: '0008', type: 'Appartement' },
|
||||
lot: { numero: '08', type: 'Appartement' },
|
||||
locataire: { nom: 'BESSON Léa' },
|
||||
totaux: { total: 800, regles: 800, impayes: 0 },
|
||||
lignes: [{ type: 'loyer', loyers: 800, total: 800 }],
|
||||
@@ -31,9 +35,11 @@ const previous = {
|
||||
],
|
||||
}
|
||||
|
||||
// Nouvelle extraction : corrige les numéros de lot, mais agrège un libellé dans
|
||||
// le nom du locataire (régression du parser), change des montants, ajoute une
|
||||
// Nouvelle extraction : rattache le premier locataire à un autre lot, agrège un
|
||||
// libellé dans son nom (régression du parser), change ses montants, ajoute une
|
||||
// opération et en perd une autre.
|
||||
// Les numéros de lot arrivent des deux côtés sous leur forme normalisée : le
|
||||
// backend les normalise avant de renvoyer les données à comparer.
|
||||
const next = {
|
||||
metadata: {
|
||||
document: { reference: '33680000', date: '2026-06-22' },
|
||||
@@ -42,7 +48,7 @@ const next = {
|
||||
},
|
||||
situation_locataires: [
|
||||
{
|
||||
lot: { numero: '07', type: 'Appartement' },
|
||||
lot: { numero: '12', type: 'Appartement' },
|
||||
locataire: { nom: 'LATAPY NINA Complément loyer' },
|
||||
totaux: { total: 1250, regles: 1200, impayes: 50 },
|
||||
lignes: [
|
||||
@@ -80,33 +86,6 @@ describe('découpage du diff', () => {
|
||||
expect(montants.fields.length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('marque comme imposée la seule réécriture de forme d’un numéro de lot', () => {
|
||||
// Le backend normalise tout numéro de lot à l'enregistrement : refuser
|
||||
// "0007" → "07" n'aurait aucun effet, la case ne doit pas le laisser croire.
|
||||
const lot = diff.locataires[0].changes.find((c) => c.path === 'lot.numero')
|
||||
expect(lot.forced).toBe(true)
|
||||
expect(lot.editable).toBe(false)
|
||||
|
||||
const merged = merge({ [lot.id]: { accepted: false } })
|
||||
expect(merged.situation_locataires[0].lot.numero).toBe('07')
|
||||
})
|
||||
|
||||
it('laisse refusable un vrai changement de lot', () => {
|
||||
const autreLot = {
|
||||
...next,
|
||||
situation_locataires: [
|
||||
{ ...next.situation_locataires[0], lot: { numero: '12', type: 'Appartement' } },
|
||||
next.situation_locataires[1],
|
||||
],
|
||||
}
|
||||
const d = computeExtractionDiff(previous, autreLot)
|
||||
const lot = d.locataires[0].changes.find((c) => c.path === 'lot.numero')
|
||||
|
||||
expect(lot.forced).toBe(false) // "0007" et "12" ne sont pas le même lot
|
||||
const merged = mergeExtraction(previous, autreLot, d, { [lot.id]: { accepted: false } })
|
||||
expect(merged.situation_locataires[0].lot.numero).toBe('0007')
|
||||
})
|
||||
|
||||
it('retient l’index d’origine des opérations supprimées', () => {
|
||||
const removed = diff.operations.find((o) => o.kind === 'removed')
|
||||
expect(removed.oldIndex).toBe(1) // TOTAL / elec
|
||||
@@ -125,7 +104,7 @@ describe('fusion sélective', () => {
|
||||
const latapy = merged.situation_locataires[0]
|
||||
|
||||
expect(latapy.locataire.nom).toBe('LATAPY NINA')
|
||||
expect(latapy.lot.numero).toBe('07')
|
||||
expect(latapy.lot.numero).toBe('12')
|
||||
expect(latapy.totaux.total).toBe(1250) // montants acceptés
|
||||
})
|
||||
|
||||
@@ -136,7 +115,7 @@ describe('fusion sélective', () => {
|
||||
expect(latapy.totaux).toEqual(previous.situation_locataires[0].totaux)
|
||||
expect(latapy.lignes).toEqual(previous.situation_locataires[0].lignes)
|
||||
// Le refus des chiffres n'annule pas les champs identitaires acceptés.
|
||||
expect(latapy.lot.numero).toBe('07')
|
||||
expect(latapy.lot.numero).toBe('12')
|
||||
expect(latapy.locataire.nom).toBe('LATAPY NINA Complément loyer')
|
||||
})
|
||||
|
||||
@@ -214,6 +193,36 @@ describe('locataires ajoutés ou supprimés', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('réécritures imposées par l’enregistrement', () => {
|
||||
// Le serveur fournit les données actuelles et leur forme canonique ; le front
|
||||
// en déduit ce qu'un refus ne pourrait pas conserver, sans connaître la règle.
|
||||
const enBase = JSON.parse(JSON.stringify(previous))
|
||||
enBase.situation_locataires[0].lot.numero = '0007' // écriture héritée
|
||||
const canonique = JSON.parse(JSON.stringify(enBase))
|
||||
canonique.situation_locataires[0].lot.numero = '07' // ce que le serveur écrirait
|
||||
|
||||
const d = computeExtractionDiff(enBase, next)
|
||||
|
||||
it('signale le champ que le serveur réécrira', () => {
|
||||
const overrides = computeServerOverrides(d, enBase, canonique)
|
||||
const lot = d.locataires[0].changes.find((c) => c.path === 'lot.numero')
|
||||
|
||||
expect(overrides[lot.id]).toBe('07')
|
||||
})
|
||||
|
||||
it('ne signale rien pour les champs que le serveur laisse tels quels', () => {
|
||||
const overrides = computeServerOverrides(d, enBase, canonique)
|
||||
const nom = d.locataires[0].changes.find((c) => c.path === 'locataire.nom')
|
||||
|
||||
expect(nom.id in overrides).toBe(false)
|
||||
expect(Object.keys(overrides)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('ne signale rien quand le serveur ne réécrit rien', () => {
|
||||
expect(computeServerOverrides(diff, previous, previous)).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('comptage pour l’affichage', () => {
|
||||
it('compte les changements retenus', () => {
|
||||
const ids = listChangeIds(diff)
|
||||
|
||||
Reference in New Issue
Block a user