mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-08-24 22:45:18 +02:00
A Rücklastschrift is still the member's own failure to ensure cover or provide valid bank details, so it belongs in the same escalation ladder as a regular reminder -- it now occupies whatever level the claim is next due for, alongside a "Rücklastschrift" preset next to the existing Mahnstufe presets. Mahnung vorbereiten no longer lets the board pick a level (it's computed automatically from what's already been sent, since the sequencing was already server-enforced) and replaces the single fee field with a small items table (Beschreibung + Betrag), so a reminder can carry several charges at once -- e.g. Rücklastschriftgebühr plus Bankgebühr plus Porto -- each landing on the claim as its own line item instead of one lump sum under a generic label. The description field offers the configured standard texts but stays free-editable. Those standard texts and amounts, plus the dunning levels themselves (name/fee/deadline), are now configurable from Optionen -> Mahnungen instead of only being editable by hand-editing repository.json, which was the case for the whole reminder policy until now. Cleaned up reminder_fee/failed_debit_fee on contribution rules while at it -- both were unused leftovers superseded by this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
638 lines
22 KiB
Python
638 lines
22 KiB
Python
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from ccma.domain.contributions import (
|
|
allocated_total,
|
|
claim_balance,
|
|
claim_items,
|
|
claim_settled_total,
|
|
claim_status,
|
|
claim_total,
|
|
donation_allocated_total,
|
|
donation_balance,
|
|
donation_status,
|
|
payment_allocated_total,
|
|
)
|
|
from ccma.domain.models import ContributionData
|
|
from ccma.storage.repository import MemberRepository, RepositoryError
|
|
|
|
|
|
def _repository_with_claim(tmp_path, *, amount="100.00", claim_id="claim-1"):
|
|
repository = MemberRepository(tmp_path)
|
|
repository.initialize()
|
|
member = repository.create_member(first_name="Claim", last_name="Test")
|
|
repository.save_contributions(
|
|
member.member_id,
|
|
ContributionData(
|
|
claims=[
|
|
{
|
|
"claim_id": claim_id,
|
|
"claim_key": "test-claim",
|
|
"title": "Testforderung",
|
|
"amount": amount,
|
|
"due_date": "2026-12-31",
|
|
"status": "open",
|
|
}
|
|
]
|
|
),
|
|
)
|
|
return repository, member
|
|
|
|
|
|
def test_legacy_claim_becomes_itemized_when_position_is_added(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path)
|
|
data, claim = repository.get_claim(member.member_id, "claim-1")
|
|
assert claim_total(claim) == Decimal("100.00")
|
|
assert claim_items(claim)[0]["item_id"] == "legacy-base"
|
|
|
|
repository.add_claim_item(
|
|
member.member_id,
|
|
"claim-1",
|
|
description="Gutschrift",
|
|
quantity="1",
|
|
unit_price="-10,00",
|
|
item_type="credit",
|
|
)
|
|
data, claim = repository.get_claim(member.member_id, "claim-1")
|
|
|
|
assert len(claim["items"]) == 2
|
|
assert claim_total(claim) == Decimal("90.00")
|
|
assert claim["amount"] == "90.00"
|
|
assert claim_balance(data, claim) == Decimal("90.00")
|
|
|
|
|
|
def test_claim_base_data_can_be_edited(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path)
|
|
|
|
repository.update_claim(
|
|
member.member_id,
|
|
"claim-1",
|
|
title="Korrigierter Beitrag",
|
|
due_date="30.06.2026",
|
|
base_amount="75,00",
|
|
description="Beitrag für sechs Monate",
|
|
)
|
|
|
|
_data, claim = repository.get_claim(member.member_id, "claim-1")
|
|
assert claim["title"] == "Korrigierter Beitrag"
|
|
assert claim["due_date"] == "2026-06-30"
|
|
assert claim["amount"] == "75.00"
|
|
assert claim["items"][0]["description"] == "Beitrag für sechs Monate"
|
|
assert claim["items"][0]["amount"] == "75.00"
|
|
assert claim["calculation"]["manual_override"]["actor"] == "Vorstand"
|
|
assert repository.get_events(member.member_id)[-1].event_type == "claim_changed"
|
|
|
|
|
|
def test_claim_cannot_be_reduced_below_allocated_payment(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path)
|
|
repository.record_payment(
|
|
member.member_id,
|
|
"claim-1",
|
|
payment_date="2026-06-21",
|
|
amount="80.00",
|
|
allocation_amount="80.00",
|
|
)
|
|
|
|
with pytest.raises(RepositoryError, match="bereits zugeordneten Betrag"):
|
|
repository.update_claim(
|
|
member.member_id,
|
|
"claim-1",
|
|
title="Zu klein",
|
|
due_date="2026-06-30",
|
|
base_amount="75.00",
|
|
description="",
|
|
)
|
|
|
|
|
|
def test_payment_can_be_split_across_multiple_claims(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path)
|
|
data = repository.get_contributions(member.member_id)
|
|
data.claims.append(
|
|
{
|
|
"claim_id": "claim-2",
|
|
"claim_key": "second-claim",
|
|
"title": "Zweite Forderung",
|
|
"amount": "50.00",
|
|
"due_date": "2026-12-31",
|
|
"status": "open",
|
|
}
|
|
)
|
|
repository.save_contributions(member.member_id, data)
|
|
|
|
payment = repository.record_payment(
|
|
member.member_id,
|
|
"claim-1",
|
|
payment_date="2026-06-21",
|
|
amount="150.00",
|
|
allocation_amount="100.00",
|
|
gnucash_transaction_id="TX-42",
|
|
reference="Jahreszahlung",
|
|
)
|
|
repository.allocate_payment(
|
|
member.member_id,
|
|
"claim-2",
|
|
payment_id=payment["payment_id"],
|
|
amount="50.00",
|
|
)
|
|
data, first = repository.get_claim(member.member_id, "claim-1")
|
|
_data, second = repository.get_claim(member.member_id, "claim-2")
|
|
|
|
assert claim_status(data, first) == "paid"
|
|
assert claim_status(data, second) == "paid"
|
|
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("150.00")
|
|
with pytest.raises(RepositoryError, match="nur 0.00 EUR"):
|
|
repository.allocate_payment(
|
|
member.member_id,
|
|
"claim-2",
|
|
payment_id=payment["payment_id"],
|
|
amount="1.00",
|
|
)
|
|
|
|
|
|
def test_combined_dummy_payment_settles_multiple_claims(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path)
|
|
data = repository.get_contributions(member.member_id)
|
|
data.claims.append(
|
|
{
|
|
"claim_id": "claim-2",
|
|
"title": "Zweite Forderung",
|
|
"amount": "50.00",
|
|
"due_date": "2019-12-31",
|
|
"status": "open",
|
|
}
|
|
)
|
|
repository.save_contributions(member.member_id, data)
|
|
|
|
payment = repository.record_combined_payment(
|
|
member.member_id,
|
|
payment_date="2019-12-31",
|
|
allocations={"claim-1": "100.00", "claim-2": "50.00"},
|
|
method="dummy",
|
|
reference="Altbestand",
|
|
)
|
|
|
|
changed = repository.get_contributions(member.member_id)
|
|
assert payment["amount"] == "150.00"
|
|
assert payment["method"] == "dummy"
|
|
assert payment_allocated_total(changed, payment["payment_id"]) == Decimal("150.00")
|
|
assert all(claim_balance(changed, claim) == 0 for claim in changed.claims)
|
|
assert repository.get_events(member.member_id)[-1].data["method"] == "dummy"
|
|
|
|
|
|
def test_payment_allocation_cannot_overpay_claim(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path, amount="12.50")
|
|
|
|
with pytest.raises(RepositoryError, match="nur noch 12.50 EUR offen"):
|
|
repository.record_payment(
|
|
member.member_id,
|
|
"claim-1",
|
|
payment_date="2026-06-21",
|
|
amount="22.50",
|
|
allocation_amount="22.50",
|
|
)
|
|
|
|
|
|
def test_payment_can_be_edited_and_reallocated_atomically(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path, amount="12.50")
|
|
data = repository.get_contributions(member.member_id)
|
|
data.claims.append(
|
|
{
|
|
"claim_id": "admission-fee",
|
|
"claim_key": "admission-fee",
|
|
"title": "Aufnahmegebühr",
|
|
"amount": "10.00",
|
|
"due_date": "2026-12-31",
|
|
"status": "open",
|
|
}
|
|
)
|
|
repository.save_contributions(member.member_id, data)
|
|
payment = repository.record_payment(
|
|
member.member_id,
|
|
"claim-1",
|
|
payment_date="2026-06-21",
|
|
amount="22.50",
|
|
allocation_amount="12.50",
|
|
reference="Vorher",
|
|
)
|
|
|
|
repository.update_payment(
|
|
member.member_id,
|
|
payment["payment_id"],
|
|
payment_date="22.06.2026",
|
|
amount="22.50",
|
|
allocations={"claim-1": "12.50", "admission-fee": "10.00"},
|
|
reference="Korrigiert",
|
|
)
|
|
|
|
changed = repository.get_contributions(member.member_id)
|
|
assert changed.payments[0]["date"] == "2026-06-22"
|
|
assert changed.payments[0]["reference"] == "Korrigiert"
|
|
assert payment_allocated_total(changed, payment["payment_id"]) == Decimal("22.50")
|
|
assert {item["claim_id"] for item in changed.allocations} == {"claim-1", "admission-fee"}
|
|
assert repository.get_events(member.member_id)[-1].event_type == "payment_changed"
|
|
|
|
|
|
def test_payment_can_be_deleted_with_its_allocations(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="10.00",
|
|
allocation_amount="10.00",
|
|
)
|
|
|
|
repository.delete_payment(member.member_id, payment["payment_id"])
|
|
|
|
data = repository.get_contributions(member.member_id)
|
|
assert data.payments == []
|
|
assert data.allocations == []
|
|
assert repository.get_events(member.member_id)[-1].event_type == "payment_deleted"
|
|
|
|
|
|
def test_payment_reference_can_be_replaced_without_touching_amount_or_allocations(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="10.00",
|
|
allocation_amount="10.00",
|
|
reference="Alte Referenz",
|
|
)
|
|
|
|
updated = repository.update_payment_reference(
|
|
member.member_id,
|
|
payment["payment_id"],
|
|
reference="Neue Referenz aus GnuCash",
|
|
gnucash_transaction_id="TX-99",
|
|
)
|
|
|
|
data = repository.get_contributions(member.member_id)
|
|
assert updated["reference"] == "Neue Referenz aus GnuCash"
|
|
assert updated["gnucash_transaction_id"] == "TX-99"
|
|
assert data.payments[0]["amount"] == "10.00"
|
|
assert payment_allocated_total(data, payment["payment_id"]) == Decimal("10.00")
|
|
assert repository.get_events(member.member_id)[-1].event_type == "payment_changed"
|
|
|
|
|
|
def test_payment_reference_update_rejects_gnucash_id_already_used_elsewhere(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",
|
|
gnucash_transaction_id="TX-1",
|
|
)
|
|
data = repository.get_contributions(member.member_id)
|
|
data.claims.append(
|
|
{
|
|
"claim_id": "claim-2",
|
|
"claim_key": "second-claim",
|
|
"title": "Zweite Forderung",
|
|
"amount": "20.00",
|
|
"due_date": "2026-12-31",
|
|
"status": "open",
|
|
}
|
|
)
|
|
repository.save_contributions(member.member_id, data)
|
|
second_payment = repository.record_payment(
|
|
member.member_id,
|
|
"claim-2",
|
|
payment_date="2026-06-22",
|
|
amount="20.00",
|
|
allocation_amount="20.00",
|
|
)
|
|
|
|
with pytest.raises(RepositoryError, match="GnuCash-ID bereits verwendet"):
|
|
repository.update_payment_reference(
|
|
member.member_id,
|
|
second_payment["payment_id"],
|
|
reference="Duplikatversuch",
|
|
gnucash_transaction_id="TX-1",
|
|
)
|
|
|
|
|
|
def test_credit_claim_settlement_is_displayed_as_positive_amount() -> None:
|
|
claim = {"claim_id": "claim-1", "title": "Kautionsrückzahlung", "amount": "-25.00"}
|
|
data = ContributionData(
|
|
claims=[claim],
|
|
credits=[{"credit_id": "credit-1", "amount": "25.00"}],
|
|
allocations=[
|
|
{
|
|
"allocation_id": "allocation-1",
|
|
"claim_id": "claim-1",
|
|
"credit_id": "credit-1",
|
|
"amount": "25.00",
|
|
}
|
|
],
|
|
)
|
|
|
|
assert allocated_total(data, "claim-1") == Decimal("-25.00")
|
|
assert claim_settled_total(data, claim) == Decimal("25.00")
|
|
|
|
|
|
def test_reminder_fee_increases_claim_and_is_audited(tmp_path) -> None:
|
|
repository, member = _repository_with_claim(tmp_path)
|
|
|
|
reminder = repository.create_reminder_draft(
|
|
member.member_id,
|
|
"claim-1",
|
|
level=1,
|
|
name="Zahlungserinnerung",
|
|
payment_deadline_days=14,
|
|
detail="Per E-Mail versandt",
|
|
items=[{"description": "Mahngebühr", "amount": "5.00"}],
|
|
)
|
|
data, claim = repository.get_claim(member.member_id, "claim-1")
|
|
|
|
assert claim_total(claim) == Decimal("100.00")
|
|
assert reminder["status"] == "draft"
|
|
assert reminder["fee_item_ids"] == []
|
|
|
|
repository.mark_reminder_sent(member.member_id, "claim-1", reminder["reminder_id"])
|
|
data, claim = repository.get_claim(member.member_id, "claim-1")
|
|
sent = data.reminders[0]
|
|
|
|
assert claim_total(claim) == Decimal("105.00")
|
|
assert sent["status"] == "sent"
|
|
assert sent["payment_deadline"]
|
|
assert sent["fee_item_ids"]
|
|
assert data.reminders[0]["detail"] == "Per E-Mail versandt"
|
|
assert repository.get_events(member.member_id)[-1].event_type == "reminder_sent"
|
|
|
|
|
|
def test_claim_with_payment_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")
|
|
|
|
|
|
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_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(
|
|
first_member.member_id,
|
|
"claim-1",
|
|
payment_date="2026-06-21",
|
|
amount="100.00",
|
|
allocation_amount="100.00",
|
|
gnucash_transaction_id="UNIQUE-1",
|
|
)
|
|
second = repository.create_member(first_name="Second", last_name="Member")
|
|
repository.save_contributions(
|
|
second.member_id,
|
|
ContributionData(
|
|
claims=[
|
|
{
|
|
"claim_id": "other-claim",
|
|
"title": "Andere Forderung",
|
|
"amount": "10.00",
|
|
"due_date": "2026-12-31",
|
|
}
|
|
]
|
|
),
|
|
)
|
|
|
|
with pytest.raises(RepositoryError, match="GnuCash-ID bereits verwendet"):
|
|
repository.record_payment(
|
|
second.member_id,
|
|
"other-claim",
|
|
payment_date="2026-06-21",
|
|
amount="10.00",
|
|
allocation_amount="10.00",
|
|
gnucash_transaction_id="unique-1",
|
|
)
|