Import de relevé bancaire CSV en libre-service (/lignes/importer-releve)
Chaque transaction du relevé devient une ligne « à trier » : type autre, répartition vide (liseré rouge existant via field_ecart), tag field_flag « IMP AAMMJJ » (liseré ambre + filtre existants), client rapproché par mots (ClientMatcher : séquence contiguë, sinon mot significatif unique -- jamais en sous-chaîne, jamais auto-créé). - src/Import/ : CsvReleveParser (ISO-8859-1 confirmé, en-tête strict, fgetcsv avec escape '' explicite -- dépréciation PHP 8.4, rejets propres avec n° de ligne), ReleveImportBatch (Batch API par lots de 25, dédoublonnage COMPTÉ par empreinte field_import_fitid -- max(0, k−m) importe les vrais doublons légitimes et dédoublonne à travers des fichiers qui se chevauchent --, totaux de contrôle au centime sur la page de résultat, résumé en tempstore privé). - ReleveUploadForm : upload private://releves (fichier conservé + usage, hors de portée du cron), parse en validateForm(), batch, redirection vers la page de résultat. - field_montant_releve : référence bancaire immuable, écrite une fois à l'import et jamais par presave ; affichée en TEXTE sous Montant TTC (widget remplacé par un #type item -- un item ne soumet rien et extractFormValues() saute le champ sans valeur soumise, la valeur survit donc à chaque save) ; masquée sur les lignes sans montant. - SkipValidationContext : contournement du contrôle de répartition requête-scopé (ferme le trou de concurrence de l'ancien state global, AUDIT-2026-09-09 §2.2) -- presave honore le service (state gardé pour compat), updateType/updateField basculent dessus. - Permissions (AUDIT §2.2 priorité 1) : access figli ledger sur toutes les routes du module + autocomplete (RouteSubscriber), import réservé Éditeur/Admin, access content retiré du rôle Authenticated (config/sync re-exportée pour les 4 rôles). - Gin : hook_gin_ignore_sticky_form_actions() -- sans ça, le bouton Importer partait dans la barre sticky du chrome masqué. - install : 8011 champs, 8012 index sur les empreintes, 8013/8014 montant_releve sur le formulaire sous le TTC (poids renumérotés). - /lignes : boutons + Ajouter / Importer / Historique dans le footer sticky (compacts), footer colspan dès la première colonne, badges de signalement qui reviennent à la ligne au lieu de déborder.
This commit is contained in:
@@ -276,6 +276,22 @@ html.gin--dark-mode #figli-home-app {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* "+ Ajouter une ligne" + "Importer un relevé" live in the sticky footer
|
||||
now (they used to lead the toolbar and crowd its filter row). Compact
|
||||
overrides for Gin's .button, which is sized for full admin forms --
|
||||
way too big inside a dense totals row. Size-only overrides (no
|
||||
colors): Gin's own light/dark button palettes keep applying. */
|
||||
#figli-home-app tr.figli-totals-row .button {
|
||||
display: inline-block;
|
||||
margin: 0 0.4rem 0 0;
|
||||
padding: 0.15rem 0.55rem;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.4;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#figli-home-app td.figli-solde-crediteur {
|
||||
color: var(--figli-positive);
|
||||
}
|
||||
@@ -334,7 +350,15 @@ html.gin--dark-mode #figli-home-app {
|
||||
font-weight: 600;
|
||||
background: color-mix(in srgb, var(--figli-warning) 15%, transparent);
|
||||
color: var(--figli-warning);
|
||||
white-space: nowrap;
|
||||
/* The Signalement column is narrow (6%) -- a badge must wrap inside
|
||||
the cell instead of overflowing into the neighboring column. The
|
||||
cell itself already allows wrapping (.figli-flag-cell); this makes
|
||||
the badge wrap too, including single long tokens (anywhere) and
|
||||
within its own padding box (max-width + border-box). */
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Column highlight to pair with the row hover, forming a crosshair over
|
||||
|
||||
@@ -108,6 +108,26 @@ html.gin--dark-mode .figli-ledger-form {
|
||||
grid-column: 4 / 5;
|
||||
}
|
||||
|
||||
/* Montant relevé bancaire (field_montant_releve): the import's
|
||||
immutable bank reference, displayed as plain text -- the widget is
|
||||
replaced by a #type => 'item' in figli_compta_ledger_form_alter(),
|
||||
no input box at all. Under Montant TTC in the same column so the two
|
||||
amounts compare at a glance while sorting an imported line; the
|
||||
"réf." label marker + tabular digits carry the "value you look at,
|
||||
not one you type" convention. Hidden entirely on lines with no bank
|
||||
amount (manually entered ones). */
|
||||
.figli-ledger-form > .field--name-field-montant-releve {
|
||||
grid-column: 4 / 5;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-montant-releve label::after {
|
||||
content: " · réf.";
|
||||
font-weight: 400;
|
||||
color: var(--flform-label);
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-montant-releve .figli-releve-value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Field basics */
|
||||
.figli-ledger-form .form-item__label {
|
||||
font-size: 0.78rem;
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Page de résultat d'import de relevé (templates/figli-compta-releve-import-result.html.twig).
|
||||
*
|
||||
* Piège #1 du CLAUDE.md respecté : le mode sombre Gin (html.gin--dark-mode)
|
||||
* ne résout pas les CSS custom properties de Gin de façon fiable sur ces
|
||||
* routes custom — couleurs explicites en local (--figli-*) + bloc dark mode
|
||||
* dédié, jamais de fallback seul.
|
||||
*/
|
||||
.figli-releve-result {
|
||||
max-width: 60rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 1.5rem 3rem;
|
||||
color: #161616;
|
||||
--figli-border: #d4d4d4;
|
||||
--figli-bg: #ffffff;
|
||||
--figli-bg-soft: #f6f6f6;
|
||||
--figli-ok: #1b5e20;
|
||||
--figli-ok-bg: #e8f5e9;
|
||||
--figli-alert: #b71c1c;
|
||||
--figli-alert-bg: #ffebee;
|
||||
}
|
||||
|
||||
.figli-releve-result h2 {
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
|
||||
.figli-releve-tag .figli-flag-badge {
|
||||
display: inline-block;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 0.75rem;
|
||||
background: #7a5c00;
|
||||
color: #ffffff;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.figli-releve-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin: 1.2rem 0;
|
||||
}
|
||||
|
||||
.figli-releve-stat {
|
||||
flex: 1 1 10rem;
|
||||
padding: 0.8rem 1rem;
|
||||
border: 1px solid var(--figli-border);
|
||||
border-radius: 6px;
|
||||
background: var(--figli-bg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.figli-releve-stat-value {
|
||||
display: block;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.figli-releve-stat-label {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
color: #5f5f5f;
|
||||
}
|
||||
|
||||
.figli-releve-totals {
|
||||
border-collapse: collapse;
|
||||
margin: 0.5rem 0 1rem;
|
||||
}
|
||||
|
||||
.figli-releve-totals th,
|
||||
.figli-releve-totals td {
|
||||
padding: 0.4rem 0.8rem 0.4rem 0;
|
||||
border-bottom: 1px solid var(--figli-border);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.figli-releve-amount {
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.figli-releve-ok {
|
||||
padding: 0.5rem 0.8rem;
|
||||
border-left: 3px solid var(--figli-ok);
|
||||
background: var(--figli-ok-bg);
|
||||
color: var(--figli-ok);
|
||||
}
|
||||
|
||||
.figli-releve-alert {
|
||||
padding: 0.5rem 0.8rem;
|
||||
border-left: 3px solid var(--figli-alert);
|
||||
background: var(--figli-alert-bg);
|
||||
color: var(--figli-alert);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.figli-releve-errors li {
|
||||
color: var(--figli-alert);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.figli-releve-dup-wrap {
|
||||
max-height: 20rem;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--figli-border);
|
||||
border-radius: 6px;
|
||||
background: var(--figli-bg-soft);
|
||||
}
|
||||
|
||||
.figli-releve-dups {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.figli-releve-dups th,
|
||||
.figli-releve-dups td {
|
||||
padding: 0.35rem 0.8rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--figli-border);
|
||||
}
|
||||
|
||||
.figli-releve-actions {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Mode sombre Gin — mêmes règles, palette inversée, cf. CLAUDE.md piège #1. */
|
||||
html.gin--dark-mode .figli-releve-result {
|
||||
color: #e6e6e6;
|
||||
--figli-border: #3a3a3a;
|
||||
--figli-bg: #1c1c1c;
|
||||
--figli-bg-soft: #232323;
|
||||
--figli-ok: #9ee493;
|
||||
--figli-ok-bg: #123016;
|
||||
--figli-alert: #ff8a80;
|
||||
--figli-alert-bg: #3a1212;
|
||||
}
|
||||
|
||||
html.gin--dark-mode .figli-releve-stat-label {
|
||||
color: #a3a3a3;
|
||||
}
|
||||
Reference in New Issue
Block a user