mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-08-25 06:55:17 +02:00
Allow deleting claims, creating standalone payments, and add a donations tab
Claims could previously only be cancelled (stornieren), which blocks once a payment is allocated. Add a hard delete that releases any linked payments/credits back to being unallocated instead of destroying them. Payments could only be created from within a claim, forcing immediate allocation. Add a bare payment creation flow in the Zahlungen tab so incoming transfers can be logged first and allocated later. Add a per-member Spenden tab (donations, backed by a new donations list on ContributionData) so amounts paid beyond the membership fee can be tracked and existing free payments allocated to them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a61ea3cb57
commit
42fb4c4224
@@ -9,6 +9,9 @@ from ccma.domain.contributions import (
|
||||
claim_settled_total,
|
||||
claim_status,
|
||||
claim_total,
|
||||
donation_allocated_total,
|
||||
donation_balance,
|
||||
donation_status,
|
||||
payment_allocated_total,
|
||||
)
|
||||
from ccma.domain.models import ContributionData
|
||||
@@ -311,6 +314,133 @@ def test_claim_with_payment_cannot_be_cancelled(tmp_path) -> None:
|
||||
repository.cancel_claim(member.member_id, "claim-1")
|
||||
|
||||
|
||||
def test_claim_can_be_deleted_and_releases_allocated_payment(tmp_path) -> None:
|
||||
repository, member = _repository_with_claim(tmp_path)
|
||||
payment = repository.record_payment(
|
||||
member.member_id,
|
||||
"claim-1",
|
||||
payment_date="2026-06-21",
|
||||
amount="60.00",
|
||||
allocation_amount="60.00",
|
||||
)
|
||||
|
||||
repository.delete_claim(member.member_id, "claim-1")
|
||||
|
||||
data = repository.get_contributions(member.member_id)
|
||||
assert data.claims == []
|
||||
assert data.allocations == []
|
||||
assert data.payments[0]["payment_id"] == payment["payment_id"]
|
||||
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("0.00")
|
||||
assert repository.get_events(member.member_id)[-1].event_type == "claim_deleted"
|
||||
|
||||
with pytest.raises(RepositoryError, match="nicht gefunden"):
|
||||
repository.get_claim(member.member_id, "claim-1")
|
||||
|
||||
|
||||
def test_claim_with_payment_can_be_deleted_even_though_it_cannot_be_cancelled(tmp_path) -> None:
|
||||
repository, member = _repository_with_claim(tmp_path)
|
||||
repository.record_payment(
|
||||
member.member_id,
|
||||
"claim-1",
|
||||
payment_date="2026-06-21",
|
||||
amount="10.00",
|
||||
allocation_amount="10.00",
|
||||
)
|
||||
|
||||
with pytest.raises(RepositoryError, match="Zahlungszuordnungen"):
|
||||
repository.cancel_claim(member.member_id, "claim-1")
|
||||
|
||||
repository.delete_claim(member.member_id, "claim-1")
|
||||
assert repository.get_contributions(member.member_id).claims == []
|
||||
|
||||
|
||||
def test_bare_payment_can_be_created_without_allocation(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Payment", last_name="Test")
|
||||
|
||||
payment = repository.create_payment(
|
||||
member.member_id,
|
||||
payment_date="2026-06-21",
|
||||
amount="42.00",
|
||||
reference="Überweisung ohne Zuordnung",
|
||||
)
|
||||
|
||||
data = repository.get_contributions(member.member_id)
|
||||
assert data.payments == [payment]
|
||||
assert data.allocations == []
|
||||
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("0.00")
|
||||
assert repository.get_events(member.member_id)[-1].event_type == "payment_recorded"
|
||||
|
||||
|
||||
def test_donation_can_be_recorded_paid_and_deleted_releases_payment(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Donation", last_name="Test")
|
||||
|
||||
donation = repository.record_donation(
|
||||
member.member_id,
|
||||
donation_date="2026-06-21",
|
||||
amount="30.00",
|
||||
reference="Sommerfest",
|
||||
purpose="Freiwillige Zusatzspende",
|
||||
)
|
||||
data = repository.get_contributions(member.member_id)
|
||||
assert donation_status(data, donation) == "open"
|
||||
assert donation_balance(data, donation) == Decimal("30.00")
|
||||
|
||||
payment = repository.record_donation_payment(
|
||||
member.member_id,
|
||||
donation["donation_id"],
|
||||
payment_date="2026-06-22",
|
||||
amount="30.00",
|
||||
allocation_amount="30.00",
|
||||
)
|
||||
data = repository.get_contributions(member.member_id)
|
||||
assert donation_allocated_total(data, donation["donation_id"]) == Decimal("30.00")
|
||||
assert donation_status(data, donation) == "allocated"
|
||||
|
||||
repository.delete_donation(member.member_id, donation["donation_id"])
|
||||
data = repository.get_contributions(member.member_id)
|
||||
assert data.donations == []
|
||||
assert data.allocations == []
|
||||
assert data.payments[0]["payment_id"] == payment["payment_id"]
|
||||
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("0.00")
|
||||
assert repository.get_events(member.member_id)[-1].event_type == "donation_deleted"
|
||||
|
||||
|
||||
def test_existing_free_payment_can_be_allocated_to_a_donation(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Donation", last_name="Allocate")
|
||||
|
||||
payment = repository.create_payment(
|
||||
member.member_id,
|
||||
payment_date="2026-06-21",
|
||||
amount="100.00",
|
||||
reference="Mitgliedsbeitrag plus Spende",
|
||||
)
|
||||
donation = repository.record_donation(
|
||||
member.member_id,
|
||||
donation_date="2026-06-21",
|
||||
amount="20.00",
|
||||
reference="Aufrundung",
|
||||
)
|
||||
|
||||
repository.allocate_payment_to_donation(
|
||||
member.member_id, donation["donation_id"], payment_id=payment["payment_id"], amount="20.00"
|
||||
)
|
||||
|
||||
data = repository.get_contributions(member.member_id)
|
||||
assert donation_balance(data, donation) == Decimal("0.00")
|
||||
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("20.00")
|
||||
|
||||
with pytest.raises(RepositoryError, match="nur noch 0.00 EUR"):
|
||||
repository.allocate_payment_to_donation(
|
||||
member.member_id, donation["donation_id"], payment_id=payment["payment_id"], amount="1.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(
|
||||
|
||||
Reference in New Issue
Block a user