Hausmasse und Oeffnungsfrist bekommen eine Oberflaeche

Die Endpunkte standen seit dem Backend-Commit, aber eintragen liess sich
nichts - die Mengen gingen nur per API rein. Damit war "2 EL Mehl" im
Verbrauchen-Dialog zwar vorgesehen, aber fuer niemanden erreichbar.

Zwei Stellen dafuer:

Stammdaten > Masse verwaltet nur die NAMEN ("Essloeffel", "Tasse"). Die
Seite ist absichtlich kurz, denn die Mengen gehoeren nicht an das Mass:
Ein Essloeffel Mehl sind etwa 10 g, einer Zucker etwa 12 g, und eine
Tasse Milch ist Milliliter. Ein globaler Wert waere bei jedem zweiten
Artikel falsch.

Kategorien > "Masse & Frist" ist deshalb der Ort, an dem die Arbeit
passiert. Beides steckt in einem Bedienfeld, weil es dieselbe Mechanik
ist: Der Wert gilt fuer die Kategorie und alles darunter, und der
naechste Vorfahr mit einem Eintrag gewinnt. Leere Felder erben - und
zeigen als Platzhalter, WAS sie erben ("erbt 5 Tage (Kategorie: Mehl &
Zucker)", "10 g" mit Abzeichen "geerbt"). Ohne das muesste man den
Kategorienbaum im Kopf verfolgen, um eine leere Eingabe zu verstehen.

Dafuer zwei kleine Ergaenzungen am Backend: CategoryOut nennt die
geerbte Frist samt Herkunft, und /categories/{id}/measures nimmt
?vererbt=true, um neben den eigenen auch die von oben geerbten Mengen zu
liefern. Die Bearbeitungsansicht ohne Flagge bleibt wie sie war.

Von Hand durchgeklickt - und dabei einen echten Fehler gefunden: Der
API-Aufruf im Browser hat das vererbt-Flag verschluckt, weil die
Funktion nur ein Argument annahm. Die Unterkategorie zeigte dadurch
leere Felder, obwohl der Server die geerbten Werte lieferte. Jetzt
stehen dort "10 g" und "3 g" mit der Quelle im Tooltip.

4 neue Tests, Suite bei 516.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-02 14:37:53 +02:00
co-authored by Claude Opus 5
parent 795e233a1b
commit f61c76034c
9 changed files with 508 additions and 10 deletions
+2
View File
@@ -21,6 +21,7 @@ from ..models import (
)
from ..schemas import CategoryCreate, CategoryOut, CategoryUpdate, FieldDefinitionOut
from ..services.fields import effective_field_definitions
from ..services.vererbung import geerbte_kategorie_frist
from .field_definitions import to_out as field_to_out
router = APIRouter(prefix="/categories", tags=["categories"])
@@ -54,6 +55,7 @@ def _to_out(db: Session, category: Category) -> CategoryOut:
out.product_count = (
db.query(Product).filter(Product.category_id == category.id).count()
)
out.inherited_open_days, out.open_days_source = geerbte_kategorie_frist(db, category)
return out
+27 -5
View File
@@ -30,7 +30,7 @@ from ..schemas import (
MeasureOut,
)
from ..services.conversion import PACKAGE_TOKENS, find_unit
from ..services.vererbung import verfuegbare_masse
from ..services.vererbung import geerbtes_mass_kategorie, verfuegbare_masse
router = APIRouter(tags=["measures"])
@@ -224,13 +224,17 @@ def delete_product_measure(
@router.get("/categories/{category_id}/measures", response_model=list[MeasureAmountOut])
def category_measures(
category_id: int,
vererbt: bool = False,
db: Session = Depends(get_db),
_: User = Depends(get_current_user),
) -> list[MeasureAmountOut]:
"""Die an DIESER Kategorie hinterlegten Mengen (ohne die geerbten).
"""Die an DIESER Kategorie hinterlegten Mengen.
Bewusst ohne Vererbung: Diese Liste ist die Bearbeitungsansicht. Was ein
Artikel am Ende wirklich bekommt, zeigt ``/products/{id}/measures``.
Ohne ``vererbt`` nur die eigenen – das ist die Bearbeitungsansicht. Mit
``vererbt=true`` kommen die von oben geerbten dazu, erkennbar an ihrer
Herkunft („Kategorie: Mehl & Zucker"); die Maske zeigt damit auch, was
gelten wuerde, wenn man den eigenen Eintrag loescht. Was ein ARTIKEL am Ende
bekommt, zeigt ``/products/{id}/measures``.
"""
if db.get(Category, category_id) is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, "Kategorie nicht gefunden")
@@ -241,13 +245,31 @@ def category_measures(
.order_by(Measure.position, Measure.id)
.all()
)
return [
eigene = [
MeasureAmountOut(
measure_id=m.id, name=m.name, short=m.short,
amount=cm.amount, base=cm.base, herkunft="Kategorie",
)
for cm, m in rows
]
if not vererbt:
return eigene
# Zusaetzlich, was diese Kategorie von oben ERBT. Die Maske zeigt damit
# beides: was hier steht und was gelten wuerde, wenn man es loescht.
category = db.get(Category, category_id)
gesetzt = {e.measure_id for e in eigene}
for measure in db.query(Measure).order_by(Measure.position, Measure.id).all():
if measure.id in gesetzt:
continue
angabe = geerbtes_mass_kategorie(db, category, measure.id)
if angabe is None:
continue
eigene.append(MeasureAmountOut(
measure_id=measure.id, name=measure.name, short=measure.short,
amount=angabe.amount, base=angabe.base, herkunft=angabe.herkunft,
))
return eigene
@router.put("/categories/{category_id}/measures/{measure_id}", response_model=MeasureAmountOut)