neue Archivfunktion und Optimierung an Artikelbearbeitung
This commit is contained in:
@@ -4,7 +4,9 @@ Alle Schreibzugriffe erhöhen den Revisionszähler der Liste und schreiben
|
||||
den neuen Wert in `row_rev` der geänderten Zeile.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -23,6 +25,7 @@ from app.schemas import MessageOut
|
||||
from app.product_lookup import lookup
|
||||
from app.schemas_shopping import (
|
||||
ArticleIn,
|
||||
ArticleSuggestion,
|
||||
ArticleOut,
|
||||
ArticleUpdateIn,
|
||||
AttributeOut,
|
||||
@@ -263,6 +266,85 @@ def get_articles(
|
||||
return [_article_out(a) for a in rows]
|
||||
|
||||
|
||||
SUGGEST_MONTHS = 24
|
||||
SUGGEST_LIMIT = 8
|
||||
SUGGEST_MIN_CHARS = 3
|
||||
|
||||
|
||||
@router.get("/articles/suggest", response_model=list[ArticleSuggestion])
|
||||
def suggest_articles(
|
||||
lst: ReadableList,
|
||||
db: DbSession,
|
||||
q: str = Query(min_length=SUGGEST_MIN_CHARS, max_length=200),
|
||||
):
|
||||
"""Vorschlaege aus dem, was auf dieser Liste schon eingetragen wurde.
|
||||
|
||||
Quelle sind die Listeneintraege der letzten 24 Monate - auch die
|
||||
bereits geloeschten, denn gerade die abgehakten und aufgeraeumten
|
||||
sind die interessanten. Was jemand vor drei Jahren einmal gekauft
|
||||
hat, hilft dagegen nicht mehr und wuerde die Liste nur verlaengern.
|
||||
|
||||
Zurueckgegeben werden die zuletzt verwendeten Angaben: Menge,
|
||||
Gebinde, Eigenschaft, Markt und Warengruppe. Wer den Vorschlag
|
||||
antippt, hat die Felder gefuellt und kann sie noch aendern.
|
||||
"""
|
||||
needle = q.strip()
|
||||
if len(needle) < SUGGEST_MIN_CHARS:
|
||||
return []
|
||||
|
||||
# LIKE-Sonderzeichen maskieren, sonst faende die Eingabe "50%" alles.
|
||||
escaped = (
|
||||
needle.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
||||
)
|
||||
cutoff = utcnow() - timedelta(days=SUGGEST_MONTHS * 30)
|
||||
|
||||
rows = db.execute(
|
||||
select(ListItem, Article)
|
||||
.join(Article, Article.id == ListItem.article_id)
|
||||
.where(
|
||||
ListItem.list_id == lst.id,
|
||||
ListItem.created_at >= cutoff,
|
||||
Article.deleted_at.is_(None),
|
||||
Article.name.like(f"%{escaped}%", escape="\\"),
|
||||
)
|
||||
.order_by(ListItem.created_at.desc())
|
||||
.limit(600)
|
||||
).all()
|
||||
|
||||
# Je Artikel den juengsten Eintrag behalten und mitzaehlen, wie oft
|
||||
# er vorkam - haeufig Verwendetes soll oben stehen.
|
||||
best: dict[str, dict] = {}
|
||||
for item, article in rows:
|
||||
entry = best.get(article.id)
|
||||
if entry is None:
|
||||
best[article.id] = {"item": item, "article": article, "count": 1}
|
||||
else:
|
||||
entry["count"] += 1
|
||||
|
||||
def rank(entry: dict) -> tuple[int, float]:
|
||||
# Erst Haeufigkeit, dann Aktualitaet.
|
||||
return (-entry["count"], -entry["item"].created_at.timestamp())
|
||||
|
||||
out = []
|
||||
for entry in sorted(best.values(), key=rank)[:SUGGEST_LIMIT]:
|
||||
item, article = entry["item"], entry["article"]
|
||||
out.append(
|
||||
ArticleSuggestion(
|
||||
article_id=article.id,
|
||||
name=article.name,
|
||||
count=item.count,
|
||||
pack_size=item.pack_size,
|
||||
pack_unit=item.pack_unit,
|
||||
variant=item.variant,
|
||||
market_id=item.market_id or article.default_market_id,
|
||||
category_id=item.category_id or article.default_category_id,
|
||||
times_used=entry["count"],
|
||||
last_used_at=item.created_at,
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@router.get("/barcode/{barcode}", response_model=ProductLookupOut)
|
||||
def resolve_barcode(barcode: str, lst: ReadableList, db: DbSession):
|
||||
"""Strichcode auflösen - erst im eigenen Bestand, dann außerhalb.
|
||||
|
||||
Reference in New Issue
Block a user