mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
Tell members which reference to put on a transfer
A data-review mail that lists the payment frequency but not how to pay leaves the
board matching anonymous transfers by hand. The mail now closes with the
reference to quote -- member number and full name, joined only where both exist
so a missing half cannot leave a dangling dash. Both are offered because either
identifies the payment on its own: a member who has forgotten their number falls
back on the name, and two members sharing a name are told apart by the number.
The note about the shortened IBAN moved out of the fixed template text into
{{data.iban_hint}}, filled only for members whose bank details are actually
listed -- it used to explain a masked IBAN to members who pay by transfer and see
no IBAN at all. It sits directly under the record as a parenthesised footnote,
which also keeps the dropped line from leaving a blank one behind.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9e9bb7d668
commit
31c1646571
@@ -38,6 +38,15 @@ DEFAULT_RECIPIENT_STATUSES: tuple[str, ...] = (
|
||||
# empty line the eye skips over.
|
||||
NOT_STORED = "(nicht hinterlegt)"
|
||||
|
||||
# Only makes sense next to a masked IBAN, so it is filled per member rather than
|
||||
# written into the template as a fixed line. It sits directly under the record and
|
||||
# reads as a footnote to it, which is also why it carries its own parentheses: a
|
||||
# dropped line must not leave a blank one behind.
|
||||
IBAN_HINT = (
|
||||
"(Die IBAN zeigen wir absichtlich nur verkürzt an – die letzten vier Stellen "
|
||||
"genügen zum Abgleich.)"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class GeneratedDataReviewMail:
|
||||
@@ -106,9 +115,7 @@ def member_data_fields(member: Member) -> list[tuple[str, str]]:
|
||||
),
|
||||
]
|
||||
)
|
||||
if any(
|
||||
value.strip() for value in (member.iban, member.mandate_reference, member.account_holder)
|
||||
):
|
||||
if has_bank_details(member):
|
||||
signed = format_date_for_display(member.mandate_signed_at)
|
||||
fields.extend(
|
||||
[
|
||||
@@ -125,6 +132,30 @@ def member_data_fields(member: Member) -> list[tuple[str, str]]:
|
||||
return [(label, value.strip() or NOT_STORED) for label, value in fields]
|
||||
|
||||
|
||||
def has_bank_details(member: Member) -> bool:
|
||||
"""Whether the club stores anything about this member's account at all. Members
|
||||
who pay by transfer have nothing here, and asking them to check a mandate they
|
||||
never gave is noise."""
|
||||
return any(
|
||||
value.strip() for value in (member.iban, member.mandate_reference, member.account_holder)
|
||||
)
|
||||
|
||||
|
||||
def payment_reference(member: Member) -> str:
|
||||
"""What the member should write on a transfer. Both halves are offered because
|
||||
either one identifies the payment on its own: a member who has forgotten their
|
||||
number can fall back on the name, and a name shared by two members is told apart
|
||||
by the number."""
|
||||
return " – ".join(
|
||||
part
|
||||
for part in (
|
||||
member.member_number.strip(),
|
||||
" ".join(value for value in (member.first_name, member.last_name) if value).strip(),
|
||||
)
|
||||
if part
|
||||
)
|
||||
|
||||
|
||||
def data_review_recipients(
|
||||
repository: MemberRepository, *, statuses: tuple[str, ...] | None = None
|
||||
) -> list[Member]:
|
||||
@@ -156,6 +187,8 @@ def data_review_mail_bytes(
|
||||
"member.iban_masked": mask_iban(member.iban),
|
||||
"data.sheet": "\n".join(f"{label}: {value}" for label, value in fields),
|
||||
"data.count": str(len(fields)),
|
||||
"data.iban_hint": IBAN_HINT if has_bank_details(member) else "",
|
||||
"payment.reference": payment_reference(member),
|
||||
}
|
||||
)
|
||||
rendered = render_template(repository, "data_review", values, {"data": entries})
|
||||
|
||||
Reference in New Issue
Block a user