Commit Graph

19 Commits

Author SHA1 Message Date
Scarriffle
85ac3dfa50 Schnellbuchungs-Karten: gueltige Einheit statt "article"
Die Ein-/Auslagern-Karten schickten unit: "article" - eine Einheit, die der
Server nicht kennt (services/units.py akzeptiert nur package/packung bzw. die
Basiseinheiten g/ml/stück). Jede Buchung ueber die Karte scheiterte damit an
"Unbekannte Einheit: article", in der Web-Oberflaeche wie in der App.

Jetzt wird die Menge in Artikeleinheiten gebucht: gibt es ein Gebinde, ist das
eine Packung, sonst die Basiseinheit des Produkts. Gegen ein laufendes Backend
geprueft (package und Basiseinheit je 200).

Aufgefallen beim Testen der Ablauf-Benachrichtigungen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:14:50 +02:00
Scarriffle
2ad197df42 iOS zeigt dieselben Dashboards und kann direkt buchen
Die Uebersicht in der App war fest verdrahtet: vier Kacheln, unabhaengig
davon, was am Rechner zusammengestellt wurde. Jetzt liest sie dieselben
Dashboards wie die Web-Oberflaeche und stellt sie nativ dar.

Das Raster laesst sich auf einem Telefon nicht sinnvoll nachbilden, deshalb
stehen die Karten untereinander - in der Lesereihenfolge des Rasters, erst
Zeile, dann Spalte. Zusammengestellt wird weiterhin am Rechner; gibt es
mehrere Dashboards, schaltet oben ein Menue um.

Neu ist die Buchungskarte: Artikel suchen, Menge, ein- oder auslagern, ohne
den Scan-Ablauf zu oeffnen. Nach dem Buchen zieht die Karte den Bestand nach
und laesst die uebrigen Karten neu laden - sonst stuende dort weiter die alte
Zahl.

Diagrammkarten bekommen bewusst keinen halbgaren Nachbau, sondern den
ehrlichen Hinweis, dass es sie in der Web-Oberflaeche gibt.

Geprueft mit der echten Antwort eines laufenden Servers: Sie decodiert in die
Modelle der App, samt zweier Karten derselben Art mit verschiedenen Artikeln.
OverviewView faellt weg, DashboardView tritt an ihre Stelle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 19:27:01 +02:00
Scarriffle
b2a38501d2 Vorgeschlagene Kategorie wird uebernommen, nicht nur angezeigt
Beim Scannen stand auf der Karte "Kategorie ... vorgeschlagen", im Formular war
das Feld danach trotzdem leer - man musste dieselbe Kategorie von Hand
nochmal auswaehlen. Der Vorschlag war damit wertlos: Wer weiss, dass Pesto eine
Sosse ist, gewinnt nichts daraus, es dem Formular noch einmal zu erzaehlen.

Die Ursache lag nicht am Server. Der Lookup liefert category_id und
category_name; die App merkte sich aber nur den Namen fuer die Anzeige und warf
die ID weg. ProductFormView bekam sie gar nicht erst gereicht - anders als die
Gruppe, die schon immer uebernommen wurde. Die Web-Oberflaeche macht es seit
jeher richtig (setNewCategoryId in pages/CheckIn.jsx), nur die App nicht.

Jetzt wird die ID durchgereicht und wie die Gruppe vorbelegt. Aenderbar bleibt
sie: Gespeichert wird ohnehin erst mit "Anlegen & weiter". Der Hinweis auf der
Karte heisst deshalb nicht mehr "vorgeschlagen", sondern "wird zugeordnet" -
das war vorher richtig und waere es jetzt nicht mehr.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:48:54 +02:00
Scarriffle
ff158a1e5f iOS: mehrere Server, TabBar, Uebersicht, Verlauf und Stammdaten
Die App konnte fuenf Dinge, die Web-Oberflaeche vierzehn. Alles darueber hinaus
- nachsehen was gestern passiert ist, einen Lagerort anlegen - zwang zurueck an
den Browser.

Mehrere Server: Statt einer Adresse und einem Token kennt die App jetzt
beliebig viele Profile, jedes mit eigenem Token im Keychain. Ein Wechsel ist
damit ein Tipp und meldet an keinem der Server ab. Jedes Profil hat einen frei
waehlbaren Namen ("Zuhause"), der oben in der App steht; ohne Angabe faellt er
auf den Rechnernamen zurueck. Eine bestehende Anmeldung wird beim ersten Start
in ein Profil ueberfuehrt - ohne das waere man nach dem Update abgemeldet.

