from decimal import Decimal import pytest from ccma.domain.contributions import ( claim_balance, claim_items, claim_status, claim_total, 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_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_reminder_fee_increases_claim_and_is_audited(tmp_path) -> None: repository, member = _repository_with_claim(tmp_path) reminder = repository.add_reminder( member.member_id, "claim-1", level=1, detail="Per E-Mail versandt", fee="5.00", ) data, claim = repository.get_claim(member.member_id, "claim-1") assert claim_total(claim) == Decimal("105.00") assert reminder["fee_item_id"] assert data.reminders[0]["detail"] == "Per E-Mail versandt" assert repository.get_events(member.member_id)[-1].event_type == "reminder_created" 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_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", )