feat: Kontoauszug vom Telefon per Teilen-Menue einlesen

Revolut und Raiffeisen lassen die CSV auch am Handy exportieren; nur N26
verlangt die Web-Version. Ein zweiter Kurzbefehl nimmt die Datei aus dem
Teilen-Menue entgegen und schickt sie mit dem gewaehlten Konto an
/quick/statement.

Am Telefon laesst sich keine Spaltenzuordnung vornehmen. Der Endpunkt
verwendet deshalb die Einstellung, mit der dieses Konto zuletzt erfolgreich
eingelesen wurde – und sagt klar Bescheid, wenn es noch keine gibt, statt
mit falschen Spalten zu raten.

Gebucht wird sofort: Ein Auszug hat hunderte Zeilen, die einzeln abzunicken
waere keine Erleichterung. Sicher ist das durch die Duplikaterkennung und
weil der Stapel im Import-Verlauf mit einem Klick zurueckzunehmen ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-22 21:06:20 +02:00
co-authored by Claude Opus 5
parent e93f565629
commit 34be575eb3
5 changed files with 180 additions and 3 deletions
+8 -1
View File
@@ -483,7 +483,14 @@
"revokeConfirm": "Marke „{{name}}\" widerrufen? Der Kurzbefehl auf dem Gerät funktioniert danach nicht mehr. Bereits erfasste Buchungen bleiben.",
"noDefault": "kein Standardkonto",
"lastUsed": "schon benutzt",
"neverUsed": "noch nie benutzt"
"neverUsed": "noch nie benutzt",
"fileGuideTitle": "Kontoauszug vom Handy einlesen",
"fileGuideIntro": "Revolut und Raiffeisen lassen die CSV auch am Telefon exportieren. Ein zweiter Kurzbefehl nimmt sie aus dem Teilen-Menü entgegen. Voraussetzung: Du hast für dieses Konto schon einmal am Rechner eingelesen – Certain verwendet dann dieselbe Spaltenzuordnung.",
"fileStep1": "Neuer Kurzbefehl → oben auf den Namen tippen → „Im Teilen-Menü anzeigen“ einschalten, Eingabetyp: Dateien",
"fileStep2": "Suche nach „Liste“ → „Aus Liste wählen“ für das Konto (die Konto-Nummern findest du über die Adresse /api/quick/accounts)",
"fileStep3": "Suche nach „URL“ → „Inhalte von URL abrufen“. Methode POST, Header X-Certain-Token wie oben, Anfragetext „Formular“ mit file = Kurzbefehleingabe und account_id = das gewählte Konto:",
"fileStep4": "„Mitteilung anzeigen“ – dann siehst du „14 gebucht · 512 schon vorhanden“",
"fileGuideNote": "Gebucht wird sofort, weil ein Auszug hunderte Zeilen hat. Das ist ungefährlich: Bereits vorhandene Buchungen werden erkannt und übersprungen, und der ganze Stapel lässt sich im Import-Verlauf mit einem Klick zurücknehmen."
},
"inbox": {
"title": "Eingang",
+8 -1
View File
@@ -483,7 +483,14 @@
"revokeConfirm": "Revoke token “{{name}}”? The shortcut on that device will stop working. Bookings already recorded stay.",
"noDefault": "no default account",
"lastUsed": "used before",
"neverUsed": "never used"
"neverUsed": "never used",
"fileGuideTitle": "Import a statement from your phone",
"fileGuideIntro": "Revolut and Raiffeisen let you export the CSV on the phone too. A second shortcut takes it from the share sheet. Prerequisite: you have imported for this account on a computer once – Certain then reuses that column mapping.",
"fileStep1": "New shortcut → tap its name → enable “Show in Share Sheet”, input type: Files",
"fileStep2": "Search for “List” → “Choose from List” for the account (account ids are at /api/quick/accounts)",
"fileStep3": "Search for “URL” → “Get Contents of URL”. Method POST, header X-Certain-Token as above, request body “Form” with file = Shortcut Input and account_id = the chosen account:",
"fileStep4": "“Show Notification” – you then see “14 booked · 512 already present”",
"fileGuideNote": "It books straight away because a statement has hundreds of rows. That is safe: existing bookings are recognised and skipped, and the whole batch can be undone with one click in the import history."
},
"inbox": {
"title": "Captured",
+21
View File
@@ -154,6 +154,27 @@ export default function Geraetemarken() {
<p className="muted small">{t("tokens.guideHomeScreen")}</p>
<p className="muted small">{t("tokens.guideAutomation")}</p>
</details>
<details style={{ marginTop: "var(--sp-3)" }}>
<summary>{t("tokens.fileGuideTitle")}</summary>
<p className="muted small">{t("tokens.fileGuideIntro")}</p>
<ol className="muted small" style={{ paddingLeft: "1.2em" }}>
<li>{t("tokens.fileStep1")}</li>
<li>{t("tokens.fileStep2")}</li>
<li>
{t("tokens.fileStep3")}
<div className="input-with-button" style={{ marginTop: 4 }}>
<input readOnly className="selectable" value={`${basis}/api/quick/statement`} />
<button type="button" className="btn sm"
onClick={() => kopieren(`${basis}/api/quick/statement`)}>
<Icon name="copy" size={14} />
</button>
</div>
</li>
<li>{t("tokens.fileStep4")}</li>
</ol>
<p className="muted small">{t("tokens.fileGuideNote")}</p>
</details>
<button className="btn sm ghost" style={{ marginTop: "var(--sp-2)" }}
onClick={() => setFrisch(null)}>
{t("tokens.hide")}