Statt einer Startseite mit Kacheln gibt es vier Tabs: Start, Scannen, Listen
und - nur fuer Administratoren, wie im Web - Verwaltung. Neu darin sind eine
Uebersicht mit den Kennzahlen des Servers, die per Tipp in die passende Liste
fuehren, der Bewegungsverlauf (auch je Artikel, weil /movements danach filtern
kann) und die Stammdaten: Lagerorte, Einheiten, Gebinde, Kategorien und
Gruppen. Die fuenf teilen sich eine Ansicht und unterscheiden sich nur darin,
wie geladen und geschrieben wird.

Zwei Dinge, die beim Pruefen gegen einen echten Server auffielen: Der Server
schickt Zeitstempel in UTC, haengt hinter SQLite aber keine Zeitzone an. Naiv
gelesen haette die App jede Uhrzeit um die eigene Zeitverschiebung daneben
angezeigt, deshalb wird die nackte Form ausdruecklich als UTC gelesen. Und die
Anzeigeeinstellungen gehoeren dem Server, werden beim Wechsel also neu geladen.

Bewusst nicht dabei: Benutzer, Einstellungen, Branding und Import/Export. Die
bleiben vorerst im Web.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:24:07 +02:00
Scarriffle
4730d791e8 App-Symbol mit eigener Fassung fuer den dunklen Modus
Die Fuellung war eine einzelne Farbe fuer alle Erscheinungsbilder. Jetzt
liegen Fassungen fuer hell, dunkel und getoent nebeneinander, damit das Symbol
im dunklen Modus nicht heraussticht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:35:52 +02:00
Scarriffle
62a1e7c469 iOS-App heisst jetzt Vorrania
Web, Backend, Deploy und Docs trugen den neuen Namen bereits - iOS war der
letzte Rest. Umgestellt sind Projekt- und Targetname, die Bundle-ID
(com.scarriffle.vorrania), das URL-Schema, die Schnellaktionen, der
Keychain-Service und die Anleitungen. ProjectGoodApp.swift heisst
VorraniaApp.swift.

Zwei Nebenwirkungen, die sich nicht vermeiden lassen: Die neue Bundle-ID
macht die App fuer iOS zu einer anderen App. Die alte bleibt auf dem
Home-Bildschirm liegen und muss von Hand weg, und das Token im Keychain
haengt an der alten Bundle-ID - die neue kommt nicht daran, also einmal neu
anmelden. Wer sich Kurzbefehle mit projectgood://checkin angelegt hat, traegt
dort vorrania://checkin ein.

Das Xcode-Projekt ist ab jetzt eingecheckt statt ignoriert, damit man ohne
XcodeGen bauen kann; nur xcuserdata und DerivedData bleiben draussen.
Gegengeprueft mit einem Simulator-Build: Bundle-ID, Anzeigename, Schema und
Schnellaktionen stehen im fertigen Bundle richtig drin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:35:45 +02:00
Scarriffle
dc1750d23a Umbenennung in Vorrania vollstaendig durchziehen
Auf ausdruecklichen Wunsch jetzt auch die internen Bezeichner, weil die
Installation noch keine echten Daten enthaelt: POSTGRES_USER/PASSWORD/DB, der
Volume-Name, die Standard-DATABASE_URL, der localStorage-Schluessel und die
iOS-Zeichenketten inklusive Keychain-Konto.

Das setzt eine leere Datenbank voraus: Der neue Volume-Name legt eine neue,
leere Datenbank an. Die alte bleibt als verwaistes Volume liegen und muss von
Hand entfernt werden.

Die Warnung in docker-compose.yml bleibt stehen, damit eine spaetere
Umbenennung nicht versehentlich mit Daten im Volume passiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:13:11 +02:00
Scarriffle
5293810529 Gruppen-Codes: Dublette behoben und Altbestand nachgezogen
Zwei Fehler in der EAN-Liste einer Gruppe, beide gemeldet und nachgestellt.

Derselbe Code stand zweimal in der Liste: einmal schreibgeschuetzt mit der
Kennzeichnung "Artikel", einmal darunter mit Notizfeld. Grund war, dass
_group_to_out die Codes der Artikel unabhaengig von den Gruppen-Codes
zusammengestellt hat - seit die Zuordnung automatisch einen Gruppen-Code
anlegt, trifft beides auf denselben Code zu. Die schreibgeschuetzte Zeile stand
oben, deshalb war das Notizfeld darunter leicht zu uebersehen.

