feat: Transfer-Schlagwort mit festem Zielkonto (z. B. REVOLUT -> Konto Revolut)
- Transfer-Regel kann direkt ein Zielkonto tragen (target_account_id); ohne -> weiterhin Auto-Erkennung aus dem Buchungstext (Konto-Texte) - Migration 0010; Endpunkt aktualisiert bestehendes Wort statt Konflikt - Import bevorzugt das feste Zielkonto, sonst _guess_target_account - Konten-Seite: Transfer-Erkennung mit Zielkonto-Auswahl + Anzeige am Chip - Test: festes Zielkonto greift ohne Konto-Texte (75 grün) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,13 +36,15 @@ function emptyForm() {
|
||||
|
||||
// Globale Transfer-Schlagwörter: Text im Buchungstext -> Transfer. Das Zielkonto
|
||||
// wird über die Erkennungs-Texte je Konto (Identifikator-Art „Text") erraten.
|
||||
function TransferKeywords() {
|
||||
function TransferKeywords({ accounts }) {
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const [words, setWords] = useState([]);
|
||||
const [input, setInput] = useState("");
|
||||
const [amount, setAmount] = useState("");
|
||||
const [target, setTarget] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const accName = (id) => accounts.find((a) => a.id === id)?.name;
|
||||
|
||||
async function laden() {
|
||||
try { setWords(await api.listTransferKeywords()); }
|
||||
@@ -58,9 +60,11 @@ function TransferKeywords() {
|
||||
const body = { word: w };
|
||||
const c = parseAmountToCents(amount);
|
||||
if (c) body.amount_cents = c;
|
||||
if (target) body.target_account_id = Number(target);
|
||||
await api.addTransferKeyword(body);
|
||||
setInput("");
|
||||
setAmount("");
|
||||
setTarget("");
|
||||
await laden();
|
||||
} catch (err) {
|
||||
toast(err.message, "error");
|
||||
@@ -82,9 +86,14 @@ function TransferKeywords() {
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); hinzufuegen(); } }} />
|
||||
<input value={amount} inputMode="decimal" placeholder={t("categories.keywordAmount")}
|
||||
style={{ flex: "0 0 120px", minWidth: 0, marginTop: 0 }}
|
||||
style={{ flex: "0 0 110px", minWidth: 0, marginTop: 0 }}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); hinzufuegen(); } }} />
|
||||
<select value={target} onChange={(e) => setTarget(e.target.value)}
|
||||
style={{ flex: "0 0 160px", minWidth: 0, marginTop: 0 }}>
|
||||
<option value="">{t("transferRules.autoTarget")}</option>
|
||||
{accounts.map((a) => <option key={a.id} value={a.id}>{a.name}</option>)}
|
||||
</select>
|
||||
<button className="btn" onClick={hinzufuegen} disabled={busy || !input.trim()}>
|
||||
<Icon name="plus" size={15} />
|
||||
</button>
|
||||
@@ -97,6 +106,7 @@ function TransferKeywords() {
|
||||
<span className="kw-chip" key={w.id}>
|
||||
{w.word}
|
||||
{w.amount_cents ? <span className="kw-amount">= {centsToInput(w.amount_cents)}</span> : null}
|
||||
<span className="kw-amount">→ {w.target_account_id ? (accName(w.target_account_id) || "?") : t("transferRules.autoTarget")}</span>
|
||||
<button type="button" title={t("common.delete")} onClick={() => entfernen(w.id)}>
|
||||
<Icon name="close" size={13} />
|
||||
</button>
|
||||
@@ -239,7 +249,7 @@ export default function Accounts() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TransferKeywords />
|
||||
<TransferKeywords accounts={accounts} />
|
||||
|
||||
{form && (
|
||||
<Modal
|
||||
|
||||
Reference in New Issue
Block a user