Tankstellen-Nadel, doppeltes Tanken-Symbol, Tankstellenname, packageClassList
Fuenf Meldungen des Eigentuemers, vier davon behoben: 1. Tankstellen-Nadel war bei 32 px ein dunkler Fleck (poi zeichnet nur einen Ring). Gefuellter Punkt in currentColor zwischen Silhouette und Symbol - die Geometrie stand im Pfad. Rot wurde ausdruecklich abgelehnt, die Umsetzung aendert keine Farbe. 2. Tanken-Symbol im Standort-Blatt wurde gefuellt UND umrandet, weil das CI-Symbol zusaetzlich stroke-Attribute mitbekam. Jetzt ueber ciSVG wie das Parkplatz-Symbol daneben; in der App dafuer imRaster an SymbolTanken (der 0,84-Faktor gilt nur in der Reiterleiste). 3. Tankstellenname: der Bestand hat zwei Bauformen - aeltere Belege tragen den Betreiber in station_name und die Anschrift daneben. Neu tankstelleTeile() liest beide Felder, entfernt Firmierungen, kuerzt Strassen und trennt die Hausnummer auch ohne Leerzeichen. Uebersicht: Marke, Strasse, Ort. Einzelbeleg: zwei Zeilen. adresseTeilen() hat keinen Aufrufer mehr und ist entfernt. 4. Zwischenablage und Kalendereintrag hatten denselben Grund: Capacitor 8 registriert nur, was in packageClassList steht, und cap sync traegt dort ausschliesslich npm-Pakete ein. Beide app-eigenen Plugins lagen im Programm und wurden nie registriert. ios-teilen-einrichten.mjs traegt sie jetzt nach, ios-signieren.sh bricht ab, wenn sie im fertigen .ipa fehlen. 5. Geteilter Beleg nur mit Datum und Uhrzeit bleibt offen - der Weg nutzt denselben belegLesen()-Pfad wie eine gewaehlte Datei, es fehlt der konkrete Beleg oder die Protokollzeile. tsc sauber, 246 Tests, vite build sauber, Panel als Modul geparst, audi_ha_test auf 2026.9.4.20 ohne Traceback. Panel und App liefern fuer die Tankstelle byteweise dasselbe.
This commit is contained in:
@@ -10574,3 +10574,155 @@ laeuft ja auf `.17` und bekommt `.19` erst, wenn die Instanz sie ausliefert. Der
|
||||
Weg bleibt: **erst die Integration auf der realen Instanz aktualisieren**
|
||||
(Einstellungen -> Version -> pruefen, installieren, neu starten), danach bietet
|
||||
die App den Schritt `.17` -> `.19` an, und der geht vorwaerts.
|
||||
|
||||
## CM. Fuenf Meldungen: die Nadel, zwei Symbole, der Tankstellenname - und der Grund, aus dem zwei eigene Plugins nie liefen (2026.9.4.20)
|
||||
|
||||
### 1. Die Tankstellen-Nadel war ein schwarzer Fleck
|
||||
|
||||
Gerendert und angesehen statt beurteilt: die Silhouette ist `#182634`, und das
|
||||
`poi`-Symbol darueber zeichnet nur einen duennen **Ring**. Bei echter
|
||||
Kartengroesse (32 px) verschwindet der Ring, uebrig bleibt eine dunkle Flaeche -
|
||||
waehrend die Fahrzeug-Nadel daneben durch das gefuellte Auto-Piktogramm sofort
|
||||
liest.
|
||||
|
||||
Vier Varianten nebeneinander gestellt (dunkel / Audi-Rot / weiss mit Kontur /
|
||||
weiss mit dunklem Punkt), jeweils gross und bei 32 px. **Rot vom Eigentuemer
|
||||
ausdruecklich abgelehnt** - am Code war zu dem Zeitpunkt nichts geaendert.
|
||||
|
||||
Umgesetzt ist die Variante, die **keine Farbe anfasst**: ein gefuellter Punkt in
|
||||
`currentColor` zwischen Silhouette und Symbol. Seine Geometrie steht im Pfad
|
||||
ohnehin (`poi-s`: 11.5/8.5 r3.5, `poi-l`: 23.5/17.5 r7.5) - erfunden ist daran
|
||||
nichts. Beide Codebasen.
|
||||
|
||||
### 2. Das Tanken-Symbol im Standort-Blatt wurde doppelt gezeichnet
|
||||
|
||||
Gemeldet als "dicker und dunkler als das Parkplatz-Icon". Der Grund steht im
|
||||
Markup: `ICONS.fuel` **ist** laengst das gefuellte CI-Symbol `fuel-station-s`,
|
||||
bekam an dieser einen Stelle aber zusaetzlich
|
||||
`stroke="currentColor" stroke-width="1.5"` mit - die Flaeche wurde also gefuellt
|
||||
UND umrandet. Exakt die Falle, die am 02.09.2026 schon `.profilbtn svg` mit dem
|
||||
Zahnrad hatte.
|
||||
|
||||
Jetzt derselbe Weg wie fuer `CI.parking` daneben (`ciSVG(...)`, ohne
|
||||
Strichattribute). Dazu die Groesse: gemessen fuellt `parking` das Raster mit
|
||||
23x23, `fuel-station-s` roh 19x22, mit dem 0,84-Faktor der Reiterleiste nur noch
|
||||
16,0x18,5. Neben dem randfuellenden Parkplatz-Symbol passt die **ungeskalierte**
|
||||
Fassung. In der App bekam `SymbolTanken` dafuer ein `imRaster` - der 0,84-Faktor
|
||||
ist die Kalibrierung fuer die Reiterleiste und gilt nur dort.
|
||||
|
||||
### 3. Tankstellenname: der Bestand hat zwei Bauformen
|
||||
|
||||
Gemeldet: im Beleg vom 10.11.2025 steht in der Uebersicht noch die Firmierung
|
||||
("...GmbH"). An den echten Datensaetzen nachgesehen - und die Ursache ist die
|
||||
Datenlage, nicht die Anzeige:
|
||||
|
||||
| | `station_name` | `station_address` |
|
||||
|---|---|---|
|
||||
| bis 2025 | `TC Sengül GmbH` (der **Betreiber**) | `Nürnberger Str.74, 91522 Ansbach` |
|
||||
| ab 08.2026 | `Shell, Pascalstr. 8, Ingolstadt` | `None` |
|
||||
|
||||
Die Markenerkennung lief nur ueber `station_name`. Bei den aelteren Belegen
|
||||
steht die Marke dort gar nicht - also blieb die Firmierung stehen, waehrend die
|
||||
Anschrift daneben ungenutzt blieb.
|
||||
|
||||
Neu `tankstelleTeile()` (beide Codebasen, wortgleich): liest **beide** Felder,
|
||||
zerlegt in Marke / Strasse / Hausnummer / Postleitzahl / Ort. Firmierungen
|
||||
(GmbH, mbH, OHG, KG, AG, e. K., Inh., & Co) fallen heraus, Strassen werden
|
||||
gekuerzt (`Nürnberger Straße` wird `Nürnberger Str.`), und die Hausnummer wird
|
||||
auch ohne Leerzeichen davor abgetrennt - so drucken die Kassen es (`Str.74`).
|
||||
|
||||
Daraus die beiden vom Eigentuemer verlangten Formen:
|
||||
|
||||
| Beleg | Uebersicht | Einzelbeleg |
|
||||
|---|---|---|
|
||||
| `TC Sengül GmbH` + Anschrift | `Nürnberger Str., Ansbach` | `Nürnberger Str. 74` / `91522 Ansbach` |
|
||||
| `Shell, Pascalstr. 8, Ingolstadt` | `Shell, Pascalstr., Ingolstadt` | `Shell, Pascalstr. 8` / `Ingolstadt` |
|
||||
| `Shell, SÖLDEN` | `Shell, SÖLDEN` | `Shell` / `SÖLDEN` |
|
||||
|
||||
Geschrieben wie eine Anschrift: das Komma trennt nur die Marke ab, zwischen
|
||||
Strasse und Nummer und zwischen Postleitzahl und Ort steht keins.
|
||||
|
||||
**Ein Rueckfall bleibt bewusst:** ergibt die Zerlegung gar nichts - nur eine
|
||||
Firmierung, keine Anschrift -, steht weiter der rohe Name da. Ihn dann
|
||||
wegzulassen hiesse, eine bekannte Tankstelle "unbekannt" zu nennen.
|
||||
|
||||
`adresseTeilen()` hat damit keinen Aufrufer mehr und ist in beiden Codebasen
|
||||
entfernt (samt seiner Tests).
|
||||
|
||||
### 4. Zwei eigene Plugins waren im Programm - und trotzdem tot
|
||||
|
||||
Gemeldet: die Zwischenablage meldet "die App muss erst aktualisiert werden",
|
||||
obwohl frisch installiert. Und, kurz darauf: der Kalendereintrag erzeugt weiter
|
||||
nur eine ics-Datei.
|
||||
|
||||
**Beides derselbe Grund**, gefunden durch Lesen der Capacitor-Quellen statt
|
||||
durch Vermuten. `CapacitorBridge.swift`, `registerPlugins()` liest
|
||||
`packageClassList` aus der `capacitor.config.json` des Buendels und loest jeden
|
||||
Eintrag ueber `NSClassFromString` auf.
|
||||
|
||||
**Capacitor 8 sucht Plugin-Klassen NICHT im Programm.** Registriert wird
|
||||
ausschliesslich, was in dieser Liste steht. Und die erzeugt `cap sync` aus den
|
||||
installierten **npm-Paketen** - app-eigene Plugins sind keine. In der
|
||||
ausgelieferten `.ipa` nachgesehen, dort stehen genau sechs Fremd-Plugins:
|
||||
SecureStorage, AppPlugin, FilesystemPlugin, PreferencesPlugin, SharePlugin,
|
||||
CapacitorUpdaterPlugin.
|
||||
|
||||
`BelegZwischenablagePlugin` und `KalenderTerminPlugin` fehlen - beide liegen
|
||||
nachweislich im Programm (5 Treffer im Binaerprogramm), werden aber nie
|
||||
registriert. Folgen, beide gemeldet:
|
||||
|
||||
* `Capacitor.isPluginAvailable("BelegZwischenablage")` ist `false` - daher die
|
||||
Meldung ueber die angeblich noetige Aktualisierung.
|
||||
* `KalenderTermin` scheitert mit `UNIMPLEMENTED`, der Rueckfall auf Datei plus
|
||||
Teilen-Blatt greift, und der kann den Kalender **prinzipiell nie erreichen**
|
||||
(Abschnitt BS). Daher "nur eine ics-Datei, kein Eintrag".
|
||||
|
||||
`scripts/ios-teilen-einrichten.mjs` traegt die Klassen jetzt nach jedem
|
||||
`cap sync` in `ios/App/App/capacitor.config.json` nach. Die Namen liest es aus
|
||||
den Swift-Dateien selbst (der objc-Name), damit sie nicht an zwei Stellen
|
||||
gepflegt werden; der Schritt ist idempotent (zweiter Lauf ergaenzt 0 Eintraege,
|
||||
geprueft).
|
||||
|
||||
Dazu ein **Waechter am fertigen `.ipa`**: fehlt eine der Klassen in
|
||||
`packageClassList`, bricht `ios-signieren.sh` ab. Gegen die ausgelieferte
|
||||
Fassung geprueft - er schlaegt dort korrekt an. Dieselbe Linie wie bei der
|
||||
`CFBundleVersion` (Abschnitt CD).
|
||||
|
||||
Die Fehlermeldung in `zwischenablage.ts` nannte eine **geratene Ursache** ("kommt
|
||||
nur mit einer neu installierten App") - die war nachweislich falsch, der
|
||||
Eigentuemer hatte frisch installiert. Sie nennt jetzt nur noch die Tatsache.
|
||||
|
||||
**Merkposten, zum zweiten Mal nach dem Preferences-Plugin (Abschnitt BR):** bei
|
||||
einem Plugin, das eine Systemgrenze ueberbrueckt, den Quelltext der Bruecke
|
||||
lesen, nicht die Dokumentation.
|
||||
|
||||
### 5. Geteilter Beleg: nur Datum und Uhrzeit - noch offen
|
||||
|
||||
Der Weg selbst ist geprueft und in Ordnung: ein ueber das Teilen-Blatt
|
||||
hereingereichter Beleg laeuft durch **exakt denselben** `belegLesen()`-Pfad wie
|
||||
eine von Hand gewaehlte Datei (`Tanken.tsx`), inklusive Dateiname aus dem Pfad.
|
||||
Es gibt dort keinen zweiten, aermeren Zweig.
|
||||
|
||||
Damit bleibt die Auswertung selbst, und die haengt am konkreten PDF - das hier
|
||||
nicht vorliegt. **Nicht geraten**: um es zu entscheiden, braucht es den Beleg
|
||||
(unter `tests/belegparser/belege/`, das ist gitignored) oder die Protokollzeile
|
||||
der Instanz zu diesem Hochladen.
|
||||
|
||||
### Verifiziert
|
||||
|
||||
`tsc --noEmit` sauber, **246 Tests** (von 244; 6 neue fuer die Zerlegung an den
|
||||
echten Belegen, eine Erwartung bewusst geaendert), `vite build` sauber, Panel als
|
||||
**Modul** geparst, `node --check` und `bash -n` auf beiden Skripten,
|
||||
`audi_ha_test` auf `2026.9.4.20` sauber gestartet, kein Traceback.
|
||||
|
||||
**Panel und App liefern fuer die Tankstelle byteweise dasselbe** - die
|
||||
Panel-Funktionen dafuer aus dem Buendel geschnitten und in Node gegen dieselben
|
||||
sieben Faelle laufen lassen, nicht nachgebaut.
|
||||
|
||||
**Live in der App**: in der Tankliste steht nirgends mehr "GmbH", der gemeldete
|
||||
Beleg liest sich `Nürnberger Str., Ansbach`, und der Einzelbeleg zeigt
|
||||
`Nürnberger Str. 74` / `91522 Ansbach`.
|
||||
|
||||
**Nicht geprueft, weil es ohne Mac nicht geht:** dass die Registrierung auf dem
|
||||
Geraet greift. Das zeigt erst der naechste Xcode-Lauf - und der bricht jetzt ab,
|
||||
wenn sie fehlt.
|
||||
|
||||
@@ -335,6 +335,21 @@ if unzip -q "$AUSGABE/App.ipa" -d "$PRUEF"; then
|
||||
echo " WARNUNG: $(basename "$ZIEL") ohne App-Gruppe - Teilen kann nichts uebergeben."
|
||||
fi
|
||||
done
|
||||
# Eigene Plugins muessen in packageClassList stehen. Capacitor 8 sucht
|
||||
# Plugin-Klassen NICHT im Programm (registerPlugins() in
|
||||
# CapacitorBridge.swift liest nur diese Liste) - eine einkompilierte
|
||||
# Klasse, die dort fehlt, existiert fuer die Bruecke nicht. Genau so kam
|
||||
# die .ipa vom 04.09.2026 heraus: Zwischenablage und Kalendertermin waren
|
||||
# im Programm und trotzdem tot.
|
||||
KONF="$PRUEF/Payload/App.app/capacitor.config.json"
|
||||
for KLASSE in $(grep -ho "@objc([A-Za-z0-9_]*)" "$APP/native"/*.swift | sed "s/@objc(//;s/)//"); do
|
||||
if ! grep -q "$KLASSE" "$KONF" 2>/dev/null; then
|
||||
echo " FEHLER: $KLASSE fehlt in packageClassList - das Plugin bliebe stumm." >&2
|
||||
rm -rf "$PRUEF"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo " packageClassList: eigene Plugins eingetragen"
|
||||
fi
|
||||
rm -rf "$PRUEF"
|
||||
|
||||
|
||||
@@ -97,6 +97,44 @@ for (const datei of PLUGIN_DATEIEN) {
|
||||
}
|
||||
melden(`Quellen nach ios/App/${ZIEL_NAME}/ kopiert`)
|
||||
|
||||
/* ------------------------------------------ Eigene Plugins registrieren */
|
||||
|
||||
/* Capacitor 8 sucht Plugin-Klassen NICHT im Programm. `registerPlugins()`
|
||||
in CapacitorBridge.swift liest ausschliesslich `packageClassList` aus der
|
||||
capacitor.config.json des Buendels und loest jeden Eintrag ueber
|
||||
NSClassFromString auf - was dort nicht steht, existiert fuer die Bruecke
|
||||
nicht, auch wenn die Klasse einkompiliert ist.
|
||||
|
||||
Genau daran sind beide eigenen Plugins gescheitert: `cap sync` erzeugt die
|
||||
Liste aus den installierten npm-Paketen, und unsere Plugins sind keine.
|
||||
In der ausgelieferten .ipa vom 04.09.2026 standen dort nur die sechs
|
||||
Fremd-Plugins - die Zwischenablage meldete deshalb 'diese App-Fassung
|
||||
enthaelt den nativen Teil noch nicht' (Nutzerbefund), obwohl
|
||||
BelegZwischenablagePlugin nachweislich im Programm lag, und der
|
||||
Kalendertermin fiel still auf das Teilen-Blatt zurueck, das den Kalender
|
||||
gar nicht erreichen kann (siehe AGENTS.md Abschnitt BS).
|
||||
|
||||
Die Klassennamen kommen aus den Swift-Dateien selbst (@objc(...)), damit
|
||||
sie nicht an zwei Stellen gepflegt werden muessen. */
|
||||
const konfigPfad = join(iosApp, "App", "capacitor.config.json")
|
||||
if (!existsSync(konfigPfad)) {
|
||||
abbruch(`${konfigPfad} nicht gefunden - erst 'npx cap sync ios' laufen lassen.`)
|
||||
}
|
||||
const eigeneKlassen = PLUGIN_DATEIEN.map((datei) => {
|
||||
const quelle = readFileSync(join(APP, "native", datei), "utf8")
|
||||
const treffer = /@objc\(([A-Za-z0-9_]+)\)/.exec(quelle)
|
||||
if (!treffer) abbruch(`${datei}: kein @objc(...)-Name gefunden`)
|
||||
return treffer[1]
|
||||
})
|
||||
const konfig = JSON.parse(readFileSync(konfigPfad, "utf8"))
|
||||
konfig.packageClassList = konfig.packageClassList ?? []
|
||||
const fehlten = eigeneKlassen.filter((k) => !konfig.packageClassList.includes(k))
|
||||
if (fehlten.length) {
|
||||
konfig.packageClassList.push(...fehlten)
|
||||
writeFileSync(konfigPfad, JSON.stringify(konfig, null, 2) + String.fromCharCode(10))
|
||||
}
|
||||
melden(`packageClassList: ${eigeneKlassen.join(", ")} eingetragen`)
|
||||
|
||||
/* ------------------------------------------------------------- URL-Schema */
|
||||
|
||||
/* Ohne dieses Schema kann die Erweiterung die App nicht holen - sie legt den
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import {
|
||||
adresseTeilen,
|
||||
kartendienstUrl,
|
||||
markeErkennen,
|
||||
tankstelleKandidaten,
|
||||
tankstelleSuchtext,
|
||||
tankstelleKurz,
|
||||
tankstelleTeile,
|
||||
tankstelleZeilen,
|
||||
tankstelleZiel,
|
||||
} from "./tankstelle"
|
||||
@@ -26,31 +27,6 @@ afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe("adresseTeilen", () => {
|
||||
it("trennt Straße und Ort am Komma", () => {
|
||||
expect(adresseTeilen("Pascalstr. 8, 85057 Ingolstadt")).toEqual({
|
||||
strasse: "Pascalstr. 8",
|
||||
ort: "85057 Ingolstadt",
|
||||
})
|
||||
})
|
||||
|
||||
it("trennt am LETZTEN Komma - Zusätze bleiben bei der Straße", () => {
|
||||
expect(adresseTeilen("Nürnberger Str.74, Gewerbepark, 91522 Ansbach")).toEqual({
|
||||
strasse: "Nürnberger Str.74, Gewerbepark",
|
||||
ort: "91522 Ansbach",
|
||||
})
|
||||
})
|
||||
|
||||
it("erfindet ohne Komma keinen Ort", () => {
|
||||
expect(adresseTeilen("Heidenheimerstr.6")).toEqual({ strasse: "Heidenheimerstr.6", ort: "" })
|
||||
})
|
||||
|
||||
it("verträgt leer und null", () => {
|
||||
expect(adresseTeilen(null)).toEqual({ strasse: "", ort: "" })
|
||||
expect(adresseTeilen(" ")).toEqual({ strasse: "", ort: "" })
|
||||
})
|
||||
})
|
||||
|
||||
describe("markeErkennen", () => {
|
||||
it("findet die Kopfmarke im gespeicherten Namen", () => {
|
||||
expect(markeErkennen("Shell, SÖLDEN")).toBe("Shell")
|
||||
@@ -92,7 +68,8 @@ describe("tankstelleZeilen", () => {
|
||||
|
||||
it("zeigt ohne Anschrift den gespeicherten Namen unveraendert", () => {
|
||||
// Sonst hiesse eine bekannte Tankstelle plötzlich "unbekannt".
|
||||
expect(tankstelleZeilen("Shell, SÖLDEN", null)).toEqual({ oben: "Shell, SÖLDEN", unten: "" })
|
||||
// Geaendert am 04.09.2026: die Marke gehoert nach oben, der Ort nach unten.
|
||||
expect(tankstelleZeilen("Shell, SÖLDEN", null)).toEqual({ oben: "Shell", unten: "SÖLDEN" })
|
||||
expect(tankstelleZeilen("Testtankstelle", null)).toEqual({ oben: "Testtankstelle", unten: "" })
|
||||
})
|
||||
|
||||
@@ -216,3 +193,59 @@ describe("kartendienstUrl", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/* Die Vorgabe des Eigentuemers vom 04.09.2026, an den ECHTEN Datensaetzen der
|
||||
Instanz geprueft - beide Bauformen des Bestandes:
|
||||
|
||||
bis 2025: name = Firmierung des Betreibers, Anschrift daneben
|
||||
ab 08.2026: name = "Marke, Strasse, Ort", keine Anschrift
|
||||
|
||||
Uebersicht: Marke, Strasse ohne Hausnummer, Ort ohne Postleitzahl
|
||||
Einzelbeleg: oben Marke + Strasse mit Hausnummer, unten Postleitzahl + Ort */
|
||||
describe("tankstelleKurz und tankstelleZeilen an echten Belegen", () => {
|
||||
it("holt Strasse und Ort aus der Anschrift, wenn im Namen nur die Firmierung steht", () => {
|
||||
// Genau der gemeldete Beleg vom 10.11.2025.
|
||||
const n = "TC Sengül GmbH"
|
||||
const a = "Nürnberger Str.74, 91522 Ansbach"
|
||||
expect(tankstelleKurz(n, a)).toBe("Nürnberger Str., Ansbach")
|
||||
expect(tankstelleZeilen(n, a)).toEqual({ oben: "Nürnberger Str. 74", unten: "91522 Ansbach" })
|
||||
})
|
||||
|
||||
it("zerlegt auch den zusammengesetzten Namen der neueren Belege", () => {
|
||||
const n = "Shell, Pascalstr. 8, Ingolstadt"
|
||||
expect(tankstelleKurz(n, null)).toBe("Shell, Pascalstr., Ingolstadt")
|
||||
expect(tankstelleZeilen(n, null)).toEqual({ oben: "Shell, Pascalstr. 8", unten: "Ingolstadt" })
|
||||
})
|
||||
|
||||
it("kuerzt die ausgeschriebene Strasse", () => {
|
||||
const n = "Aral, Nürnberger Straße 74, 91522 Ansbach"
|
||||
expect(tankstelleKurz(n, null)).toBe("Aral, Nürnberger Str., Ansbach")
|
||||
})
|
||||
|
||||
it("laesst die Firmierung ueberall weg", () => {
|
||||
expect(tankstelleKurz("Hermann Mogler Mineralölg. GmbH", "Heidenheimerstr.6, 89551 Königsbronn")).toBe(
|
||||
"Heidenheimerstr., Königsbronn",
|
||||
)
|
||||
})
|
||||
|
||||
it("behaelt den rohen Namen, wenn sich sonst nichts gewinnen laesst", () => {
|
||||
// Sonst hiesse eine bekannte Tankstelle ploetzlich "unbekannt".
|
||||
expect(tankstelleKurz("Testtankstelle", null)).toBe("Testtankstelle")
|
||||
expect(tankstelleKurz("A. Zrenner GmbH", null)).toBe("A. Zrenner GmbH")
|
||||
expect(tankstelleKurz(null, null)).toBe("unbekannt")
|
||||
})
|
||||
|
||||
it("trennt Hausnummer auch ohne Leerzeichen davor", () => {
|
||||
// So drucken die Kassen es: "Str.74", nicht "Str. 74".
|
||||
expect(tankstelleTeile(null, "Heidenheimerstr.6, 89551 Königsbronn")).toMatchObject({
|
||||
strasse: "Heidenheimerstr.",
|
||||
hausnummer: "6",
|
||||
plz: "89551",
|
||||
ort: "Königsbronn",
|
||||
})
|
||||
expect(tankstelleTeile(null, "Am Anger 7a, 85111 Adelschlag")).toMatchObject({
|
||||
strasse: "Am Anger",
|
||||
hausnummer: "7a",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -72,38 +72,160 @@ export function markeErkennen(name: string | null | undefined): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
/** `"Pascalstr. 8, 85057 Ingolstadt"` → Straße und Ort. */
|
||||
export function adresseTeilen(adresse: string | null | undefined): {
|
||||
/* Kennzeichen einer Firmierung. Ein Teil, der so etwas enthaelt, ist der
|
||||
BETREIBER und gehoert nach Vorgabe des Eigentuemers nicht in die Anzeige -
|
||||
"TC Sengül GmbH" oder "Hermann Mogler Mineralölg. GmbH" stehen auf keinem
|
||||
Preisschild und in keiner Karte. */
|
||||
const FIRMA = /\b(gmbh|mbh|ohg|kg|ag|e\.\s?k\.|inh\.|& co)\b/i
|
||||
|
||||
/* "91522 Ansbach" - vier Stellen wegen Oesterreich (der Belegparser hat
|
||||
denselben Fall, siehe shell_beleg_parser.py). */
|
||||
const PLZ_ORT = /^(\d{4,5})\s+(.+)$/
|
||||
|
||||
/* Strasse mit Hausnummer am Ende. Greedy bis zum letzten Nicht-Ziffernzeichen,
|
||||
damit auch die zusammengeschriebene Form der Belege trifft:
|
||||
"Nürnberger Str.74" -> "Nürnberger Str." + "74". */
|
||||
const STRASSE_NR = /^(.*\D)\s*(\d+\s*[a-zA-Z]?)$/
|
||||
|
||||
/**
|
||||
* "Nürnberger Straße" → "Nürnberger Str.", "Heidenheimerstraße" →
|
||||
* "Heidenheimerstr.". Vorgabe des Eigentuemers fuer die Uebersicht; die Belege
|
||||
* liefern beide Schreibweisen, je nach Kasse.
|
||||
*
|
||||
* Die Grossschreibung des Treffers entscheidet, ob "Str." oder "str." daraus
|
||||
* wird - sonst stuende mitten im Wort ein grosses S.
|
||||
*/
|
||||
export function strasseKuerzen(strasse: string): string {
|
||||
return strasse.replace(/stra(?:ß|ss)e\b\.?/gi, (treffer) =>
|
||||
treffer[0] === treffer[0]?.toUpperCase() ? "Str." : "str.",
|
||||
)
|
||||
}
|
||||
|
||||
export interface Tankstellenteile {
|
||||
/** Kopfmarke, sofern erkennbar. Nie die Firmierung des Betreibers. */
|
||||
marke: string
|
||||
/** Ohne Hausnummer, in der kurzen Schreibweise. */
|
||||
strasse: string
|
||||
hausnummer: string
|
||||
plz: string
|
||||
ort: string
|
||||
} {
|
||||
const roh = (adresse ?? "").trim()
|
||||
if (!roh) return { strasse: "", ort: "" }
|
||||
const komma = roh.lastIndexOf(",")
|
||||
if (komma < 0) return { strasse: roh, ort: "" }
|
||||
return { strasse: roh.slice(0, komma).trim(), ort: roh.slice(komma + 1).trim() }
|
||||
/** Der gespeicherte Name, unveraendert - der letzte Rueckfall. */
|
||||
roh: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Die beiden Zeilen der Tankstellen-Angabe (Vorgabe des Eigentümers):
|
||||
* oben Kopfmarke und Straße mit Hausnummer, unten Postleitzahl und Ort.
|
||||
* Zerlegt, was ein Beleg ueber die Tankstelle hergibt.
|
||||
*
|
||||
* Ohne erkennbare Marke steht oben nur die Straße - die Firmierung des
|
||||
* Betreibers tritt NICHT an ihre Stelle, sie ist nach außen unsichtbar.
|
||||
* Trägt der Beleg gar keine Anschrift, bleibt der gespeicherte Name die
|
||||
* einzige Auskunft und wird unverändert gezeigt ("Shell, SÖLDEN"); ihn dann
|
||||
* wegzulassen hieße, eine bekannte Tankstelle "unbekannt" zu nennen.
|
||||
* WARUM BEIDE FELDER GELESEN WERDEN
|
||||
* ---------------------------------
|
||||
* Der Bestand hat zwei Bauformen, am 04.09.2026 an den echten Datensaetzen
|
||||
* nachgesehen:
|
||||
*
|
||||
* `oben` ist nie leer: nennt der Beleg nichts, heißt es dort "unbekannt".
|
||||
* bis 2025: station_name = "TC Sengül GmbH" (der Betreiber!)
|
||||
* station_address = "Nürnberger Str.74, 91522 Ansbach"
|
||||
* ab 08.2026: station_name = "Shell, Pascalstr. 8, Ingolstadt"
|
||||
* station_address = null
|
||||
*
|
||||
* Die Markenerkennung lief bis hierher nur ueber `station_name`. Bei den
|
||||
* aelteren Belegen steht die Marke dort gar nicht - deshalb blieb in der
|
||||
* Uebersicht die Firmierung stehen ("...GmbH", Nutzerbefund 04.09.2026),
|
||||
* waehrend die Anschrift daneben ungenutzt blieb. Gelesen werden deshalb
|
||||
* beide Felder: die Anschrift zuerst (sie ist die strukturierte Quelle), der
|
||||
* Name danach fuer Marke und alles, was die Anschrift nicht hergibt.
|
||||
*/
|
||||
export function tankstelleTeile(
|
||||
name: string | null | undefined,
|
||||
adresse: string | null | undefined,
|
||||
): Tankstellenteile {
|
||||
const teile: Tankstellenteile = {
|
||||
marke: markeErkennen(name) ?? "",
|
||||
strasse: "",
|
||||
hausnummer: "",
|
||||
plz: "",
|
||||
ort: "",
|
||||
roh: (name ?? "").trim(),
|
||||
}
|
||||
|
||||
const quellen: string[] = []
|
||||
for (const stueck of (adresse ?? "").split(",")) quellen.push(stueck)
|
||||
for (const stueck of (name ?? "").split(",")) {
|
||||
const t = stueck.trim()
|
||||
// Die Marke steht schon fest; als Ort taugt sie nicht.
|
||||
if (teile.marke && t.toLowerCase() === teile.marke.toLowerCase()) continue
|
||||
quellen.push(t)
|
||||
}
|
||||
|
||||
for (const stueck of quellen) {
|
||||
const t = stueck.trim()
|
||||
if (!t || FIRMA.test(t)) continue
|
||||
|
||||
const plzOrt = PLZ_ORT.exec(t)
|
||||
if (plzOrt) {
|
||||
if (!teile.plz) teile.plz = plzOrt[1] ?? ""
|
||||
if (!teile.ort) teile.ort = (plzOrt[2] ?? "").trim()
|
||||
continue
|
||||
}
|
||||
|
||||
const mitNummer = STRASSE_NR.exec(t)
|
||||
if (mitNummer) {
|
||||
if (!teile.strasse) {
|
||||
teile.strasse = strasseKuerzen((mitNummer[1] ?? "").trim())
|
||||
teile.hausnummer = (mitNummer[2] ?? "").replace(/\s+/g, "")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Weder Postleitzahl noch Hausnummer: der Ort. Eine Strasse ohne Nummer
|
||||
// laesst sich davon nicht sicher unterscheiden - im Zweifel der Ort, weil
|
||||
// er in der Uebersicht die wichtigere Angabe ist.
|
||||
if (!teile.ort) teile.ort = t
|
||||
}
|
||||
|
||||
return teile
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Zeile der Uebersicht: **Marke, Strasse ohne Hausnummer, Ort ohne
|
||||
* Postleitzahl** (Vorgabe des Eigentuemers, 04.09.2026).
|
||||
*
|
||||
* Ergibt die Zerlegung gar nichts - etwa wenn nur eine Firmierung ohne
|
||||
* Anschrift gespeichert ist - bleibt der rohe Name stehen. Ihn dann
|
||||
* wegzulassen hiesse, eine bekannte Tankstelle "unbekannt" zu nennen.
|
||||
*/
|
||||
export function tankstelleKurz(
|
||||
name: string | null | undefined,
|
||||
adresse: string | null | undefined,
|
||||
): string {
|
||||
const t = tankstelleTeile(name, adresse)
|
||||
const zeile = [t.marke, t.strasse, t.ort].filter(Boolean).join(", ")
|
||||
return zeile || t.roh || "unbekannt"
|
||||
}
|
||||
|
||||
/**
|
||||
* Die beiden Zeilen im Einzelbeleg (Vorgabe des Eigentuemers, 04.09.2026):
|
||||
* oben **Marke, Strasse mit Hausnummer**, unten **Postleitzahl und Ort**.
|
||||
*
|
||||
* Geschrieben wie eine Anschrift, also "Shell, Pascalstr. 8" und "85057
|
||||
* Ingolstadt" - das Komma trennt die Marke ab, zwischen Strasse und Nummer
|
||||
* und zwischen Postleitzahl und Ort steht keins.
|
||||
*
|
||||
* `oben` ist nie leer: gibt der Beleg nur einen Ort her, rueckt der nach oben,
|
||||
* und kennt er gar nichts, heisst es dort "unbekannt".
|
||||
*/
|
||||
export function tankstelleZeilen(
|
||||
name: string | null | undefined,
|
||||
adresse: string | null | undefined,
|
||||
): { oben: string; unten: string } {
|
||||
const { strasse, ort } = adresseTeilen(adresse)
|
||||
if (!strasse && !ort) return { oben: (name ?? "").trim() || "unbekannt", unten: "" }
|
||||
const oben = [markeErkennen(name) ?? "", strasse].filter(Boolean).join(", ")
|
||||
return { oben: oben || "unbekannt", unten: ort }
|
||||
const t = tankstelleTeile(name, adresse)
|
||||
const strasse = [t.strasse, t.hausnummer].filter(Boolean).join(" ")
|
||||
let oben = [t.marke, strasse].filter(Boolean).join(", ")
|
||||
let unten = [t.plz, t.ort].filter(Boolean).join(" ")
|
||||
if (!oben && !unten) return { oben: t.roh || "unbekannt", unten: "" }
|
||||
if (!oben) {
|
||||
oben = unten
|
||||
unten = ""
|
||||
}
|
||||
return { oben, unten }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,16 +67,23 @@ function base64ZuDatei(base64: string, name: string): File {
|
||||
*/
|
||||
export async function pdfAusZwischenablage(): Promise<File> {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
/* Der native Teil steckt in der .ipa, nicht im OTA-Bündel: eine App, die
|
||||
nur über die Aktualisierung in der App gewachsen ist, hat ihn nicht.
|
||||
Genau das war bis zum 04.09.2026 nicht zu erkennen - der Fehlschlag kam
|
||||
mit demselben Text wie eine abgelehnte Rückfrage ("Kein Zugriff"), und
|
||||
der schickt in die falsche Richtung. `isPluginAvailable` fragt die
|
||||
Brücke, ob die Klasse im Programm ueberhaupt vorhanden ist. */
|
||||
/* Meldet sich der native Teil nicht bei der Bruecke, hilft nur die
|
||||
Dateiauswahl - der Web-Weg kann ein PDF grundsaetzlich nicht liefern
|
||||
(Begruendung im Modulkopf).
|
||||
|
||||
Bis zum 04.09.2026 stand hier die Vermutung, die App sei nur ueber die
|
||||
Aktualisierung gewachsen. Sie war falsch: der Eigentuemer hatte frisch
|
||||
installiert, und BelegZwischenablagePlugin lag nachweislich im
|
||||
Programm. Capacitor 8 sucht Plugin-Klassen aber nicht im Programm,
|
||||
sondern registriert ausschliesslich, was in packageClassList der
|
||||
capacitor.config.json steht - und `cap sync` traegt dort nur
|
||||
npm-Pakete ein, keine app-eigenen Plugins. Behoben in
|
||||
scripts/ios-teilen-einrichten.mjs; hier steht deshalb nur noch die
|
||||
Tatsache und nicht mehr eine geratene Ursache. */
|
||||
if (!Capacitor.isPluginAvailable("BelegZwischenablage")) {
|
||||
throw new ZwischenablageFehler(
|
||||
"Diese App-Fassung enthält den nativen Teil noch nicht - er kommt nur mit " +
|
||||
'einer neu installierten App, nicht über die Aktualisierung. Bis dahin "Datei auswählen".',
|
||||
'Diese App-Fassung kann die Zwischenablage nicht abfragen - bitte ' +
|
||||
'"Datei auswählen".',
|
||||
)
|
||||
}
|
||||
let ergebnis: { base64?: string; name?: string }
|
||||
|
||||
@@ -38,9 +38,17 @@ const CI_POI = '<path fill="currentColor" d="M4 8.5C4 4.32 7.32 1 11.5 1S19 4.32
|
||||
const CI_GPS = 'M12.5 3.51V6.5h-1V3.51a8.46 8.46 0 0 0-7.99 7.99H6.5v1H3.51a8.46 8.46 0 0 0 7.99 7.99V17.5h1v2.99a8.46 8.46 0 0 0 7.99-7.99H17.5v-1h2.99a8.46 8.46 0 0 0-7.99-7.99m0-1a9.46 9.46 0 0 1 8.99 8.99h2.01v1h-2.01a9.46 9.46 0 0 1-8.99 8.99v2.01h-1v-2.01a9.46 9.46 0 0 1-8.99-8.99H.5v-1h2.01a9.46 9.46 0 0 1 8.99-8.99V.5h1z'
|
||||
|
||||
function nadelSvg(px = 32): string {
|
||||
// Der Ring im poi-Icon ist bei Kartengroesse zu duenn, um sich von der
|
||||
// dunklen Silhouette abzuheben - die Nadel las sich als schwarzer Fleck
|
||||
// (Nutzerbefund 04.09.2026). Der gefuellte Punkt schliesst genau den
|
||||
// Kreis, der im Pfad ohnehin steht (poi-s: 11.5/8.5 r3.5, poi-l:
|
||||
// 23.5/17.5 r7.5) - erfunden ist daran nichts, und keine Farbe aendert
|
||||
// sich: currentColor ist dasselbe Weiss wie die uebrigen Konturlinien.
|
||||
const punkt = '<circle cx="11.5" cy="8.5" r="2.9" fill="currentColor"/>'
|
||||
return (
|
||||
`<svg viewBox="0 0 24 24" width="${px}" height="${px}" fill="none" aria-hidden="true">` +
|
||||
CI_POI_SIL +
|
||||
punkt +
|
||||
CI_POI +
|
||||
"</svg>"
|
||||
)
|
||||
|
||||
@@ -598,7 +598,7 @@ export function Standort() {
|
||||
)}
|
||||
</div>
|
||||
<div className="dm-standortmenu__werte">
|
||||
<SymbolTanken groesse={16} />
|
||||
<SymbolTanken groesse={16} imRaster />
|
||||
<span>{de(fahrzeug.tankPct)} %</span>
|
||||
<span>{fahrzeug.reichweite != null ? `${de(fahrzeug.reichweite)} km` : "Reichweite unbekannt"}</span>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import { Blattzeile, KRAFTSTOFFSORTEN, Leerzustand } from "./bausteine"
|
||||
import { BelegPopup } from "./BelegPopup"
|
||||
import { markenlogoUrl } from "./bilder"
|
||||
import { tankstelleKurz } from "../daten/tankstelle"
|
||||
import { LOESCH_HINWEIS } from "./bestaetigung"
|
||||
import { Zeilenmenue } from "./Zeilenmenue"
|
||||
|
||||
@@ -124,7 +125,7 @@ export function Tanken({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
|
||||
kinder={
|
||||
<Blattzeile
|
||||
label={datum(tank.ts)}
|
||||
labelZusatz={tank.station_name ?? ""}
|
||||
labelZusatz={tankstelleKurz(tank.station_name, tank.station_address)}
|
||||
wert={tank.liters != null ? `${de(tank.liters, 2)} l` : ""}
|
||||
wertZusatz={preis != null ? `${de(preis, 2)} €/l` : ""}
|
||||
beiKlick={() => geheZu("fill", tank.tank_id)}
|
||||
|
||||
@@ -167,14 +167,25 @@ export function SymbolPaket({ groesse }: SymbolProps) {
|
||||
|
||||
Die 48er-Variante hieße fuel-station-l und wird nicht gebraucht: die
|
||||
Reiter zeichnen mit 21 bzw. 26px, also im 24er-Raster. */
|
||||
export function SymbolTanken({ groesse }: SymbolProps) {
|
||||
/* Der 0,84-Faktor ist die Kalibrierung fuer die Reiterleiste: dort steht das
|
||||
Symbol neben vier anderen und waere roh das hoechste (Befund 02.09.2026,
|
||||
19x22 gegen 24er-Raster). Ausserhalb der Leiste stimmt er nicht - im
|
||||
Standort-Blatt steht daneben das randfuellende Parkplatz-Symbol (23x23),
|
||||
und die geskalierte Fassung (16,0x18,5) wirkte daneben zu klein.
|
||||
`imRaster` laesst das Symbol seine eigene Groesse behalten. */
|
||||
export function SymbolTanken({ groesse, imRaster = false }: SymbolProps & { imRaster?: boolean }) {
|
||||
const pfad = (
|
||||
<path fill="currentColor" d="M4 3.5A2.5 2.5 0 0 1 6.5 1h6A2.5 2.5 0 0 1 15 3.5V10h.5a2.5 2.5 0 0 1 2.5 2.5v5a1.5 1.5 0 0 0 3 0V8.07a16.4 16.4 0 0 0-2.89-5.76l.78-.62a17.4 17.4 0 0 1 3.1 6.17l.01.07v9.57a2.5 2.5 0 0 1-5 0v-5c0-.83-.67-1.5-1.5-1.5H15v11h1v1H3v-1h1zM5 22h9V3.5c0-.83-.67-1.5-1.5-1.5h-6C5.67 2 5 2.67 5 3.5zm1-12h7v1H6z" />
|
||||
)
|
||||
return (
|
||||
<RahmenFlaeche
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
imRaster ? pfad : (
|
||||
<g transform="translate(1.5,1.92) scale(0.84)">
|
||||
<path fill="currentColor" d="M4 3.5A2.5 2.5 0 0 1 6.5 1h6A2.5 2.5 0 0 1 15 3.5V10h.5a2.5 2.5 0 0 1 2.5 2.5v5a1.5 1.5 0 0 0 3 0V8.07a16.4 16.4 0 0 0-2.89-5.76l.78-.62a17.4 17.4 0 0 1 3.1 6.17l.01.07v9.57a2.5 2.5 0 0 1-5 0v-5c0-.83-.67-1.5-1.5-1.5H15v11h1v1H3v-1h1zM5 22h9V3.5c0-.83-.67-1.5-1.5-1.5h-6C5.67 2 5 2.67 5 3.5zm1-12h7v1H6z" />
|
||||
{pfad}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":"2026.9.4.19","sha256":"58969a387bfa72c3d452c04b689e8ec01440c9a2a89b7a097d43fe0418092ae6","bytes":322568,"gebaut":"2026-09-04T14:06:53Z"}
|
||||
{"version":"2026.9.4.20","sha256":"95213bb0998806d4b59b4d520d2dd93a4f1c7ce48991e6f77b38e9dbca8247a8","bytes":322945,"gebaut":"2026-09-04T14:28:33Z"}
|
||||
Binary file not shown.
@@ -981,7 +981,16 @@ function tankstellenMarkerSVG(px = 32) {
|
||||
const box = gross ? 48 : 24;
|
||||
const sil = gross ? CI.poiLSil : CI.poiSSil;
|
||||
const icon = gross ? CI.poiL : CI.poiS;
|
||||
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${sil}${icon}</svg>`;
|
||||
// Der Ring im poi-Icon ist bei Kartengroesse zu duenn, um sich von der
|
||||
// dunklen Silhouette abzuheben - die Nadel las sich als schwarzer Fleck
|
||||
// (Nutzerbefund 04.09.2026). Der gefuellte Punkt schliesst genau den
|
||||
// Kreis, der im Pfad ohnehin steht (poi-s: 11.5/8.5 r3.5, poi-l:
|
||||
// 23.5/17.5 r7.5) - erfunden ist daran nichts, und keine Farbe aendert
|
||||
// sich: currentColor ist dasselbe Weiss wie die uebrigen Konturlinien.
|
||||
const punkt = gross
|
||||
? '<circle cx="23.5" cy="17.5" r="6.2" fill="currentColor"/>'
|
||||
: '<circle cx="11.5" cy="8.5" r="2.9" fill="currentColor"/>';
|
||||
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${sil}${punkt}${icon}</svg>`;
|
||||
}
|
||||
|
||||
/* Fahrzustand fürs Menü: kommt aus der Zündung (CAR.zuendung, vom Backend aus
|
||||
@@ -1322,23 +1331,86 @@ function markeErkennen(name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function adresseTeilen(adresse) {
|
||||
const roh = (adresse || "").trim();
|
||||
if (!roh) return { strasse: "", ort: "" };
|
||||
const komma = roh.lastIndexOf(",");
|
||||
if (komma < 0) return { strasse: roh, ort: "" };
|
||||
return { strasse: roh.slice(0, komma).trim(), ort: roh.slice(komma + 1).trim() };
|
||||
/* Kennzeichen einer Firmierung. Ein Teil, der so etwas enthaelt, ist der
|
||||
BETREIBER und gehoert nach Vorgabe des Eigentuemers nicht in die Anzeige. */
|
||||
const FIRMA_MUSTER = /\b(gmbh|mbh|ohg|kg|ag|e\.\s?k\.|inh\.|& co)\b/i;
|
||||
const PLZ_ORT_MUSTER = /^(\d{4,5})\s+(.+)$/;
|
||||
/* Greedy bis zum letzten Nicht-Ziffernzeichen, damit auch die
|
||||
zusammengeschriebene Form der Belege trifft: "Nürnberger Str.74". */
|
||||
const STRASSE_NR_MUSTER = /^(.*\D)\s*(\d+\s*[a-zA-Z]?)$/;
|
||||
|
||||
/* "Nürnberger Straße" -> "Nürnberger Str.". Die Grossschreibung des Treffers
|
||||
entscheidet, sonst stuende mitten im Wort ein grosses S. */
|
||||
function strasseKuerzen(strasse) {
|
||||
return strasse.replace(/stra(?:ß|ss)e\b\.?/gi, (t) => (t[0] === t[0].toUpperCase() ? "Str." : "str."));
|
||||
}
|
||||
|
||||
/* Oben Kopfmarke und Strasse mit Hausnummer, unten Postleitzahl und Ort.
|
||||
Ohne erkennbare Marke steht oben nur die Strasse - die Firmierung des
|
||||
Betreibers tritt NICHT an ihre Stelle. Ohne Anschrift bleibt der
|
||||
gespeicherte Name die einzige Auskunft und wird unveraendert gezeigt. */
|
||||
/* Zerlegt, was ein Beleg ueber die Tankstelle hergibt.
|
||||
|
||||
Der Bestand hat zwei Bauformen, am 04.09.2026 an den echten Datensaetzen
|
||||
nachgesehen:
|
||||
bis 2025: station_name = "TC Sengül GmbH" (der Betreiber!)
|
||||
station_address = "Nürnberger Str.74, 91522 Ansbach"
|
||||
ab 08.2026: station_name = "Shell, Pascalstr. 8, Ingolstadt"
|
||||
station_address = null
|
||||
|
||||
Die Markenerkennung lief bis hierher nur ueber den Namen - bei den aelteren
|
||||
Belegen steht die Marke dort gar nicht, deshalb blieb in der Uebersicht die
|
||||
Firmierung stehen ("...GmbH", Nutzerbefund 04.09.2026), waehrend die
|
||||
Anschrift daneben ungenutzt blieb. Gelesen werden jetzt beide Felder. */
|
||||
function tankstelleTeile(name, adresse) {
|
||||
const teile = { marke: markeErkennen(name) || "", strasse: "", hausnummer: "", plz: "", ort: "", roh: (name || "").trim() };
|
||||
const quellen = (adresse || "").split(",");
|
||||
for (const stueck of (name || "").split(",")) {
|
||||
const t = stueck.trim();
|
||||
if (teile.marke && t.toLowerCase() === teile.marke.toLowerCase()) continue;
|
||||
quellen.push(t);
|
||||
}
|
||||
for (const stueck of quellen) {
|
||||
const t = stueck.trim();
|
||||
if (!t || FIRMA_MUSTER.test(t)) continue;
|
||||
const plzOrt = PLZ_ORT_MUSTER.exec(t);
|
||||
if (plzOrt) {
|
||||
if (!teile.plz) teile.plz = plzOrt[1];
|
||||
if (!teile.ort) teile.ort = plzOrt[2].trim();
|
||||
continue;
|
||||
}
|
||||
const mitNummer = STRASSE_NR_MUSTER.exec(t);
|
||||
if (mitNummer) {
|
||||
if (!teile.strasse) {
|
||||
teile.strasse = strasseKuerzen(mitNummer[1].trim());
|
||||
teile.hausnummer = mitNummer[2].replace(/\s+/g, "");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* Weder Postleitzahl noch Hausnummer: der Ort. Eine Strasse ohne Nummer
|
||||
laesst sich davon nicht sicher unterscheiden - im Zweifel der Ort, weil
|
||||
er in der Uebersicht die wichtigere Angabe ist. */
|
||||
if (!teile.ort) teile.ort = t;
|
||||
}
|
||||
return teile;
|
||||
}
|
||||
|
||||
/* Zeile der Uebersicht: Marke, Strasse ohne Hausnummer, Ort ohne Postleitzahl
|
||||
(Vorgabe des Eigentuemers, 04.09.2026). Ergibt die Zerlegung nichts, bleibt
|
||||
der rohe Name stehen - ihn wegzulassen hiesse, eine bekannte Tankstelle
|
||||
"unbekannt" zu nennen. */
|
||||
function tankstelleKurz(name, adresse) {
|
||||
const t = tankstelleTeile(name, adresse);
|
||||
return [t.marke, t.strasse, t.ort].filter(Boolean).join(", ") || t.roh || "unbekannt";
|
||||
}
|
||||
|
||||
/* Die beiden Zeilen im Einzelbeleg: oben Marke und Strasse mit Hausnummer,
|
||||
unten Postleitzahl und Ort - geschrieben wie eine Anschrift, das Komma
|
||||
trennt nur die Marke ab. `oben` ist nie leer. */
|
||||
function tankstelleZeilen(name, adresse) {
|
||||
const { strasse, ort } = adresseTeilen(adresse);
|
||||
if (!strasse && !ort) return { oben: (name || "").trim() || "unbekannt", unten: "" };
|
||||
const oben = [markeErkennen(name) || "", strasse].filter(Boolean).join(", ");
|
||||
return { oben: oben || "unbekannt", unten: ort };
|
||||
const t = tankstelleTeile(name, adresse);
|
||||
const strasse = [t.strasse, t.hausnummer].filter(Boolean).join(" ");
|
||||
let oben = [t.marke, strasse].filter(Boolean).join(", ");
|
||||
let unten = [t.plz, t.ort].filter(Boolean).join(" ");
|
||||
if (!oben && !unten) return { oben: t.roh || "unbekannt", unten: "" };
|
||||
if (!oben) { oben = unten; unten = ""; }
|
||||
return { oben, unten };
|
||||
}
|
||||
|
||||
/* Suchbegriffe fuer die Geokodierung, in der Reihenfolge des Versuchs: erst
|
||||
@@ -1768,7 +1840,13 @@ function vStandort() {
|
||||
: `${ciSVG(CI.parking)}<span>${zustand.seitTs != null ? "Geparkt seit " + dauerSeitText(zustand.seitTs) : "Fahrzeug steht, Parkdauer unbekannt"}</span>`}
|
||||
</div>
|
||||
<div class="standortmenu-werte">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS.fuel}</svg>
|
||||
<!-- CI-Symbole sind Flaechen und tragen ihr fill selbst. Die
|
||||
Strichattribute von oben zogen zusaetzlich eine 1,5er Kontur um
|
||||
jede Flaeche - das Symbol wurde gefuellt UND umrandet gezeichnet
|
||||
und wirkte neben dem Parkplatz-Symbol dicker und dunkler
|
||||
(Nutzerbefund 04.09.2026). Dieselbe Falle wie beim Zahnrad in
|
||||
.profilbtn svg. Jetzt derselbe Weg wie fuer CI.parking daneben. -->
|
||||
${ciSVG(CI.fuelStationS, 16)}
|
||||
<span>${de(CAR.tankPct)} %</span>
|
||||
<span>${CAR.reichweite != null ? de(CAR.reichweite) + " km" : "Reichweite unbekannt"}</span>
|
||||
</div>
|
||||
@@ -4674,7 +4752,7 @@ function vFuel() {
|
||||
<span>Ø ${de(schnittpreis(fm), 2)} €/l</span></span></button>
|
||||
<div class="acc-b ${mi === 0 ? "on" : ""}" id="fm${j}${m}">`;
|
||||
fm.forEach((f) => { const preis = literpreis(f); h += swipeZeile(`fill:${f.tank_id}`, `<button class="leaf" data-go="fill:${f.tank_id}">
|
||||
<span class="k">${dedat(new Date(f.ts))}<small>${esc(f.station_name || "")}</small></span>
|
||||
<span class="k">${dedat(new Date(f.ts))}<small>${esc(tankstelleKurz(f.station_name, f.station_address))}</small></span>
|
||||
<span class="v">${f.liters != null ? de(f.liters, 2) + " l" : ""}<small>${preis != null ? de(preis, 2) + " €/l" : ""}</small></span>
|
||||
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
|
||||
h += `</div></div>`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "audi_dashboard",
|
||||
"name": "Audi Dashboard",
|
||||
"version": "2026.9.4.19",
|
||||
"version": "2026.9.4.20",
|
||||
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||
"codeowners": [
|
||||
|
||||
Reference in New Issue
Block a user