feat: Zeilenfilter für Auszüge mit mehreren Pockets + Gebühren-Richtung
Der Revolut-Auszug enthält ALLE Produkte in einer Datei – im CHF-Export stehen 533 Giro-Zeilen und 255 Zeilen des Ersparten, jedes mit eigenem Kontostand- Verlauf. Ohne Trennung landeten beide auf demselben Konto und die Pocket- Bewegungen zählten doppelt. - Import-Vorlage kennt einen Zeilenfilter („nur Zeilen aus Spalte X mit Wert Y"), Migration 0012; damit bekommt jedes Pocket sein eigenes Konto - Gebührenzeilen (Betrag 0.00) können Belastung ODER Erstattung sein – beide sehen identisch aus. Die Richtung kommt jetzt aus der Kontostand-Spalte; ohne sie bleibt die Belastung der Normalfall - Tests für beides; 90 grün Nachgerechnet an den echten Auszügen: Raiffeisen 223.37, Revolut CHF 61.88, Erspartes 6.71, Revolut EUR 0.68 – alle vier stimmen zeilengenau mit der Kontostand-Spalte überein. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,8 @@ function defaultConfig() {
|
||||
skip_rows: 0,
|
||||
amount_mode: "signed",
|
||||
column_map: {},
|
||||
filter_column: null,
|
||||
filter_value: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -91,6 +93,7 @@ export default function Import() {
|
||||
delimiter: src.delimiter, decimal_sep: src.decimal_sep, date_format: src.date_format,
|
||||
encoding: src.encoding, has_header: src.has_header, skip_rows: src.skip_rows,
|
||||
amount_mode: src.amount_mode, column_map: src.column_map || {},
|
||||
filter_column: src.filter_column ?? null, filter_value: src.filter_value || "",
|
||||
});
|
||||
}
|
||||
// Ein zusammengeführtes Vorlagen-Dropdown: Werte typ-getaggt "preset:i" / "tpl:id".
|
||||
@@ -135,6 +138,10 @@ export default function Import() {
|
||||
fd.append("date_format", config.date_format);
|
||||
fd.append("amount_mode", config.amount_mode);
|
||||
fd.append("column_map", JSON.stringify(config.column_map));
|
||||
if (config.filter_column != null && config.filter_value) {
|
||||
fd.append("filter_column", String(config.filter_column));
|
||||
fd.append("filter_value", config.filter_value);
|
||||
}
|
||||
const res = await api.importPreview(fd);
|
||||
setRows(res.rows.map((r) => ({ ...r })));
|
||||
setRecon(res.reconciliation || null);
|
||||
@@ -517,6 +524,23 @@ export default function Import() {
|
||||
))}
|
||||
</div>
|
||||
<p className="muted small">{t("import.extraColsHint")}</p>
|
||||
<div className="map-grid">
|
||||
<label>
|
||||
<span>{t("import.filterColumn")}</span>
|
||||
<select value={config.filter_column ?? ""}
|
||||
onChange={(e) => setCfg({ filter_column: e.target.value === "" ? null : Number(e.target.value) })}>
|
||||
<option value="">{t("import.filterNone")}</option>
|
||||
{colOptions.map((c) => <option key={c.i} value={c.i}>{c.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{t("import.filterValue")}</span>
|
||||
<input value={config.filter_value}
|
||||
placeholder={t("import.filterValuePlaceholder")}
|
||||
onChange={(e) => setCfg({ filter_value: e.target.value })} />
|
||||
</label>
|
||||
</div>
|
||||
<p className="muted small">{t("import.filterHint")}</p>
|
||||
</details>
|
||||
|
||||
<div className="field-inline" style={{ marginTop: "var(--sp-3)" }}>
|
||||
|
||||
Reference in New Issue
Block a user