Commit Graph

72 Commits

Author SHA1 Message Date
Scarriffle
012e3461ae Backend: Chargenliste - Art (food/object) + nur Charge-Artikel
/lots/rows liefert jetzt die Art (Lebensmittel/Gegenstand) mit und listet nur
Charge-Artikel (Lebensmittel + Verbrauchsgegenstand). Lots von "Menge je
Lagerort" (und etwaige Einzelstueck-Altlasten) fallen raus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 14:12:35 +02:00
Scarriffle
1c88206838 Backend: alle Chargen auflisten + Lagerort mehrerer Chargen auf einmal setzen
GET /lots/rows liefert alle Chargen mit Artikel-Infos (Name, Menge, Einheit,
Lagerort) fuer eine uebergreifende Chargenliste. POST /lots/bulk-location setzt
den Lagerort vieler Chargen in einem Rutsch (reine Ortsangabe, ohne Bewegung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 13:48:19 +02:00
Scarriffle
85ab62359a Backend: Bild beim Anlegen im Hintergrund holen (Anlegen viel schneller)
create_product holte ein per URL vorgeschlagenes Bild synchron waehrend des
Anlegens (httpx.get) - das blockierte die Antwort um mehrere Sekunden. Der
Abruf laeuft jetzt als BackgroundTask mit eigener Session nach der Antwort; das
Anlegen kehrt sofort zurueck, das Bild erscheint kurz danach.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 12:26:43 +02:00
Scarriffle
4728a839ea Backend: Produktliste gebuendelt laden (N+1 weg, deutlich schneller)
list_products rief product_to_out je Artikel auf - pro Artikel mehrere Abfragen
(Bestand, abgelaufene Chargen, Codes, Bild, Lazy-Relationen). Bei vielen
Artikeln = hunderte Queries und mehrere Sekunden. Neu: products_to_out_bulk holt
Bestand/Abgelaufen/Codes/Bild je einmal fuer alle und laedt Relationen eager
(joinedload/selectinload). Ergebnis identisch (Test), aber nur noch wenige Queries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 12:23:12 +02:00
Scarriffle
45031df1f4 Backend: Product.updated_at (Zuletzt geaendert)
Neue Spalte updated_at, die bei jeder Stammdaten-Aenderung automatisch nachzieht
(onupdate). Migration legt sie nullable an und setzt Altbestaende auf created_at.
In ProductOut ausgegeben - Basis fuer eine "Zuletzt geaendert"-Spalte im Web.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 11:12:03 +02:00
Scarriffle
10a948df15 Backend: Verbrauchsgegenstand/Einzelstueck sind immer Gegenstaende
product_tracking leitet die Art bisher nur aus der Kategorie ab. Beim Umstellen
Lebensmittel -> Verbrauchsgegenstand faellt die (Lebensmittel-)Kategorie weg,
sodass der Artikel faelschlich "food" blieb und in der Lebensmittelliste hing.
individual/bulk implizieren jetzt "object", unabhaengig von der Kategorie.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 21:30:09 +02:00
Scarriffle
6c870584dd Backend: Verbrauchsgegenstand (Product.bulk) - Gegenstand wie ein Lebensmittel
Neues Flag bulk auf Product: Gegenstand als Charge mit Menge/Einheit/MHD/
Mindestbestand fuehren (z.B. Sonnencreme in ml), ohne eindeutigen Code.
Migration (ADD COLUMN), Schemas und Anlege-Pfad ergaenzt. Kernlogik nutzt
foodLike = kein Gegenstand ODER bulk; Bestand/Einkaufsliste unveraendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 20:29:01 +02:00
Scarriffle
72256f4fe9 Backend: globale Einzelstueck-Liste (GET /items) + Bild-Version an Produkten
Neuer Endpoint GET /items liefert alle Einzelstuecke ueber alle Produkte, angereichert mit Produkt-, Kategorie-, Lagerort- und Shop-Angaben (Beziehungen vorgeladen). ItemOut bekommt category_id/category_name. ProductOut bekommt image_version (Epoch der letzten Bildaenderung, None ohne Bild) - identisch zum ETag der Bild-Route, damit Clients Bilder cachen und nur bei Aenderung neu laden. Grundlage fuer die neue Einzelstueck-Liste (Web+iOS) und den iOS-Bild-Cache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:37:17 +02:00
Scarriffle
c8c1686395 Amazon-Kaufdatum erkennen; Preis-Vorschlaege beim Anlegen; Lagerort-Dropdowns mit Pfad
1) Beleganalyse erkennt Datumsangaben mit Monatsnamen (DE+EN, z.B. Amazon 'Bestelldatum 29 April 2026'), nicht nur 29.04.2026 - so wird das Kaufdatum solcher Rechnungen gefunden. 2) Beim direkten Anlegen eines Einzelstuecks mit Beleg werden jetzt alle erkannten Preise als Vorschlag angeboten (Dropdown), statt stillschweigend nur den wahrscheinlichsten zu nehmen. 3) Alle Lagerort-Dropdowns zeigen den vollen Pfad (Hedingen -> Keller -> Schublade 1) statt nur den Namen, damit gleichnamige Orte unterscheidbar sind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:24:08 +02:00
Scarriffle
21ab0a825a Nachschlagen: Lagerort-QR zeigt alle Artikel an diesem Ort
Neuer Endpunkt GET /location/{code} liefert alle Artikel an einem Lagerort inkl. der Unterorte (Lot-Bestaende und Einzelstuecke), sortiert nach Name mit Menge in Artikeleinheiten. In der App loest der /l/-QR beim Nachschlagen jetzt diese Liste auf; ein Tipp auf einen Eintrag oeffnet den Artikel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:10:29 +02:00
Scarriffle
d518b4aa23 Lagerorte per 10-Zeichen-Code statt fortlaufender ID; iOS-Einlagern ohne Kamerazwang
Lagerort-IDs sind jetzt ein zufaelliger 10-Zeichen-Code (wie die Einzelstueck-UIDs) statt einer fortlaufenden Zahl - so kollidiert die Stammdaten-Sicherung zwischen zwei Instanzen praktisch nie mehr, und der Code ist zugleich der Inhalt des QR /l/<code>. Alle Fremdschluessel (lots, movements, items, Mindestbestaende, parent_id) ziehen mit; die Umstellung laeuft einmalig und transaktional beim Serverstart (_migrate_locations_to_code) und rollt bei Fehlern komplett zurueck. Vor dem Deploy ein DB-Backup machen.

