diff --git a/backend/app/main.py b/backend/app/main.py index b0b3bab..d948e82 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -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 diff --git a/backend/app/routers/groups.py b/backend/app/routers/groups.py index abca975..a18fc85 100644 --- a/backend/app/routers/groups.py +++ b/backend/app/routers/groups.py @@ -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 diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 885f689..bfc4924 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -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): diff --git a/backend/app/services/group_codes.py b/backend/app/services/group_codes.py index 1ec21be..bd829b5 100644 --- a/backend/app/services/group_codes.py +++ b/backend/app/services/group_codes.py @@ -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() diff --git a/backend/tests/test_categories.py b/backend/tests/test_categories.py index 74389e5..5cddc10 100644 --- a/backend/tests/test_categories.py +++ b/backend/tests/test_categories.py @@ -171,3 +171,22 @@ def test_fremder_alias_wird_nicht_ueberschrieben(db): assert _gruppen_codes(db, mehl) == set() assert db.query(Barcode).filter(Barcode.code == "4001").one().product_id == anderer.id + + +def test_backfill_holt_bestehende_zuordnungen_nach(db): + """Zuordnungen von vor der automatischen Codepflege bekommen ihren Code.""" + from app.services.group_codes import backfill + + mehl = Group(name="Mehl") + db.add(mehl) + db.commit() + # Bewusst ohne sync() angelegt - so sah der Altbestand aus. + db.add(Product(name="Aldi Mehl", barcode="4001", group_id=mehl.id)) + db.commit() + assert _gruppen_codes(db, mehl) == set() + + assert backfill(db) == 1 + assert _gruppen_codes(db, mehl) == {"4001"} + # Wiederholtes Ausfuehren legt nichts doppelt an. + assert backfill(db) == 0 + assert _gruppen_codes(db, mehl) == {"4001"} diff --git a/ios/AppIcon.icon/icon.json b/ios/AppIcon.icon/icon.json index f940700..f2247d0 100644 --- a/ios/AppIcon.icon/icon.json +++ b/ios/AppIcon.icon/icon.json @@ -1,6 +1,16 @@ { "fill" : { - "automatic-gradient" : "srgb:0.55985,0.47253,1.00000,1.00000" + "automatic-gradient" : "display-p3:0.68468,0.62596,1.03953,1.00000", + "orientation" : { + "start" : { + "x" : 0.5, + "y" : 0 + }, + "stop" : { + "x" : 0.5, + "y" : 0.7 + } + } }, "groups" : [ { @@ -12,6 +22,12 @@ "value" : { "solid" : "extended-gray:1.00000,1.00000" } + }, + { + "appearance" : "tinted", + "value" : { + "automatic-gradient" : "extended-gray:0.25000,1.00000" + } } ], "glass" : false, diff --git a/web/src/components/BarcodeList.jsx b/web/src/components/BarcodeList.jsx index 72a872c..a4b51be 100644 --- a/web/src/components/BarcodeList.jsx +++ b/web/src/components/BarcodeList.jsx @@ -47,8 +47,14 @@ export default function BarcodeList({
  • {b.code} - {/* Notiz auch nachtraeglich aenderbar - automatisch angelegte Codes - hatten sonst nie eine Gelegenheit, eine zu bekommen. */} + {/* Gehört der Code über einen Artikel zur Gruppe, steht das dabei – + eine Notiz ist trotzdem möglich. */} + {b.product_name && ( + <> + Artikel + {b.product_name} + + )} {onEditNote && !disabled ? ( {b.note} )} - {!disabled && ( + {/* Codes, die über einen Artikel hier stehen, lassen sich nicht + entfernen – sie kämen beim nächsten Speichern sofort zurück. + Dafür muss der Artikel die Gruppe wechseln. */} + {!disabled && !b.product_name && (