Jetzt gibt es eine Zeile je Code. Der Gruppen-Code fuehrt den Artikel mit, ueber
den er dazugehoert (neues Feld product_name in BarcodeOut), zeigt weiterhin die
Kennzeichnung "Artikel" - und hat trotzdem ein Notizfeld. Der Muelleimer
entfaellt bei diesen Codes, denn sie kaemen beim naechsten Speichern des
Artikels sofort zurueck; dafuer muss der Artikel die Gruppe wechseln.

Zweitens fehlte fuer bestehende Daten der Code ganz. Die automatische Pflege
greift nur beim Anlegen und Aendern eines Artikels; Zuordnungen, die es vorher
schon gab, hatten nie einen Gruppen-Code bekommen. In der Verwaltung stand der
Code deshalb ausschliesslich als schreibgeschuetzte Artikel-Zeile - genau die
Stelle, an der sich keine Notiz hinterlegen liess. Neu holt backfill() das beim
Start nach: fuer jeden Artikel mit Gruppe und Barcode wird der Gruppen-Code
angelegt, sofern er fehlt. Gefahrlos wiederholbar.

Getestet: 58 pytest-Tests gruen, einer neu (Backfill legt den fehlenden Code an
und beim zweiten Lauf nichts doppelt). Der gemeldete Fall wurde vorher gegen die
laufende API nachgestellt - Altbestand ohne Gruppen-Code und ein doppelt
gelisteter Code nach einer Neuanlage - und danach als behoben bestaetigt: eine
Zeile je Code, mit Artikelnamen und Notizfeld. Web-Build laeuft durch.
Die Oberflaeche habe ich nicht selbst bedient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:41:43 +02:00
Scarriffle
158196f67b Gruppen und Kategorien getrennt, EAN-Codes an der Gruppe automatisch gefuehrt
Die Umbenennung von Gruppen in "Kategorien" war falsch: Es sind zwei
verschiedene Dinge. Sie ist zurueckgenommen, Kategorien kommen als eigene Ebene
dazu.

GRUPPE zaehlt Bestaende mehrerer Marken zusammen - Mehl von Rewe, Aldi und
Migros ergeben "5 kg Mehl". Dafuer Mindestbestand mit Einheit und EAN-Codes.
KATEGORIE ordnet allein die Artikelliste ("zeig mir alle Suesswaren"), ist
verschachtelbar wie ein Lagerort und hat weder Bestand noch EAN-Codes. Ein
Artikel kann beides, eines oder keines haben.

Die Vermischung war aelter als die Umbenennung: guessGroup in offUtils.js hat
aus der Open-Food-Facts-KATEGORIE eine GRUPPE geraten. Das ist entfernt. Die
OFF-Einordnung steuert jetzt die Kategorie, wo sie hingehoert; eine Gruppe
entsteht nur ueber einen hinterlegten Gruppen-Code oder bewusste Auswahl.

EAN-Codes an der Gruppe: Das war kein Anzeigefehler. Beim Anlegen eines Artikels
wurde ausschliesslich group_id gesetzt - ein Gruppen-Code entstand nie, die
Liste war tatsaechlich leer. Die Meldung "bereits vergeben" kam daher, dass der
Code am Artikel hing. Jetzt pflegt services/group_codes.py den Code mit: beim
Zuordnen kommt er hinzu, beim Gruppenwechsel wandert er mit, beim Entfernen der
Gruppe oder Loeschen des Artikels verschwindet er. Beim Scannen aendert sich
nichts an der Reihenfolge - der Artikel wird weiterhin zuerst gefunden; der
Gruppen-Eintrag ist Beleg in der Verwaltung und Rueckfall. Traegt man denselben
Code von Hand nach, ist das kein Fehler mehr, sondern die Auskunft, dass er ueber
den Artikel bereits dort steht.

Kategorien im Backend: neue Tabelle mit parent_id (Muster von Location),
products.category_id per ADD COLUMN IF NOT EXISTS nachgezogen, deutsche
zweistufige Startliste analog zu den eingebauten Einheiten. Die Startliste wird
nur angelegt, wenn ueberhaupt noch keine Kategorie existiert - wer sie bewusst
leerraeumt, findet sie nicht wieder. Beim Setzen einer Oberkategorie wird
geprueft, dass keine Kategorie sich selbst oder einem eigenen Nachfahren
untergeordnet wird; sonst entstuende ein Ring und jede Baumdarstellung liefe
endlos. Der Produktfilter schliesst Unterkategorien ein, category_id=0 liefert
die Artikel ohne Kategorie. Export und Import fuehren die Kategorie als Pfad
("Suesswaren & Snacks > Schokolade") in einer Spalte, damit die CSV in Excel
bedienbar bleibt.

