mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
Allow editing reminders (incl. undoing "sent") and prefill a Rücklastschrift reason
A generated/sent Mahnung email couldn't be corrected before: sent reminders were
locked, so a wording mistake meant living with it. Add edit_reminder_draft() for
draft/generated reminders and revert_reminder_sent() to safely undo a "sent"
reminder (removing the fee items it booked, refusing if a payment already covers
them or a higher Mahnstufe already exists) so it can be edited and resent.
ReminderDialog now supports an edit mode (reminder=... prefills name/detail/
items/deadline/channel, level stays fixed) and claim_tab gained a "Mahnung
bearbeiten" button that reverts-then-edits for sent reminders automatically,
asking for confirmation first since it un-books the fee.
The Rücklastschrift preset now also prefills "Details" with a short explanation
("Leider konnte die Lastschrift ... nicht eingelöst werden.") since that field
goes straight into the reminder email -- the board can append the concrete
reason (mangels Deckung, Konto ungültig, ...) right there instead of the field
starting empty.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f3bdffd347
commit
186af530b0
@@ -17,6 +17,8 @@ from ccma.domain.contributions import (
|
||||
CONTRIBUTION_OVERRIDE_KIND_LABELS,
|
||||
allocated_total,
|
||||
claim_balance,
|
||||
claim_items,
|
||||
claim_settled_total,
|
||||
claim_total,
|
||||
contribution_override_ranges_overlap,
|
||||
credit_allocated_total,
|
||||
@@ -2108,6 +2110,97 @@ class MemberRepository:
|
||||
references={"claim_id": claim_id, "reminder_id": reminder_id},
|
||||
)
|
||||
|
||||
def edit_reminder_draft(
|
||||
self,
|
||||
member_id: str,
|
||||
claim_id: str,
|
||||
reminder_id: str,
|
||||
*,
|
||||
name: str,
|
||||
payment_deadline_days: int,
|
||||
detail: str = "",
|
||||
items: list[dict[str, str]] | None = None,
|
||||
channel: str = "email",
|
||||
) -> dict:
|
||||
try:
|
||||
normalized_items = _normalize_reminder_items(items)
|
||||
except ValueError as exc:
|
||||
raise RepositoryError(str(exc)) from exc
|
||||
if payment_deadline_days < 1 or payment_deadline_days > 365:
|
||||
raise RepositoryError("Die Zahlungsfrist muss zwischen 1 und 365 Tagen liegen.")
|
||||
data, _claim = self.get_claim(member_id, claim_id)
|
||||
reminder = self._find_reminder(data, claim_id, reminder_id)
|
||||
if str(reminder.get("status", "draft")) not in {"draft", "generated"}:
|
||||
raise RepositoryError("Nur ein Entwurf kann bearbeitet werden.")
|
||||
reminder["name"] = name.strip() or reminder["name"]
|
||||
reminder["detail"] = detail.strip()
|
||||
reminder["channel"] = channel.strip() or "email"
|
||||
reminder["payment_deadline_days"] = payment_deadline_days
|
||||
reminder["items"] = normalized_items
|
||||
self.save_contributions(member_id, data)
|
||||
self.append_event(
|
||||
member_id,
|
||||
event_type="reminder_draft_edited",
|
||||
summary=f"Mahnungsentwurf bearbeitet: {reminder['name']}",
|
||||
references={"claim_id": claim_id, "reminder_id": str(reminder["reminder_id"])},
|
||||
data={"fee": money_text(reminder_items_total(reminder))},
|
||||
)
|
||||
return reminder
|
||||
|
||||
def revert_reminder_sent(self, member_id: str, claim_id: str, reminder_id: str) -> dict:
|
||||
"""Undoes mark_reminder_sent: removes the fee items it booked onto the claim
|
||||
and puts the reminder back into "draft" so its text/items can be edited and it
|
||||
can be sent again -- e.g. after generating a Mahnung email and noticing the
|
||||
wording needs a fix. Refuses if a later Mahnstufe already exists (would break
|
||||
the level ordering) or if payments already booked against the claim would no
|
||||
longer fit once the fee is removed."""
|
||||
data, claim = self.get_claim(member_id, claim_id)
|
||||
reminder = self._find_reminder(data, claim_id, reminder_id)
|
||||
if str(reminder.get("status", "")) != "sent":
|
||||
raise RepositoryError("Nur eine bereits versandte Mahnung kann zurückgesetzt werden.")
|
||||
if any(
|
||||
str(item.get("claim_id", "")) == claim_id
|
||||
and int(item.get("level", 0)) > int(reminder.get("level", 0))
|
||||
and str(item.get("status", "draft")) in {"draft", "generated", "sent"}
|
||||
for item in data.reminders
|
||||
):
|
||||
raise RepositoryError(
|
||||
"Es existiert bereits eine höhere Mahnstufe; diese Mahnung kann nicht "
|
||||
"zurückgesetzt werden."
|
||||
)
|
||||
fee_item_ids = set(reminder.get("fee_item_ids") or [])
|
||||
if fee_item_ids:
|
||||
removed_total = sum(
|
||||
(
|
||||
decimal_value(item.get("amount", "0"))
|
||||
for item in claim_items(claim)
|
||||
if str(item.get("item_id", "")) in fee_item_ids
|
||||
),
|
||||
Decimal("0"),
|
||||
)
|
||||
new_total = claim_total(claim) - removed_total
|
||||
if claim_settled_total(data, claim) > new_total:
|
||||
raise RepositoryError(
|
||||
"Für diese Forderung wurden bereits Zahlungen verbucht, die die "
|
||||
"Mahngebühr abdecken; der Versand kann nicht zurückgesetzt werden."
|
||||
)
|
||||
claim["items"] = [
|
||||
item for item in claim_items(claim) if str(item.get("item_id", "")) not in fee_item_ids
|
||||
]
|
||||
claim["amount"] = money_text(claim_total(claim))
|
||||
reminder["status"] = "draft"
|
||||
reminder["sent_at"] = None
|
||||
reminder["payment_deadline"] = None
|
||||
reminder["fee_item_ids"] = []
|
||||
self.save_contributions(member_id, data)
|
||||
self.append_event(
|
||||
member_id,
|
||||
event_type="reminder_sent_reverted",
|
||||
summary=f"Versand zurückgesetzt: {reminder.get('name', '')}",
|
||||
references={"claim_id": claim_id, "reminder_id": str(reminder["reminder_id"])},
|
||||
)
|
||||
return reminder
|
||||
|
||||
def register_reminder_document(
|
||||
self,
|
||||
member_id: str,
|
||||
|
||||
Reference in New Issue
Block a user