iOS-Einlagern oeffnet nicht mehr sofort die Kamera, sondern ein Formular mit Artikelsuche; die Kamera kommt erst per Button. Im Formular laesst sich der Lagerort zusaetzlich per /l/-QR scannen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 12:15:25 +02:00
Scarriffle
6e48cd0a7a Stammdaten-Export/Import (JSON) + Lagerort-Eltern bleibt beim Anlegen
- Backend: services/master_data.py exportiert/importiert Kategorien (inkl.
  eigener Felder), Lagerorte, Einheiten und Gebinde als JSON – MIT IDs, damit
  gedruckte QR-Codes und Verweise nach dem Wiederherstellen passen. Import-Modus
  skip (Vorhandenes lassen) oder overwrite (per ID aktualisieren); Namens-
  Konflikte werden uebersprungen, nicht abgebrochen. Postgres-Sequenzen werden
  danach angehoben. Endpunkte GET /export/master-data, POST /import/master-data.
  5 neue Tests, Suite 162 gruen.
- Web: neue Karte "Stammdaten sichern & wiederherstellen (JSON)" in Import/Export.
- Web: beim Lagerort-Anlegen bleibt der uebergeordnete Ort in der Auswahl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:17:14 +02:00
Scarriffle
625b6ee263 Lagerorte: Baum-Picker beim Anlegen; Unter-Ort-Bestand zählt zum Bedarf
- Web: der übergeordnete Lagerort wird beim Anlegen jetzt über den
  aufklappbaren Baum (CategorySelect) gewählt statt über das flache Dropdown.
- Backend: Mindestbestand je Lagerort wird jetzt gegen den Bestand INKL. aller
  Unter-Lagerorte geprueft. Ein Bedarf auf „Hedingen" gilt also als gedeckt,
  wenn der Vorrat in „Hedingen -> Keller" liegt. Helfer
  location_subtree_stock_base; 2 neue Tests, Suite 157 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 10:50:31 +02:00
Scarriffle
4a950acd85 Backend: Zeilenpreise als Kaufpreis-Kandidaten anbieten
Bei einer Rechnung mit mehreren Artikeln soll man den Zeilenpreis des passenden
Produkts wählen können. Die Kandidatenliste enthaelt daher wieder alle
geldartigen Betraege (Zeilen- und Summenbetraege), nur ohne MwSt-Satz und
Nicht-Geld-Zahlen; der Auto-Tipp bleibt die Gesamtsumme und steht vorne.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 10:45:08 +02:00
Scarriffle
88b9bdcbea Backend: Beleg-Analyse präziser (Preis, Kaufdatum, Garantieende)
Anhand echter Galaxus-Rechnungen nachgeschärft:
- Preis: MwSt-Satz ("8.10 %") wird als Prozentangabe ignoriert; von den
  Betraegen nahe einem Summen-Stichwort gewinnt der GRÖSSTE (Endbetrag inkl.,
  steht rechts) statt des naechstgelegenen (bisher der Exkl.-Betrag).
- Kaufdatum: eindeutige Stichworte (Rechnungs-/Bestell-/Kaufdatum) haben Vorrang;
  "Fälligkeitsdatum"/Zahlungsziel werden nicht mehr als Kaufdatum gewertet.
- Garantieende: ein ausdruecklich genanntes Enddatum im Garantie-Umfeld
  (z.B. "… - 06.10.2027") hat Vorrang vor dem gerechneten. 4 neue Tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 10:40:18 +02:00
Scarriffle
bbf10b36c6 Backend: Beleg-Analyse um Kaufdatum + Shop; Analyse-Endpoint
- guess_acquired_on (Kaufdatum) und guess_shop (bekannter Shop erkannt -> id;
  sonst Kandidatenname zum Anlegen) ergaenzt.
- Upload liefert zusaetzlich suggested_acquired_on / shop_id / shop_name.
- Neuer POST /items/analyze-document: analysiert einen Beleg OHNE zu speichern
  (zum Vorbefuellen beim Anlegen). Gemeinsamer Helfer _doc_suggestions.
- 6 neue Tests; Suite 150 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 09:30:35 +02:00
Scarriffle
67bc2dd87d Backend: Preiserkennung präziser (nicht mehr jede Zahl)
Nur noch Beträge, die nach Preis aussehen: direkt hinter einem Summen-Stichwort
oder mit Währung (CHF/EUR/€/…) in der Nähe. Ohne solchen Hinweis kein Vorschlag.
Datumsangaben (31.12.2027) werden per Lookaround nicht mehr als Betrag (31.12)
missgelesen. Kandidatenliste enthaelt entsprechend nur plausible Preise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 07:06:12 +02:00
Scarriffle
d0d854be5a Backend: Mindestbestand je Lagerort für Produkte und Gruppen
Zusaetzlich zum globalen Mindestbestand: je Produkt und je Gruppe laesst sich pro
Lagerort ein Mindestbestand (in Artikeleinheiten) hinterlegen.
- Neue Tabellen product_location_min_stock / group_location_min_stock.
- PUT /products/{id}/location-min-stock und /groups/{id}/location-min-stock
  ersetzen die Eintraege; Produkt-/Gruppen-Ausgabe liefert sie mit.
