Modernes Design (Standard, Win95 als Legacy), Zeitraum abrechnen, Abrechnung erledigt, Linux-Build

- Design: „modern“ (hell, flach, wie Windows 11, normaler Windows-Rahmen) ist Standard;
  „win95“ bleibt unter Datei > Einstellungen wählbar (Einstellung „design“).
- Export: Zeitraum von–bis (nach Aktivierungsdatum oder Abrechnungszeitraum des Anbieters),
  exportierte Positionen werden als abgerechnet gekennzeichnet (Betrag/Händler eingefroren) und
  per Fingerabdruck auch in späteren Dateien/Abrechnungen nicht noch einmal abgerechnet.
- Datei > Abrechnung erledigt / wieder öffnen: erledigte Abrechnungen sind gegen Änderungen gesperrt.
- DB-Schema v4 (Migration inkl. Fingerabdrücke für alte Positionen).
- Linux-Build (python:3.12-bookworm) als zweiter Pipeline-Job mit Tests und Selbsttest;
  Release-Upload für beide Jobs über build/release_hochladen.sh.
- Behoben: normaler Windows-Rahmen stürzte beim Start ab (Hauptrahmen.nativ fehlte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kerim AtilganandClaude Opus 5.5 committed 2026-11-02 20:47:00 +01:00
1 parent 97c73b82c7
commit 851fd6600a
17 files changed
+829 -80

No files matched your search

+96
View File
@@ -419,3 +419,99 @@ def test_migration_v2_haendler_ohne_airtime(tmp_path):
d.standard_staffel_setzen([(1, 100), (5, None)])
assert d.staffel_fuer(h.id) == [(1, 100), (5, None)]
d.close()
# --------------------------------------------------------------------------- Zeitraum / abgerechnet / erledigt
def test_zeitraum_lesen_und_bezugsdatum():
from datetime import date
from provtool import zeitraum as zr
assert zr.datum_lesen("1.10.2026") == date(2026, 10, 1) and zr.datum_lesen("01.10.26") == date(2026, 10, 1)
assert zr.datum_lesen("") is None
with pytest.raises(ValueError):
zr.datum_lesen("32.13.2026")
assert zr.abrechnungsdatum("2026.10.08-2026.10.15") == date(2026, 10, 8)
assert zr.abrechnungsdatum("VF2609") == date(2026, 9, 1) == zr.abrechnungsdatum("CY202609UM")
assert zr.abrechnungsdatum("VF2609P6 Fil 0") == date(2026, 9, 1)
p = {"zeitraum": "VF2609", "datum": "2026-08-30"}
assert zr.bezugsdatum(p, zr.AKTIVIERUNG) == date(2026, 8, 30)
assert zr.bezugsdatum(p, zr.ABRECHNUNG) == date(2026, 9, 1)
okt = zr.Zeitraum(date(2026, 10, 1), date(2026, 10, 31))
assert okt.enthaelt({"datum": "2026-10-31"}) and not okt.enthaelt({"datum": "2026-11-01"})
assert not okt.enthaelt({"datum": None})
assert zr.vormonat(date(2026, 3, 15)) == (date(2026, 2, 1), date(2026, 2, 28))
assert zr.dieser_monat(date(2026, 12, 5)) == (date(2026, 12, 1), date(2026, 12, 31))
def test_abgerechnet_einfrieren_und_doppelt_erkennen(db, freenet_csv):
from provtool.db import AbrechnungErledigt
hid = db.haendler_speichern(Haendler(0, "A"), [("freenet", "111")])
a1 = db.abrechnung_neu("Okt")
db.importieren(a1, lies_datei(freenet_csv))
pos = [p for p in db.positionen(a1) if p["haendler_id"] == hid]
assert len(pos) == 4 and all(p["fp"] for p in pos)
erste = pos[:2]
assert db.abrechnen([p["id"] for p in erste]) == 2
# abgerechnet: Betrag und Händler eingefroren
h = db.haendler(hid)
h.standard_prozent = 50
db.haendler_speichern(h, []) # Kennung weg, 50 %
db.neu_berechnen(a1)
nach = {p["id"]: p for p in db.positionen(a1)}
for p in erste:
assert nach[p["id"]]["haendler_id"] == hid and nach[p["id"]]["betrag"] == p["betrag"]
with pytest.raises(AbrechnungErledigt):
db.betrag_setzen(erste[0]["id"], 1)
assert db.prozent_anwenden([erste[0]["id"]], 10) == 0
# dieselbe Datei in einer neuen Abrechnung: die beiden abgerechneten Positionen sind „doppelt“
db.haendler_speichern(db.haendler(hid), [("freenet", "111")])
a2 = db.abrechnung_neu("Nov")
db.importieren(a2, lies_datei(freenet_csv))
doppelt = [p for p in db.positionen(a2) if p["doppelt"]]
assert len(doppelt) == 2 and all(p["doppelt"] == "Okt" for p in doppelt)
assert db.abrechnen([p["id"] for p in doppelt]) == 0, "doppelte Positionen dürfen nicht erneut abgerechnet werden"
# Kennzeichnung aufheben -> nicht mehr doppelt
assert db.abrechnung_aufheben([p["id"] for p in erste]) == 2
assert not any(p["doppelt"] for p in db.positionen(a2))
def test_abrechnung_erledigt_sperrt(db, freenet_csv):
from provtool.db import AbrechnungErledigt
hid = db.haendler_speichern(Haendler(0, "A"), [("freenet", "111")])
aid = db.abrechnung_neu("Okt")
db.importieren(aid, lies_datei(freenet_csv))
assert db.erledigt_setzen(aid, True) == 4 # nur die 4 Händler-Positionen, nicht die unzugeordnete
assert db.erledigt(aid)
assert all(p["abgerechnet"] for p in db.positionen(aid) if p["haendler_id"] == hid)
with pytest.raises(AbrechnungErledigt):
db.importieren(aid, lies_datei(freenet_csv))
with pytest.raises(AbrechnungErledigt):
db.datei_entfernen(db.dateien(aid)[0]["id"])
unz = next(p for p in db.positionen(aid) if p["haendler_id"] <= 0)
db.haendler_zuweisen([unz["id"]], hid)
assert next(p for p in db.positionen(aid) if p["id"] == unz["id"])["haendler_id"] <= 0, "erledigt: keine Änderung"
assert db.abrechnungen()[0]["abgerechnet"] == 4
# wieder öffnen, Positionen bleiben abgerechnet bzw. werden auf Wunsch freigegeben
db.erledigt_setzen(aid, False)
assert not db.erledigt(aid) and all(p["abgerechnet"] for p in db.positionen(aid) if p["haendler_id"] == hid)
db.erledigt_setzen(aid, True)
db.erledigt_setzen(aid, False, positionen_aufheben=True)
assert not any(p["abgerechnet"] for p in db.positionen(aid))
def test_migration_v3_fingerabdruck(tmp_path, freenet_csv):
import sqlite3
d = Datenbank(tmp_path / "x.db")
aid = d.abrechnung_neu("Alt")
d.importieren(aid, lies_datei(freenet_csv))
d.close()
con = sqlite3.connect(tmp_path / "x.db") # wie eine v3-Datenbank: ohne Fingerabdrücke
con.execute("UPDATE position SET fp=NULL")
con.commit()
con.close()
d = Datenbank(tmp_path / "x.db")
assert all(p["fp"] for p in d.positionen(aid))
assert len({p["fp"] for p in d.positionen(aid)}) == 5
d.close()