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>
This commit is contained in:
@@ -23,6 +23,7 @@ from .routers import (
|
||||
views,
|
||||
)
|
||||
from .seed import ensure_builtin_categories, ensure_builtin_units, ensure_first_admin
|
||||
from .services.group_codes import backfill as backfill_group_codes
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
@@ -69,6 +70,8 @@ async def lifespan(app: FastAPI):
|
||||
ensure_builtin_units(db)
|
||||
ensure_builtin_categories(db)
|
||||
ensure_first_admin(db)
|
||||
# Codes bestehender Gruppen-Zuordnungen nachziehen.
|
||||
backfill_group_codes(db)
|
||||
finally:
|
||||
db.close()
|
||||
yield
|
||||
|
||||
@@ -23,21 +23,36 @@ def _group_to_out(db: Session, group: Group) -> GroupOut:
|
||||
out = GroupOut.model_validate(group)
|
||||
products = db.query(Product).filter(Product.group_id == group.id).all()
|
||||
out.product_count = len(products)
|
||||
out.barcodes = [
|
||||
BarcodeOut.model_validate(b)
|
||||
for b in db.query(Barcode).filter(Barcode.group_id == group.id).order_by(Barcode.id).all()
|
||||
]
|
||||
# Codes der Artikel in dieser Gruppe mitliefern. Sie waren bisher nirgends
|
||||
# sichtbar, wodurch eine Kategorie mit Artikeln "0 EANs" anzeigte.
|
||||
product_codes: list[ProductBarcodeOut] = []
|
||||
# Zu welchem Artikel gehoert ein Code? Der Gruppen-Code entsteht beim
|
||||
# Zuordnen automatisch; die Herkunft soll trotzdem sichtbar bleiben.
|
||||
name_zu_code: dict[str, str] = {}
|
||||
for product in products:
|
||||
if product.barcode:
|
||||
name_zu_code[product.barcode] = product.name
|
||||
for alias in db.query(Barcode).filter(Barcode.product_id == product.id):
|
||||
name_zu_code[alias.code] = product.name
|
||||
|
||||
out.barcodes = []
|
||||
for b in db.query(Barcode).filter(Barcode.group_id == group.id).order_by(Barcode.id).all():
|
||||
eintrag = BarcodeOut.model_validate(b)
|
||||
eintrag.product_name = name_zu_code.get(b.code)
|
||||
out.barcodes.append(eintrag)
|
||||
# Codes der Artikel in dieser Gruppe mitliefern. Sie waren bisher nirgends
|
||||
# sichtbar, wodurch eine Gruppe mit Artikeln "0 EANs" anzeigte.
|
||||
# Was schon als Gruppen-Code gefuehrt wird, hier ueberspringen - sonst
|
||||
# steht derselbe Code zweimal in der Liste, einmal davon schreibgeschuetzt.
|
||||
schon_da = {b.code for b in out.barcodes}
|
||||
product_codes: list[ProductBarcodeOut] = []
|
||||
for product in products:
|
||||
if product.barcode and product.barcode not in schon_da:
|
||||
product_codes.append(
|
||||
ProductBarcodeOut(
|
||||
code=product.barcode, product_id=product.id, product_name=product.name
|
||||
)
|
||||
)
|
||||
for alias in db.query(Barcode).filter(Barcode.product_id == product.id).order_by(Barcode.id):
|
||||
if alias.code in schon_da:
|
||||
continue
|
||||
product_codes.append(
|
||||
ProductBarcodeOut(
|
||||
code=alias.code, product_id=product.id, product_name=product.name
|
||||
|
||||
@@ -46,6 +46,8 @@ class BarcodeOut(BaseModel):
|
||||
id: int
|
||||
code: str
|
||||
note: str | None = None
|
||||
# Nur bei Gruppen-Codes gefüllt: der Artikel, über den der Code hier steht.
|
||||
product_name: str | None = None
|
||||
|
||||
|
||||
class ProductBarcodeOut(BaseModel):
|
||||
|
||||
@@ -64,6 +64,28 @@ def sync(db: Session, product: Product) -> None:
|
||||
db.add(Barcode(code=code, group_id=product.group_id))
|
||||
|
||||
|
||||
def backfill(db: Session) -> int:
|
||||
"""Holt die Codes bestehender Zuordnungen nach.
|
||||
|
||||
``sync`` greift nur beim Anlegen und Ändern eines Artikels. Zuordnungen, die
|
||||
es vor dieser Funktion schon gab, hätten sonst nie einen Gruppen-Code
|
||||
bekommen: In der Verwaltung stand der Code dann ausschließlich als
|
||||
schreibgeschützte Artikel-Zeile, ohne Möglichkeit für eine Notiz.
|
||||
|
||||
Läuft beim Start und ist gefahrlos wiederholbar.
|
||||
"""
|
||||
betroffen = (
|
||||
db.query(Product)
|
||||
.filter(Product.group_id.isnot(None), Product.barcode.isnot(None))
|
||||
.all()
|
||||
)
|
||||
vorher = db.query(Barcode).filter(Barcode.group_id.isnot(None)).count()
|
||||
for product in betroffen:
|
||||
sync(db, product)
|
||||
db.commit()
|
||||
return db.query(Barcode).filter(Barcode.group_id.isnot(None)).count() - vorher
|
||||
|
||||
|
||||
def detach(db: Session, product: Product) -> None:
|
||||
"""Vor dem Löschen eines Artikels dessen Gruppen-Code aufräumen."""
|
||||
code = (product.barcode or "").strip()
|
||||
|
||||
Reference in New Issue
Block a user