1) Beleganalyse erkennt Datumsangaben mit Monatsnamen (DE+EN, z.B. Amazon 'Bestelldatum 29 April 2026'), nicht nur 29.04.2026 - so wird das Kaufdatum solcher Rechnungen gefunden. 2) Beim direkten Anlegen eines Einzelstuecks mit Beleg werden jetzt alle erkannten Preise als Vorschlag angeboten (Dropdown), statt stillschweigend nur den wahrscheinlichsten zu nehmen. 3) Alle Lagerort-Dropdowns zeigen den vollen Pfad (Hedingen -> Keller -> Schublade 1) statt nur den Namen, damit gleichnamige Orte unterscheidbar sind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
169 lines
5.5 KiB
Python
169 lines
5.5 KiB
Python
"""Lokale Garantieende-Schätzung aus Belegtext (ohne echtes PDF)."""
|
|
|
|
from datetime import date
|
|
|
|
from app.services.warranty import (
|
|
guess_acquired_on,
|
|
guess_price_candidates,
|
|
guess_price_cents,
|
|
guess_shop,
|
|
guess_warranty_until,
|
|
)
|
|
|
|
|
|
def test_zeitraum_plus_kaufdatum_vom_stueck():
|
|
# Beleg nennt nur den Zeitraum; das Kaufdatum kommt vom Einzelstück.
|
|
text = "Vielen Dank für Ihren Einkauf. 24 Monate Garantie auf dieses Gerät."
|
|
assert guess_warranty_until(text, acquired_on=date(2024, 3, 31)) == date(2026, 3, 31)
|
|
|
|
|
|
def test_jahre_werden_verstanden():
|
|
text = "2 Jahre Gewährleistung"
|
|
assert guess_warranty_until(text, acquired_on=date(2023, 1, 15)) == date(2025, 1, 15)
|
|
|
|
|
|
def test_zeitraum_plus_rechnungsdatum_aus_dem_beleg():
|
|
text = "Rechnungsdatum: 05.06.2024\nGarantie: 12 Monate"
|
|
assert guess_warranty_until(text) == date(2025, 6, 5)
|
|
|
|
|
|
def test_warranty_keyword_before_period_english():
|
|
text = "Warranty period is 36 months from purchase."
|
|
assert guess_warranty_until(text, acquired_on=date(2022, 2, 1)) == date(2025, 2, 1)
|
|
|
|
|
|
def test_explizites_datum_neben_stichwort():
|
|
text = "Garantie gültig bis 31.12.2027."
|
|
assert guess_warranty_until(text) == date(2027, 12, 31)
|
|
|
|
|
|
def test_ohne_hinweise_kein_vorschlag():
|
|
assert guess_warranty_until("Nur ein Kassenbon ohne alles.") is None
|
|
|
|
|
|
def test_leerer_text_ist_none():
|
|
assert guess_warranty_until("", acquired_on=date(2024, 1, 1)) is None
|
|
|
|
|
|
# ---- Preis ----
|
|
|
|
def test_preis_nahe_total_stichwort():
|
|
text = "Artikel 49.00\nVersand 5.90\nGesamtbetrag: 54.90 CHF"
|
|
assert guess_price_cents(text) == 5490
|
|
|
|
|
|
def test_preis_mit_tausendertrenner_apostroph():
|
|
text = "Total CHF 1'299.00"
|
|
assert guess_price_cents(text) == 129900
|
|
|
|
|
|
def test_preis_deutsches_format():
|
|
text = "Rechnungsbetrag 1.299,00 EUR"
|
|
assert guess_price_cents(text) == 129900
|
|
|
|
|
|
def test_preis_ohne_waehrung_und_ohne_stichwort_ist_none():
|
|
# Reine Zahlen ohne Waehrung/Summen-Stichwort werden NICHT als Preis geraten.
|
|
assert guess_price_cents("Position A 12,90\nPosition B 199,00\nDanke.") is None
|
|
|
|
|
|
def test_preis_mit_waehrung_ohne_stichwort_nimmt_groessten():
|
|
text = "Kabel 12.90 CHF\nGeraet 199.00 CHF"
|
|
assert guess_price_cents(text) == 19900
|
|
|
|
|
|
def test_datum_wird_nicht_als_preis_gelesen():
|
|
assert guess_price_cents("Garantie gültig bis 31.12.2027.") is None
|
|
assert guess_price_candidates("Kaufdatum 05.06.2024, Garantie bis 31.12.2027") == []
|
|
|
|
|
|
def test_preis_ohne_betrag_ist_none():
|
|
assert guess_price_cents("Kein Preis hier.") is None
|
|
assert guess_price_cents("") is None
|
|
|
|
|
|
def test_preis_kandidaten_bester_zuerst_ohne_dubletten():
|
|
text = "Artikel 49.00 CHF\nVersand 5.90 CHF\nGesamtbetrag: 54.90 CHF\nnochmal 49.00 CHF"
|
|
kandidaten = guess_price_candidates(text)
|
|
assert kandidaten[0] == 5490 # bester Tipp (Summe) vorne
|
|
assert set(kandidaten) == {5490, 4900, 590} # ohne Dubletten
|
|
assert guess_price_candidates("") == []
|
|
|
|
|
|
# ---- Kaufdatum & Shop ----
|
|
|
|
def test_kaufdatum_aus_beleg():
|
|
assert guess_acquired_on("Rechnungsdatum: 05.06.2024") == date(2024, 6, 5)
|
|
assert guess_acquired_on("nichts hier") is None
|
|
|
|
|
|
def test_kaufdatum_amazon_monatsname():
|
|
# Amazon nennt das Kaufdatum als "29 April 2026" (Monatsname statt 29.04.).
|
|
text = "Bestelldatum 29 April 2026\nBestellnummer 028-6796274-2062709"
|
|
assert guess_acquired_on(text) == date(2026, 4, 29)
|
|
|
|
|
|
def test_kaufdatum_englischer_monatsname():
|
|
assert guess_acquired_on("Invoice date: April 29, 2026") == date(2026, 4, 29)
|
|
|
|
|
|
def test_shop_erkennt_bekannten_namen():
|
|
shops = [(1, "Digitec"), (2, "Galaxus")]
|
|
assert guess_shop("Rechnung von Digitec AG, Zürich", shops) == (1, "Digitec")
|
|
|
|
|
|
def test_shop_schlaegt_neuen_namen_vor():
|
|
sid, name = guess_shop("ACME Electronics GmbH\nRechnung\nDatum 01.01.2024", [(1, "Galaxus")])
|
|
assert sid is None
|
|
assert name == "ACME Electronics GmbH"
|
|
|
|
|
|
def test_shop_ohne_text_leer():
|
|
assert guess_shop("", [(1, "Digitec")]) == (None, None)
|
|
|
|
|
|
# ---- Realer Galaxus-Beleg (vereinfacht) ----
|
|
|
|
GALAXUS = """Kaufbeleg / Rechnung Auftrag 161498087
|
|
Bestell- / Rechnungsdatum 05.10.2025
|
|
Fälligkeitsdatum 26.10.2025
|
|
Währung CHF
|
|
Delock USB2.0-Ladekabel 10079671 2 8.10% 16.56 17.90 33.12 35.80
|
|
24 Monate Garantie (06.10.2025 - 06.10.2027)
|
|
Logitech G Pro X 13489068 1 8.10% 102.68 111.00 102.68 111.00
|
|
24 Monate Garantie (06.10.2025 - 06.10.2027)
|
|
Gesamtbetrag 135.80 146.80
|
|
Gesamtbetrag enthält folgende Mehrwertsteuer:
|
|
MwSt. 8.10% 11.00
|
|
Total aller Lieferungen und Leistungen CHF 146.80
|
|
03.11.2025 Zahlung geleistet -146.80
|
|
Offener Betrag 0.00
|
|
"""
|
|
|
|
|
|
def test_galaxus_kaufdatum_nicht_faelligkeit():
|
|
assert guess_acquired_on(GALAXUS) == date(2025, 10, 5)
|
|
|
|
|
|
def test_galaxus_preis_ist_gesamt_inkl():
|
|
assert guess_price_cents(GALAXUS) == 14680
|
|
|
|
|
|
def test_galaxus_mwst_satz_ist_kein_preis():
|
|
kandidaten = guess_price_candidates(GALAXUS)
|
|
assert 810 not in kandidaten # 8.10 % ist der Steuersatz, kein Preis
|
|
assert kandidaten[0] == 14680 # bester Tipp: Gesamt inkl.
|
|
|
|
|
|
def test_galaxus_garantieende_aus_zeitraum():
|
|
assert guess_warranty_until(GALAXUS, acquired_on=date(2025, 10, 5)) == date(2027, 10, 6)
|
|
|
|
|
|
def test_galaxus_zeilenpreise_sind_kandidaten():
|
|
# Bei mehreren Artikeln soll man den Zeilenpreis wählen können.
|
|
kandidaten = guess_price_candidates(GALAXUS)
|
|
assert 14680 in kandidaten # Gesamt inkl.
|
|
assert 11100 in kandidaten # Logitech, Betrag/Preis inkl.
|
|
assert 3580 in kandidaten # Kabel, Betrag inkl.
|
|
assert 810 not in kandidaten # MwSt-Satz weiterhin draußen
|