Verbesserte EÜR-Abrechnung
This commit is contained in:
+33
-5
@@ -5,6 +5,7 @@ namespace App\Domains\Event\Actions\CreateIncomeSurplusStatement;
|
|||||||
use App\Enumerations\ParticipationType;
|
use App\Enumerations\ParticipationType;
|
||||||
use App\Models\CostUnit;
|
use App\Models\CostUnit;
|
||||||
use App\Models\Event;
|
use App\Models\Event;
|
||||||
|
use App\Models\Invoice;
|
||||||
use App\Providers\PdfGenerateAndDownloadProvider;
|
use App\Providers\PdfGenerateAndDownloadProvider;
|
||||||
use App\Repositories\CostUnitRepository;
|
use App\Repositories\CostUnitRepository;
|
||||||
use App\ValueObjects\Amount;
|
use App\ValueObjects\Amount;
|
||||||
@@ -52,8 +53,9 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
|
|
||||||
$eventData = $this->event->toResource()->toArray(new Request());
|
$eventData = $this->event->toResource()->toArray(new Request());
|
||||||
|
|
||||||
$income = $this->buildIncome($eventData, $otherIncome);
|
|
||||||
$expenses = $this->buildExpenses($costUnit);
|
$expenses = $this->buildExpenses($costUnit);
|
||||||
|
$donations = $this->buildExpenses($costUnit, donationsOnly: true);
|
||||||
|
$income = $this->buildIncome($eventData, $otherIncome, $donations['total']);
|
||||||
|
|
||||||
$result = new Amount($income['total']->getAmount() - $expenses['total']->getAmount(), 'Euro');
|
$result = new Amount($income['total']->getAmount() - $expenses['total']->getAmount(), 'Euro');
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
'createdAt' => new \DateTime()->format('d.m.Y'),
|
'createdAt' => new \DateTime()->format('d.m.Y'),
|
||||||
'income' => $income,
|
'income' => $income,
|
||||||
'expenses' => $expenses,
|
'expenses' => $expenses,
|
||||||
|
'donations' => $donations,
|
||||||
'result' => $result,
|
'result' => $result,
|
||||||
'money' => self::money(...),
|
'money' => self::money(...),
|
||||||
])->render();
|
])->render();
|
||||||
@@ -70,6 +73,7 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
$response->filename = 'EUER-' . $this->event->identifier . '.pdf';
|
$response->filename = 'EUER-' . $this->event->identifier . '.pdf';
|
||||||
$response->income = $income;
|
$response->income = $income;
|
||||||
$response->expenses = $expenses;
|
$response->expenses = $expenses;
|
||||||
|
$response->donations = $donations;
|
||||||
$response->result = $result;
|
$response->result = $result;
|
||||||
$response->pdfContent = PdfGenerateAndDownloadProvider::fromHtml($html, 'portrait');
|
$response->pdfContent = PdfGenerateAndDownloadProvider::fromHtml($html, 'portrait');
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
*
|
*
|
||||||
* @return array{categories: array<int, array{name: string, total: Amount, entries: array<int, array{name: string, amount: Amount}>}>, total: Amount}
|
* @return array{categories: array<int, array{name: string, total: Amount, entries: array<int, array{name: string, amount: Amount}>}>, total: Amount}
|
||||||
*/
|
*/
|
||||||
private function buildIncome(array $eventData, float $otherIncome): array
|
private function buildIncome(array $eventData, float $otherIncome, Amount $donations): array
|
||||||
{
|
{
|
||||||
// Beiträge aller Teilnahmearten in einer Zeile: Für die Mittelverwendung zählt, was an Beiträgen
|
// Beiträge aller Teilnahmearten in einer Zeile: Für die Mittelverwendung zählt, was an Beiträgen
|
||||||
// hereingekommen ist, nicht von wem.
|
// hereingekommen ist, nicht von wem.
|
||||||
@@ -118,6 +122,8 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
'name' => 'Fördermittel (' . self::money($supportRate) . ' € p.P./Tag)',
|
'name' => 'Fördermittel (' . self::money($supportRate) . ' € p.P./Tag)',
|
||||||
'amount' => new Amount($eventData['supportPerson']['amount']->getAmount(), 'Euro'),
|
'amount' => new Amount($eventData['supportPerson']['amount']->getAmount(), 'Euro'),
|
||||||
],
|
],
|
||||||
|
// Gespendete Auslagen: als Ausgabe ihres Typs gebucht, hier die Gegenbuchung.
|
||||||
|
['name' => 'Spenden', 'amount' => $donations],
|
||||||
];
|
];
|
||||||
|
|
||||||
$categories = [
|
$categories = [
|
||||||
@@ -134,14 +140,17 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
/**
|
/**
|
||||||
* Die Ausgabenseite: eine Zeile je Ausgabentyp, dazu die Belege für die Anlage.
|
* Die Ausgabenseite: eine Zeile je Ausgabentyp, dazu die Belege für die Anlage.
|
||||||
*
|
*
|
||||||
|
* Mit `$donationsOnly` nur die gespendeten Auslagen in derselben Gliederung -- für die Anlage „Spenden"
|
||||||
|
* und die Summe unter den Förderungen.
|
||||||
|
*
|
||||||
* @return array{groups: array<int, array{name: string, sum: Amount, rows: array<int, array{number: string, date: string, purpose: string, amount: Amount}>}>, total: Amount}
|
* @return array{groups: array<int, array{name: string, sum: Amount, rows: array<int, array{number: string, date: string, purpose: string, amount: Amount}>}>, total: Amount}
|
||||||
*/
|
*/
|
||||||
private function buildExpenses(CostUnit $costUnit): array
|
private function buildExpenses(CostUnit $costUnit, bool $donationsOnly = false): array
|
||||||
{
|
{
|
||||||
$groups = [];
|
$groups = [];
|
||||||
$total = new Amount(0, 'Euro');
|
$total = new Amount(0, 'Euro');
|
||||||
|
|
||||||
foreach ($this->costUnits->groupExpensesByType($costUnit) as $group) {
|
foreach ($this->costUnits->groupExpensesByType($costUnit, $donationsOnly) as $group) {
|
||||||
$rows = [];
|
$rows = [];
|
||||||
|
|
||||||
foreach ($group['invoices'] as $invoice) {
|
foreach ($group['invoices'] as $invoice) {
|
||||||
@@ -150,7 +159,7 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
'date' => $invoice->created_at?->format('d.m.Y') ?? '',
|
'date' => $invoice->created_at?->format('d.m.Y') ?? '',
|
||||||
// Ohne die Anmerkung: dort steht, was die Kassenwart*in beim Korrigieren notiert hat,
|
// Ohne die Anmerkung: dort steht, was die Kassenwart*in beim Korrigieren notiert hat,
|
||||||
// und das gehört auf den Beleg, nicht in den Zweck.
|
// und das gehört auf den Beleg, nicht in den Zweck.
|
||||||
'purpose' => $invoice->purposeText(),
|
'purpose' => self::purpose($invoice),
|
||||||
'amount' => Amount::fromString($invoice->amount),
|
'amount' => Amount::fromString($invoice->amount),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -167,6 +176,25 @@ class CreateIncomeSurplusStatementCommand
|
|||||||
return ['groups' => $groups, 'total' => $total];
|
return ['groups' => $groups, 'total' => $total];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Zweck eines Belegs für die Anlage.
|
||||||
|
*
|
||||||
|
* Erfasster oder abgeleiteter Zahlungsgrund wie in der Beleg-Übersicht. Nur wenn es keinen gibt --
|
||||||
|
* ältere Abrechnungen ohne jede Zweckangabe --, steht dort ersatzweise, wem was erstattet wurde. Bewusst
|
||||||
|
* hier und nicht in {@see Invoice::purposeText()}: Dort würde der Ersatztext als erfasster Zahlungsgrund
|
||||||
|
* erscheinen und beim Korrigieren als Änderung protokolliert.
|
||||||
|
*/
|
||||||
|
private static function purpose(Invoice $invoice): string
|
||||||
|
{
|
||||||
|
$purpose = $invoice->purposeText();
|
||||||
|
|
||||||
|
if (trim($purpose) !== '') {
|
||||||
|
return $purpose;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim('Auslagenerstattung ' . $invoice->contact_name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ein Betrag in deutscher Schreibweise: Punkt als Tausender-, Komma als Dezimaltrennzeichen.
|
* Ein Betrag in deutscher Schreibweise: Punkt als Tausender-, Komma als Dezimaltrennzeichen.
|
||||||
*
|
*
|
||||||
|
|||||||
+7
@@ -29,5 +29,12 @@ class CreateIncomeSurplusStatementResponse
|
|||||||
*/
|
*/
|
||||||
public array $expenses = [];
|
public array $expenses = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die gespendeten Auslagen, gegliedert wie die Ausgaben -- die Gegenbuchung zur Zeile „Spenden".
|
||||||
|
*
|
||||||
|
* @var array{groups: array<int, array{name: string, sum: Amount, rows: array<int, array<string, mixed>>}>, total: Amount}|array{}
|
||||||
|
*/
|
||||||
|
public array $donations = [];
|
||||||
|
|
||||||
public ?Amount $result = null;
|
public ?Amount $result = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,9 +172,13 @@ class CostUnitRepository {
|
|||||||
* damit immer dieselbe Gliederung, und eine fehlende Zeile lässt sich nicht mit einer vergessenen
|
* damit immer dieselbe Gliederung, und eine fehlende Zeile lässt sich nicht mit einer vergessenen
|
||||||
* verwechseln.
|
* verwechseln.
|
||||||
*
|
*
|
||||||
|
* Gespendete Auslagen zählen hier mit: Für die Einnahmen-Überschuss-Rechnung ist die Spende eine Ausgabe
|
||||||
|
* ihres Typs und zugleich eine Einnahme. Mit `$donationsOnly` liefert die Methode genau diesen Spendenanteil
|
||||||
|
* in derselben Gliederung -- die Gegenbuchungen lassen sich so ihrer Ausgabenkategorie zuordnen.
|
||||||
|
*
|
||||||
* @return array<string, array{type: InvoiceType, invoices: array<int, Invoice>, sum: Amount}>
|
* @return array<string, array{type: InvoiceType, invoices: array<int, Invoice>, sum: Amount}>
|
||||||
*/
|
*/
|
||||||
public function groupExpensesByType(CostUnit $costUnit) : array {
|
public function groupExpensesByType(CostUnit $costUnit, bool $donationsOnly = false) : array {
|
||||||
$groups = [];
|
$groups = [];
|
||||||
|
|
||||||
foreach (InvoiceType::countingAsExpense() as $invoiceType) {
|
foreach (InvoiceType::countingAsExpense() as $invoiceType) {
|
||||||
@@ -186,7 +190,11 @@ class CostUnitRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($costUnit->invoices()->orderBy('invoice_number')->get() as $invoice) {
|
foreach ($costUnit->invoices()->orderBy('invoice_number')->get() as $invoice) {
|
||||||
if (!self::countsAsExpense($invoice) || !isset($groups[$invoice->type])) {
|
if (
|
||||||
|
!self::isBooked($invoice) ||
|
||||||
|
!isset($groups[$invoice->type]) ||
|
||||||
|
($donationsOnly && !$invoice->donation)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,14 +209,22 @@ class CostUnitRepository {
|
|||||||
* Ob ein Beleg als Ausgabe zählt.
|
* Ob ein Beleg als Ausgabe zählt.
|
||||||
*
|
*
|
||||||
* Abgelehnt und gelöscht sind keine Ausgabe, weil kein Geld fließt. Dasselbe gilt für eine gespendete
|
* Abgelehnt und gelöscht sind keine Ausgabe, weil kein Geld fließt. Dasselbe gilt für eine gespendete
|
||||||
* Auslage: Auf die Auszahlung wurde verzichtet, die Kostenstelle gibt nichts aus.
|
* Auslage: Auf die Auszahlung wurde verzichtet, die Kostenstelle gibt nichts aus. Die Einnahmen-Überschuss-
|
||||||
|
* Rechnung sieht das anders und bucht die Spende als Ausgabe und Einnahme, siehe
|
||||||
|
* {@see self::groupExpensesByType()}.
|
||||||
*/
|
*/
|
||||||
private static function countsAsExpense(Invoice $invoice) : bool {
|
private static function countsAsExpense(Invoice $invoice) : bool {
|
||||||
|
return self::isBooked($invoice) && !$invoice->donation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob ein Beleg überhaupt gebucht ist -- abgelehnt und gelöscht sind es nicht.
|
||||||
|
*/
|
||||||
|
private static function isBooked(Invoice $invoice) : bool {
|
||||||
return !in_array($invoice->status, [
|
return !in_array($invoice->status, [
|
||||||
InvoiceStatus::INVOICE_STATUS_DENIED,
|
InvoiceStatus::INVOICE_STATUS_DENIED,
|
||||||
InvoiceStatus::INVOICE_STATUS_DELETED,
|
InvoiceStatus::INVOICE_STATUS_DELETED,
|
||||||
], true)
|
], true);
|
||||||
&& !$invoice->donation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sumupEstimatedByInvoiceType(CostUnit $costUnit, InvoiceType $invoiceType) : Amount {
|
public function sumupEstimatedByInvoiceType(CostUnit $costUnit, InvoiceType $invoiceType) : Amount {
|
||||||
|
|||||||
@@ -186,8 +186,8 @@
|
|||||||
|
|
||||||
<div class="footnote">
|
<div class="footnote">
|
||||||
Berücksichtigt sind nur tatsächlich vereinnahmte Beiträge und erfasste Belege. Nicht enthalten sind
|
Berücksichtigt sind nur tatsächlich vereinnahmte Beiträge und erfasste Belege. Nicht enthalten sind
|
||||||
offene Beiträge, Budget- und Schätzwerte, erstattete Beiträge sowie Auslagen, auf deren Auszahlung
|
offene Beiträge, Budget- und Schätzwerte sowie erstattete Beiträge. Gespendete Auslagen sind bei den
|
||||||
verzichtet wurde.
|
Ausgaben enthalten und zugleich als Spenden unter den Förderungen ausgewiesen.
|
||||||
@if($event->tax_liable)
|
@if($event->tax_liable)
|
||||||
<br />Alle Beträge sind Bruttobeträge inklusive {{ $event->vat_rate }} % Umsatzsteuer.
|
<br />Alle Beträge sind Bruttobeträge inklusive {{ $event->vat_rate }} % Umsatzsteuer.
|
||||||
@endif
|
@endif
|
||||||
@@ -203,33 +203,27 @@
|
|||||||
<h1>Anlage: Belege</h1>
|
<h1>Anlage: Belege</h1>
|
||||||
<div class="subline">{{ $event->name }} · Stand: {{ $createdAt }}</div>
|
<div class="subline">{{ $event->name }} · Stand: {{ $createdAt }}</div>
|
||||||
|
|
||||||
@foreach($documentedGroups as $group)
|
@include('pdfs.partials.receipt-groups', ['groups' => $documentedGroups])
|
||||||
<h3>{{ $group['name'] }}</h3>
|
@endif
|
||||||
<table class="receipts">
|
|
||||||
<thead>
|
@php
|
||||||
<tr>
|
$donatedGroups = array_filter($donations['groups'], fn ($group) => count($group['rows']) > 0);
|
||||||
<th style="width: 22mm;">Beleg-Nr.</th>
|
@endphp
|
||||||
<th style="width: 18mm;">Datum</th>
|
|
||||||
<th>Zweck</th>
|
@if(count($donatedGroups) > 0)
|
||||||
<th class="value">Betrag</th>
|
<div class="page-break"></div>
|
||||||
|
|
||||||
|
<h1>Anlage: Spenden</h1>
|
||||||
|
<div class="subline">{{ $event->name }} · Stand: {{ $createdAt }}</div>
|
||||||
|
|
||||||
|
@include('pdfs.partials.receipt-groups', ['groups' => $donatedGroups])
|
||||||
|
|
||||||
|
<table class="statement">
|
||||||
|
<tr class="sum">
|
||||||
|
<td>Summe Spenden</td>
|
||||||
|
<td class="value">{{ $money($donations['total']) }} €</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($group['rows'] as $row)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $row['number'] }}</td>
|
|
||||||
<td>{{ $row['date'] }}</td>
|
|
||||||
<td>{{ $row['purpose'] }}</td>
|
|
||||||
<td class="value">{{ $money($row['amount']) }} €</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
<tr class="group-sum">
|
|
||||||
<td colspan="3">Zwischensumme {{ $group['name'] }}</td>
|
|
||||||
<td class="value">{{ $money($group['sum']) }} €</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
@endforeach
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{{-- Belege je Ausgabentyp mit Zwischensumme; nur Gruppen mit Belegen übergeben. --}}
|
||||||
|
@foreach($groups as $group)
|
||||||
|
<h3>{{ $group['name'] }}</h3>
|
||||||
|
<table class="receipts">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 22mm;">Beleg-Nr.</th>
|
||||||
|
<th style="width: 18mm;">Datum</th>
|
||||||
|
<th>Zweck</th>
|
||||||
|
<th class="value">Betrag</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($group['rows'] as $row)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $row['number'] }}</td>
|
||||||
|
<td>{{ $row['date'] }}</td>
|
||||||
|
<td>{{ $row['purpose'] }}</td>
|
||||||
|
<td class="value">{{ $money($row['amount']) }} €</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
<tr class="group-sum">
|
||||||
|
<td colspan="3">Zwischensumme {{ $group['name'] }}</td>
|
||||||
|
<td class="value">{{ $money($group['sum']) }} €</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
@endforeach
|
||||||
@@ -202,11 +202,95 @@ class EventIncomeSurplusStatementTest extends TestCase
|
|||||||
// Kein Geldfluss, also keine Ausgabe:
|
// Kein Geldfluss, also keine Ausgabe:
|
||||||
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 999.0, InvoiceStatus::INVOICE_STATUS_DENIED);
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 999.0, InvoiceStatus::INVOICE_STATUS_DENIED);
|
||||||
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 888.0, InvoiceStatus::INVOICE_STATUS_DELETED);
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 888.0, InvoiceStatus::INVOICE_STATUS_DELETED);
|
||||||
|
|
||||||
|
// Eine Spende ist Ausgabe ihres Typs (und zugleich Einnahme unter den Förderungen):
|
||||||
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 777.0, InvoiceStatus::INVOICE_STATUS_NEW, donation: true);
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 777.0, InvoiceStatus::INVOICE_STATUS_NEW, donation: true);
|
||||||
|
|
||||||
$this->assertEqualsWithDelta(150.0, $this->group('Programmkosten')['sum']->getAmount(), 0.001);
|
$this->assertEqualsWithDelta(927.0, $this->group('Programmkosten')['sum']->getAmount(), 0.001);
|
||||||
$this->assertEqualsWithDelta(30.0, $this->group('Verpflegungskosten')['sum']->getAmount(), 0.001);
|
$this->assertEqualsWithDelta(30.0, $this->group('Verpflegungskosten')['sum']->getAmount(), 0.001);
|
||||||
$this->assertEqualsWithDelta(180.0, $this->statement()->expenses['total']->getAmount(), 0.001);
|
$this->assertEqualsWithDelta(957.0, $this->statement()->expenses['total']->getAmount(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Spenden
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function test_without_donations_the_donation_line_is_zero(): void
|
||||||
|
{
|
||||||
|
$this->makeEvent();
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 100.0, InvoiceStatus::INVOICE_STATUS_NEW);
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(0.0, $this->entry('Spenden')->getAmount(), 0.001);
|
||||||
|
$this->assertEqualsWithDelta(0.0, $this->statement()->donations['total']->getAmount(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_a_donation_is_an_expense_of_its_type_and_funding_income(): void
|
||||||
|
{
|
||||||
|
$this->makeEvent();
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 100.0, InvoiceStatus::INVOICE_STATUS_NEW);
|
||||||
|
$resultWithoutDonation = $this->statement()->result->getAmount();
|
||||||
|
|
||||||
|
$donated = $this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 40.0, InvoiceStatus::INVOICE_STATUS_APPROVED, donation: true);
|
||||||
|
|
||||||
|
$statement = $this->statement();
|
||||||
|
$program = $this->group('Programmkosten');
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(140.0, $program['sum']->getAmount(), 0.001);
|
||||||
|
$this->assertContains($donated->invoice_number, array_column($program['rows'], 'number'));
|
||||||
|
|
||||||
|
// Gegenbuchung unter den Förderungen (ohne Anmeldung keine Fördermittel, also nur die Spende).
|
||||||
|
$this->assertSame('Förderungen', $statement->income['categories'][1]['name']);
|
||||||
|
$this->assertEqualsWithDelta(40.0, $this->entry('Spenden')->getAmount(), 0.001);
|
||||||
|
$this->assertEqualsWithDelta(40.0, $statement->income['categories'][1]['total']->getAmount(), 0.001);
|
||||||
|
|
||||||
|
// Ausgabe und Einnahme heben sich auf.
|
||||||
|
$this->assertEqualsWithDelta($resultWithoutDonation, $statement->result->getAmount(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_donations_are_grouped_like_expenses(): void
|
||||||
|
{
|
||||||
|
$this->makeEvent();
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 100.0, InvoiceStatus::INVOICE_STATUS_NEW);
|
||||||
|
$programDonation = $this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 25.0, InvoiceStatus::INVOICE_STATUS_NEW, donation: true);
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 15.0, InvoiceStatus::INVOICE_STATUS_EXPORTED, donation: true);
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_CATERING, 60.0, InvoiceStatus::INVOICE_STATUS_APPROVED, donation: true);
|
||||||
|
|
||||||
|
$donations = $this->statement()->donations;
|
||||||
|
|
||||||
|
// Dieselbe Gliederung wie die Ausgaben, auch Typen ohne Spende stehen darin.
|
||||||
|
$this->assertSame(
|
||||||
|
array_column($this->statement()->expenses['groups'], 'name'),
|
||||||
|
array_column($donations['groups'], 'name')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(40.0, $this->donationGroup('Programmkosten')['sum']->getAmount(), 0.001);
|
||||||
|
$this->assertEqualsWithDelta(60.0, $this->donationGroup('Verpflegungskosten')['sum']->getAmount(), 0.001);
|
||||||
|
$this->assertEqualsWithDelta(100.0, $donations['total']->getAmount(), 0.001);
|
||||||
|
$this->assertEqualsWithDelta(100.0, $this->entry('Spenden')->getAmount(), 0.001);
|
||||||
|
|
||||||
|
// Derselbe Beleg steht als Ausgabe und als Spende.
|
||||||
|
$this->assertContains($programDonation->invoice_number, array_column($this->group('Programmkosten')['rows'], 'number'));
|
||||||
|
$this->assertContains($programDonation->invoice_number, array_column($this->donationGroup('Programmkosten')['rows'], 'number'));
|
||||||
|
|
||||||
|
// Nicht gespendete Belege stehen nicht in der Spenden-Anlage.
|
||||||
|
$this->assertCount(2, $this->donationGroup('Programmkosten')['rows']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_donations_list_only_booked_donations(): void
|
||||||
|
{
|
||||||
|
$this->makeEvent();
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 20.0, InvoiceStatus::INVOICE_STATUS_NEW, donation: true);
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 999.0, InvoiceStatus::INVOICE_STATUS_DENIED, donation: true);
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 888.0, InvoiceStatus::INVOICE_STATUS_DELETED, donation: true);
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PARTICIPATION_REFUND, 220.0, InvoiceStatus::INVOICE_STATUS_EXPORTED, donation: true);
|
||||||
|
|
||||||
|
$donations = $this->statement()->donations;
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(20.0, $donations['total']->getAmount(), 0.001);
|
||||||
|
$this->assertCount(1, $this->donationGroup('Programmkosten')['rows']);
|
||||||
|
$this->assertNotContains('Beitragserstattung', array_column($donations['groups'], 'name'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_a_refund_is_no_expense(): void
|
public function test_a_refund_is_no_expense(): void
|
||||||
@@ -311,12 +395,34 @@ class EventIncomeSurplusStatementTest extends TestCase
|
|||||||
public function test_the_comment_is_no_longer_part_of_the_purpose(): void
|
public function test_the_comment_is_no_longer_part_of_the_purpose(): void
|
||||||
{
|
{
|
||||||
// Die Anmerkung trägt, was die Kassenwart*in beim Korrigieren notiert hat -- sie steht auf dem
|
// Die Anmerkung trägt, was die Kassenwart*in beim Korrigieren notiert hat -- sie steht auf dem
|
||||||
// Beleg-PDF und hat im Zweck nichts zu suchen. Bestandsbelege ohne erfassten Zahlungsgrund haben
|
// Beleg-PDF und hat im Zweck nichts zu suchen.
|
||||||
// hier deshalb eine leere Zelle; nachtragen lässt er sich beim Korrigieren.
|
|
||||||
$this->makeEvent();
|
$this->makeEvent();
|
||||||
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 100.0, InvoiceStatus::INVOICE_STATUS_NEW);
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 100.0, InvoiceStatus::INVOICE_STATUS_NEW);
|
||||||
|
|
||||||
$this->assertSame('', $this->group('Programmkosten')['rows'][0]['purpose']);
|
$this->assertStringNotContainsString('Materialkauf', $this->group('Programmkosten')['rows'][0]['purpose']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_an_empty_purpose_falls_back_to_the_refund_of_the_contact(): void
|
||||||
|
{
|
||||||
|
// Ältere Abrechnungen ohne jede Zweckangabe: ersatzweise, wem erstattet wurde -- als Ausgabe wie als Spende.
|
||||||
|
$this->makeEvent();
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 100.0, InvoiceStatus::INVOICE_STATUS_NEW);
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 30.0, InvoiceStatus::INVOICE_STATUS_NEW, donation: true);
|
||||||
|
$this->makeInvoice(
|
||||||
|
InvoiceType::INVOICE_TYPE_PROGRAM,
|
||||||
|
50.0,
|
||||||
|
InvoiceStatus::INVOICE_STATUS_NEW,
|
||||||
|
purpose: 'Material für den Bastelnachmittag'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
['Auslagenerstattung Mika Muster', 'Auslagenerstattung Mika Muster', 'Material für den Bastelnachmittag'],
|
||||||
|
array_column($this->group('Programmkosten')['rows'], 'purpose')
|
||||||
|
);
|
||||||
|
$this->assertSame(
|
||||||
|
['Auslagenerstattung Mika Muster'],
|
||||||
|
array_column($this->donationGroup('Programmkosten')['rows'], 'purpose')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -388,6 +494,20 @@ class EventIncomeSurplusStatementTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_a_donation_still_does_not_count_in_the_expense_overview(): void
|
||||||
|
{
|
||||||
|
// Die Bildschirm-Übersicht kennt keine Spenden-Einnahme -- dort bleibt die Spende außen vor.
|
||||||
|
$this->makeEvent();
|
||||||
|
$this->makeInvoice(InvoiceType::INVOICE_TYPE_PROGRAM, 777.0, InvoiceStatus::INVOICE_STATUS_NEW, donation: true);
|
||||||
|
|
||||||
|
$amount = new CostUnitRepository()->sumupByInvoiceType(
|
||||||
|
$this->costUnit->fresh(),
|
||||||
|
InvoiceType::where('slug', InvoiceType::INVOICE_TYPE_PROGRAM)->first()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(0.0, $amount->getAmount(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Helfer
|
| Helfer
|
||||||
@@ -435,6 +555,18 @@ class EventIncomeSurplusStatementTest extends TestCase
|
|||||||
$this->fail('Ausgaben-Gruppe "' . $name . '" fehlt.');
|
$this->fail('Ausgaben-Gruppe "' . $name . '" fehlt.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array{name: string, sum: Amount, rows: array<int, array<string, mixed>>} */
|
||||||
|
private function donationGroup(string $name): array
|
||||||
|
{
|
||||||
|
foreach ($this->statement()->donations['groups'] as $group) {
|
||||||
|
if ($group['name'] === $name) {
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fail('Spenden-Gruppe "' . $name . '" fehlt.');
|
||||||
|
}
|
||||||
|
|
||||||
private function makeEvent(): Event
|
private function makeEvent(): Event
|
||||||
{
|
{
|
||||||
$fee = EventParticipationFee::create([
|
$fee = EventParticipationFee::create([
|
||||||
|
|||||||
Reference in New Issue
Block a user