Chaque compte rendu reporte la dette du précédent dans une ligne
solde_anterieur. Les agrégats sommaient ces lignes comme le reste : la
même créance était recomptée à chaque document, et un remboursement ne
pouvait jamais s'inscrire — une dette soldée restait affichée à vie.
Sur la base réelle, la page Revenus annonçait ainsi 247 354 € d'impayés
pour une dette de 49 374 €, et désignait comme deuxième et troisième
débiteurs deux locataires à jour depuis avril (SURBECK 690,10 € et
GUINAIS 445,81 €, tous deux soldés).
Deux natures cohabitent, et c'est la colonne qui la porte, pas la ligne :
une ligne de report a un `total` déjà compté le mois d'avant, mais ses
`regles` sont un encaissement bien réel de la période. Écarter la ligne
entière ferait disparaître de l'argent reçu (1 298,81 € ici).
- flux (facturé, encaissé) : cumulés sur la période, report exclu ;
- stock (restant dû) : lu dans le dernier compte rendu de chaque immeuble ;
- taux de recouvrement : réglé sur facturé, report exclu des deux côtés,
sans quoi rattraper une vieille dette ferait dépasser 100 %.
Résultat : 85 748 € facturés, 98,2 % de recouvrement, 49 374 € encore
dus. Les règles vivent dans services/revenus_query.py, pour que le
dashboard s'y branche au lieu de les réinventer.
/summary borne désormais tous ses blocs à la période demandée : les KPIs
et by_immeuble ignoraient `months` alors que by_month le respectait.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aucun appelant : le tableau de bord se construit sur financial-summary,
monthly-trends, recent-revenus et immeubles-shortcuts. Les compteurs
qu'il exposait restent disponibles en ligne de commande via
`plesna-gerance db-info`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'API et l'interface partagent le même port, et la route attrape-tout qui
sert le SPA passait avant le 404 : n'importe quelle URL /api/* erronée
repartait en index.html avec un 200. Côté client, une faute de frappe
dans une URL ne ressemblait pas à une erreur mais à une réponse vide, et
le vrai motif n'apparaissait qu'en inspectant le corps de la réponse.
Les URL commençant par /api/ sont désormais exclues de l'attrape-tout et
retombent sur un 404 JSON. Les routes de l'interface restent servies par
index.html.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code sans aucun appelant, retiré : DatabaseService.get_revenus_summary
(annoté list[dict] alors qu'il retournait un dict), get_depenses_summary,
get_tag_by_id et storage.file_exists.
Les tags étaient gérés à deux adresses : /api/tags (lecture, appelée par
trois composants) et /api/config/tags (lecture, création, renommage,
appelée par la seule page de configuration). Tout est regroupé sur
/api/tags, dans le module qui leur est dédié ; config.py ne garde que les
settings et ConfigPage est recâblée. Ces routes n'avaient aucun test :
sept en couvrent maintenant la création, le renommage, l'unicité et les
noms vides.
/api/stats disparaît également : sous-ensemble de /api/dashboard/stats,
il n'était appelé par personne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les parseurs géométriques (par cellules de tableau) traitent les 5 PDF du
corpus sans jamais déclencher le repli : les parseurs texte étaient 586
lignes de regex fragiles, non testées, à maintenir à chaque évolution du
format de sortie. Les références golden sont inchangées après leur
suppression — l'extraction produit exactement la même chose.
Le parseur géométrique devient donc le seul chemin : ce qu'il ne lit pas
est perdu. L'orchestrateur distingue maintenant les deux cas :
- aucun lot lu -> ExtractionError (422 côté API). Un compte rendu sans
lot n'existe pas : c'est le tableau qui n'a pas été reconnu, et mieux
vaut échouer que d'enregistrer un document vide découvert bien plus
tard, au moment de relire les chiffres ;
- aucune opération -> accepté. Un mois sans dépense reste plausible.
_extract_lot_code_from_description était la seule fonction encore
utilisée : elle rejoint utils.lots sous le nom
extract_lot_numero_from_description, à côté de normalize_lot_numero.
extract_text_from_pdf, sans appelant, disparaît au passage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les agrégats par immeuble joignaient revenus et locataires à partir du
lot. Comme un lot accumule ses occupants successifs, chaque ligne de
revenu était comptée autant de fois qu'il y a eu de locataires : aucune
erreur levée, juste des montants faux qui dérivent avec l'ancienneté du
parc.
Sur la base réelle (40 lots, 46 locataires), /api/revenus/immeubles et
le bloc by_immeuble de /api/revenus/summary annonçaient :
total 348 111,95 € au lieu de 332 899,45 € (+4,6 %)
réglés 100 697,66 € au lieu de 85 545,16 € (+17,7 %)
taux 28,9 % au lieu de 25,7 %
Les montants sont désormais agrégés par immeuble AVANT la jointure des
locataires, qui ne sert plus qu'au dénombrement. Les deux endpoints
partagent ces sous-requêtes et la construction de la réponse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les parseurs sont la partie la plus exposée aux régressions silencieuses :
un décalage de colonne ne lève aucune exception, il produit des montants
faux. Rien ne les couvrait jusqu'ici.
Chaque PDF du corpus local produit une empreinte (structure des lots,
totaux par lot, montants par catégorie, détail de chaque opération)
comparée à une référence figée. Les libellés sensibles — locataire,
fournisseur, description — sont réduits à un hash court : un changement
reste détecté sans recopier la donnée.
Ni les PDF (`data/`) ni les références (`tests/golden/`) ne sont
versionnés : la suite se saute d'elle-même là où le corpus est absent.
Régénération après un changement volontaire de parseur :
uv run pytest tests/test_parsers_golden.py --regen-golden
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La page et le panneau de differences avaient ete ecrits avant que main ne pose
un design system unique : classes Tailwind brutes, et surtout un detail
depliable reste sur fond clair alors que son contenu venait de passer en sombre.
Reprend les classes du systeme (page, card, table, btn, badge, subcard,
empty-state, spinner) et ramene le detail au fond sombre. Les avertissements de
reecriture passent en amber-400, illisibles jusqu'ici sur fond sombre.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La liste des documents montrait la date d'import, figee au premier
enregistrement : c'est la date de la derniere extraction ayant produit les
donnees affichees qui renseigne, surtout apres un balayage de re-extraction. La
colonne `extracted_at` la porte, mise a jour a chaque enregistrement, et la date
d'import reste consultable en infobulle.
Le projet n'ayant pas d'outil de migration (`create_all` laisse intactes les
tables existantes), `init_db` rattrape les colonnes ajoutees apres coup : sans
cela une base deja installee cesserait de fonctionner. Les documents deja
presents recoivent leur date d'import, qui est bien celle de leur extraction.
Une fois la selection appliquee sans echec, le balayage renvoie vers la liste des
documents ; en cas d'echec on reste sur place, les lignes concernees portant leur
message.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Le balayage n'imposait que le tout ou rien par document. Chaque modification a
desormais sa case : un champ de metadonnee, le nom ou le type d'un locataire,
une operation ajoutee ou supprimee. Les champs textuels retenus sont en outre
modifiables a la main, pour les cas ou ni l'ancienne ni la nouvelle valeur ne
convient. Tout reste coche par defaut : le cas courant tient toujours en un clic.
Les montants et lignes d'un locataire forment un seul bloc : les retenir
separement produirait un total ne correspondant plus a ses lignes, alors que ces
chiffres alimentent les revenus et les depenses.
Le document enregistre est construit par fusion (mergeExtraction) : on part des
donnees en base et on n'y applique que ce qui est retenu. Le diff porte donc
maintenant de quoi rejouer chaque changement (identifiant, chemin, valeurs
brutes) et l'index d'origine des operations supprimees, pour les remettre a leur
place si on refuse leur disparition. Les tags suivent la liste d'operations
finale, y compris ceux d'une operation restauree.
Une reecriture de forme d'un numero de lot ("0001" -> "01") est signalee comme
imposee et sa case verrouillee : le backend normalise tout enregistrement, une
case sans effet aurait laisse croire l'inverse.
Ajoute vitest : la fusion decide de ce qui est ecrit en base, elle est couverte
par 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nouvelle page /re-extraction, accessible depuis la liste des documents : elle
rejoue l'extraction de chaque PDF stocke, document par document, avec une
progression et une estimation du temps restant (une extraction prend plusieurs
secondes) et un bouton d'arret. Rien n'est ecrit en base pendant le balayage.
Les documents dont l'extraction change sont listes avec le nombre de
differences par section, deja coches ; les inchanges sont replies et les
documents sans PDF ou en erreur isoles. Le detail depliable reutilise
l'affichage de diff de la re-extraction unitaire (extrait dans
ExtractionDiffDetails). Un seul bouton enregistre toute la selection.
Les tags de depenses sont reportes par signature d'operation et non par index :
une operation ajoutee ou retiree par le nouveau parser ne decale plus les
categories. Ce qui n'est pas reapparie recoit une prediction, modifiable dans le
detail avant enregistrement.
L'edition d'un document et l'application du balayage passent par
PUT /api/documents/{id}, qui vise le document par son ID.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le remplacement par ID mettait a jour le document en le supprimant puis en le
recreant : la ligne changeait d'ID, cassant les liens qui la referencent (URL
d'edition, telechargement du PDF) juste apres un balayage de re-extraction. Le
document est desormais mis a jour sur place, seules les donnees derivees
(revenus, depenses) etant regenerees.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
POST /api/documents/{id}/re-extract retourne desormais, en plus des donnees
re-extraites, les donnees actuelles et les tags des depenses : un seul appel
suffit pour comparer avant/apres et reporter les tags. L'endpoint devient
synchrone pour que FastAPI l'execute dans un thread, l'extraction bloquant
plusieurs secondes par PDF.
PUT /api/documents/{id} enregistre la nouvelle extraction en visant le
document par son ID (save_document(replace_document_id=...)) : une extraction
qui corrige la reference ou la date met a jour le bon document au lieu d'en
creer un second, et refuse la collision avec un document voisin. Le PDF
stocke est conserve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aucun import ne le referencait : le televersement se fait depuis la zone
de depot de l'accueil et le bouton de l'en-tete.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La comparaison portait sur « crediteur » sans accent alors que le parser
ecrit « créditeur » des qu'il a lu la mention dans le PDF : aucun solde
ne passait le test, tous s'affichaient en rouge.
Compare desormais sur une forme normalisee (sans accents ni casse), ce
qui couvre les deux formes presentes en base sans avoir a migrer les
documents deja extraits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cartes, en-tetes, champs de filtre, tableaux, badges, paginations et
indicateurs de chargement des tableaux de bord etaient proches mais
jamais identiques (bordure presente ou absente, paddings et arrondis
variables). Ils utilisent maintenant les classes communes.
Les colonnes tronquees des tableaux sont elargies et les sept filtres
des depenses tiennent sur une ligne, la largeur retrouvee le permettant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tout ce flux (visionneuse de donnees, fiches locataire et operation,
tagging, autocompletion, panneau de differences) etait en theme clair,
au milieu d'une application sombre : le contraste sautait a chaque
passage d'une page a l'edition d'un document.
Les surfaces, bordures, textes et etats colores suivent desormais la
palette commune. Les champs natifs (date, nombre, cases a cocher)
recoivent color-scheme: dark pour que le calendrier et les widgets
systeme s'affichent en sombre eux aussi.
Les fiches de metadonnees se repartissent sur 2 puis 3 colonnes selon la
largeur disponible, au lieu d'une seule colonne.
Le bouton « Annuler » du bandeau de tagging devient « Retour a
l'edition », qui decrit son action reelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chaque page plafonnait sa largeur differemment (max-w-4xl pour Config,
5xl pour IA, 6xl pour l'accueil et les documents, 7xl pour les revenus
et les depenses) : sur un ecran large, les donnees se tassaient au
centre. Les pages n'ont plus de plafond, le contenu occupe l'ecran avec
une marge laterale fixe.
Pour tirer parti de la place gagnee : Config affiche ses deux sections
cote a cote, et le split apercu PDF / donnees passe de 50-50 a 40-60 en
faveur des donnees.
Pendant le tagging, le bouton « Annuler » de l'en-tete d'edition est
masque : il faisait doublon avec celui du bandeau de tagging, qui a un
sens different (retour a l'edition, et non abandon). La sortie vers la
liste reste le fil d'Ariane.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les pages melangeaient deux themes (sombre pour les tableaux de bord,
clair pour le flux d'extraction) et repetaient des utilitaires Tailwind
divergents : trois styles de boutons, quatre variantes de cartes, des
champs de saisie a paddings differents.
Definit dans style.css une palette unique (gray-950 fond, gray-900
surfaces, gray-700 bordures, blue-600 accent) et les classes composants
associees : page, card, btn, input, table, badge, spinner, empty-state.
Le shell s'y aligne et sa navigation est generee depuis une liste.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les PDF et les saisies manuelles écrivent le même lot de plusieurs façons
("6", "06", "0006"), et chaque forme créait jusqu'ici un lot distinct en
base. utils/lots.py fixe la forme canonique sur 2 chiffres et sert de point
d'entrée unique pour la normalisation.
Elle est appliquée à la source dans les parseurs (locataires texte et
tableau, codes lot des opérations), et en dernier recours dans
get_or_create_lot et save_document, pour couvrir les extractions éditées à
la main via l'API. Les numéros à plus de 2 chiffres significatifs ne sont
pas tronqués.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pdfplumber fragmente parfois le tableau en plusieurs bbox disjoints quand un
filet de séparation manque entre deux lots (ex: page 3 du sample), ce qui
faisait disparaître silencieusement les lots tombés dans l'interstice ou
situés après le fragment ignoré. Corrige aussi la troncature du nom de
locataire quand il s'étale sur plusieurs lignes visuelles ou coïncide avec
la ligne de période (lot coupé entre deux pages).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le docker-compose.yml tire désormais git.opytex.org/lafrite/plesna-gerance:latest
et persiste les données dans un volume nommé (plesna-data) plutôt qu'un bind-mount.
make docker fait pull+up ; make docker-build reste disponible pour tester l'image en local.
Le runner Gitea ne peut pas booter un builder docker-container : le
bootstrap tente de puller moby/buildkit:buildx-stable-1, ce qui échoue.
On utilise le BuildKit intégré au démon (DOCKER_BUILDKIT=1 docker build)
puis docker push, en conservant metadata-action pour tags et labels.
Les tags/labels sont lus ligne à ligne (labels avec espaces préservés).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le runner act_runner ne peut pas booter un conteneur builder
(driver docker-container). On réutilise le démon Docker de l'hôte,
suffisant pour un push linux/amd64.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build de l'image unique depuis backend.Dockerfile et push vers le
registre (secrets REGISTRY_*). Déclenché sur main, tags v* et manuel.
Tags d'image : latest/main sur la branche par défaut, semver
(X.Y.Z / X.Y / X) sur les tags. Pas de tag par SHA (économie d'espace
registre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le backend FastAPI sert déjà le SPA buildé : on abandonne le duo
nginx + backend au profit d'une seule image multi-stage (build Vue
puis service par le backend). docker-compose passe à un service
unique, avec PLESNA_DATA_DIR déclarant le stockage persistant.
- backend.Dockerfile : stage node build -> copie frontend/dist,
CMD en `uv run --no-sync` (plus de resync des deps dev au démarrage)
- docker-compose.yml : service unique `app`, volume ./data,
PLESNA_DATA_DIR=/app/data
- supprime frontend/Dockerfile, frontend/nginx.conf, frontend/.dockerignore
- .dockerignore : ignore **/node_modules, data, data_bck
- README : section Production mise à jour
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace les parseurs texte+regex (fragiles sur l'alignement en colonnes des
PDF Oralia mal construits) par une extraction géométrique : reconstruction des
lignes visuelles par regroupement vertical tolérant des mots, puis affectation
de chaque valeur à sa colonne via les filets du tableau (pdfplumber find_tables).
Corrections apportées :
- locataires : montant/libellé « divers » dans la bonne colonne (plus le total
cumulé du lot), nom de locataire correct (le logo/en-tête hors filets est
ignoré), lignes multi-période et pages recollées.
- opérations : fournisseur séparé de la description (TOTALENERGIES ≠ DIDIER
NETTOYAGE, PPR ≠ BOUVARD), colonne Déductible remplie, Débit/Crédit distingués,
fournisseur des honoraires reporté sur le bloc, fragments de description
recollés (LATAPY, AUDOUIN).
- code lot : gère « S10 - », « S 17 - » (espace) et « S01 SOLDE » (sans tiret).
Branchés dans extractor.py avec repli sur les anciens parseurs si un tableau n'a
pas de filets détectables. Validés par réconciliation comptable : locataires
124/124 lots, opérations 7/7 PDF et 25/25 catégories, au centime.
Ajoute le bouton « Relancer l'extraction » dans l'écran d'édition : re-extrait
depuis le PDF stocké et met en évidence les différences avec la version
précédente (panneau récapitulatif + anneaux « modifié » sur les cartes). Le diff
des opérations s'aligne par contenu (robuste aux changements d'ordre/nombre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace l'<iframe> (lecteur PDF natif du moteur) par un rendu PDF.js
sur <canvas>, une page par canvas empilee et scrollable. Rendu
identique partout (Qt WebEngine, WebView2, navigateur), worker bundle
localement par Vite -> fonctionne hors-ligne.
Utilise le build *legacy* de pdfjs-dist : il embarque les polyfills
requis par les Chromium embarques (Qt WebEngine, WebView2), plus
anciens que Chrome. Le build par defaut appelle
`Map.prototype.getOrInsertComputed` (proposition JS recente) sans
polyfill, ce qui cassait silencieusement le rendu dans la fenetre
bureau. Le catch logue desormais l'erreur au lieu de l'avaler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Permet de distribuer l'application en executable Windows double-clic,
sans Python/Node/terminal pour l'utilisateur final.
- `desktop.py` : demarre le serveur sur un port libre dans un thread et
ouvre une fenetre native (pywebview). `PLESNA_DEBUG=1` active les
DevTools. Commande `plesna-gerance desktop` (import paresseux).
- `paths.py` : resolution centralisee des chemins selon le contexte
(dev, executable PyInstaller, surcharge par variables d'env). Les
donnees vont dans un dossier utilisateur inscriptible (%APPDATA%),
les ressources dans le bundle. connection.py/storage.py utilisent
`get_data_dir()`, app.py `resource_path()`.
- app.py : type MIME `.mjs` force (modules ES / worker PDF.js sous
Windows).
- Groupes de deps optionnels desktop / desktop-linux / build.
- packaging/ : spec PyInstaller, build_windows.ps1, installeur Inno
Setup. Workflow GitHub build-windows.yml (runner Windows).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace l'appel externe `pdftotext -layout` (poppler-utils) par
pdfplumber, une bibliotheque Python pure : plus aucune dependance
systeme, ce qui permettra d'empaqueter l'application en executable
autonome.
- `read_pdf()` retourne un `PdfContent` (texte layout + mots de la
premiere page avec coordonnees) ; `extract_text_from_pdf()` est
conserve comme wrapper de compatibilite.
- Metadonnees : la reference est desormais extraite via les
coordonnees geometriques des mots (fiable sur l'en-tete
multi-colonnes), regex du destinataire durcie (forme pointee
`S.C.I.`).
- Operations : reduction des espaces multiples dans les descriptions.
- Dockerfile : suppression de poppler-utils.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Config ruff dans pyproject.toml : règles E/W/F/I/UP/B, whitelist des appels
d'injection FastAPI (Depends/File/Form/Query) pour B008, B904 ignoré
(traduction volontaire des exceptions en réponses HTTP)
- Auto-fixes : tri des imports, suppression d'imports inutilisés, annotations
PEP 604, f-strings sans placeholder, modes open redondants
- Suppression de variables inutilisées (config.reset_setting, parser locataires)
ruff check . : All checks passed ; 61 tests OK
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- connection: création de l'engine centralisée dans _build_engine (source
unique de config) ; init_db réutilise reset_connection + get_engine au lieu
de dupliquer create_engine
- service: _normalize_amount garantit qu'un montant non numérique issu de
l'extraction (string, dict…) n'entre jamais en base dans une colonne Float ;
appliqué à solde_montant
- tests: couverture de _normalize_amount et du solde sous forme de string
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sql_executor: remplace le filtre regex fragile par une autorisation SQLite
(set_authorizer) en complément de mode=ro ; rejette les instructions multiples
- uploads: lecture bornée des PDF (helper read_upload_limited, limite 20 Mo,
HTTP 413) branchée sur /extract et /save-with-pdf
- tests: suite pytest (54 tests) couvrant amounts, dates, storage, sql_executor,
uploads et DatabaseService.save_document ; pytest ajouté en dépendance dev
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Empêche le path traversal du catch-all SPA (vérif is_relative_to dist)
- Migre on_event("startup") vers le handler lifespan de FastAPI
- Masque les exceptions internes des erreurs 500 (log au lieu de str(e))
- Remplace datetime.utcnow déprécié par un helper UTC timezone-aware
- Simplifie except (ValueError, Exception) redondant
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ConfigPage.vue with two sections: Ollama configuration (URL, model,
timeout) with source badges and per-field reset, and tag management with
inline rename and creation. Add /config route and nav link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a key-value settings table (Setting model) with DB > env > default
resolution chain. Replace hardcoded Ollama constants in ollama_service
with dynamic _get_config() lookups. Add /api/config/ endpoints for
settings CRUD and tag management (create, rename).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clicking the pencil icon in revenus/depenses tables now passes query
params to identify the source row. The edit page auto-expands the
matching section, scrolls to the element, and briefly highlights it
with a blue ring that fades after 2 seconds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch save endpoint from JSON to multipart upload so the PDF file is
sent alongside extracted data and tags.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove pdfjs-dist dependency and simplify PdfPreview component to use
a native browser iframe, reducing bundle size and complexity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add backend and frontend Dockerfiles, docker-compose setup with nginx
reverse proxy, .dockerignore files and Makefile for dev/prod workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add new /api/revenus endpoints for rental income data
- GET /summary: KPIs, monthly trends, breakdown by property, top unpaid
- GET /details: detailed rental records with filtering
- GET /by-lot: revenue aggregation by unit
- GET /immeubles: properties list with revenue stats
- Add RevenusPage with full dashboard layout
- 6 KPI cards: total revenue, payments, unpaid, collection rate, active tenants, occupied units
- Monthly evolution chart (rent, payments, unpaid)
- Property breakdown donut chart
- Top unpaid tenants list
- Detailed revenue table with pagination and CSV export
- Add filters panel (property, type, dates, unpaid only, history months)
- Display property address instead of code in all components
- Add navigation link in header (Revenus)
- Add dashboard API endpoints for financial summary, recent revenus, monthly trends, and immeuble shortcuts
- Create new dashboard components: FinancialSummary with sparklines, QuickActions, RecentRevenus, MiniTrendChart, ImmeubleShortcuts
- Refactor HomePage with prominent drag & drop zone and data-driven cards
- Financial cards now show last document data with 6-month trend sparklines in background
- Remove redundant import button, keep single upload zone at top
- Add EditableField component for inline editing of any field
- Update DataRow, LocataireCard, OperationCard to support inline editing
- Add CRUD operations for locataires and operations (add/remove)
- Update JsonViewer to propagate changes via v-model
- Add TagAutocomplete component with search/filter functionality
- Replace select dropdown with autocomplete in TaggingStep