From 4dd625c09f4ab9a913be887dee980bc32e978901 Mon Sep 17 00:00:00 2001 From: Marcel Peterkau Date: Fri, 14 Aug 2026 21:28:52 +0200 Subject: [PATCH] Only list open or already-linked claims/donations in the payment dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show a claim or donation in the Zahlung anlegen/bearbeiten allocation table only if it still has an open balance, or is already linked to this payment -- fully settled ones no longer clutter the list. Visibility is decided from a snapshot taken when the dialog opens (or when a donation is created inline), not recomputed live, so a row never disappears just because the user temporarily unassigned it with "Zuordnung lösen" -- it stays there to add back with "Zuordnung setzen" until the dialog is saved. Also fixes a capacity display bug this surfaced: "Maximal zuordenbar" was computed by mixing the persisted claim/donation balance with the live, currently-edited allocation amount, so it inflated every time the amount was changed during a session. It's now computed against a fixed baseline captured at dialog open, so it stays accurate throughout. Co-Authored-By: Claude Sonnet 5 --- src/ccma/ui/payment_dialog.py | 52 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/ccma/ui/payment_dialog.py b/src/ccma/ui/payment_dialog.py index fb78c8d..ff41443 100644 --- a/src/ccma/ui/payment_dialog.py +++ b/src/ccma/ui/payment_dialog.py @@ -21,10 +21,16 @@ from ccma.ui.donation_dialog import DonationEditDialog class _AllocationTable: """Embeds a combined claims + donations allocation tree into a payment dialog. + Only claims/donations that are still open (or already linked to this payment) are + listed, so fully-settled ones don't clutter the picture. A row's visibility is + decided once, from a snapshot taken when the dialog opens (or when a new donation + is created inline) -- it never disappears again for the rest of the editing + session just because the user unassigned it. That way "Zuordnung lösen" followed + by "Zuordnung setzen" always works on the same row instead of the target vanishing. + Mutates the ``claim_allocations``/``donation_allocations`` dicts it is given in - place, so the owning dialog can read them back at save time. Lets the user create - a new donation on the fly and allocate against it immediately, which is the whole - point of surfacing this inside the payment dialog rather than as a separate step. + place, so the owning dialog can read them back at save time. Nothing is written to + the repository until the dialog's own Save button does so. """ def __init__( @@ -42,33 +48,53 @@ class _AllocationTable: self.claim_allocations = claim_allocations self.donation_allocations = donation_allocations self.on_change = on_change + # Fixed snapshot of what this payment already covered when the dialog opened. + # Capacities are computed against this baseline rather than the live, editable + # dicts above -- otherwise "Maximal zuordenbar" would inflate every time the + # user changes an amount, since claim_balance() reflects only persisted state. + self._baseline_claim_allocations = dict(claim_allocations) + self._baseline_donation_allocations = dict(donation_allocations) + self._visible_claim_ids: set[str] = set(claim_allocations) + self._visible_donation_ids: set[str] = set(donation_allocations) self._load_targets() self._build(parent) self._refresh() def _load_targets(self) -> None: self.data = self.repository.get_contributions(self.member_id) + self._visible_claim_ids |= { + str(claim.get("claim_id", "")) + for claim in self.data.claims + if str(claim.get("claim_id", "")) + and claim_status(self.data, claim) != "cancelled" + and claim_balance(self.data, claim) > 0 + } + self._visible_donation_ids |= { + str(item.get("donation_id", "")) + for item in self.data.donations + if str(item.get("donation_id", "")) and donation_balance(self.data, item) > 0 + } self.claims_by_id = { str(claim.get("claim_id", "")): claim for claim in self.data.claims - if str(claim.get("claim_id", "")) - and ( - claim_status(self.data, claim) != "cancelled" - or str(claim.get("claim_id", "")) in self.claim_allocations - ) + if str(claim.get("claim_id", "")) in self._visible_claim_ids + } + self.donations_by_id = { + str(item.get("donation_id", "")): item + for item in self.data.donations + if str(item.get("donation_id", "")) in self._visible_donation_ids } - self.donations_by_id = {str(item.get("donation_id", "")): item for item in self.data.donations} def _capacities(self) -> None: self.claim_capacity: dict[str, Decimal] = {} for claim_id, claim in self.claims_by_id.items(): - current = decimal_value(self.claim_allocations.get(claim_id, "0")) - self.claim_capacity[claim_id] = max(claim_balance(self.data, claim) + current, Decimal("0")) + baseline = decimal_value(self._baseline_claim_allocations.get(claim_id, "0")) + self.claim_capacity[claim_id] = max(claim_balance(self.data, claim) + baseline, Decimal("0")) self.donation_capacity: dict[str, Decimal] = {} for donation_id, donation in self.donations_by_id.items(): - current = decimal_value(self.donation_allocations.get(donation_id, "0")) + baseline = decimal_value(self._baseline_donation_allocations.get(donation_id, "0")) self.donation_capacity[donation_id] = max( - donation_balance(self.data, donation) + current, Decimal("0") + donation_balance(self.data, donation) + baseline, Decimal("0") ) def _build(self, parent: tk.Misc) -> None: