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.dates import DateValidationError, validate_member_dates
from ccma.domain.models import HOUSEKEEPER_MEMBER_FIELD_LABELS
from ccma.rules.api import RuleContext, task from ccma.rules.api import RuleContext, task
RULE_ID = "birthdate-check" 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): for year in range(year_from, context.today.year + 2):
actions.extend(_membership_claims(context, started_at, accepted_at, year)) actions.extend(_membership_claims(context, started_at, accepted_at, year))
return actions 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: def _hashable_copy(data: Any, *, hash_field: str = CONTENT_HASH_FIELD) -> Any:
if isinstance(data, dict): 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): if isinstance(data, list):
return [_hashable_copy(item, hash_field=hash_field) for item in data] return [_hashable_copy(item, hash_field=hash_field) for item in data]
return data return data
+32 -9
View File
@@ -20,7 +20,14 @@ from ccma.domain.contributions import (
payment_allocated_total, payment_allocated_total,
) )
from ccma.domain.dates import DateValidationError, normalize_date_input, validate_member_dates 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 from ccma.storage.atomic import json_content_hash_matches, read_json, write_json_atomic
@@ -143,7 +150,9 @@ class MemberRepository:
try: try:
config = read_json(self.root / "repository.json") config = read_json(self.root / "repository.json")
if not json_content_hash_matches(config): 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: if int(config.get("schema_version", 0)) != 1:
errors.append("repository.json: nicht unterstützte schema_version") errors.append("repository.json: nicht unterstützte schema_version")
policy = config.get("member_number_policy") or {} policy = config.get("member_number_policy") or {}
@@ -157,7 +166,10 @@ class MemberRepository:
for member_dir in self._member_directories(): for member_dir in self._member_directories():
try: try:
member, _contributions = self.preflight_member_record(member_dir.name) 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( validate_member_dates(
birth_date=member.birth_date, birth_date=member.birth_date,
accepted_at=member.accepted_at, accepted_at=member.accepted_at,
@@ -186,12 +198,16 @@ class MemberRepository:
for asset_dir in self._asset_directories(): for asset_dir in self._asset_directories():
try: try:
asset = self.get_asset(asset_dir.name) 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: if asset.asset_id != asset_dir.name:
errors.append(f"{asset_dir.name}/asset.json: asset_id stimmt nicht mit Ordner überein") errors.append(f"{asset_dir.name}/asset.json: asset_id stimmt nicht mit Ordner überein")
if asset.schema_version != 1: if asset.schema_version != 1:
errors.append( 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: if asset.status not in ASSET_STATUS_LABELS:
errors.append(f"{asset_dir.name}/asset.json: ungültiger Asset-Status") errors.append(f"{asset_dir.name}/asset.json: ungültiger Asset-Status")
@@ -425,7 +441,9 @@ class MemberRepository:
existing.current_holder_member_id existing.current_holder_member_id
and money_text(deposit_amount) != str(existing.deposit_amount_default) 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.label = asset.label.strip()
asset.category = asset.category.strip() asset.category = asset.category.strip()
asset.inventory_number = asset.inventory_number.strip() asset.inventory_number = asset.inventory_number.strip()
@@ -1220,14 +1238,17 @@ class MemberRepository:
try: try:
member_raw = read_json(self._member_path(member_id) / "member.json") member_raw = read_json(self._member_path(member_id) / "member.json")
if not json_content_hash_matches(member_raw): 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): except (OSError, ValueError, TypeError, json.JSONDecodeError):
pass pass
try: try:
contributions_raw = read_json(self._member_path(member_id) / "contributions.json") contributions_raw = read_json(self._member_path(member_id) / "contributions.json")
if not json_content_hash_matches(contributions_raw): if not json_content_hash_matches(contributions_raw):
warnings.append( 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): except (OSError, ValueError, TypeError, json.JSONDecodeError):
pass pass
@@ -1238,7 +1259,9 @@ class MemberRepository:
try: try:
asset_raw = read_json(self._asset_path(asset_id) / "asset.json") asset_raw = read_json(self._asset_path(asset_id) / "asset.json")
if not json_content_hash_matches(asset_raw): 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): except (OSError, ValueError, TypeError, json.JSONDecodeError):
pass pass
return warnings return warnings
+19 -4
View File
@@ -5,7 +5,7 @@ from collections.abc import Callable
from datetime import datetime from datetime import datetime
from tkinter import messagebox, ttk 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.storage.repository import MemberRepository, RepositoryError
from ccma.ui.dialogs import AssetClaimDialog, IntegrityWarningDialog from ccma.ui.dialogs import AssetClaimDialog, IntegrityWarningDialog
from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage 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( ttk.Button(finance_actions, text="Verlustforderung", command=self._create_loss_claim).pack(
side="left", padx=(0, 8) side="left", padx=(0, 8)
) )
ttk.Button(finance_actions, text="Reparaturforderung", command=self._create_repair_claim).pack(side="left") ttk.Button(
self.asset_claims = ttk.Treeview(finance_tab, columns=("title", "due", "amount", "member"), show="headings") 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 ( for key, title, width in (
("title", "Forderung", 240), ("title", "Forderung", 240),
("due", "Fällig", 110), ("due", "Fällig", 110),
@@ -429,7 +437,14 @@ class AssetTab(ttk.Frame):
claim_type="asset_repair", 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 member_id = self.asset.current_holder_member_id
if not member_id: if not member_id:
messagebox.showerror( messagebox.showerror(
+15 -1
View File
@@ -261,12 +261,26 @@ class ClaimTab(ttk.Frame):
for allocation in self.data.allocations for allocation in self.data.allocations
if str(allocation.get("claim_id", "")) == self.claim_id and str(allocation.get("credit_id", "")) 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( credit_group = self.ledger.insert(
"", "",
"end", "end",
iid="group:credits", iid="group:credits",
text=f"Gutschriften ({len(credit_allocations)})", 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",), tags=("credit-group",),
open=True, open=True,
) )
+79 -14
View File
@@ -1,8 +1,7 @@
import tkinter as tk import tkinter as tk
from collections.abc import Callable from collections.abc import Callable
from tkinter import messagebox, ttk
from datetime import date from datetime import date
from tkinter import messagebox, ttk
from ccma.domain.dates import age_label, date_input_hint from ccma.domain.dates import age_label, date_input_hint
from ccma.domain.models import Asset, Member from ccma.domain.models import Asset, Member
@@ -35,7 +34,15 @@ class NewMemberDialog(tk.Toplevel):
self.number_policy = repository.get_member_number_policy() self.number_policy = repository.get_member_number_policy()
self.variables = { self.variables = {
name: tk.StringVar() 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._build_ui()
self.bind("<Escape>", lambda _event: self.destroy()) 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 = ttk.Entry(frame, textvariable=self.variables[key], width=38)
entry.grid(row=row, column=1, sticky="ew", pady=5) entry.grid(row=row, column=1, sticky="ew", pady=5)
self.entries[key] = entry 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 = tk.Text(frame, width=38, height=5, wrap="word")
self.notes_text.grid(row=len(fields), column=1, sticky="ew", pady=5) self.notes_text.grid(row=len(fields), column=1, sticky="ew", pady=5)
buttons = ttk.Frame(frame) buttons = ttk.Frame(frame)
buttons.grid(row=len(fields) + 1, column=0, columnspan=2, sticky="e", pady=(16, 0)) 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="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()) self.after_idle(lambda: self.entries["label"].focus_set())
def _activate_modal(self) -> None: def _activate_modal(self) -> None:
@@ -171,7 +189,13 @@ class NewAssetDialog(tk.Toplevel):
class EditAssetDialog(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) super().__init__(master)
self.repository = repository self.repository = repository
self.asset_id = asset_id self.asset_id = asset_id
@@ -310,7 +334,12 @@ class IssueAssetDialog(tk.Toplevel):
frame.rowconfigure(3, weight=1) frame.rowconfigure(3, weight=1)
asset = self.repository.get_asset(self.asset_id) 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").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)) 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 = ttk.Entry(frame, textvariable=self.search_var, width=42)
self.search_entry.grid(row=1, column=1, sticky="ew", pady=5) 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 "" selected_id = self.preselected_member_id if self.preselected_member_id else ""
if filtered: 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.selection_set(target_id)
self.member_tree.focus(target_id) self.member_tree.focus(target_id)
self.member_tree.see(target_id) self.member_tree.see(target_id)
@@ -457,7 +490,12 @@ class AssetClaimDialog(tk.Toplevel):
asset = self.repository.get_asset(self.asset_id) asset = self.repository.get_asset(self.asset_id)
member = self.repository.get_member(self.member_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").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="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( ttk.Label(frame, text=f"{member.member_number or member.member_id} · {member.display_name}").grid(
row=1, column=1, sticky="w", pady=5 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"), (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( ttk.Entry(frame, textvariable=self.variables[key], width=42).grid(
row=row_offset + index, column=1, sticky="ew", pady=5 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 = 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.grid(row=row_offset + 3, column=1, sticky="ew", pady=5)
self.description_text.insert("1.0", self.preset_description) self.description_text.insert("1.0", self.preset_description)
@@ -488,7 +538,12 @@ class AssetClaimDialog(tk.Toplevel):
buttons = ttk.Frame(frame) buttons = ttk.Frame(frame)
buttons.grid(row=row_offset + 5, column=0, columnspan=2, sticky="e", pady=(16, 0)) 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="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()) self.after_idle(lambda: frame.focus_set())
def _activate_modal(self) -> None: 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?" "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=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) row=1, column=0, sticky="w", pady=(12, 0)
) )
buttons = ttk.Frame(frame) buttons = ttk.Frame(frame)
buttons.grid(row=2, column=0, sticky="e", pady=(18, 0)) 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="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: def _activate_modal(self) -> None:
_activate_modal_window(self) _activate_modal_window(self)
+3 -1
View File
@@ -545,7 +545,9 @@ class MainWindow(ttk.Frame):
self.tabs.refresh_icons(self.icons) self.tabs.refresh_icons(self.icons)
def _show_validation_warning(self) -> None: 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: if not display_errors:
return return
messagebox.showwarning( 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.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.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 ASSET_STATUS_LABELS, Event
from ccma.domain.models import Event from ccma.domain.models import MEMBERSHIP_STATUS_LABELS as STATUS_LABELS
from ccma.storage.repository import MemberRepository, RepositoryError from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.document_dialog import DocumentTemplateDialog
from ccma.ui.dialogs import IntegrityWarningDialog from ccma.ui.dialogs import IntegrityWarningDialog
from ccma.ui.document_dialog import DocumentTemplateDialog
from ccma.ui.file_open import open_path from ccma.ui.file_open import open_path
from ccma.ui.labels import display_label, storage_key from ccma.ui.labels import display_label, storage_key
from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage
from ccma.ui.scrolling import ScrollableFrame from ccma.ui.scrolling import ScrollableFrame
CLAIM_TABLE_COLUMNS = ( CLAIM_TABLE_COLUMNS = (
("title", "Forderung", 220), ("title", "Forderung", 220),
("due", "Fällig", 100), ("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( ttk.Button(asset_actions, text="Asset öffnen", command=self._open_selected_asset).pack(
side="left", padx=(0, 8) side="left", padx=(0, 8)
) )
ttk.Button(asset_actions, text="Ausgewähltes Asset zurücknehmen", command=self._return_selected_asset).pack( ttk.Button(
side="left" asset_actions,
text="Ausgewähltes Asset zurücknehmen",
command=self._return_selected_asset,
).pack(
side="left",
) )
documents_tab.columnconfigure(0, weight=1) documents_tab.columnconfigure(0, weight=1)
@@ -493,7 +496,11 @@ class MemberTab(ttk.Frame):
suffix = "" suffix = ""
if key == self.claim_sort_column: if key == self.claim_sort_column:
suffix = " v" if self.claim_sort_descending else " ^" 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: def _open_selected_claim(self) -> None:
selected = self.claims.selection() selected = self.claims.selection()
-1
View File
@@ -5,7 +5,6 @@ from collections.abc import Callable, Iterable
from dataclasses import dataclass from dataclasses import dataclass
from tkinter import ttk from tkinter import ttk
MessageType = str MessageType = str
+40 -8
View File
@@ -4,11 +4,16 @@ from collections.abc import Callable
from tkinter import messagebox, ttk from tkinter import messagebox, ttk
from ccma.domain.dates import format_date_for_display 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.labels import storage_key
from ccma.ui.sections import titled_frame from ccma.ui.sections import titled_frame
MEMBER_TABLE_COLUMNS = ( MEMBER_TABLE_COLUMNS = (
("number", "Nummer", 110), ("number", "Nummer", 110),
("first_name", "Vorname", 160), ("first_name", "Vorname", 160),
@@ -306,7 +311,11 @@ class MembersTab(ttk.Frame):
suffix = "" suffix = ""
if key == self.sort_column: if key == self.sort_column:
suffix = " v" if self.sort_descending else " ^" 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: def _open_selected(self) -> None:
selected = self.tree.selection() 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.grid(row=0, column=1, sticky="w")
self.status_filter.bind("<<ComboboxSelected>>", lambda _event: self._render_assets()) 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: for key, title, width in ASSET_TABLE_COLUMNS:
self.tree.heading(key, text=title, command=lambda column=key: self._toggle_sort(column)) self.tree.heading(key, text=title, command=lambda column=key: self._toggle_sort(column))
self.tree.column(key, width=width, anchor="w") 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()) self.tree.bind("<<TreeviewSelect>>", lambda _event: self._update_actions())
actions = ttk.Frame(self) actions = ttk.Frame(self)
actions.grid(row=3, column=0, sticky="e", pady=(10, 0)) 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.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 = ttk.Button(actions, text="Öffnen", command=self._open_selected, state="disabled")
self.open_button.pack(side="left", padx=(0, 8)) 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.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.return_button.pack(side="left")
self.refresh(self.assets) self.refresh(self.assets)
@@ -437,7 +465,11 @@ class AssetsTab(ttk.Frame):
suffix = "" suffix = ""
if key == self.sort_column: if key == self.sort_column:
suffix = " v" if self.sort_descending else " ^" 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: def _selected_asset_id(self) -> str:
selected = self.tree.selection() 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() 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
)