Fix supervisor payments mobile tabs, reconcile donor, report method buckets, and refund/donation accounting

- Supervisor Payments: mode tabs now wrap on mobile instead of overflowing off-screen.
- Reconciling a Yoco transaction as a donation now lets staff pick who it's from.
- Reports payment-method breakdown now buckets into Cash/Card/EFT/Other everywhere,
  folding Apple Pay, Google Pay, and Yoco-portal payments into Card.
- Refunds now net against their original method's bucket (Cashup/Finance/Profit reports,
  My Payments filtering/display) instead of vanishing or falling into "Other".
- Refund form's method dropdown mirrors the real payment methods and auto-fills from the
  payment being refunded, replacing an ambiguous generic "Refund" option.
- Fixed donation remaining/unallocated balance inflating instead of shrinking when a
  donation is refunded (assign-donation endpoint, cashup reports, donations reports,
  and the Assign Donation panel all summed refund legs with the wrong sign).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 09:08:53 +02:00
co-authored by Claude Sonnet 5
parent 70605923bf
commit 4bee1b24f7
7 changed files with 125 additions and 39 deletions
@@ -83,7 +83,7 @@ const reconcileYocoTransaction = async (req, res) => {
}
const { id } = req.params;
const { registrationId, eventId } = req.body || {};
const { registrationId, eventId, userId: bodyUserId } = req.body || {};
const ytx = await Yoco.findUnique({ where: { id } });
if (!ytx) return res.status(404).json({ success: false, message: 'YocoTransaction not found' });
@@ -118,7 +118,15 @@ const reconcileYocoTransaction = async (req, res) => {
let resolvedUserId = null;
if (registration?.userId) {
resolvedUserId = registration.userId;
} else if (ytx?.raw?.payload?.metadata?.userId) {
} else if (bodyUserId) {
// Staff explicitly picked the donor while reconciling (e.g. as a donation) — trust that
// over metadata guesswork, but still verify the user actually exists.
try {
const exists = await prisma.user.findUnique({ where: { id: String(bodyUserId) } });
if (exists) resolvedUserId = String(bodyUserId);
} catch {}
}
if (!resolvedUserId && ytx?.raw?.payload?.metadata?.userId) {
const metaUserId = String(ytx.raw.payload.metadata.userId);
try {
const exists = await prisma.user.findUnique({ where: { id: metaUserId } });