Vodafone: Zuordnung über Kundennamen in GROSSBUCHSTABEN, feste Händler-Zuweisung, nur erste Seite
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
da2e3c9ba1
commit
a837ddc4d4
6 files changed
+235
-27
No files matched your search
+58
-5
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
from provtool.importer import ImportErgebnis
|
||||
from provtool.money import apply_percent
|
||||
|
||||
SCHEMA_VERSION = 1
|
||||
SCHEMA_VERSION = 2
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS meta (schluessel TEXT PRIMARY KEY, wert TEXT);
|
||||
@@ -83,6 +83,16 @@ CREATE INDEX IF NOT EXISTS ix_position_abr ON position(abrechnung_id);
|
||||
|
||||
UNZUGEORDNET = 0 # Pseudo-Händler-ID für Positionen ohne bekannte Kennung
|
||||
|
||||
# Sonder-Kennung: Vodafone-Positionen, deren Kundenname komplett in GROSSBUCHSTABEN steht
|
||||
# (so erfasst ein Unterhändler seine Kunden). Hat Vorrang vor der VO-Nr.
|
||||
CAPS_ANBIETER = "vodafone_caps"
|
||||
CAPS_WERT = "*"
|
||||
|
||||
|
||||
def ist_grossgeschrieben(name: str | None) -> bool:
|
||||
buchstaben = [c for c in (name or "") if c.isalpha() and c != "ß"]
|
||||
return len(buchstaben) >= 2 and all(c.isupper() for c in buchstaben)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Haendler:
|
||||
@@ -107,9 +117,18 @@ class Datenbank:
|
||||
self.con.row_factory = sqlite3.Row
|
||||
self.con.execute("PRAGMA foreign_keys = ON")
|
||||
self.con.executescript(SCHEMA)
|
||||
self._migrieren()
|
||||
self.con.execute("INSERT OR IGNORE INTO meta VALUES ('schema_version', ?)", (str(SCHEMA_VERSION),))
|
||||
self.con.commit()
|
||||
|
||||
def _migrieren(self):
|
||||
spalten = {r[1] for r in self.con.execute("PRAGMA table_info(position)")}
|
||||
if "haendler_fest" not in spalten:
|
||||
# NULL = automatisch über Kennungen, sonst fest zugewiesene Händler-ID (0 = bewusst keiner)
|
||||
self.con.execute("ALTER TABLE position ADD COLUMN haendler_fest INTEGER")
|
||||
self.con.execute("UPDATE meta SET wert=? WHERE schluessel='schema_version'", (str(SCHEMA_VERSION),))
|
||||
self.con.commit()
|
||||
|
||||
def close(self):
|
||||
self.con.close()
|
||||
|
||||
@@ -163,6 +182,9 @@ class Datenbank:
|
||||
besitzer = self.con.execute("SELECT h.name FROM kennung k JOIN haendler h ON h.id=k.haendler_id "
|
||||
"WHERE k.anbieter=? AND k.wert=?", (anbieter, wert)).fetchone()
|
||||
if besitzer:
|
||||
if anbieter == CAPS_ANBIETER:
|
||||
raise ValueError("Die Regel „Vodafone-Kundenname in GROSSBUCHSTABEN“ ist bereits dem "
|
||||
f"Händler „{besitzer[0]}“ zugeordnet – sie kann nur einen Händler haben.")
|
||||
raise ValueError(f"Die Kennung {wert} ({anbieter}) ist bereits dem Händler „{besitzer[0]}“ zugeordnet.")
|
||||
self.con.execute("INSERT INTO kennung (haendler_id, anbieter, wert) VALUES (?,?,?)", (hid, anbieter, wert))
|
||||
return hid
|
||||
@@ -179,6 +201,20 @@ class Datenbank:
|
||||
return {(r[0], r[1]): r[2] for r in self.con.execute(
|
||||
"SELECT k.anbieter, k.wert, k.haendler_id FROM kennung k JOIN haendler h ON h.id=k.haendler_id WHERE h.aktiv=1")}
|
||||
|
||||
def zuordner(self):
|
||||
"""Liefert eine Funktion (anbieter, kennung, name, haendler_fest) -> Händler-ID."""
|
||||
kmap = self.kennung_map()
|
||||
caps = kmap.get((CAPS_ANBIETER, CAPS_WERT))
|
||||
aktive = {h.id for h in self.haendler_liste(nur_aktive=True)}
|
||||
|
||||
def finde(anbieter: str, kennung: str, name: str | None = None, fest: int | None = None) -> int:
|
||||
if fest is not None:
|
||||
return fest if (fest == UNZUGEORDNET or fest in aktive) else UNZUGEORDNET
|
||||
if caps and anbieter == "vodafone" and ist_grossgeschrieben(name):
|
||||
return caps
|
||||
return kmap.get((anbieter, kennung), UNZUGEORDNET)
|
||||
return finde
|
||||
|
||||
# ------------------------------------------------------------------ Regeln
|
||||
def regeln(self, hid: int) -> dict[str, float]:
|
||||
return {r[0]: r[1] for r in self.con.execute(
|
||||
@@ -241,7 +277,7 @@ class Datenbank:
|
||||
self.con.execute("DELETE FROM abrechnung_datei WHERE id=?", (datei_id,))
|
||||
|
||||
def importieren(self, aid: int, erg: ImportErgebnis) -> int:
|
||||
kmap = self.kennung_map()
|
||||
finde = self.zuordner()
|
||||
start = self.con.execute("SELECT coalesce(max(reihenfolge), 0) FROM position WHERE abrechnung_id=?",
|
||||
(aid,)).fetchone()[0]
|
||||
with self.con:
|
||||
@@ -250,7 +286,7 @@ class Datenbank:
|
||||
(aid, erg.datei, erg.sha256, erg.format, _now(), len(erg.positionen)))
|
||||
did = cur.lastrowid
|
||||
for i, p in enumerate(erg.positionen, start=start + 1):
|
||||
hid = kmap.get((p.anbieter, p.kennung), UNZUGEORDNET)
|
||||
hid = finde(p.anbieter, p.kennung, p.name)
|
||||
proz = self.prozent_fuer(hid, p.provisionsart)
|
||||
self.con.execute(
|
||||
"INSERT INTO position (abrechnung_id, datei_id, reihenfolge, anbieter, zeitraum, provisionsart, kennung, "
|
||||
@@ -265,14 +301,31 @@ class Datenbank:
|
||||
|
||||
def positionen(self, aid: int) -> list[dict]:
|
||||
"""Alle Positionen mit aktueller Händler-Zuordnung (über die Kennungen)."""
|
||||
kmap = self.kennung_map()
|
||||
finde = self.zuordner()
|
||||
out = []
|
||||
for r in self.con.execute("SELECT * FROM position WHERE abrechnung_id=? ORDER BY reihenfolge", (aid,)):
|
||||
d = dict(r)
|
||||
d["haendler_id"] = kmap.get((d["anbieter"], d["kennung"]), UNZUGEORDNET)
|
||||
d["haendler_id"] = finde(d["anbieter"], d["kennung"], d["name"], d["haendler_fest"])
|
||||
out.append(d)
|
||||
return out
|
||||
|
||||
def haendler_zuweisen(self, pids: list[int], hid: int | None) -> None:
|
||||
"""Weist Positionen fest einem Händler zu (hid=None: wieder automatisch zuordnen).
|
||||
Nicht manuell geänderte Beträge werden nach den Regeln des neuen Händlers berechnet."""
|
||||
with self.con:
|
||||
for pid in pids:
|
||||
self.con.execute("UPDATE position SET haendler_fest=? WHERE id=?", (hid, pid))
|
||||
finde = self.zuordner()
|
||||
with self.con:
|
||||
for pid in pids:
|
||||
r = self.con.execute("SELECT * FROM position WHERE id=?", (pid,)).fetchone()
|
||||
if r is None or r["manuell"]:
|
||||
continue
|
||||
neu_hid = finde(r["anbieter"], r["kennung"], r["name"], r["haendler_fest"])
|
||||
proz = self.prozent_fuer(neu_hid, r["provisionsart"])
|
||||
self.con.execute("UPDATE position SET betrag=?, prozent=? WHERE id=?",
|
||||
(apply_percent(r["betrag_orig"], proz), proz, pid))
|
||||
|
||||
def betrag_setzen(self, pid: int, cent: int) -> None:
|
||||
with self.con:
|
||||
self.con.execute("UPDATE position SET betrag=?, manuell=1, prozent=NULL WHERE id=?", (cent, pid))
|
||||
|
||||
Reference in new issue
Block a user