mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-08-25 06:55:17 +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>
84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
from email.parser import BytesParser
|
|
from email.policy import default
|
|
|
|
from test_reminders import _overdue_claim_repository
|
|
|
|
from ccma.services.reminder_mail import generate_and_send_reminder_mail
|
|
|
|
|
|
def test_reminder_mail_is_archived_and_marks_reminder_sent(tmp_path):
|
|
repository, member = _overdue_claim_repository(tmp_path / "store")
|
|
member.email = "reminder@example.org"
|
|
repository.save_member(member)
|
|
organization = repository.get_configuration()["organization"]
|
|
organization.update(
|
|
{
|
|
"name": "Chaos Computer Club Mannheim e.V.",
|
|
"email": "verwaltung@example.org",
|
|
"iban": "DE98670505050038907751",
|
|
"bic": "MANSDE66XXX",
|
|
}
|
|
)
|
|
repository.save_organization(organization)
|
|
reminder = repository.create_reminder_draft(
|
|
member.member_id,
|
|
"claim-1",
|
|
level=1,
|
|
name="Zahlungserinnerung",
|
|
payment_deadline_days=14,
|
|
detail="Bitte Mitgliedsnummer angeben.",
|
|
)
|
|
export_path = tmp_path / "Zahlungserinnerung.eml"
|
|
|
|
generated = generate_and_send_reminder_mail(
|
|
repository,
|
|
member.member_id,
|
|
"claim-1",
|
|
reminder["reminder_id"],
|
|
output_path=export_path,
|
|
sender_name="Verwaltung C3MA",
|
|
sender_email="verwaltung@example.org",
|
|
signature="Der Vorstand",
|
|
)
|
|
|
|
assert generated.export_path.read_bytes() == generated.archive_path.read_bytes()
|
|
message = BytesParser(policy=default).parsebytes(generated.export_path.read_bytes())
|
|
assert message["To"] == "reminder@example.org"
|
|
assert "Zahlungserinnerung" in message["Subject"]
|
|
assert "100.00 Euro" in message.get_content()
|
|
assert "DE98670505050038907751" in message.get_content()
|
|
data = repository.get_contributions(member.member_id)
|
|
assert data.reminders[0]["status"] == "sent"
|
|
assert data.reminders[0]["payment_deadline"]
|
|
assert repository.get_events(member.member_id)[-1].event_type == "reminder_email_sent"
|
|
assert generated.archive_path.parent.name == "Mahnungen"
|
|
|
|
|
|
def test_reminder_mail_includes_fee_in_open_balance(tmp_path):
|
|
repository, member = _overdue_claim_repository(tmp_path / "store")
|
|
member.email = "reminder@example.org"
|
|
repository.save_member(member)
|
|
reminder = repository.create_reminder_draft(
|
|
member.member_id,
|
|
"claim-1",
|
|
level=1,
|
|
name="Erste Mahnung",
|
|
payment_deadline_days=14,
|
|
items=[{"description": "Mahngebühr", "amount": "5.00"}],
|
|
)
|
|
|
|
generated = generate_and_send_reminder_mail(
|
|
repository,
|
|
member.member_id,
|
|
"claim-1",
|
|
reminder["reminder_id"],
|
|
output_path=tmp_path / "Mahnung.eml",
|
|
sender_name="Verwaltung C3MA",
|
|
sender_email="verwaltung@example.org",
|
|
signature="Der Vorstand",
|
|
)
|
|
|
|
message = BytesParser(policy=default).parsebytes(generated.export_path.read_bytes())
|
|
assert "Offener Betrag: 105.00 Euro" in message.get_content()
|
|
assert "Mahngebühr: 5.00 Euro" in message.get_content()
|