Web: neue Seite Kategorien mit Baumdarstellung, Filter ueber der Produktliste,
getrennte Auswahlfelder im Produktformular mit je einer Zeile Erklaerung, und
beim Einlagern laesst sich eine Gruppe samt Einheit und Mindestbestand direkt
anlegen, ohne den Vorgang zu verlassen.

iOS: Kategorie-Filter ueber der Produktliste, Unterkategorien eingerueckt. Die
Artikelzeile nennt jetzt das Gebinde und warnt, wenn der Mindestbestand
unterschritten ist (rot) oder weniger als ein Viertel Luft bleibt (orange).
Getrennte Auswahlfelder fuer Kategorie und Gruppe, Gruppe direkt anlegbar.

Ausserdem die Eingabefelder in der App: .textFieldStyle(.roundedBorder) zeichnet
in der dunklen Darstellung einen fast schwarzen Kasten. Ersetzt durch eine
Systemfuellung, die sich Hell und Dunkel anpasst und zurueckhaltend bleibt.

Getestet: 54 pytest-Tests gruen, 14 davon neu (Nachfahren-Sammler, OFF-Zuordnung
inklusive Vorrang der Unterkategorie, und die komplette Codepflege an der
Gruppe). Gegen die laufende API geprueft: Startliste ohne Dubletten, Filter auf
Ober- und Unterkategorie, Ringschutz, Loeschen einer Kategorie laesst Artikel
und Unterkategorien bestehen, Export/Import-Rundlauf mit Kategoriepfad, und der
Durchlauf aus der Meldung - Artikel mit Gruppe anlegen, Code steht danach in der
Gruppe. Web-Build und iOS-Geraetebuild fehler- und warnungsfrei.
Die Oberflaechen habe ich nicht selbst bedient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 20:59:14 +02:00
Scarriffle
8dcc17b22f EAN-Codes einer Kategorie nachvollziehbar gemacht
Eine Kategorie mit Artikeln zeigte "0 EANs", und der Versuch, den Code eines
eigenen Artikels einzutragen, scheiterte mit "Dieser Code ist bereits vergeben".
Beides war fachlich richtig, aber nirgends erklaert.

Hintergrund: Die Code-Liste einer Kategorie ist eine Vorratsliste fuer Artikel,
die es noch nicht gibt. Sie greift nur, wenn beim Einlagern ein unbekannter Code
gescannt wird - dann landet der neu angelegte Artikel in dieser Kategorie
(routers/products.lookup). Haengt ein Code bereits an einem Artikel, findet die
Suche immer zuerst den Artikel; ein gleichlautender Kategorie-Eintrag koennte
nie wirken. Die Spalte zaehlte bisher ausschliesslich diese Vorratscodes, nie
die Codes der enthaltenen Artikel - daher die irritierende 0.

Die Kategorie liefert jetzt zusaetzlich die Codes ihrer Artikel mit
(product_barcodes, rein informativ). Beide Herkuenfte stehen in einer Liste:
Artikel-Codes sind als solche gekennzeichnet, nennen den Artikel und lassen
sich hier nicht loeschen, weil sie am Artikel haengen. Bewusst ohne Dublette in
der Datenbank - ein zweiter Datensatz koennte nie greifen und beim Loeschen des
Artikels verwaisen. Die Spalte zaehlt beide Herkuenfte.

Die Fehlermeldung sagt jetzt, wem ein Code gehoert: beim Artikel mit Namen und
dem Hinweis, dass Artikel-Codes hier nicht eingetragen werden muessen; bei einer
anderen Kategorie mit deren Namen.

Signal beim Einlagern: Wird ein Code erkannt, der einer Kategorie zugeordnet
ist, steht jetzt deutlich sichtbar "Wird automatisch der Kategorie X zugeordnet"
samt Begruendung - sowohl im Web als auch in der App, und in beiden Faellen
(Open-Food-Facts-Treffer und voellig unbekannter Code). Nach dem Anlegen meldet
die Web-Oberflaeche zurueck, welche Kategorie es geworden ist. Vorher stand die
Zuordnung nur als Nebensatz in grauer Kleinschrift.

Getestet: Gegen die laufende API geprueft, dass eine Kategorie die Codes ihrer
Artikel meldet (Haupt-Barcode und zusaetzliche Alias-Codes), dass das Eintragen
eines Artikel-Codes und eines fremden Kategorie-Codes mit der jeweils richtigen
Begruendung abgewiesen wird und dass ein echter Vorratscode weiterhin angelegt
werden kann. iOS-Geraetebuild und Web-Build fehlerfrei, 40 pytest-Tests gruen.

