mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-02 11:40:13 +02:00
feat: add itemized claims and payments
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
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",
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
def test_ui_modules_import_without_creating_root_window() -> None:
|
||||
import ccma.app # noqa: F401
|
||||
import ccma.ui.claim_tab # noqa: F401
|
||||
import ccma.ui.main_window # noqa: F401
|
||||
import ccma.ui.member_tab # noqa: F401
|
||||
import ccma.ui.splash # noqa: F401
|
||||
|
||||
Reference in New Issue
Block a user