- Neue Einkaufsliste GET /shopping-list/by-location: Bedarfe je Ort (Produkte +
  Gruppen), Bestand-am-Ort gegen Mindestbestand-am-Ort.
- Helfer location_stock_base (Bestand je Ort). 4 neue Tests, Suite 144 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 06:58:08 +02:00
Scarriffle
2bc871f229 Preis-Vorschlag: Auswahl aus mehreren erkannten Beträgen
Findet die Beleg-Analyse mehrere Beträge, liefert der Upload jetzt eine
Kandidatenliste (bester Tipp zuerst). Web und iOS bieten dann ein Dropdown zur
Auswahl des richtigen Kaufpreises, statt nur den automatischen Tipp. 1 neuer
Test; Backend-Suite gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 06:39:03 +02:00
Scarriffle
ba65e48d47 Backend: Kaufpreis aus Beleg-PDF schätzen
guess_price_cents zieht den Kaufpreis aus dem Belegtext: bevorzugt den Betrag
nach einem Summen-Stichwort (Gesamtbetrag/Total/…), sonst den groessten Betrag.
Versteht Tausendertrenner (1'299.00) und deutsches Format (1.299,00). Der
Upload liefert den Vorschlag als suggested_price_cents mit. 5 neue Tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 20:57:30 +02:00
Scarriffle
61270e6d00 Backend: Kaufpreis + Belege an Einzelstücken, PDF-Garantie-Vorschlag
- Item: price_cents + currency (Kaufpreis rappen-/centgenau). Migration ergaenzt.
- Neue Tabelle item_documents (mehrere Belege je Stueck, PDF oder Bild); Blob
  wird verzoegert geladen, damit Item-Listen leicht bleiben.
- Endpunkte: Upload (POST), Ansehen/Download (GET), Loeschen (DELETE) je Stueck.
  Dateiname beim Download gehaertet (keine Header-Injektion).
- services/warranty.py: schaetzt aus PDF-Text lokal ein Garantieende
  (Zeitraum + Kaufdatum, oder Datum nahe Garantie-Stichwort); pypdf ergaenzt.
  Der Upload liefert den Vorschlag zurueck. 10 neue Tests, Suite 136 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 20:49:08 +02:00
Scarriffle
b49546b297 Lagerorte umhängen (Backend + Web)
Bestehende Lagerorte lassen sich jetzt nachträglich einem anderen Elternort
zuordnen oder auf die oberste Ebene holen.
- Backend: LocationUpdate um parent_id (Name jetzt optional); update_location
  haengt um, mit Schutz gegen Ringe (weder auf sich selbst noch auf einen
  eigenen Unterort) und Existenzpruefung des Ziels. 6 Tests.
- Web: Lagerorte-Seite bekommt je Ort Bearbeiten (Name + Elternort ueber den
  CategorySelect-Baum, eigene Unterorte ausgeschlossen); api.updateLocation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 15:58:54 +02:00
Scarriffle
a70a99b387 Dashboard zaehlt Einzelstuecke mit; Baum-Dropdown scrollbar
Auswertungen (by_category, expiry_split, stats) iterierten nur Lots. Gegenstaende
mit Menge-je-Lagerort liegen als Lots und tauchten auf – Einzelstuecke liegen
aber als Items und fehlten dadurch komplett (Dashboard zeigte z.B. nur die
Powerbank, nicht Kamera/Kabel). Neuer Helfer _bestand_beitraege deckt beide
Speicherformen ab: jedes Item zaehlt als ein Stueck ohne MHD. Regressionstest
ergaenzt; volle Suite gruen (120).

CategorySelect: Scrollen IM aufklappbaren Menue schloss es, weil jeder Scroll als
Seiten-Scroll gewertet wurde. Jetzt schliesst nur ein Scroll ausserhalb des
Menues; die lange Liste laesst sich scrollen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 12:09:25 +02:00
Scarriffle
2ebdf94b16 Web: QR-Etiketten-Export (CSV für P-touch & Co.)
Auf "Import / Export" neue Karte: Kategorien auswaehlen (inkl. Unterkategorien)
und je Einzelstueck eine CSV-Zeile herunterladen - Spalten QR-Inhalt (…/i/<UID>),
UID, Produkt, Marke, Kategorie, Lagerort. In der Etiketten-Software als Datenbank
verknuepfen, dann entsteht QR + Text automatisch - kein einzelnes Bild-Download
und Zuordnen mehr.

Backend: GET /export/labels?category_ids=... liefert die Zeilen (JSON); den
QR-Inhalt setzt das Web mit window.location.origin dazu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 09:22:03 +02:00
Scarriffle
7f7129c87a iOS: Einzelstücke (UID/QR) + Scan öffnet das Stück; Seed ohne Modell-Kaufdatum
iOS-Parität zum Web: Item-Modell + API, Einzelstueck-Liste in der Produkt-
Detailansicht (anlegen, je-Stueck Lagerort/Kaufdatum/Garantie/Bezugsquelle/Notiz
aendern, entfernen mit Grund, QR je Stueck via CoreImage). Anlegen-Formular hat
den Schalter "Einzelstuecke". Der Scanner erkennt Einzelstueck-QR (…/i/<UID>) und
oeffnet direkt das Stueck.

Seed: "Kaufdatum"/"Garantie bis" nicht mehr als Modell-Felder auf Elektronik -
diese Angaben gehoeren je Einzelstueck ans Item, nicht ans Modell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 07:48:25 +02:00
Scarriffle
5ce4411d1e Backend: Einzelstücke (Items) mit UID je Gegenstand
Gegenstands-Produkte koennen als Einzelstuecke gefuehrt werden (Product.individual):
jedes physische Stueck ist ein Item mit eigener kurzer UID (fuer QR), eigenem
Lagerort, Kaufdatum, Garantie, Bezugsquelle und Notiz. So laesst sich dasselbe
Modell mehrfach getrennt fuehren (Powerbank 2024 + 2025).

Neu: models.Item + Product.individual (+Migration), Schemas, services/items.py
(UID-Erzeugung, Anreicherung), routers/items.py (CRUD, /items/by-uid/{uid} zur
QR-Aufloesung, /items/{id}/remove mit Grund als Bewegung). current_stock zaehlt
bei Einzelstueck-Produkten die Items. Tests + HTTP-Smoke gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 00:26:15 +02:00
Scarriffle
5c8b46a0d5 Foto-Upload: 413 beheben und Kamera stabilisieren
- nginx client_max_body_size auf 12m (Standard 1 MB loeste beim Bild-Upload
  einen 413 aus, bevor das Backend den Upload sah).
- Backend-Bildlimit auf 8 MB angehoben, bleibt unter dem nginx-Limit, damit
  Uebergroesse eine klare 400-Meldung statt eines nackten 413 ergibt.
- Web: Foto vor dem Upload clientseitig verkleinern (max 1600px, JPEG) -
  schneller, kleiner in der DB, zuverlaessig unter dem Limit.
- iOS: Kamera ueber fullScreenCover statt sheet und Schliessen deterministisch
  ueber ein Binding - behebt das "Kamera geht direkt wieder zu"-Flackern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 23:11:59 +02:00
Scarriffle
044f63446f Inline-Anlage (Kategorien/Felder), Artikelfoto und CSV-Spalte "art"
- Web: Kategorien/Unterkategorien und eigene Felder direkt im Artikelformular
  anlegen (ohne Umweg über die Kategorien-Seite).
- Artikelfoto per Kamera oder Galerie hochladen – Backend-Endpunkt
  (PUT/DELETE /products/{id}/image), Web (Foto machen / Galerie) und iOS
  (Kamera + PhotosPicker, inline in der Produktansicht angezeigt).
- CSV-Import: neue Spalte "art" (Gegenstand/Lebensmittel) steuert den Modus
  automatisch angelegter Kategorien; Export schreibt sie mit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:18:36 +02:00
Scarriffle
5b952524d7 Gegenstands-Verwaltung (Non-Food) neben Lebensmitteln
Die Kategorie bestimmt die Verwaltungsart (food/object). Gegenstaende:
Menge je Lagerort statt Chargen/MHD, Umlagern (ohne Grund) und Entfernen
mit Pflicht-Grund samt Entnahme-Statistik. Beliebig viele eigene Felder
je Kategorie (vererbt an Unterkategorien), "gekauft bei" ueber eine
verwaltbare Shop-Liste und ein Produktlink. Barcode-Lookup zusaetzlich
ueber Open Products Facts.

Der Lebensmittel-Teil (Chargen/MHD/FEFO) bleibt unveraendert. Umgesetzt in
Backend (FastAPI, +Tests gruen), Web (React) und iOS (SwiftUI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 15:33:56 +02:00
Scarriffle
f62289f975 Mehrere Dashboards und dieselbe Karte mehrfach
Zwei Grenzen sassen tief im Datenmodell. Erstens war "i" im gespeicherten
Layout zugleich die Kartenart; da react-grid-layout eindeutige Kennungen
verlangt, ging jede Art genau einmal - zwei Artikel gleichzeitig beobachten war
damit unmoeglich. Zweitens war dashboard_layouts.user_id unique, also genau ein
Dashboard je Benutzer.

Jetzt ist "i" die Kennung dieser einen Karte und "type" ihre Art, dazu kommt
"props" fuer das, was nur diese Karte angeht (etwa welcher Artikel). Aeltere
Anordnungen haben kein "type"; fuer sie gilt die alte Kennung als Art, sodass
gespeicherte Startseiten unveraendert weiterlaufen.

Die Tabelle bekommt Name und Reihenfolge, das unique faellt. Sein Name haengt
davon ab, wie die Tabelle entstanden ist, deshalb wird er in pg_constraint
nachgeschlagen statt geraten.

Eine Feinheit beim Anlegen: Wer noch auf der Admin-Vorgabe sitzt und sein
erstes eigenes Dashboard anlegt, wuerde die Vorgabe schlagartig verlieren -
sobald eigene Zeilen da sind, zaehlen nur noch die. Deshalb wird sie vorher
uebernommen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 19:11:49 +02:00
Scarriffle
794a81f58b Lagerorte und Einheiten umbenennen
Beide kannte die API bisher nur als Anlegen und Loeschen. Wer sich vertippt
hatte, musste den Eintrag wegwerfen und neu anlegen - und verlor dabei genau
das, was daran haengt: Chargen zeigen auf die Lagerort-ID, Produkte und Gruppen
auf die Einheiten-ID. Ein Tippfehler kostete also Zuordnungen.

Neu ist je ein PATCH nach dem Vorbild der Gebinde, samt Pruefung auf doppelte
Namen ohne Ruecksicht auf Gross- und Kleinschreibung. Anders als beim Loeschen
duerfen auch eingebaute Einheiten umbenannt werden - auch das wie bei den
Gebinden. Art und Faktor einer Einheit bleiben dagegen fest: Sie stecken in
bereits umgerechneten Bestaenden, eine Aenderung wuerde die still verfaelschen.

Die Tests halten fest, worum es eigentlich geht - dass die Verweise das
Umbenennen ueberleben.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:23:51 +02:00
Scarriffle
5171a5ecab Backendstart repariert: 204-Route mit future-annotations
Derselbe Fehler wie damals beim Dashboard. In package_types.py stand
"from __future__ import annotations"; dadurch wird "-> None" zu einer
Zeichenkette, die FastAPI zu NoneType aufloest und als Antwortmodell wertet -
zusammen mit 204 bricht der Aufbau der Anwendung ab, und der Container kommt
gar nicht erst hoch.

Der Import ist raus, mit Begruendung im Quelltext. Die uebrigen Router mit
diesem Import (branding, dashboard, transfer) sind geprueft: Sie geben in
204-Routen Response zurueck.

Damit es nicht ein drittes Mal erst am Container auffaellt, importiert ein
Test jetzt app.main. Genau dabei loest der Fehler aus - der Test deckt damit
auch jede kuenftige Route ab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:28:12 +02:00
Scarriffle
fd9045226b Gebinde verwalten - mit Einzahl und Mehrzahl
Bisher war die Auswahl eine fest im Frontend verdrahtete Liste und es gab nur
eine Form: ueberall stand "3 Glas".

Neu unter Verwaltung > Gebinde: anlegen, umbenennen, loeschen, jeweils mit
Einzahl und Mehrzahl. Die eingebauten Gebinde lassen sich in der Schreibweise
aendern, aber nicht loeschen; ein Gebinde, das ein Artikel verwendet, ebenfalls
nicht.

Der Artikel speichert weiterhin nur die Einzahl als Text - so bleiben
vorhandene Artikel, Sicherungen und CSV-Dateien gueltig, und eine unbekannte
Bezeichnung faellt schlicht auf die Einzahl zurueck. Deshalb zieht ein
Umbenennen die Artikel mit; sonst zeigten sie auf eine Bezeichnung, die es
nicht mehr gibt.

Die Mehrzahl greift jetzt in Artikelliste, Artikelseite (Chargen und Gebinde-
Auswahl), Auslagern und in allen Ablauf- und Einkaufslisten samt Startseite.
Einheiten wie Gramm oder Liter bleiben unveraendert - die haben im Deutschen
keine Mehrzahl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:24:42 +02:00
Scarriffle
0ba968985f Bild uebernehmen: sichtbare Rueckmeldung und Nachholen bei fehlender Kopie
Zwei Ursachen dafuer, dass der Knopf folgenlos wirkte.

Sichtbar: Uebernehmen schreibt nur ins Formular, das Artikelbild wechselt erst
beim Speichern - es gab aber nichts, woran man das erkannt haette. Die Spalte
"Bisher" zeigt jetzt sofort das uebernommene Bild mit Kennzeichnung, der Knopf
heisst danach "Uebernommen", und eine Meldung erklaert den naechsten Schritt.

Tatsaechlich folgenlos war der haeufigste Fall: Stimmte die Bildadresse
laengst und nur der Abruf war damals fehlgeschlagen, aenderte sich beim
Speichern nichts - und die Bedingung fragte genau nach einer Aenderung. Sie
richtet sich jetzt danach, woher die vorhandene Kopie stammt. Fehlt sie, wird
geholt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:03:28 +02:00
Scarriffle
8766e35e91 Artikeldaten von Open Food Facts vergleichen und einzeln uebernehmen
Neuer Knopf unter "Nachschlagen" auf der Artikelseite. Er fragt OFF erneut und
stellt die Antwort den eigenen Daten gegenueber: Name, Marke, Packungsgroesse,
Kategorie und Bild, jeweils mit eigenem Uebernehmen-Knopf und einem
"Alle uebernehmen". Uebernommen wird nur ins Formular - gespeichert erst mit
"Speichern", damit man bis zuletzt bei den eigenen Daten bleiben kann.

Dafuer noetig: GET /products/{id}/off. /lookup taugt hier nicht, weil es bei
einem bekannten Barcode den eigenen Artikel meldet und OFF gar nicht erst
fragt. Der neue Endpunkt probiert auch die zusaetzlichen EAN-Codes durch - oft
ist nur einer davon bei OFF hinterlegt.

Damit ist das Bild unabhaengig davon nachholbar, ob ein Backup eine
Bildadresse enthielt.

Nebenbei: Der Nachschlagen-Knopf uebergab die Gruppenliste, wo die
Einheitenliste erwartet wird - die Einheit eines Vorschlags wurde deshalb nie
richtig zugeordnet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:00:30 +02:00
Scarriffle
2fa274eae8 Backup-Import: Bildadresse ging weiterhin verloren
Der vorige Anlauf war wirkungslos. _import_json baut aus jedem JSON-Eintrag eine
eigene Zeile fuer _get_or_create_product - und image_url stand in dieser Zeile
nicht drin. Die Abfrage lief also immer ins Leere.

Zwei Tests gehen jetzt durch _import_json statt nur durch
_get_or_create_product, weil die Adresse genau zwischen den beiden herausfiel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:52:04 +02:00
Scarriffle
3763f2f114 Artikelbild: nur noch an einer Stelle, hochkant, und beim Import beruecksichtigt
Das alte Bild in der Chargen-Karte entfaellt - seit dem neuen Bild neben den
Kennfeldern stand es doppelt auf der Seite.

Der Rahmen ist jetzt hochkant (3:4) statt quadratisch. Lebensmittelverpackungen
sind fast immer hoeher als breit; im Quadrat blieben links und rechts leere
Streifen.

Beim Import wurde image_url komplett verworfen, obwohl das JSON-Backup sie
enthaelt - eingelesene Artikel blieben deshalb dauerhaft ohne Bild. Die Adresse
wird jetzt uebernommen (bei bestehenden Artikeln nur ergaenzend, nie
ueberschreibend); die lokale Kopie holt der Bildabruf beim ersten Ansehen.
Bewusst nicht waehrend des Imports: Ein Backup mit 200 Artikeln wuerde sonst
200 Downloads in einer Anfrage abarbeiten.

Ausserdem holen jetzt auch Artikelliste, Ein- und Auslagern ihr Vorschaubild
aus der eigenen Datenbank statt direkt von Open Food Facts. Einzige Ausnahme
bleibt der OFF-Vorschlag beim Scannen: Dort existiert der Artikel noch nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:48:20 +02:00
Scarriffle
c155116b4e Artikelbilder lokal speichern und auf der Artikelseite anzeigen
Open Food Facts liefert nur eine Bildadresse. Direkt dorthin zu verlinken hiesse:
Das Bild verschwindet, wenn OFF es austauscht, die Installation braucht Internet,
und jeder Seitenaufruf verraet OFF, welche Artikel jemand ansieht.

Das Bild wird daher einmal geholt und in einer eigenen Tabelle product_images
abgelegt - eigene Tabelle, damit Artikellisten die Blobs nicht mitziehen.
Geholt wird beim Anlegen, bei geaenderter Bildadresse und beim ersten Abruf
(so bekommen auch bestehende Artikel ihre Kopie, ohne Wanderung ueber alle
Datensaetze).

GET /products/{id}/image verlangt eine Anmeldung - aus den Bildern liesse sich
sonst ohne Konto ablesen, was im Vorrat liegt. Da ein <img src> keinen
Authorization-Header schickt, laedt die Oberflaeche das Bild ueber fetch und
zeigt es als Objekt-URL.

Angezeigt wird es rechts neben Barcode, Name und Marke. Fehlt ein Bild, rendert
die Komponente nichts und die Felder nehmen die volle Breite ein.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:35:10 +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
2423e74cae Umbenennung in Vorrania
Sichtbarer Name ueberall: Kopfzeichen der Oberflaeche, Seitentitel, Favicon,
API-Titel, User-Agent zu Open Food Facts, Dateinamen der Sicherungen, README,
Roadmap, Installer- und Update-Skript, npm-Paketname.

Bewusst NICHT umbenannt, jeweils mit Begruendung im Code:
- POSTGRES_USER/DB und der Volume-Name bleiben "project_good". Ein neuer
  Volume-Name legt eine leere Datenbank an, ein neuer Benutzername scheitert am
  bestehenden Volume - vorhandene Bestaende waeren verloren.
- Der localStorage-Schluessel bleibt, sonst waeren alle Browser einmal
  ausgesperrt.
- ios/ bleibt unangetastet (wird getrennt bearbeitet).

Die Klon-Adresse im README zeigt jetzt auf .../vorrania.git; das Repo in Gitea
muss dafuer umbenannt werden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:05:32 +02:00
Scarriffle
26f7ed21d8 Ein- und Auslagerungen als Wasserfall- bzw. Brueckendiagramm
Bisher waren es zwei Linien mit der Anzahl der Vorgaenge. Gemeint war eine
Bruecke: die Bestandslinie, und schwebende gruene/rote Balken, die jeden ihrer
Spruenge erklaeren.

Damit Balken und Linie ueberhaupt zusammen in ein Bild duerfen, zaehlen die
Balken jetzt Mengen in Artikeleinheiten statt Vorgaengen - beides liegt so auf
derselben Achse. Zwei Skalen in einem Diagramm bleiben ausgeschlossen.

Neuer Endpunkt GET /dashboard/flow liefert je Abschnitt Anfangsbestand, Zugang
und Abgang aus einer Abfrage. Getrennt abgefragt koennten die Abschnitte von
timeline und activity um eine Schrittweite auseinanderliegen - dann stuende ein
Balken neben dem Sprung, den er erklaert.

/dashboard/activity bleibt fuer externe Zugriffe erhalten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:45:59 +02:00
Scarriffle
d6a717e39c Ablaufkarten trennen: "Bald ablaufend" zeigt kein Abgelaufenes mehr
Eine Karte mit dieser Ueberschrift, in der bereits Ueberfaelliges steht,
ist irrefuehrend. KarteAblauf bekommt daher einen Modus soon/expired/alle
statt des bisherigen Schalters "nurAbgelaufen".

Neu ist die Karte "Ablauf-Uebersicht" (alle), die beides zusammen zeigt.
Sie steht auch in der eingebauten Vorgabe, damit dort ohne Zutun weiterhin
Ueberfaelliges sichtbar ist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:24:30 +02:00
Scarriffle
1fd1e89872 Zeitraum im Kartenkopf; Verlauf feiner als ein Punkt pro Tag
Zeitraum-Auswahl:
Sie stand ueber dem Diagramm und nahm ihm Hoehe weg. Jetzt sitzt sie klein im
Kartenkopf neben dem Titel - die ganze Kartenhoehe gehoert dem Diagramm. Die
Wahl wird in der Anordnung mitgespeichert, bleibt also nach dem Neuladen
erhalten; ausserhalb des Bearbeitungsmodus geschieht das still im Hintergrund.

Feinere Aufloesung:
Ein Punkt je Tag verbarg, WANN ein- und ausgelagert wurde. Die Abtastrate
richtet sich jetzt nach dem Zeitraum:
  bis  2 Tage  -> stuendlich
  bis 14 Tage  -> alle 6 Stunden
  bis 120 Tage -> taeglich
  darueber     -> woechentlich
So bleibt die Punktzahl immer zwischen etwa 25 und 105 - fein genug zum
Erkennen, grob genug zum Zeichnen. Ein Test haelt diese Spanne fest.

Die Zeitachse traegt entsprechend Uhrzeit statt Datum, wenn stuendlich
abgetastet wird. Punkte heissen jetzt "at" (Zeitpunkt) statt "date".
Zeitstempel aus SQLite kommen ohne Zeitzone zurueck und werden vereinheitlicht,
sonst schluege die Differenzbildung fehl.

Auswahl erweitert: 24 Stunden, 2 Tage, 7, 30, 90 Tage, 6 Monate, 1 Jahr.

Geprueft: "npm run build" laeuft durch. pytest weiterhin nicht ausfuehrbar -
kein Python auf diesem Rechner; die Tests wurden an die neue Aufloesung
angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:36:33 +02:00
Scarriffle
97a6e94ea1 Kurzmeldungen als Overlay, Liniendiagramm fuellt die Karte, Zeitraum ab 1 Tag
Meldungen:
"Gespeichert", "Anordnung gespeichert" und die uebrigen Erfolgsmeldungen standen
als Leiste mitten in der Seite - beim Speichern sprang dadurch das ganze Layout
ein Stueck nach unten. Sie erscheinen jetzt als Overlay am unteren Rand und
verschwinden nach fuenf Sekunden von selbst; ein Kreuz blendet sie sofort aus.
Umgestellt in Uebersicht, Einstellungen, Gefahrenbereich, Benutzer,
Produktformular sowie Ein- und Auslagern.

Bewusst NICHT umgestellt: Fehlermeldungen. Ein Fehler, der nach fuenf Sekunden
verschwindet, ist genau dann weg, wenn man ihn noch braucht - der bleibt an
seiner Stelle stehen.

Rollbalken beim Liniendiagramm:
Den hatte ich mir mit der Zeitraum-Auswahl selbst eingebaut. Das Diagramm hatte
eine feste Hoehe von 200 px; zusammen mit der Auswahl darueber passte das nicht
mehr in die Karte. Jetzt wird auch die Hoehe gemessen, das Diagramm fuellt den
verbleibenden Platz.

Zeitraum "1 Tag":
Ein einzelner Tag ergibt genau einen Messpunkt - eine Linie aus einem Punkt
waere unsinnig. Das Diagramm zeigt in dem Fall die blanke Zahl. Die
Backend-Begrenzung laesst jetzt einen Tag zu.

Geprueft: "npm run build" laeuft durch; kein Verweis mehr auf die entfernten
Meldungszustaende.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:23:53 +02:00
Scarriffle
aa58914374 Kartengroessen korrigiert, Vorschau im Menue, Zeitraum-Auswahl bei Verlaeufen
Zu kleine Karten (Konstruktionsfehler):
Bei Schnellzugriff, Status und den Kennzahlen war die Mindesthoehe gleich der
Standardhoehe - und beide zu klein. Bei 96 px Zeilenhoehe blieben nach Kartenkopf
und Innenabstand nur 23 px fuer den Inhalt, eine Kennzahl braucht allein rund
45 px. Deshalb erschien schon in der Grundeinstellung ein Rollbalken, und
kleiner ging es nicht.
- Zeilenhoehe von 96 auf 40 px: die Hoehe laesst sich jetzt fein abstufen.
- Jede Karte hat eine Mindesthoehe, in die ihr Inhalt nachweislich passt
  (Rechnung steht als Kommentar am Verzeichnis).
- Gespeicherte Anordnungen aus der alten Fassung werden beim Laden auf die
  Mindestgroesse angehoben, statt zu flach zu bleiben.

Vorschau im Menue "Karte hinzufuegen":
Der Name allein verriet nicht, was eine Karte zeigt. Jede Karte hat jetzt eine
kleine Skizze (Tabelle, Ring, Balken, Linie, Kennzahl) und eine Beschreibung.
Bewusst gezeichnete Skizzen statt echter Vorschau - sonst muesste das Menue fuer
jede Karte Daten laden.

Zeitraum bei den Verlaufskarten:
Bestandsverlauf, Artikelverlauf und Ein-/Auslagerungen haben eine Auswahl
7 Tage bis 1 Jahr.

Ringdiagramme:
Der Ring klebte oben in der Karte, darunter blieb viel Leerraum. Er fuellt jetzt
die Karte und sitzt mittig.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:09:05 +02:00
Scarriffle
4246e0d98f Fix Startabbruch: 204-Route in dashboard.py brach den Backend-Start ab
AssertionError beim Import: "Status code 204 must not have a response body".

Ursache ist eine Wechselwirkung, die nur in dieser Datei auftritt: dashboard.py
nutzt "from __future__ import annotations". Dadurch ist die Rueckgabeangabe
"-> None" eine Zeichenkette, die FastAPI zu NoneType aufloest und als
Antwortmodell wertet. Zusammen mit status_code=204 - das keinen Rumpf haben darf -
schlaegt die Pruefung beim Registrieren der Route fehl und das Backend startet
gar nicht. Andere Router im Projekt haben dieses __future__-Import nicht, deshalb
funktioniert dasselbe Muster dort.

Die Route folgt jetzt dem bereits bewaehrten Muster aus branding.py: Der
Statuscode steht am Response, nicht im Dekorator. Der Grund steht als Kommentar
an der Funktion, damit die Falle nicht erneut zuschnappt.

Geprueft: Alle uebrigen Routen der Datei haben ein explizites response_model im
Dekorator und sind davon nicht betroffen. Ein Start des Backends konnte ich hier
nicht pruefen - auf diesem Rechner ist weder Python noch Docker vorhanden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 09:58:01 +02:00
Scarriffle
84ce596446 Modulare Startseite: Kartenraster, eigene Anordnung je Benutzer, Auswertungen
Die Uebersicht war fest verdrahtet und fuer alle gleich. Jetzt besteht sie aus
Karten in einem 12-Spalten-Raster, die sich ziehen, in der Groesse aendern und
einrasten lassen (react-grid-layout). "Anordnen" schaltet das frei, gespeichert
wird ausdruecklich - Probieren bleibt folgenlos.

Anordnung:
- Neue Tabelle dashboard_layouts; user_id = NULL ist die Admin-Vorgabe.
- Jeder Benutzer hat seine eigene Anordnung. Admins speichern eine Vorgabe fuer
  alle und koennen sie ueber die Einstellung dashboard_enforced verbindlich
  machen; dann lehnt das Backend das Speichern eigener Anordnungen ab.
- "Auf Vorgabe zuruecksetzen" verwirft die persoenliche Anordnung.

15 Karten (Verzeichnis web/src/dashboard/cards.jsx, neue Karte = ein Eintrag):
Schnellzugriff, Status, Artikelzahl, Artikeleinheiten, Bald ablaufend,
Abgelaufen, Einkaufsliste, letzte Bewegungen sowie sechs Auswertungen
(Ablauf-Ring, Kategorien-Ring, Kategorien nach Zustand, Bestandsverlauf,
Verlauf eines Artikels, Ein-/Auslagerungen).

Diagramme als eigene SVG-Komponenten statt Diagramm-Paket:
- Zustandsfarben (ok/bald/abgelaufen) sind Statusangaben aus den Tokens und
  stehen nie ohne Beschriftung; Kategorien nutzen eine gepruefte Farbreihe in
  fester Ordnung, ab sieben Kategorien wird zu "Andere" gebuendelt.
- Eine Werteachse, duenne Marken, zurueckhaltendes Gitter, Fadenkreuz mit
  Kurzinfo. Hell und Dunkel haben eigene Farbstufen.

Mengen durchgehend in Artikeleinheiten (Glaeser, Packungen, Stueck): Gramm und
Stueck lassen sich nicht addieren, Gebinde schon. Der bisher dreifach
vorhandene Helfer liegt jetzt einmal in services/conversion.py::article_unit.

Der Verlauf wird rueckwaerts vom heutigen Bestand aus den Bewegungen
rekonstruiert. Bekannte Ungenauigkeit, im Code und in der Roadmap vermerkt:
Die Umrechnung nutzt die heutige Packungsgroesse.

Geprueft: "npm run build" laeuft durch. Die neuen pytest-Tests
(backend/tests/test_dashboard.py) konnten hier nicht laufen - auf diesem
Rechner ist kein Python installiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 09:48:16 +02:00
Scarriffle
4448718d8d Logo-Upload sichtbar machen; empfohlene Abmessungen angeben
Fehler: Ein hochgeladenes Logo blieb unsichtbar, auch nach dem Neuladen.
Die Oberflaeche prueft per Anfrage, ob ein Bild hinterlegt ist, und nutzte dafuer
HEAD. FastAPI registriert fuer eine GET-Route aber - anders als Starlette - kein
HEAD; die Anfrage lief in ein 405, und jedes hinterlegte Bild galt als "nicht
vorhanden". Der Upload selbst war die ganze Zeit in Ordnung.

- Neuer Endpunkt GET /branding liefert nur den Status ({"logo": true, ...}),
  ohne Bilddaten zu uebertragen. Die Oberflaeche fragt jetzt diesen ab.
- Die Bild-Route beantwortet zusaetzlich HEAD, damit sie sich erwartungsgemaess
  verhaelt.

Ausserdem:
- Die empfohlenen Abmessungen stehen jetzt sichtbar ueber der Vorschau
  (Logo etwa 400x72 px, Favicon 64x64 px, jeweils hoechstens 512 KB).
- Nach dem Hochladen wird die tatsaechliche Bildgroesse angezeigt, damit man
  sie mit der Empfehlung vergleichen kann.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:55:17 +02:00
Scarriffle
8f549636a8 Marke statt Produktname am EAN-Code, Layout bricht um statt abzuschneiden
Am Gruppen-Code stand der Produktname. Innerhalb einer Gruppe hilft der aber
nicht weiter: In der Gruppe "Zucker" heisst jeder Artikel irgendwie "Zucker",
in "Mehl" irgendwie "…mehl". Unterscheiden laesst sich das nur ueber die Marke.
Der Endpunkt liefert jetzt beides; angezeigt wird die Marke, faellt bei
fehlender Marke auf den Namen zurueck und zeigt den vollen Namen im Tooltip.
Nebenbei ist die Marke meist deutlich kuerzer als der Name.

Abgeschnittene Tabelle: Die Spalte "verwalten" wurde gekappt. Ursache war nicht
die Breite an sich, sondern dass Grid-Kinder standardmaessig nicht unter ihre
Inhaltsbreite schrumpfen (min-width: auto). Dadurch lief die Tabelle aus ihrer
Karte heraus und wurde beschnitten, statt dass das vorhandene overflow-x
gegriffen haette. Mit min-width: 0 an den Grid-Kindern scrollt sie jetzt.
Zusaetzlich hat die Tabelle eine Mindestbreite von 640px - lieber waagerecht
scrollen als Spalten so weit quetschen, bis Text verschwindet.

Umbruch statt Quetschen: Die feste Umbruchbreite von 820px passte nicht zur
tatsaechlichen Lage - entscheidend ist, wie viel Platz die Spalten wirklich
brauchen, nicht wie breit der Bildschirm ist. Jetzt auto-fit mit Mindestbreite
je Spalte: Zwei Spalten, solange beide genug Platz haben, sonst automatisch
untereinander. Seiten mit inhaltsreichem Seitenblock (EAN-Codes) verlangen mehr
und stapeln frueher.

Durchgerechnet: Bei 1920 voller Breite zwei Spalten zu je 668px, Tabelle passt.
Bei halber Breite (960) eine Spalte zu 676px, Tabelle passt weiterhin. Erst bei
sehr schmalen Fenstern (unter etwa 900px Gesamtbreite) scrollt die Tabelle
waagerecht. In keinem Fall wird noch etwas abgeschnitten.

Getestet: 58 pytest-Tests gruen, Web-Build laeuft durch. Gegen die laufende API
geprueft, dass Marke und Name am Code mitkommen und die fehlende Marke sauber
auf den Namen zurueckfaellt. Die Breiten sind rechnerisch geprueft, die
Darstellung im Browser habe ich nicht selbst angesehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:53:29 +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