Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions PROJECTS.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# 📋 Projekte - Payments System

**Status**: Production-ready
**Last Updated**: 2026-07-24
**Last Updated**: 2026-07-30

## 🔍 Recent Activity

- **2026-07-30 — Focus Queue: real undo, "due today," and `is_focus` retirement (issue #281)**: three related fixes to `/focus/`. (1) The complete checkbox only ever called `mark_completed()` — an accidental click had no way back; it now toggles complete/incomplete and swaps just that row so a just-completed task stays visible (struck through, still checked) as an immediate undo. (2) `due_date` used to only decorate a row; a new "📅 Today" button sets it, and due-today-or-overdue tasks now always sort ahead of the rest of the queue regardless of priority. (3) `is_focus` (P-028, week-scoped, dashboard-only) is retired — its only "add to focus" UI path was lost back in P-050 phase 4, leaving no way to set it at all. Rather than restoring a second star toggle, the dashboard's weekly widget now lists `due_date <= today` tasks, sharing the Focus Queue's own signal (migration `0018_remove_practicetodo_is_focus`); resolves the overlap issue #281 flagged.
- **2026-07-24 — Configurable overdue-invoice threshold (issue #195)**: the "sent invoice counts as overdue" cutoff was hardcoded to 30 days in three separate places (`InvoiceActionsWidgetBuilder.get_overdue_invoices()`, feeding both the dashboard and the Focus Queue's `invoice_unpaid` task; `ClientDetailContextBuilder`'s payment-reminder urgency, which also had a hardcoded 14-day "medium" tier). Added `Practice.overdue_after_days` (default 30, admin-editable in the existing "Payment Terms" fieldset) and wired all three call sites to it; the medium tier now reuses the existing `payment_terms_days` field instead of a second hardcoded 14. The `bank_import_reminder_days` half of #195 turned out to be moot — that widget (`BankImportReminderWidgetBuilder`) was already deleted as dead code during P-050 phase 4. Also fixed a migration-graph conflict (duplicate `0015` leaves) left behind when PRs #270 and #271 merged independently.
- **2026-07-23 — Configurable bank-import CSV format (issue #11)**: `utils/bank_import.py`'s CSV parser was hardcoded to GLS Bank's column names and semicolon delimiter. Added `csv_delimiter`/`csv_column_*` fields to `Practice` (admin-editable, GLS defaults preserved), so self-hosters on other banks can point the parser at their own export's delimiter and column headers without touching code. UI strings no longer imply GLS-only support.

> Ältere Einträge: [docs/CHANGELOG.md](docs/CHANGELOG.md)

Expand Down
71 changes: 34 additions & 37 deletions app/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: my-practice\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-29 22:30+0200\n"
"POT-Creation-Date: 2026-07-29 23:11+0200\n"
"PO-Revision-Date: 2026-06-22 23:01+0200\n"
"Language-Team: German\n"
"Language: de\n"
Expand Down Expand Up @@ -525,7 +525,7 @@ msgstr "Fokus-Warteschlange (P-050)"
msgid "Set automatically for materialized tasks — not manually editable."
msgstr "Wird für materialisierte Aufgaben automatisch gesetzt — nicht manuell editierbar."

#: my_practice/admin.py:1038 my_practice/models/todo.py:155
#: my_practice/admin.py:1038 my_practice/models/todo.py:149
msgid "Task"
msgstr "Aufgabe"

Expand Down Expand Up @@ -2876,35 +2876,27 @@ msgstr "Aufgabenkategorie zur Organisation"
msgid "Task priority level"
msgstr "Aufgabenpriorität"

#: my_practice/models/todo.py:115
msgid "Mark as a focus task for the current week"
msgstr "Als Fokus-Aufgabe für die aktuelle Woche markieren"

#: my_practice/models/todo.py:116
msgid "Focus task"
msgstr "Fokus-Aufgabe"

#: my_practice/models/todo.py:120
#: my_practice/models/todo.py:114
msgid "Optional due date"
msgstr "Optionales Fälligkeitsdatum"

#: my_practice/models/todo.py:122
#: my_practice/models/todo.py:116
msgid "When the task was completed"
msgstr "Wann die Aufgabe abgeschlossen wurde"

#: my_practice/models/todo.py:127
#: my_practice/models/todo.py:121
msgid "Hide from the focus queue until this date"
msgstr "Bis zu diesem Datum aus der Fokus-Warteschlange ausblenden"

#: my_practice/models/todo.py:137
#: my_practice/models/todo.py:131
msgid "Manual entry, or a materialized system signal (unpaid invoice, missing session log, etc.)"
msgstr "Manueller Eintrag oder ein materialisiertes Systemsignal (unbezahlte Rechnung, fehlendes Sitzungsprotokoll usw.)"

#: my_practice/models/todo.py:146
#: my_practice/models/todo.py:140
msgid "Related object type, for materialized tasks (e.g. Client, Invoice)"
msgstr "Typ des verknüpften Objekts, für materialisierte Aufgaben (z. B. Klient, Rechnung)"

#: my_practice/models/todo.py:156
#: my_practice/models/todo.py:150
msgid "Tasks"
msgstr "Aufgaben"

Expand Down Expand Up @@ -3084,11 +3076,12 @@ msgid_plural "%(count)s sessions"
msgstr[0] "%(count)s Sitzung"
msgstr[1] "%(count)s Sitzungen"

#: my_practice/utils/dashboard_context_builder.py:130
msgid "Focus"
msgstr "Fokus"
#: my_practice/utils/dashboard_context_builder.py:131
#: templates/includes/weekly_focus_widget_content.html:33
msgid "Due today"
msgstr "Heute fällig"

#: my_practice/utils/dashboard_context_builder.py:140
#: my_practice/utils/dashboard_context_builder.py:142
#: templates/my_practice/analytics.html:107
#: templates/my_practice/analytics.html:731
msgid "Capacity"
Expand Down Expand Up @@ -4621,31 +4614,23 @@ msgstr "Quartalsübersicht anzeigen"
msgid "Tax prepayment recorded for Q%(quarter)s"
msgstr "Steuervorauszahlung für Q%(quarter)s erfasst"

#: templates/includes/weekly_focus_widget_content.html:11
#: templates/includes/weekly_focus_widget_content.html:13
msgid "Sessions this week"
msgstr "Sitzungen diese Woche"

#: templates/includes/weekly_focus_widget_content.html:25
#: templates/includes/weekly_focus_widget_content.html:27
msgid "No sessions this week."
msgstr "Keine Sitzungen diese Woche."

#: templates/includes/weekly_focus_widget_content.html:31
msgid "Focus tasks"
msgstr "Fokus-Aufgaben"

#: templates/includes/weekly_focus_widget_content.html:41
#: templates/includes/weekly_focus_widget_content.html:43
#: templates/my_practice/calendar_approval_queue.html:38
msgid "Mark as done"
msgstr "Als erledigt markieren"

#: templates/includes/weekly_focus_widget_content.html:46
msgid "Remove focus"
msgstr "Fokus entfernen"

#: templates/includes/weekly_focus_widget_content.html:60
#: templates/includes/weekly_focus_widget_content.html:57
#, python-format
msgid "No focus tasks. Click ⭐ on a task in the <a href=\"%(focus_queue_url)s\">focus queue</a>."
msgstr "Keine Fokus-Aufgaben. Klicke ⭐ auf einer Aufgabe im <a href=\"%(focus_queue_url)s\">Fokus</a>."
msgid "Nothing due today. Set a due date from the <a href=\"%(focus_queue_url)s\">focus queue</a>."
msgstr "Nichts ist heute fällig. Setze ein Fälligkeitsdatum im <a href=\"%(focus_queue_url)s\">Fokus</a>."

#: templates/includes/widget_base.html:39
msgid "Toggle widget"
Expand Down Expand Up @@ -8810,6 +8795,21 @@ msgstr "Benutzername"
msgid "Password"
msgstr "Passwort"

#~ msgid "Mark as a focus task for the current week"
#~ msgstr "Als Fokus-Aufgabe für die aktuelle Woche markieren"

#~ msgid "Focus task"
#~ msgstr "Fokus-Aufgabe"

#~ msgid "Focus"
#~ msgstr "Fokus"

#~ msgid "Focus tasks"
#~ msgstr "Fokus-Aufgaben"

#~ msgid "Remove focus"
#~ msgstr "Fokus entfernen"

#~ msgid "No sessions for 60+ days"
#~ msgstr "Keine Sitzungen seit 60+ Tagen"

Expand All @@ -8833,9 +8833,6 @@ msgstr "Passwort"
#~ msgid "⚠️ Overdue since %(date)s"
#~ msgstr "⚠️ Überfällig seit %(date)s"

#~ msgid "📅 Due today"
#~ msgstr "📅 Heute fällig"

#, python-format
#~ msgid "📅 Due in %(days)s day"
#~ msgid_plural "📅 Due in %(days)s days"
Expand Down
49 changes: 17 additions & 32 deletions app/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: my-practice\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-29 22:30+0200\n"
"POT-Creation-Date: 2026-07-29 23:11+0200\n"
"PO-Revision-Date: 2026-06-22 23:01+0200\n"
"Language-Team: English\n"
"Language: en\n"
Expand Down Expand Up @@ -525,7 +525,7 @@ msgstr ""
msgid "Set automatically for materialized tasks — not manually editable."
msgstr ""

#: my_practice/admin.py:1038 my_practice/models/todo.py:155
#: my_practice/admin.py:1038 my_practice/models/todo.py:149
msgid "Task"
msgstr ""

Expand Down Expand Up @@ -2876,35 +2876,27 @@ msgstr ""
msgid "Task priority level"
msgstr ""

#: my_practice/models/todo.py:115
msgid "Mark as a focus task for the current week"
msgstr ""

#: my_practice/models/todo.py:116
msgid "Focus task"
msgstr ""

#: my_practice/models/todo.py:120
#: my_practice/models/todo.py:114
msgid "Optional due date"
msgstr ""

#: my_practice/models/todo.py:122
#: my_practice/models/todo.py:116
msgid "When the task was completed"
msgstr ""

#: my_practice/models/todo.py:127
#: my_practice/models/todo.py:121
msgid "Hide from the focus queue until this date"
msgstr ""

#: my_practice/models/todo.py:137
#: my_practice/models/todo.py:131
msgid "Manual entry, or a materialized system signal (unpaid invoice, missing session log, etc.)"
msgstr ""

#: my_practice/models/todo.py:146
#: my_practice/models/todo.py:140
msgid "Related object type, for materialized tasks (e.g. Client, Invoice)"
msgstr ""

#: my_practice/models/todo.py:156
#: my_practice/models/todo.py:150
msgid "Tasks"
msgstr ""

Expand Down Expand Up @@ -3084,11 +3076,12 @@ msgid_plural "%(count)s sessions"
msgstr[0] ""
msgstr[1] ""

#: my_practice/utils/dashboard_context_builder.py:130
msgid "Focus"
#: my_practice/utils/dashboard_context_builder.py:131
#: templates/includes/weekly_focus_widget_content.html:33
msgid "Due today"
msgstr ""

#: my_practice/utils/dashboard_context_builder.py:140
#: my_practice/utils/dashboard_context_builder.py:142
#: templates/my_practice/analytics.html:107
#: templates/my_practice/analytics.html:731
msgid "Capacity"
Expand Down Expand Up @@ -4621,30 +4614,22 @@ msgstr ""
msgid "Tax prepayment recorded for Q%(quarter)s"
msgstr ""

#: templates/includes/weekly_focus_widget_content.html:11
#: templates/includes/weekly_focus_widget_content.html:13
msgid "Sessions this week"
msgstr ""

#: templates/includes/weekly_focus_widget_content.html:25
#: templates/includes/weekly_focus_widget_content.html:27
msgid "No sessions this week."
msgstr "No sessions this week."

#: templates/includes/weekly_focus_widget_content.html:31
msgid "Focus tasks"
msgstr ""

#: templates/includes/weekly_focus_widget_content.html:41
#: templates/includes/weekly_focus_widget_content.html:43
#: templates/my_practice/calendar_approval_queue.html:38
msgid "Mark as done"
msgstr ""

#: templates/includes/weekly_focus_widget_content.html:46
msgid "Remove focus"
msgstr ""

#: templates/includes/weekly_focus_widget_content.html:60
#: templates/includes/weekly_focus_widget_content.html:57
#, python-format
msgid "No focus tasks. Click ⭐ on a task in the <a href=\"%(focus_queue_url)s\">focus queue</a>."
msgid "Nothing due today. Set a due date from the <a href=\"%(focus_queue_url)s\">focus queue</a>."
msgstr ""

#: templates/includes/widget_base.html:39
Expand Down
16 changes: 16 additions & 0 deletions app/my_practice/migrations/0018_remove_practicetodo_is_focus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 6.0.7 on 2026-07-29 20:55

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("my_practice", "0017_practice_overdue_after_days"),
]

operations = [
migrations.RemoveField(
model_name="practicetodo",
name="is_focus",
),
]
6 changes: 0 additions & 6 deletions app/my_practice/models/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,6 @@ class TaskType(StrEnum):
default="medium",
help_text=_("Task priority level"),
)
is_focus = models.BooleanField(
default=False,
help_text=_("Mark as a focus task for the current week"),
verbose_name=_("Focus task"),
)

# Dates
due_date = models.DateField(null=True, blank=True, help_text=_("Optional due date"))
completed_at = models.DateTimeField(
Expand Down
30 changes: 0 additions & 30 deletions app/my_practice/tests/test_views_todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,33 +184,3 @@ def test_htmx_post_returns_content(self):
)
# HTMX response: either partial HTML or redirect
self.assertIn(response.status_code, [200, 302])


class TodoToggleFocusTest(TestCase):
"""Tests for todo_toggle_focus view."""

def setUp(self):
self.practice = _make_practice("todo-focus-1")
self.user = User.objects.create_user(username="todouser6", password="testpass123")
link_user_to_practice(self.user, self.practice)
self.tc = _setup_client(self.user, self.practice)
self.todo = PracticeTodo.objects.create(
practice=self.practice, title="Focus Me", priority="medium", is_focus=False
)

def test_non_htmx_redirects(self):
response = self.tc.post(reverse("todo_toggle_focus", args=[self.todo.pk]))
self.assertEqual(response.status_code, 302)
self.todo.refresh_from_db()
self.assertTrue(self.todo.is_focus)

def test_htmx_post_returns_200(self):
response = self.tc.post(
reverse("todo_toggle_focus", args=[self.todo.pk]),
HTTP_HX_REQUEST="true",
)
self.assertEqual(response.status_code, 200)

def test_nonexistent_returns_404(self):
response = self.tc.post(reverse("todo_toggle_focus", args=[99999]))
self.assertEqual(response.status_code, 404)
Loading