Dabei zwei Uebersetzungsfehler durch deutsche Anfuehrungszeichen gefunden: Das
schliessende Zeichen war ein gerades ", das den String vorzeitig beendete.
Korrigiert und die uebrigen Vorkommen im Projekt gleich mit vereinheitlicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:52:34 +02:00
Scarriffle
47ca4802aa Darstellung der App nachgebessert, Gruppen heissen jetzt Kategorien
Die App zeigte an mehreren Stellen Rohwerte statt lesbarer Angaben, und die
Bedienelemente sahen nicht nach Bedienelementen aus.

Deutsche Lokalisierung: Im Chargen-Abschnitt stand "10. Dec 2027" - deutsches
Format mit englischem Monatsnamen. Die Ursache lag nicht in der Formatierung,
sondern darin, dass die App als englische App gebaut wurde
(CFBundleDevelopmentRegion = $(DEVELOPMENT_LANGUAGE), also "en"). Jetzt ist
Deutsch als Sprache gesetzt; damit zeichnet iOS DatePicker und Monatsnamen von
selbst richtig. Der MonthYearPicker legt seinen Kalender zusaetzlich fest auf
de_DE, damit die Monatsnamen auch bei anderssprachigem Geraet stimmen.

Datumsformat: Die Anzeige folgt jetzt der Servereinstellung (Einstellungen ->
Darstellung), wie die Web-Oberflaeche. Neu DisplaySettings mit den fuenf
Formaten, den deutschen Beschriftungen der Basiseinheiten und der Mengenangabe
in der Leitangabe. Die doppelt ausprogrammierte MHD-Formatierung in
ListViews und CheckOutView ist damit weg.

Chargen-Abschnitt: Jede Charge steht jetzt in einem eigenen Abschnitt mit
Kopfzeile statt gestapelt in einer einzigen Formularzeile - das war der
gequetschte Eindruck. Loeschen sitzt in der Kopfzeile.

Mengenfelder haben einen sichtbaren Rahmen. Ohne ihn las sich das
rechtsbuendige Feld wie fester Text, und es war nicht erkennbar, dass sich die
Menge ueberhaupt aendern laesst.

Kamera: Der Sucher fuellte per ZStack und ignoresSafeArea den ganzen Bildschirm
und wirkte dadurch erdrueckend. Er sitzt jetzt als 4:3-Feld oben, darunter
Hinweis, Meldungen und Knoepfe in gewohnter Form. Am ScannerViewController
musste nichts geaendert werden.

Einkaufsliste: Artikel und Kategorien tragen ein Symbol, Kategoriezeilen
zusaetzlich eine Kennzeichnung. Den Artikelzeilen fehlte bisher jede Einheit
("fehlt 2 - Bestand 7 von 10"); sie nennen jetzt Packungen und Basismenge.

Produktdetails: Die Felder waren blanke TextFields - sobald ein Wert drinstand,
verschwand der Platzhalter und man las nur noch "Bratbutter" / "M-Classic" /
"450". Jetzt mit Beschriftung links, Einheit hinter der Packungsgroesse (vorher
stand dort der rohe API-Wert "gram") und einem Abschnitt "Erkennung" mit
Barcode. Dasselbe im Formular zum Anlegen.

Gruppen heissen in der Oberflaeche jetzt Kategorien - in Web und App. Tabelle,
Feld group_id und die Endpunkte behalten ihren Namen, damit bestehende Zugriffe
wie Home Assistant weiterlaufen; der Unterschied ist rein sprachlich. Die
Kategorie laesst sich nun auch in der App zuweisen, was vorher gar nicht ging.

Dabei ein Fehler gefunden und behoben: Swift laesst nil-Optionals beim Kodieren
weg, das Backend wertet nur mitgeschickte Felder aus. "Keine Kategorie" oder
eine geloeschte Marke waeren damit stumm verpufft. ProductUpdateRequest kodiert
diese Felder jetzt ausdruecklich. Der Mindestbestand steht bewusst nicht mehr
darin, weil die App ihn nicht bearbeitet und ein mitgesendetes null ihn
geloescht haette.

