Fix ruff lint violations

This commit is contained in:
Marcel Peterkau
2026-06-27 10:46:54 +02:00
parent 9944652dfb
commit 3876f8c5ab
12 changed files with 217 additions and 49 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
from ccma.domain.models import HOUSEKEEPER_MEMBER_FIELD_LABELS
from ccma.domain.dates import DateValidationError, validate_member_dates
from ccma.domain.models import HOUSEKEEPER_MEMBER_FIELD_LABELS
from ccma.rules.api import RuleContext, task
RULE_ID = "birthdate-check"
@@ -44,7 +44,11 @@ def evaluate(context: RuleContext):
)
)
year_from = started_at.year if getattr(context.settings, "retroactive_claims", False) else context.today.year
year_from = (
started_at.year
if getattr(context.settings, "retroactive_claims", False)
else context.today.year
)
for year in range(year_from, context.today.year + 2):
actions.extend(_membership_claims(context, started_at, accepted_at, year))
return actions
+5 -1
View File
@@ -11,7 +11,11 @@ CONTENT_HASH_FIELD = "content_hash"
def _hashable_copy(data: Any, *, hash_field: str = CONTENT_HASH_FIELD) -> Any:
if isinstance(data, dict):
return {key: _hashable_copy(value, hash_field=hash_field) for key, value in data.items() if key != hash_field}
return {
key: _hashable_copy(value, hash_field=hash_field)
for key, value in data.items()
if key != hash_field
}
if isinstance(data, list):
return [_hashable_copy(item, hash_field=hash_field) for item in data]
return data
+32 -9
View File
@@ -20,7 +20,14 @@ from ccma.domain.contributions import (
payment_allocated_total,
)
from ccma.domain.dates import DateValidationError, normalize_date_input, validate_member_dates
from ccma.domain.models import ASSET_STATUS_LABELS, MEMBERSHIP_STATUS_LABELS, Asset, ContributionData, Event, Member
from ccma.domain.models import (
ASSET_STATUS_LABELS,
MEMBERSHIP_STATUS_LABELS,
Asset,
ContributionData,
Event,
Member,
)
from ccma.storage.atomic import json_content_hash_matches, read_json, write_json_atomic
@@ -143,7 +150,9 @@ class MemberRepository:
try:
config = read_json(self.root / "repository.json")
if not json_content_hash_matches(config):
errors.append("repository.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert.")
errors.append(
"repository.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert."
)
if int(config.get("schema_version", 0)) != 1:
errors.append("repository.json: nicht unterstützte schema_version")
policy = config.get("member_number_policy") or {}
@@ -157,7 +166,10 @@ class MemberRepository:
for member_dir in self._member_directories():
try:
member, _contributions = self.preflight_member_record(member_dir.name)
errors.extend(f"{member_dir.name}/{warning}" for warning in self.member_hash_warnings(member_dir.name))
errors.extend(
f"{member_dir.name}/{warning}"
for warning in self.member_hash_warnings(member_dir.name)
)
validate_member_dates(
birth_date=member.birth_date,
accepted_at=member.accepted_at,
@@ -186,12 +198,16 @@ class MemberRepository:
for asset_dir in self._asset_directories():
try:
asset = self.get_asset(asset_dir.name)
errors.extend(f"{asset_dir.name}/{warning}" for warning in self.asset_hash_warnings(asset_dir.name))
errors.extend(
f"{asset_dir.name}/{warning}"
for warning in self.asset_hash_warnings(asset_dir.name)
)
if asset.asset_id != asset_dir.name:
errors.append(f"{asset_dir.name}/asset.json: asset_id stimmt nicht mit Ordner überein")
if asset.schema_version != 1:
errors.append(
f"{asset_dir.name}/asset.json: nicht unterstützte schema_version {asset.schema_version}"
f"{asset_dir.name}/asset.json: "
f"nicht unterstützte schema_version {asset.schema_version}"
)
if asset.status not in ASSET_STATUS_LABELS:
errors.append(f"{asset_dir.name}/asset.json: ungültiger Asset-Status")
@@ -425,7 +441,9 @@ class MemberRepository:
existing.current_holder_member_id
and money_text(deposit_amount) != str(existing.deposit_amount_default)
):
raise RepositoryError("Die Kaution kann nur geändert werden, wenn das Asset nicht ausgegeben ist.")
raise RepositoryError(
"Die Kaution kann nur geändert werden, wenn das Asset nicht ausgegeben ist."
)
asset.label = asset.label.strip()
asset.category = asset.category.strip()
asset.inventory_number = asset.inventory_number.strip()
@@ -1220,14 +1238,17 @@ class MemberRepository:
try:
member_raw = read_json(self._member_path(member_id) / "member.json")
if not json_content_hash_matches(member_raw):
warnings.append("member.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert.")
warnings.append(
"member.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert."
)
except (OSError, ValueError, TypeError, json.JSONDecodeError):
pass
try:
contributions_raw = read_json(self._member_path(member_id) / "contributions.json")
if not json_content_hash_matches(contributions_raw):
warnings.append(
"contributions.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert."
"contributions.json: Hash fehlt oder stimmt nicht; "
"Datei wurde vermutlich extern geändert."
)
except (OSError, ValueError, TypeError, json.JSONDecodeError):
pass
@@ -1238,7 +1259,9 @@ class MemberRepository:
try:
asset_raw = read_json(self._asset_path(asset_id) / "asset.json")
if not json_content_hash_matches(asset_raw):
warnings.append("asset.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert.")
warnings.append(
"asset.json: Hash fehlt oder stimmt nicht; Datei wurde vermutlich extern geändert."
)
except (OSError, ValueError, TypeError, json.JSONDecodeError):
pass
return warnings
+19 -4
View File
@@ -5,7 +5,7 @@ from collections.abc import Callable
from datetime import datetime
from tkinter import messagebox, ttk
from ccma.domain.models import ASSET_STATUS_LABELS, Asset, Event
from ccma.domain.models import ASSET_STATUS_LABELS, Event
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.dialogs import AssetClaimDialog, IntegrityWarningDialog
from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage
@@ -196,8 +196,16 @@ class AssetTab(ttk.Frame):
ttk.Button(finance_actions, text="Verlustforderung", command=self._create_loss_claim).pack(
side="left", padx=(0, 8)
)
ttk.Button(finance_actions, text="Reparaturforderung", command=self._create_repair_claim).pack(side="left")
self.asset_claims = ttk.Treeview(finance_tab, columns=("title", "due", "amount", "member"), show="headings")
ttk.Button(
finance_actions,
text="Reparaturforderung",
command=self._create_repair_claim,
).pack(side="left")
self.asset_claims = ttk.Treeview(
finance_tab,
columns=("title", "due", "amount", "member"),
show="headings",
)
for key, title, width in (
("title", "Forderung", 240),
("due", "Fällig", 110),
@@ -429,7 +437,14 @@ class AssetTab(ttk.Frame):
claim_type="asset_repair",
)
def _open_claim_dialog(self, *, preset_title: str, preset_amount: str, preset_description: str, claim_type: str) -> None:
def _open_claim_dialog(
self,
*,
preset_title: str,
preset_amount: str,
preset_description: str,
claim_type: str,
) -> None:
member_id = self.asset.current_holder_member_id
if not member_id:
messagebox.showerror(
+15 -1
View File
@@ -261,12 +261,26 @@ class ClaimTab(ttk.Frame):
for allocation in self.data.allocations
if str(allocation.get("claim_id", "")) == self.claim_id and str(allocation.get("credit_id", ""))
]
allocated_credit_total = money_text(
sum(
(decimal_value(item.get("amount", "0")) for item in credit_allocations),
Decimal("0"),
)
)
credit_group = self.ledger.insert(
"",
"end",
iid="group:credits",
text=f"Gutschriften ({len(credit_allocations)})",
values=("", "", "", "", f"{money_text(sum((decimal_value(item.get('amount', '0')) for item in credit_allocations), Decimal('0')))} EUR", "", ""),
values=(
"",
"",
"",
"",
f"{allocated_credit_total} EUR",
"",
"",
),
tags=("credit-group",),
open=True,
)
+79 -14
View File
@@ -1,8 +1,7 @@
import tkinter as tk
from collections.abc import Callable
from tkinter import messagebox, ttk
from datetime import date
from tkinter import messagebox, ttk
from ccma.domain.dates import age_label, date_input_hint
from ccma.domain.models import Asset, Member
@@ -35,7 +34,15 @@ class NewMemberDialog(tk.Toplevel):
self.number_policy = repository.get_member_number_policy()
self.variables = {
name: tk.StringVar()
for name in ("first_name", "last_name", "nickname", "email", "phone", "birth_date", "member_number")
for name in (
"first_name",
"last_name",
"nickname",
"email",
"phone",
"birth_date",
"member_number",
)
}
self._build_ui()
self.bind("<Escape>", lambda _event: self.destroy())
@@ -145,13 +152,24 @@ class NewAssetDialog(tk.Toplevel):
entry = ttk.Entry(frame, textvariable=self.variables[key], width=38)
entry.grid(row=row, column=1, sticky="ew", pady=5)
self.entries[key] = entry
ttk.Label(frame, text="Interne Notiz").grid(row=len(fields), column=0, sticky="nw", pady=5, padx=(0, 12))
ttk.Label(frame, text="Interne Notiz").grid(
row=len(fields),
column=0,
sticky="nw",
pady=5,
padx=(0, 12),
)
self.notes_text = tk.Text(frame, width=38, height=5, wrap="word")
self.notes_text.grid(row=len(fields), column=1, sticky="ew", pady=5)
buttons = ttk.Frame(frame)
buttons.grid(row=len(fields) + 1, column=0, columnspan=2, sticky="e", pady=(16, 0))
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
ttk.Button(buttons, text="Asset anlegen", style="Accent.TButton", command=self._create).pack(side="left")
ttk.Button(
buttons,
text="Asset anlegen",
style="Accent.TButton",
command=self._create,
).pack(side="left")
self.after_idle(lambda: self.entries["label"].focus_set())
def _activate_modal(self) -> None:
@@ -171,7 +189,13 @@ class NewAssetDialog(tk.Toplevel):
class EditAssetDialog(tk.Toplevel):
def __init__(self, master: tk.Misc, repository: MemberRepository, asset_id: str, on_saved: Callable[[Asset], None]):
def __init__(
self,
master: tk.Misc,
repository: MemberRepository,
asset_id: str,
on_saved: Callable[[Asset], None],
):
super().__init__(master)
self.repository = repository
self.asset_id = asset_id
@@ -310,7 +334,12 @@ class IssueAssetDialog(tk.Toplevel):
frame.rowconfigure(3, weight=1)
asset = self.repository.get_asset(self.asset_id)
ttk.Label(frame, text="Asset").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(row=0, column=1, sticky="w", pady=5)
ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(
row=0,
column=1,
sticky="w",
pady=5,
)
ttk.Label(frame, text="Suche").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
self.search_entry = ttk.Entry(frame, textvariable=self.search_var, width=42)
self.search_entry.grid(row=1, column=1, sticky="ew", pady=5)
@@ -378,7 +407,11 @@ class IssueAssetDialog(tk.Toplevel):
)
selected_id = self.preselected_member_id if self.preselected_member_id else ""
if filtered:
target_id = selected_id if selected_id and any(member.member_id == selected_id for member in filtered) else filtered[0].member_id
target_id = (
selected_id
if selected_id and any(member.member_id == selected_id for member in filtered)
else filtered[0].member_id
)
self.member_tree.selection_set(target_id)
self.member_tree.focus(target_id)
self.member_tree.see(target_id)
@@ -457,7 +490,12 @@ class AssetClaimDialog(tk.Toplevel):
asset = self.repository.get_asset(self.asset_id)
member = self.repository.get_member(self.member_id)
ttk.Label(frame, text="Asset").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(row=0, column=1, sticky="w", pady=5)
ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(
row=0,
column=1,
sticky="w",
pady=5,
)
ttk.Label(frame, text="Mitglied").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Label(frame, text=f"{member.member_number or member.member_id} · {member.display_name}").grid(
row=1, column=1, sticky="w", pady=5
@@ -470,11 +508,23 @@ class AssetClaimDialog(tk.Toplevel):
(f"Fällig am ({date_input_hint()}) *", "due_date"),
)
):
ttk.Label(frame, text=label).grid(row=row_offset + index, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Label(frame, text=label).grid(
row=row_offset + index,
column=0,
sticky="w",
pady=5,
padx=(0, 12),
)
ttk.Entry(frame, textvariable=self.variables[key], width=42).grid(
row=row_offset + index, column=1, sticky="ew", pady=5
)
ttk.Label(frame, text="Beschreibung").grid(row=row_offset + 3, column=0, sticky="nw", pady=5, padx=(0, 12))
ttk.Label(frame, text="Beschreibung").grid(
row=row_offset + 3,
column=0,
sticky="nw",
pady=5,
padx=(0, 12),
)
self.description_text = tk.Text(frame, width=42, height=5, wrap="word")
self.description_text.grid(row=row_offset + 3, column=1, sticky="ew", pady=5)
self.description_text.insert("1.0", self.preset_description)
@@ -488,7 +538,12 @@ class AssetClaimDialog(tk.Toplevel):
buttons = ttk.Frame(frame)
buttons.grid(row=row_offset + 5, column=0, columnspan=2, sticky="e", pady=(16, 0))
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
ttk.Button(buttons, text="Forderung anlegen", style="Accent.TButton", command=self._create).pack(side="left")
ttk.Button(
buttons,
text="Forderung anlegen",
style="Accent.TButton",
command=self._create,
).pack(side="left")
self.after_idle(lambda: frame.focus_set())
def _activate_modal(self) -> None:
@@ -539,13 +594,23 @@ class IntegrityWarningDialog(tk.Toplevel):
"Haben Sie alle Daten geprüft und soll der Hash jetzt aktualisiert werden?"
)
ttk.Label(frame, text=message, justify="left").grid(row=0, column=0, sticky="w")
ttk.Label(frame, text="\n".join(f"{item}" for item in self.warnings), style="Warning.TLabel", justify="left").grid(
ttk.Label(
frame,
text="\n".join(f"{item}" for item in self.warnings),
style="Warning.TLabel",
justify="left",
).grid(
row=1, column=0, sticky="w", pady=(12, 0)
)
buttons = ttk.Frame(frame)
buttons.grid(row=2, column=0, sticky="e", pady=(18, 0))
ttk.Button(buttons, text="Nein", command=self.destroy).pack(side="left", padx=(0, 8))
ttk.Button(buttons, text="Ja, bestätigen", style="Accent.TButton", command=self._confirm).pack(side="left")
ttk.Button(
buttons,
text="Ja, bestätigen",
style="Accent.TButton",
command=self._confirm,
).pack(side="left")
def _activate_modal(self) -> None:
_activate_modal_window(self)
+3 -1
View File
@@ -545,7 +545,9 @@ class MainWindow(ttk.Frame):
self.tabs.refresh_icons(self.icons)
def _show_validation_warning(self) -> None:
display_errors = [item for item in self.validation_errors if "Hash fehlt oder stimmt nicht" not in item]
display_errors = [
item for item in self.validation_errors if "Hash fehlt oder stimmt nicht" not in item
]
if not display_errors:
return
messagebox.showwarning(
+14 -7
View File
@@ -8,17 +8,16 @@ from tkinter import messagebox, ttk
from ccma.domain.contributions import CLAIM_STATUS_LABELS, claim_status, claim_total, money_text
from ccma.domain.dates import age_label, date_input_hint, format_date_for_display
from ccma.domain.models import ASSET_STATUS_LABELS, MEMBERSHIP_STATUS_LABELS as STATUS_LABELS
from ccma.domain.models import Event
from ccma.domain.models import ASSET_STATUS_LABELS, Event
from ccma.domain.models import MEMBERSHIP_STATUS_LABELS as STATUS_LABELS
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.document_dialog import DocumentTemplateDialog
from ccma.ui.dialogs import IntegrityWarningDialog
from ccma.ui.document_dialog import DocumentTemplateDialog
from ccma.ui.file_open import open_path
from ccma.ui.labels import display_label, storage_key
from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage
from ccma.ui.scrolling import ScrollableFrame
CLAIM_TABLE_COLUMNS = (
("title", "Forderung", 220),
("due", "Fällig", 100),
@@ -287,8 +286,12 @@ class MemberTab(ttk.Frame):
ttk.Button(asset_actions, text="Asset öffnen", command=self._open_selected_asset).pack(
side="left", padx=(0, 8)
)
ttk.Button(asset_actions, text="Ausgewähltes Asset zurücknehmen", command=self._return_selected_asset).pack(
side="left"
ttk.Button(
asset_actions,
text="Ausgewähltes Asset zurücknehmen",
command=self._return_selected_asset,
).pack(
side="left",
)
documents_tab.columnconfigure(0, weight=1)
@@ -493,7 +496,11 @@ class MemberTab(ttk.Frame):
suffix = ""
if key == self.claim_sort_column:
suffix = " v" if self.claim_sort_descending else " ^"
self.claims.heading(key, text=f"{title}{suffix}", command=lambda column=key: self._toggle_claim_sort(column))
self.claims.heading(
key,
text=f"{title}{suffix}",
command=lambda column=key: self._toggle_claim_sort(column),
)
def _open_selected_claim(self) -> None:
selected = self.claims.selection()
-1
View File
@@ -5,7 +5,6 @@ from collections.abc import Callable, Iterable
from dataclasses import dataclass
from tkinter import ttk
MessageType = str
+40 -8
View File
@@ -4,11 +4,16 @@ from collections.abc import Callable
from tkinter import messagebox, ttk
from ccma.domain.dates import format_date_for_display
from ccma.domain.models import ASSET_STATUS_LABELS, MEMBERSHIP_STATUS_LABELS, Asset, HousekeeperFinding, Member
from ccma.domain.models import (
ASSET_STATUS_LABELS,
MEMBERSHIP_STATUS_LABELS,
Asset,
HousekeeperFinding,
Member,
)
from ccma.ui.labels import storage_key
from ccma.ui.sections import titled_frame
MEMBER_TABLE_COLUMNS = (
("number", "Nummer", 110),
("first_name", "Vorname", 160),
@@ -306,7 +311,11 @@ class MembersTab(ttk.Frame):
suffix = ""
if key == self.sort_column:
suffix = " v" if self.sort_descending else " ^"
self.tree.heading(key, text=f"{title}{suffix}", command=lambda column=key: self._toggle_sort(column))
self.tree.heading(
key,
text=f"{title}{suffix}",
command=lambda column=key: self._toggle_sort(column),
)
def _open_selected(self) -> None:
selected = self.tree.selection()
@@ -368,7 +377,11 @@ class AssetsTab(ttk.Frame):
)
self.status_filter.grid(row=0, column=1, sticky="w")
self.status_filter.bind("<<ComboboxSelected>>", lambda _event: self._render_assets())
self.tree = ttk.Treeview(self, columns=tuple(key for key, _title, _width in ASSET_TABLE_COLUMNS), show="headings")
self.tree = ttk.Treeview(
self,
columns=tuple(key for key, _title, _width in ASSET_TABLE_COLUMNS),
show="headings",
)
for key, title, width in ASSET_TABLE_COLUMNS:
self.tree.heading(key, text=title, command=lambda column=key: self._toggle_sort(column))
self.tree.column(key, width=width, anchor="w")
@@ -377,13 +390,28 @@ class AssetsTab(ttk.Frame):
self.tree.bind("<<TreeviewSelect>>", lambda _event: self._update_actions())
actions = ttk.Frame(self)
actions.grid(row=3, column=0, sticky="e", pady=(10, 0))
self.edit_button = ttk.Button(actions, text="Bearbeiten", command=self._edit_selected, state="disabled")
self.edit_button = ttk.Button(
actions,
text="Bearbeiten",
command=self._edit_selected,
state="disabled",
)
self.edit_button.pack(side="left", padx=(0, 8))
self.open_button = ttk.Button(actions, text="Öffnen", command=self._open_selected, state="disabled")
self.open_button.pack(side="left", padx=(0, 8))
self.issue_button = ttk.Button(actions, text="Ausgeben", command=self._issue_selected, state="disabled")
self.issue_button = ttk.Button(
actions,
text="Ausgeben",
command=self._issue_selected,
state="disabled",
)
self.issue_button.pack(side="left", padx=(0, 8))
self.return_button = ttk.Button(actions, text="Zurücknehmen", command=self._return_selected, state="disabled")
self.return_button = ttk.Button(
actions,
text="Zurücknehmen",
command=self._return_selected,
state="disabled",
)
self.return_button.pack(side="left")
self.refresh(self.assets)
@@ -437,7 +465,11 @@ class AssetsTab(ttk.Frame):
suffix = ""
if key == self.sort_column:
suffix = " v" if self.sort_descending else " ^"
self.tree.heading(key, text=f"{title}{suffix}", command=lambda column=key: self._toggle_sort(column))
self.tree.heading(
key,
text=f"{title}{suffix}",
command=lambda column=key: self._toggle_sort(column),
)
def _selected_asset_id(self) -> str:
selected = self.tree.selection()
+4 -1
View File
@@ -422,4 +422,7 @@ def test_housekeeper_reports_json_hash_mismatch(tmp_path) -> None:
findings = Housekeeper(repository).run()
assert any(finding.code == "json_hash_mismatch" and finding.member_id == member.member_id for finding in findings)
assert any(
finding.code == "json_hash_mismatch" and finding.member_id == member.member_id
for finding in findings
)