Allocate open claims and donations directly from the payment dialog

Recording a payment previously meant saving it bare, then separately
opening a claim to allocate money to it. Zahlung anlegen now lists a
member's open claims and donations right in the same dialog with a
select + amount field to assign parts of the payment on the spot, and a
"Neue Spende anlegen" button to create a donation inline and allocate to
it immediately -- covering members who pay more than the membership fee
in one transfer.

Shared the same allocation table in the existing payment-edit dialog so
editing a payment shows and preserves donation allocations too; before
this, saving an edited payment silently dropped any donation allocation
because update_payment only round-tripped claim allocations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marcel Peterkau
2026-08-14 21:22:08 +02:00
co-authored by Claude Sonnet 5
parent 42fb4c4224
commit a5d9abd59a
3 changed files with 530 additions and 163 deletions
+95
View File
@@ -441,6 +441,101 @@ def test_existing_free_payment_can_be_allocated_to_a_donation(tmp_path) -> None:
)
def test_payment_can_be_created_with_immediate_claim_and_donation_allocation(tmp_path) -> None:
repository, member = _repository_with_claim(tmp_path, amount="30.00")
donation = repository.record_donation(
member.member_id, donation_date="2026-06-21", amount="20.00", reference="Aufrundung"
)
payment = repository.create_payment(
member.member_id,
payment_date="2026-06-21",
amount="50.00",
claim_allocations={"claim-1": "30.00"},
donation_allocations={donation["donation_id"]: "20.00"},
reference="Mitgliedsbeitrag plus Spende",
)
data = repository.get_contributions(member.member_id)
_data, claim = repository.get_claim(member.member_id, "claim-1")
assert claim_balance(data, claim) == Decimal("0.00")
assert donation_balance(data, donation) == Decimal("0.00")
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("50.00")
def test_payment_creation_rejects_allocations_exceeding_amount(tmp_path) -> None:
repository, member = _repository_with_claim(tmp_path, amount="30.00")
with pytest.raises(RepositoryError, match="übersteigen den"):
repository.create_payment(
member.member_id,
payment_date="2026-06-21",
amount="10.00",
claim_allocations={"claim-1": "30.00"},
)
def test_updating_payment_without_donation_allocations_preserves_existing_ones(tmp_path) -> None:
repository = MemberRepository(tmp_path)
repository.initialize()
member = repository.create_member(first_name="Donation", last_name="Preserve")
donation = repository.record_donation(
member.member_id, donation_date="2026-06-21", amount="20.00", reference="Sommerfest"
)
payment = repository.record_donation_payment(
member.member_id,
donation["donation_id"],
payment_date="2026-06-21",
amount="20.00",
allocation_amount="20.00",
)
repository.update_payment(
member.member_id,
payment["payment_id"],
payment_date="22.06.2026",
amount="20.00",
allocations={},
reference="Korrigierte Referenz",
)
data = repository.get_contributions(member.member_id)
assert donation_allocated_total(data, donation["donation_id"]) == Decimal("20.00")
assert data.payments[0]["reference"] == "Korrigierte Referenz"
def test_updating_payment_with_donation_allocations_replaces_them(tmp_path) -> None:
repository = MemberRepository(tmp_path)
repository.initialize()
member = repository.create_member(first_name="Donation", last_name="Replace")
first_donation = repository.record_donation(
member.member_id, donation_date="2026-06-21", amount="20.00", reference="Erste Spende"
)
second_donation = repository.record_donation(
member.member_id, donation_date="2026-06-21", amount="20.00", reference="Zweite Spende"
)
payment = repository.record_donation_payment(
member.member_id,
first_donation["donation_id"],
payment_date="2026-06-21",
amount="20.00",
allocation_amount="20.00",
)
repository.update_payment(
member.member_id,
payment["payment_id"],
payment_date="2026-06-21",
amount="20.00",
allocations={},
donation_allocations={second_donation["donation_id"]: "20.00"},
)
data = repository.get_contributions(member.member_id)
assert donation_allocated_total(data, first_donation["donation_id"]) == Decimal("0.00")
assert donation_allocated_total(data, second_donation["donation_id"]) == Decimal("20.00")
def test_gnucash_id_is_unique_across_member_store(tmp_path) -> None:
repository, first_member = _repository_with_claim(tmp_path / "store")
repository.record_payment(