Getestet: iOS-Geraetebuild fehler- und warnungsfrei, Web-Build laeuft durch,
40 pytest-Tests gruen. Die Datumsformatierung wurde eigenstaendig uebersetzt
und gegen elf Faelle geprueft (alle fuenf Formate, Monatsangabe, fehlendes und
unlesbares Datum) - alle korrekt. Im gebauten Bundle ist belegt, dass
CFBundleDevelopmentRegion auf "de" steht. Gegen die laufende API geprueft:
/settings und /groups liefern die erwarteten Felder, das Leeren der Kategorie
wirkt jetzt und der Mindestbestand bleibt dabei erhalten.
Die Oberflaeche selbst habe ich nicht auf dem Geraet bedient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:04:58 +02:00
Scarriffle
f3ee10e4d4 Einkaufsliste, Ablaufliste und Produktverwaltung in der App
Der Startbildschirm bekommt drei weitere Einstiege: Einkaufsliste (Artikel und
Gruppen unter Mindestbestand), Bald ablaufend (Chargen mit Restfrist, abgelaufene
rot) und Produkte. Alle Listen lassen sich durch Herunterziehen aktualisieren.

In den Produktdetails sind Name, Marke, Packungsgroesse, Gebinde-Bezeichnung und
MHD-Angabe aenderbar. Die Chargen werden mit Menge und MHD aufgelistet; Antippen
oeffnet die Korrektur von Menge und MHD (inklusive Umschalten auf Monat/Jahr),
Wischen loescht eine Charge. Mengen werden in der Leitangabe des Artikels
angezeigt und beim Speichern in Basiseinheiten zurueckgerechnet, weil die
Chargen-Schnittstelle in Basiseinheiten arbeitet.

Dabei ist ein bestehender Fehler aufgefallen und mitbehoben: Fuer das Gebinde
kennt das Backend nur das Schluesselwort "package" (services/conversion.py
akzeptiert package/packung/pkg/pack). Die App hat stattdessen die
Gebinde-Bezeichnung selbst als Einheit geschickt. Bei Produkten mit eigener
Bezeichnung - "Glas", "Dose", "Flasche", "Tuete" - schlugen Einlagern und
Auslagern damit mit "Unbekannte Einheit: Dose" fehl. Nur die Standardbezeichnung
"Packung" funktionierte zufaellig, weil sie in der Liste der Schluesselwoerter
steht. Anzeige und uebertragener Wert sind jetzt getrennt (neuer Typ
UnitOption): angezeigt wird weiterhin "Glas", geschickt wird "package".

Ausserdem die Warnung zu den Bildschirmausrichtungen behoben - das iPad verlangt
alle Ausrichtungen, solange die App nicht auf Vollbild besteht.

Getestet: iOS-Geraetebuild fehler- und warnungsfrei. Die Endpunkte hinter den
neuen Ansichten sind gegen die laufende API geprueft, ebenso das Aendern und
Loeschen von Chargen. Der Einheiten-Fehler wurde an der echten API
nachgestellt ("Unbekannte Einheit: Dose") und mit "package" als 200 bestaetigt.
Die neuen Ansichten habe ich nicht auf dem Geraet bedient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:16:15 +02:00
Scarriffle
c4acc8bec7 MHD per Texterkennung einlesen
In der Chargenzeile beim Einlagern gibt es einen Knopf "MHD scannen". Er
oeffnet die Texterkennung von VisionKit; erkannte Daten erscheinen als
Vorschlag und werden erst uebernommen, wenn man sie antippt. Bewusst kein
automatisches Uebernehmen: Eine Fehllesung beim MHD faellt sonst erst auf,
wenn die Ware bereits falsch einsortiert ist.

Der Parser deckt die Schreibweisen ab, die auf Verpackungen vorkommen -
Tagesdaten (12.09.2026, 2026-09-12, 12/09/2026, 12.09.26) und Monatsangaben
(09/2026, 09.2026, 2026-09, 09/26). Erkennt der Aufdruck nur Monat und Jahr,
stellt sich die Eingabe sichtbar auf Monat/Jahr um.

Damit aus Chargennummern und EAN-Codes keine Phantasiedaten werden, greifen
mehrere Einschraenkungen: Muster mit Tag werden zuerst geprueft, ein bereits
erkannter Textabschnitt wird nicht erneut ausgewertet, Monate ausserhalb 1-12
entfallen und das Jahr muss im Fenster von fuenf Jahren rueckwaerts bis
dreissig vorwaerts liegen.

Der Knopf erscheint nur, wenn das Geraet Texterkennung unterstuetzt
(Neural Engine); sonst bleibt die Eingabe von Hand.

Getestet: Der Parser wurde eigenstaendig uebersetzt und gegen 14 Faelle
geprueft, alle korrekt - darunter, dass "12.09.2026" nicht als Monatsangabe
gelesen wird, dass eine EAN keine Treffer erzeugt und dass unplausible Jahre
und Monate verworfen werden. Der iOS-Geraetebuild laeuft fehlerfrei durch.
Die Erkennung an einer echten Verpackung habe ich nicht selbst ausprobiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:10:24 +02:00
Scarriffle
30307d6cfb MHD nur mit Monat und Jahr in Web-UI und iOS-App
Setzt den Backend-Umbau in beiden Oberflaechen um.

Die Genauigkeit gilt jeweils fuer den ganzen Einlager-Vorgang und nicht je
Charge: Auf einer Packung steht entweder ein Tagesdatum oder nur Monat/Jahr,
gemischt kommt das nicht vor. Vorbelegt wird sie aus dem Produkt, laesst sich
aber im Vorgang umstellen.

Web-UI: Umschalter im Kopf der Chargenliste; das Eingabefeld wechselt zwischen
type="date" und type="month". Im Produktformular gibt es das neue Feld
"MHD-Angabe" neben der Gebinde-Bezeichnung. Anzeige laeuft ueber den
Settings-Context, damit das eingestellte Datumsformat erhalten bleibt und
Monatsangaben ueberall als "09/2026" erscheinen (Auslagern, Uebersicht,
Chargentabelle). Die Abgelaufen-Warnung prueft bei Monatsangaben gegen den
Monatsletzten - sonst haette eine Packung schon am Monatsersten als abgelaufen
gegolten.

iOS: Auswahl "Tagesdatum / nur Monat/Jahr" im Einlagern-Formular und beim
Anlegen eines Artikels. SwiftUI hat keinen DatePicker ohne Tag, deshalb ein
eigener MonthYearPicker aus zwei Auswahlfeldern; die Jahresliste reicht zwei
Jahre zurueck (bereits abgelaufene Ware) und fuenfzehn nach vorn (Konserven).
Die Chargenauswahl beim Auslagern zeigt Monatsangaben ebenfalls ohne Tag.

Getestet: Web-Build (vite) und iOS-Geraetebuild laufen fehlerfrei durch, die
App ist auf dem iPhone installiert. Das Verhalten in der Oberflaeche ist noch
nicht von Hand durchgeklickt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 17:57:09 +02:00
Scarriffle
d680aeff0e Mengenfeld beschriftet und "Angemeldet bleiben" ergaenzt
Beim Einlagern stand in der Chargenzeile eine nackte 1 ohne Beschriftung. Das
las sich wie eine Nummerierung der Charge, war aber das Mengenfeld. Die Zeile
hat jetzt links die Beschriftung "Menge", der Wert steht rechtsbuendig; die 1
bleibt als Vorgabe, weil meistens genau ein Stueck eingelagert wird. Beim
Auslagern war dieselbe Zeile ebenso unbeschriftet und ist jetzt gleich aufgebaut.

Anmeldung: Das Token wanderte bisher immer dauerhaft in den Schluesselbund.
Der neue Haken "Angemeldet bleiben" laesst sich abwaehlen - dann gilt die
Anmeldung nur, solange die App laeuft, und es bleibt nichts auf dem Geraet
zurueck. Die Wahl wird gemerkt, damit der Haken beim naechsten Mal richtig steht.

Dabei ist aufgefallen, dass der Benutzername nirgends gespeichert wurde: Nach
einem Neustart stand im Menue nur noch "Angemeldet als" ohne Namen. Er wird
jetzt zusammen mit dem Token abgelegt und beim Abmelden wieder entfernt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 17:35:10 +02:00
Scarriffle
8e94f15c84 App-Icon eingebunden, fehlendes Auslagern-Symbol und Barcode-Erkennung verbessert
Auslagern-Kachel: "arrow.up.from.line" existiert als SF Symbol nicht, deshalb
blieb das Icon leer und man sah nur die eingefaerbte Kachelflaeche. Ersetzt
durch "arrow.up.to.line", das Gegenstueck zum "arrow.down.to.line" beim
Einlagern. Alle uebrigen im Projekt verwendeten Symbolnamen wurden gegen die
Symbolliste des Systems geprueft und sind gueltig.

App-Icon: Das mitgelieferte AppIcon.icon (Icon-Composer-Format) wird als
Ressource eingebunden und ueber ASSETCATALOG_COMPILER_APPICON_NAME gesetzt.
Die Icon-Eintraege landen korrekt in der selbstgepflegten Info.plist.

Barcode-Erkennung, nachdem ein dunkelgruener Code auf weiss nicht gelesen wurde:
- Aufloesung auf 1920x1080 statt der Voreinstellung, damit kontrastarme und
  kleine Codes ueberhaupt sauber aufgeloest werden.
- Dauerhafter Autofokus mit Beschraenkung auf den Nahbereich; weiches
  Nachfuehren aus, da es hier nur verzoegert.
