Favicon: Bisher diente der Barcode-Glyph des App-Icons als Standard. Jetzt
zeigt der Browser-Tab dasselbe Zeichen wie die Seitenleiste - das "box"-Icon
aus components/Icon.jsx auf der Akzentflaeche. Die Farbe folgt der Oberflaeche:
im dunklen Modus der hellere Akzent, damit das Symbol auf beiden Untergruenden
sitzt. Ein eigenes Favicon laesst sich weiterhin hochladen und hat Vorrang.
Gefahrenbereich (Einstellungen, nur Admin) mit drei Stufen, damit man nicht
mehr wegwirft als noetig:
1. Alle Bestaende loeschen - Chargen und Bewegungsverlauf. Der Katalog bleibt
vollstaendig erhalten, der Bestand steht ueberall auf null.
2. Alle Artikel loeschen - zusaetzlich der Artikelkatalog samt seiner
EAN-Codes. Gruppen, Kategorien, Lagerorte und Einheiten bleiben. Die Codes,
die an Gruppen haengen, bleiben ebenfalls.
3. Alles zuruecksetzen - zusaetzlich Gruppen, Kategorien, Lagerorte, Einheiten.
Benutzer, API-Tokens, Einstellungen und das eigene Logo bleiben in allen drei
Stufen erhalten. Anders liesse man sich aussperren und muesste externe Zugriffe
wie Home Assistant neu einrichten. Nach Stufe 3 werden die eingebauten
Einheiten und die Kategorie-Startliste sofort neu angelegt - ohne Einheiten
liesse sich nichts mehr einlagern, die Installation waere unbrauchbar.
Absicherung: Jede Aktion nennt vorher mit Zahlen, was genau verschwindet
("Betrifft 43 Chargen und 12 Bewegungen"), und laeuft erst, wenn das Wort
LOESCHEN eingetippt wurde. Ein Fehlklick kann nichts anrichten. Der Abschnitt
ist rot abgesetzt und weist auf das JSON-Backup hin.
Getestet: 57 pytest-Tests gruen, 3 davon neu (Bestaende loeschen laesst den
Katalog stehen, Artikel loeschen laesst Gruppen, Lagerorte und Einheiten sowie
den Gruppen-Code stehen, Startkategorien kommen zurueck). Gegen die laufende
API alle drei Stufen nacheinander durchgespielt und die Zahlen nach jedem
Schritt geprueft; Benutzer bleiben, Einheiten sind danach wieder da, ohne
Adminrechte antwortet der Endpunkt mit 401. Das Favicon wurde gerendert und
mit dem Zeichen der Seitenleiste verglichen. Web-Build laeuft durch.
Die Oberflaeche habe ich nicht selbst bedient - insbesondere habe ich keine
der Loeschaktionen ueber die Web-Oberflaeche ausgeloest.
Nebenbei die deutschen Anfuehrungszeichen im ganzen Projekt vereinheitlicht
(schliessendes Zeichen typografisch statt gerade).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""Pflegt die EAN-Codes an einer Gruppe automatisch mit.
|
||
|
||
Bisher entstand ein Gruppen-Code ausschließlich von Hand. Wurde ein Artikel
|
||
einer Gruppe zugeordnet, blieb dessen EAN-Code dort unsichtbar – die Gruppe
|
||
zeigte „0 EANs“, obwohl Artikel darin lagen, und der Versuch, den Code
|
||
nachzutragen, scheiterte mit „bereits vergeben“ (er hing ja am Artikel).
|
||
|
||
Jetzt wandert der Haupt-Barcode eines Artikels automatisch an die Gruppe:
|
||
|
||
* beim Anlegen und beim Zuordnen einer Gruppe → Code kommt hinzu
|
||
* beim Wechsel der Gruppe → Code wandert mit
|
||
* beim Entfernen der Gruppe oder Löschen des Artikels → Code verschwindet
|
||
|
||
Beim Scannen ändert das nichts an der Reihenfolge: ``routers.products.lookup``
|
||
findet weiterhin zuerst den Artikel, dessen Gruppe zählt. Der Gruppen-Eintrag
|
||
ist Beleg in der Verwaltung und Rückfall, falls der Artikel später verschwindet.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from ..models import Barcode, Product
|
||
|
||
|
||
def _group_row(db: Session, code: str) -> Barcode | None:
|
||
"""Vorhandene Gruppen-Zeile zu diesem Code (nicht: Artikel-Alias)."""
|
||
return (
|
||
db.query(Barcode)
|
||
.filter(Barcode.code == code, Barcode.group_id.isnot(None))
|
||
.first()
|
||
)
|
||
|
||
|
||
def sync(db: Session, product: Product) -> None:
|
||
"""Bringt den Gruppen-Code eines Artikels auf den aktuellen Stand.
|
||
|
||
Deckt Anlegen, Ändern und Entfernen der Gruppe gleichermaßen ab, damit die
|
||
Aufrufer sich nicht merken müssen, was vorher galt.
|
||
"""
|
||
code = (product.barcode or "").strip()
|
||
if not code:
|
||
return
|
||
|
||
row = _group_row(db, code)
|
||
|
||
# Keine Gruppe mehr: einen früher angelegten Eintrag wieder entfernen.
|
||
if product.group_id is None:
|
||
if row is not None:
|
||
db.delete(row)
|
||
return
|
||
|
||
if row is not None:
|
||
# Gruppe gewechselt: Zeile umhängen statt löschen und neu anlegen,
|
||
# damit eine hinterlegte Notiz erhalten bleibt.
|
||
row.group_id = product.group_id
|
||
return
|
||
|
||
# Hängt der Code bereits als Alias an einem Artikel, gehört er dort hin -
|
||
# code ist eindeutig, eine zweite Zeile wäre nicht möglich.
|
||
if db.query(Barcode).filter(Barcode.code == code).first() is not None:
|
||
return
|
||
|
||
db.add(Barcode(code=code, group_id=product.group_id))
|
||
|
||
|
||
def detach(db: Session, product: Product) -> None:
|
||
"""Vor dem Löschen eines Artikels dessen Gruppen-Code aufräumen."""
|
||
code = (product.barcode or "").strip()
|
||
if not code:
|
||
return
|
||
row = _group_row(db, code)
|
||
if row is not None:
|
||
db.delete(row)
|