Calculation errors for refunded amounts

This commit is contained in:
2026-09-04 01:15:37 +02:00
parent 5beb2b1d97
commit c1c1a4f143
28 changed files with 1436 additions and 26 deletions
+4 -1
View File
@@ -32,7 +32,10 @@ class CostUnitResource {
$amounts = [];
$overAllAmount = new Amount(0, 'Euro');
$overAllEstimatedAmount = new Amount(0, 'Euro');
foreach (InvoiceType::orderBy('sort_order')->get() as $invoiceType) {
// Nur echte Aufwandsarten: Eine Beitragserstattung ist die Rücknahme einer Einnahme und wird auf
// der Einnahmenseite bereits berücksichtigt -- hier gezählt, stünde sie ein zweites Mal in der
// Bilanz. `totalAmount` weiter oben bleibt davon unberührt, das ist die Kassensicht.
foreach (InvoiceType::countingAsExpense() as $invoiceType) {
$overAllAmount->addAmount($costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType));
$overAllEstimatedAmount->addAmount($costUnitRepository->sumupEstimatedByInvoiceType($this->costUnit, $invoiceType));
$amounts[$invoiceType->slug]['string'] = $costUnitRepository->sumupByInvoiceType($this->costUnit, $invoiceType)->toString();
+37
View File
@@ -96,6 +96,14 @@ class EventResource extends JsonResource{
$returnArray['income'] = $this->calculateIncomes($returnArray['participants'], $returnArray['supportPerson']['amount']);
// Eigene Zeile in der Übersicht: In den Zeilen je Teilnahmeart hätte der Betrag nichts zu suchen,
// dort stehen nur aktive Anmeldungen.
$retainedFromUnregistered = $this->sumPaidOfUnregistered();
$returnArray['retainedFromUnregistered'] = [
'value' => $retainedFromUnregistered->getAmount(),
'readable' => $retainedFromUnregistered->toString(),
];
$totalBalanceReal = new Amount(0, 'Euro');
$totalBalanceExpected = new Amount(0, 'Euro');
@@ -272,6 +280,13 @@ class EventResource extends JsonResource{
$realAmount->addAmount(new Amount($participantData['amount']['paid']['value'], 'Euro'));
}
// Was abgemeldete Teilis gezahlt haben und nicht zurückbekommen, gehört in beide Spalten: Das
// Geld liegt beim Verband (real) und fließt nicht mehr ab (erwartet). Ohne diese Zeile stünde
// jede Veranstaltung mit Abmeldungen dauerhaft schlechter da, als sie ist.
$retained = $this->sumPaidOfUnregistered();
$realAmount->addAmount($retained);
$expectedAmount->addAmount($retained);
return ['real' => [
'amount' => $realAmount,
'readable' => $realAmount->toString()
@@ -283,6 +298,28 @@ class EventResource extends JsonResource{
];
}
/**
* Was von abgemeldeten Teilis beim Verband geblieben ist.
*
* `amount_paid` führt nach einer Erstattung genau den einbehaltenen Rest; wurde nie erstattet, steht
* dort der volle gezahlte Beitrag. Beides ist Geld, das der Veranstaltung zusteht.
*
* Bewusst eine direkte Abfrage wie in {@see self::getParticipants()} nebenan -- ein einzelner
* Repository-Aufruf zwischen den Inline-Queries dieser Klasse würde sie uneinheitlicher machen.
*/
public function sumPaidOfUnregistered() : Amount
{
$sum = new Amount(0, 'Euro');
foreach ($this->event->participants()->whereNotNull('unregistered_at')->get() as $participant) {
if ($participant->amount_paid !== null) {
$sum->addAmount($participant->amount_paid);
}
}
return $sum;
}
public function getParticipants(string $participationType) : array {
$returnData = [];
$returnData['amount'] = [
@@ -29,6 +29,12 @@ class ParticipantRefundResource extends JsonResource
'reason' => $this->resource->reason,
'reasonLabel' => $this->resource->reasonLabel(),
'reasonNote' => $this->resource->reason_note,
// Was beim Verband bleibt. `hasRetention` erspart dem Frontend den Betragsvergleich samt
// Rundungsfrage -- es soll nur entscheiden, ob der Hinweis angezeigt wird.
'hasRetention' => $this->resource->hasRetention(),
'retainedAmount' => $this->resource->retained_amount?->toString() ?? '0,00 Euro',
'retentionReasonLabel' => $this->resource->retentionReasonLabel(),
'retentionReasonNote' => $this->resource->retention_reason_note,
'releasedAt' => $this->resource->released_at?->format('d.m.Y'),
'acceptedAt' => $this->resource->accepted_at?->format('d.m.Y'),
'cancelledAt' => $this->resource->cancelled_at?->format('d.m.Y'),