- Antippen stellt gezielt auf eine Stelle scharf und misst dort die Belichtung.
- Licht-Schalter in der Navigationsleiste beider Scan-Bildschirme; beim
  Verlassen wird das Licht sicher wieder ausgeschaltet.
- Mehr Symbologien (ITF-14, DataMatrix, PDF417, Aztec, Code93). Es werden nur
  die Typen gesetzt, die die Kamera meldet - sonst wirft AVFoundation zur
  Laufzeit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 17:28:21 +02:00
Scarriffle
7b0a78b421 Signing fuer Geraetebuilds in project.yml hinterlegt
Ohne Team-ID bricht ein Build gegen "generic/platform=iOS" beim Signieren ab.
Mit CODE_SIGN_STYLE=Automatic und der Team-ID laeuft der Geraetebuild durch und
wird mit dem vorhandenen Entwicklerzertifikat und dem Team-Provisioning-Profil
signiert. Wer mit einem anderen Apple-Konto baut, ersetzt die Team-ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 17:07:55 +02:00
Scarriffle
3a0c130cb1 iOS-App kompiliert erstmals: Info.plist gesichert und zwei Build-Fehler behoben
Die App war bisher nie durch einen Compiler gelaufen. Mit Xcode 26.6 baut sie
jetzt fehler- und warnungsfrei gegen das iOS-16-Deployment-Target.

Info.plist: Der "info:"-Block in project.yml hat die handgepflegte
Sources/Info.plist bei jedem "xcodegen generate" ueberschrieben und dabei
Schluessel verloren, die nur in der Datei standen - insbesondere
NSAppTransportSecurity/NSAllowsLocalNetworking. Ohne diese Ausnahme haette die
App den Server im Heimnetz per http:// ueberhaupt nicht erreichen koennen.
Die Datei ist jetzt die einzige Quelle: project.yml verweist nur noch per
INFOPLIST_FILE darauf und nimmt sie aus den Sources heraus. Die Bundle-Keys,
die vorher nur die erzeugte Fassung hatte (CFBundleExecutable,
CFBundleIdentifier, CFBundlePackageType ...), stehen nun in der Datei selbst.

ProductViews: onChange(of:) wurde in der zweiwertigen iOS-17-Form verwendet und
war damit ein harter Fehler - auf die einwertige Form umgestellt.

ProjectGoodApp: application(_:performActionFor:) als async-Fassung reicht das
nicht-Sendable UIApplicationShortcutItem ueber eine Actor-Grenze. Das ist heute
eine Warnung und im Swift-6-Sprachmodus ein Fehler; jetzt die Variante mit
Completion-Handler.

Der dritte Verdacht hat sich nicht bestaetigt: AudioServicesPlaySystemSound in
ScannerView uebersetzt ohne zusaetzliches "import AudioToolbox", weil
AVFoundation es bereits mitbringt. Deshalb dort keine Aenderung.

Das erzeugte ProjectGood.xcodeproj wandert in .gitignore, da project.yml es
vollstaendig beschreibt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 16:57:27 +02:00
Scarriffle
4bb9a10366 iOS-App angefangen; Import-Modi; MHD-Formatierung; Gebinde in Ablauf-Ansichten
iOS (neu, ios/):
- SwiftUI-App: Login (Server + Keychain-Token), Kamera-Scanner (EAN/UPC/Code128),
  Einlagern mit mehreren Chargen und eigenem MHD, Auslagern mit Chargenauswahl
  oder FEFO, Artikelsuche, Anlegen mit Open-Food-Facts-Vorbefuellung.
- Home-Screen-Shortcuts: Schnellaktionen (langer Druck) und URL-Schema
  projectgood://checkin | ://checkout fuer eigene Symbole via Kurzbefehle.
- project.yml (XcodeGen) + README mit Build-Anleitung. NICHT kompiliert - auf
  diesem Rechner ist kein Xcode vorhanden.

Import-Modi (wie besprochen sinnvoll):
- add (Standard, nichts loeschen), replace_listed (Bestaende der in der Datei
  genannten Produkte ersetzen - fuer Inventur), replace_all (alles ersetzen).
  Geleerte Bestaende werden als Korrektur-Bewegung protokolliert; die Oberflaeche
  fragt bei den zerstoerenden Modi nach.

Anzeige:
- "Bald ablaufend"/"Abgelaufen" zeigen jetzt das Gebinde ("1 Glas") mit der
  Basiseinheit klein darunter (ExpiringItem liefert die Einheiten mit).
- MHD als Zeitspanne mit korrektem Numerus: "in 3 Tagen", "in 1 Woche",
  "vor 2 Wochen", dazu heute/morgen/gestern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 14:59:04 +02:00