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:
@@ -103,18 +103,23 @@ async function computeEventFinancials(eventId) {
|
||||
quantityByOption[optId] = (quantityByOption[optId] || 0) + t.quantity;
|
||||
}
|
||||
|
||||
// Real inflows only — excludes donation-application legs, which would otherwise double-count
|
||||
// money already counted once via the source donation (e.g. a R250 donation with R50 assigned
|
||||
// to a registration must total R250 received, not R300).
|
||||
const nonRefundPayments = payments.filter(p => p.amount > 0 && !isDonationLeg(p));
|
||||
// Excludes donation-application legs, which would otherwise double-count money already
|
||||
// counted once via the source donation (e.g. a R250 donation with R50 assigned to a
|
||||
// registration must total R250 received, not R300). Refunds (negative amount) are kept in —
|
||||
// a card refund must subtract from the 'card' bucket it was refunded against, not vanish from
|
||||
// the per-method breakdown while still being netted out of totalRevenue below.
|
||||
const realPayments = payments.filter(p => !isDonationLeg(p));
|
||||
// Donations are never mutated once assigned — assignment creates a separate "leg" Payment
|
||||
// row (isDonation:false, originalPaymentId -> the donation), so a donation's registrationId
|
||||
// stays null forever. Its actual unallocated amount is its original amount minus every leg
|
||||
// that already references it, not simply "every donation with no registrationId".
|
||||
// that already references it, not simply "every donation with no registrationId". A refund
|
||||
// of the donation itself also creates such a leg, with a negative amount — Math.abs() so a
|
||||
// refund reduces the unallocated balance instead of inflating it (a raw signed sum would
|
||||
// subtract a negative, adding the refund back on top).
|
||||
const legsByDonationId = new Map();
|
||||
for (const p of payments) {
|
||||
if (p.originalPaymentId && !p.isDonation) {
|
||||
legsByDonationId.set(p.originalPaymentId, (legsByDonationId.get(p.originalPaymentId) || 0) + p.amount);
|
||||
legsByDonationId.set(p.originalPaymentId, (legsByDonationId.get(p.originalPaymentId) || 0) + Math.abs(p.amount));
|
||||
}
|
||||
}
|
||||
const donationPayments = payments.filter(p => p.isDonation);
|
||||
@@ -123,7 +128,7 @@ async function computeEventFinancials(eventId) {
|
||||
const totalDonations = donationPayments.reduce((sum, p) => sum + p.amount, 0);
|
||||
|
||||
const paymentsByMethod = emptyByMethod();
|
||||
for (const p of nonRefundPayments) {
|
||||
for (const p of realPayments) {
|
||||
paymentsByMethod[bucketForMethod(p.method)] += p.amount;
|
||||
}
|
||||
const totalRevenue = payments.reduce((sum, p) => sum + (isDonationLeg(p) ? 0 : p.amount), 0);
|
||||
|
||||
Reference in New Issue
Block a user