diff --git a/CHANGELOG.md b/CHANGELOG.md index 383f82b..acb404c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project follows [Semantic Versioning](https://semver.org/). - Supervisors can now unassign a donation that was previously applied to a registration, from a new "Assigned donations" list on the Payments page. This reverses the allocation (the registration's balance goes back up and the donation becomes available again), reverting the registration's status and revoking any tickets issued only because that allocation completed payment — blocked if a ticket has already been scanned. The registrant is notified by email/WhatsApp, mirroring the notification sent when a donation is first applied. +### Fixed + +- The internal admin notification for a donation applied to a registration read "Payment recorded" / "Registration payment", indistinguishable from a real incoming payment even though no new money changed hands — it now says "Donation applied" throughout, matching the registrant-facing email's distinct wording. + ### Performance - Reconciling a Yoco card payment or sending a payment link on the supervisor Payments page no longer blocks the response on ticket-PDF generation and email/WhatsApp sends — these now run in the background, matching how manual payments already worked. diff --git a/backend/src/utils/notifications.js b/backend/src/utils/notifications.js index 84cf5ed..44f7c1c 100644 --- a/backend/src/utils/notifications.js +++ b/backend/src/utils/notifications.js @@ -697,6 +697,29 @@ function buildDonationUnassignedFromRegistrant(payment) { return { subject, text, html: emailWrapper(body, { preheader }) }; } +// Internal admin notice for a donation-assignment — same table layout as +// buildPaymentAdminNotice (which reads payment.user as "Payer" — for a leg that's the donor, +// since legs copy userId from the original donation, not the registrant), relabeled so it +// doesn't read as a fresh incoming payment: no new money changed hands here, an +// already-recorded donation was just reallocated to a registration. +function buildDonationAssignmentAdminNotice(payment) { + const notice = buildPaymentAdminNotice(payment); + const eventTitle = payment.registration?.event?.title || 'Event'; + const payerName = payment.user?.name || '—'; + const subject = `Donation applied: ${fmtAmount(payment.amount)} — ${payerName} (${eventTitle})`; + return { + ...notice, + subject, + html: notice.html + .replace('Payment recorded', 'Donation applied') + .replace('Internal notification', 'Internal notification — donation applied to a registration') + .replace('>Registration payment<', '>Donation applied<'), + text: notice.text + .replace('Payment recorded', 'Donation applied') + .replace('Type: Registration payment', 'Type: Donation applied'), + }; +} + // Internal admin notice for a donation-unassignment — same table layout as // buildPaymentAdminNotice (which reads payment.user as "Payer" — for a leg that's the donor, // since legs copy userId from the original donation, not the registrant), with copy adjusted @@ -711,8 +734,11 @@ function buildDonationUnassignmentAdminNotice(payment) { subject, html: notice.html .replace('Payment recorded', 'Donation unassigned') - .replace('Internal notification', 'Internal notification — donation removed from registration'), - text: notice.text.replace('Payment recorded', 'Donation unassigned'), + .replace('Internal notification', 'Internal notification — donation removed from registration') + .replace('>Registration payment<', '>Donation unassigned<'), + text: notice.text + .replace('Payment recorded', 'Donation unassigned') + .replace('Type: Registration payment', 'Type: Donation unassigned'), }; } @@ -1057,7 +1083,7 @@ async function sendDonationAssignmentEmails(paymentId) { } else { sends.push(waTextAny(user, buildWADonationAppliedToRegistrant(payment))); } - const adminMsg = buildPaymentAdminNotice(payment); + const adminMsg = buildDonationAssignmentAdminNotice(payment); if (adminMsg.to && adminMsg.to.length) { sends.push(sendMail({ to: adminMsg.to.join(','), subject: adminMsg.subject, html: adminMsg.html, text: adminMsg.text })); }