|
|
@@ -31,7 +31,7 @@ class DashboardTests(unittest.TestCase):
|
|
|
for name in needed - existing:
|
|
|
kind = (
|
|
|
"INTEGER"
|
|
|
- if name == "Beleg_Tage_offen"
|
|
|
+ if name in {"Beleg_Tage_offen", "Wiedervorlage_Staffel_Nr"}
|
|
|
else "REAL" if name in {"Beleg_Betrag", "Kunde_offen_gesamt"} else "TEXT"
|
|
|
)
|
|
|
self.db.connection.execute(f"ALTER TABLE dbo.Forderungen_Kopf ADD [{name}] {kind}")
|
|
|
@@ -66,6 +66,13 @@ class DashboardTests(unittest.TestCase):
|
|
|
self.db.connection.execute(
|
|
|
"UPDATE dbo.Forderungen_Kopf SET Rechnung_Gutschrift='Gutschrift', Abteilung='', Staffel_Text=NULL, Verursacher_Benutzer_ID=NULL, Verursacher_Text=NULL, Beleg_Faellig_Datum=NULL WHERE Beleg_Nr='CR'"
|
|
|
)
|
|
|
+ # Model source-owned classifications independently of the dashboard clock.
|
|
|
+ self.db.connection.executescript("""
|
|
|
+ UPDATE dbo.Forderungen_Kopf SET Wiedervorlage_Staffel_Nr=2, Wiedervorlage_Staffel_Text='In 3 Tagen fällig';
|
|
|
+ UPDATE dbo.Forderungen_Kopf SET Wiedervorlage_Staffel_Nr=1, Wiedervorlage_Staffel_Text='Überfällig' WHERE Beleg_Nr='R1';
|
|
|
+ UPDATE dbo.Forderungen_Kopf SET Wiedervorlage_Staffel_Nr=3, Wiedervorlage_Staffel_Text='Noch nicht fällig' WHERE Beleg_Nr IN ('X1','NV');
|
|
|
+ UPDATE dbo.Forderungen_Kopf SET Wiedervorlage_Staffel_Nr=NULL, Wiedervorlage_Staffel_Text=NULL WHERE Beleg_Nr='CR';
|
|
|
+ """)
|
|
|
# The production query now reads a DWH view. Model that view in SQLite;
|
|
|
# keep the detail overlays live so update/authorization tests still run real SQL.
|
|
|
overlays = {
|
|
|
@@ -201,50 +208,64 @@ class DashboardTests(unittest.TestCase):
|
|
|
|
|
|
self.assertIn(number_format(Decimal(segment["amount"])) + " €", response.text)
|
|
|
|
|
|
- def test_follow_up_boundaries_and_null_group(self):
|
|
|
+ def test_follow_up_source_staffels_and_21_day_boundary(self):
|
|
|
cases = [
|
|
|
- ("2026-11-01", "overdue"),
|
|
|
- ("2026-11-02", "soon"),
|
|
|
- ("2026-11-05 23:30:00", "soon"),
|
|
|
- ("2026-11-06", "not_due"),
|
|
|
- ("2026-11-16 23:30:00", "not_due"),
|
|
|
- ("2026-11-17", "deferred"),
|
|
|
+ ("2026-11-01", "1", "Überfällig"),
|
|
|
+ ("2026-11-02", "2", "In 3 Tagen fällig"),
|
|
|
+ ("2026-11-05 23:30:00", "2", "In 3 Tagen fällig"),
|
|
|
+ ("2026-11-06", "3", "Noch nicht fällig"),
|
|
|
+ ("2026-11-17", "3", "Noch nicht fällig"),
|
|
|
+ ("2026-11-22 23:30:00", "3", "Noch nicht fällig"),
|
|
|
+ ("2026-11-23", "4", "zurückgestellt"),
|
|
|
]
|
|
|
- for value, expected in cases:
|
|
|
+ for value, expected, label in cases:
|
|
|
with self.subTest(date=value):
|
|
|
self.db.connection.execute(
|
|
|
"UPDATE dbo.Forderungen_Details SET Wiedervorlage=? WHERE Beleg_Nr='R1'", (value,)
|
|
|
)
|
|
|
+ self.db.connection.execute(
|
|
|
+ "UPDATE dbo.Forderungen_Kopf SET Wiedervorlage_Staffel_Nr=?, Wiedervorlage_Staffel_Text=? WHERE Beleg_Nr='R1'",
|
|
|
+ (expected, label),
|
|
|
+ )
|
|
|
data = build_dashboard(self.db, "tester", self.now, stack="wiedervorlage")
|
|
|
group = next(group for group in data["views"]["Altersstaffel"] if group["label"] == "< 4 Wochen")
|
|
|
- self.assertEqual(group["segments"][0]["id"], expected)
|
|
|
- response = self.client.get(group["segments"][0]["url"])
|
|
|
+ segment = group['segments'][0]
|
|
|
+ self.assertEqual(segment["id"], expected)
|
|
|
+ self.assertEqual(segment["label"], label)
|
|
|
+ self.assertEqual(parse_qs(urlsplit(segment['url']).query)['WVStaffel'], [expected])
|
|
|
+ response = self.client.get(segment["url"])
|
|
|
self.assertIn('id="list-count">1', response.text)
|
|
|
+ self.assertIn(f'aria-label="{label} entfernen"', response.text)
|
|
|
ids = [item["id"] for item in data["stack_items"]]
|
|
|
- self.assertEqual(ids, ["overdue", "soon", "not_due", "deferred", "undated"])
|
|
|
+ self.assertEqual(ids, ["2", "3", "4", None])
|
|
|
self.assertNotIn("Wiedervorlage", data["views"])
|
|
|
|
|
|
- def test_follow_up_falls_back_to_document_due_date(self):
|
|
|
+ def test_follow_up_uses_source_staffel_even_when_dates_disagree(self):
|
|
|
self.db.connection.execute("UPDATE dbo.Forderungen_Details SET Wiedervorlage=NULL WHERE Beleg_Nr='R1'")
|
|
|
self.db.connection.execute(
|
|
|
- "UPDATE dbo.Forderungen_Kopf SET Beleg_Faellig_Datum='2026-11-17' WHERE Client_DB='1' AND Beleg_Nr='R1'"
|
|
|
+ "UPDATE dbo.Forderungen_Kopf SET Beleg_Faellig_Datum='2099-01-01', "
|
|
|
+ "Wiedervorlage_Staffel_Nr=10, Wiedervorlage_Staffel_Text='Bezeichnung aus Datenquelle' "
|
|
|
+ "WHERE Client_DB='1' AND Beleg_Nr='R1'"
|
|
|
)
|
|
|
data = build_dashboard(self.db, "tester", self.now, stack="wiedervorlage")
|
|
|
group = next(group for group in data["views"]["Altersstaffel"] if group["label"] == "< 4 Wochen")
|
|
|
- self.assertEqual(group["segments"][0]["id"], "deferred")
|
|
|
+ self.assertEqual(group["segments"][0]["id"], "10")
|
|
|
+ self.assertEqual(group["segments"][0]["label"], "Bezeichnung aus Datenquelle")
|
|
|
+ self.assertEqual([item['id'] for item in data['stack_items']], ['2', '3', '10', None])
|
|
|
self.assertIn('id="list-count">1', self.client.get(group["segments"][0]["url"]).text)
|
|
|
|
|
|
- def test_follow_up_year_boundary_and_berlin_calendar_day(self):
|
|
|
+ def test_follow_up_source_staffels_are_independent_of_dashboard_clock(self):
|
|
|
self.db.connection.execute("UPDATE dbo.Forderungen_Details SET Wiedervorlage='2027-01-01' WHERE Beleg_Nr='R1'")
|
|
|
now = datetime.fromisoformat("2026-12-28T23:30:00+00:00")
|
|
|
groups = build_dashboard(self.db, "tester", now, stack="wiedervorlage")["views"]["Altersstaffel"]
|
|
|
group = next(group for group in groups if group["label"] == "< 4 Wochen")
|
|
|
- self.assertEqual(group['segments'][0]['url'], group['url'])
|
|
|
- params = parse_qs(urlsplit(group['url']).query)
|
|
|
+ params = parse_qs(urlsplit(group['segments'][0]['url']).query)
|
|
|
self.assertEqual(decode_filter_values(params['Staffel'][0]), ['< 4 Wochen'])
|
|
|
+ self.assertEqual(params['WVStaffel'], ['1'])
|
|
|
+ self.assertNotIn('WVStaffel', parse_qs(urlsplit(group['url']).query))
|
|
|
self.assertNotIn('WiedervorlageVon', params)
|
|
|
self.assertNotIn('WiedervorlageBis', params)
|
|
|
- self.assertEqual(group["segments"][0]["id"], "soon")
|
|
|
+ self.assertEqual(group["segments"][0]["id"], "1")
|
|
|
|
|
|
def test_follow_up_stack_roundtrips_for_all_rubrics_and_invoice_scope(self):
|
|
|
for only in [False, True]:
|
|
|
@@ -259,10 +280,13 @@ class DashboardTests(unittest.TestCase):
|
|
|
self.assertNotIn('WiedervorlageVon', params)
|
|
|
self.assertNotIn('WiedervorlageBis', params)
|
|
|
self.assertNotIn('Abteilung', params)
|
|
|
- self.assertEqual(segment['url'], group['url'])
|
|
|
+ self.assertEqual(params['WVStaffel'], [segment['id'] or '__empty__'])
|
|
|
+ self.assertNotIn('WVStaffel', parse_qs(urlsplit(group['url']).query))
|
|
|
response = self.client.get(segment["url"])
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
- self.assertIn('id="list-count">' + str(group["count"]), response.text)
|
|
|
+ self.assertIn('id="list-count">' + str(segment["count"]), response.text)
|
|
|
+ from app.jinja_templates import number_format
|
|
|
+ self.assertIn(number_format(Decimal(segment['amount'])) + ' €', response.text)
|
|
|
|
|
|
def test_stack_selection_and_legacy_follow_up_links(self):
|
|
|
response = self.client.get("/app/forderungen/dashboard?stack=wiedervorlage&metric=anzahl&only_invoices=true")
|