Zahlungszwecke

This commit is contained in:
2026-09-07 14:17:50 +02:00
parent 350c8dd0d0
commit 40d5634764
27 changed files with 927 additions and 205 deletions
+193
View File
@@ -0,0 +1,193 @@
<?php
namespace Tests\Feature;
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceCommand;
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceResponse;
use App\Enumerations\CostUnitType;
use App\Enumerations\InvoiceStatus;
use App\Enumerations\InvoiceType;
use App\Enumerations\TravelReason;
use App\Models\CostUnit;
use App\Models\Invoice;
use App\Models\Tenant;
use App\Resources\InvoiceResource;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
/**
* Der Reisegrund ist eine Auswahl, kein Freitext mehr.
*
* Gespeichert wird der Schlüssel des Grundes -- außer bei "Anderer Grund": Der Schlüssel `other` sagt
* für sich nichts aus, deshalb steht dort der Text. Dieselbe Spalte führt damit weiter den Freitext der
* Bestandsbelege, ohne dass etwas umgeschrieben werden musste.
*/
class TravelReasonTest extends TestCase
{
use RefreshDatabase;
private Tenant $tenant;
protected function setUp(): void
{
parent::setUp();
$this->tenant = Tenant::create([
'slug' => 'wm',
'name' => 'Wilde Möhre',
'address_1' => 'Musterweg 1',
'email' => 't@example.com',
'email_finance' => 'finance@example.com',
'url' => parse_url(config('app.url'), PHP_URL_HOST),
'account_name' => 'Test e.V.',
'account_iban' => 'DE00',
'account_bic' => 'XY',
'city' => 'Stadt',
'postcode' => '00000',
'invoice_prefix' => 'WM',
'is_active_local_group' => true,
'has_active_instance' => true,
]);
app()->instance('tenant', $this->tenant);
DB::table('cost_unit_types')->insert(['slug' => CostUnitType::COST_UNIT_TYPE_EVENT, 'name' => 'Veranstaltung']);
DB::table('invoice_status')->insert(['slug' => InvoiceStatus::INVOICE_STATUS_NEW]);
DB::table('invoice_types')->insert([
'slug' => InvoiceType::INVOICE_TYPE_TRAVELLING,
'name' => 'Fahrtkosten',
'sort_order' => 1,
'selectable' => true,
'counts_as_expense' => true,
]);
Mail::fake();
}
private function makeCostUnit(): CostUnit
{
return CostUnit::create([
'tenant' => $this->tenant->slug,
'name' => 'Sommerlager',
'type' => CostUnitType::COST_UNIT_TYPE_EVENT,
'distance_allowance' => 0.25,
'mail_on_new' => false,
'allow_new' => true,
'archived' => false,
]);
}
/**
* Der Weg, den eine eingereichte Fahrtkostenabrechnung nimmt. `contactEmail` bleibt leer, damit kein
* Bestätigungsmailversand mitläuft.
*/
private function submit(?string $reason, ?string $note = null, ?string $travellers = 'Mika und Kim'): CreateInvoiceResponse
{
return new CreateInvoiceCommand(new CreateInvoiceRequest(
costUnit: $this->makeCostUnit(),
contactName: 'Max Mustermann',
invoiceType: InvoiceType::INVOICE_TYPE_TRAVELLING,
totalAmount: 42.0,
receiptFile: null,
isDonation: false,
travelRoute: 'Halle Leipzig',
travelReason: $reason,
travelReasonNote: $note,
travellers: $travellers,
))->execute();
}
/*
|--------------------------------------------------------------------------
| Die Auswahl
|--------------------------------------------------------------------------
*/
public function test_the_options_are_ordered_and_only_the_last_one_needs_a_note(): void
{
$options = TravelReason::options();
$this->assertSame(
['event_travel', 'material_transport', 'purchase', 'other'],
array_column($options, 'value')
);
$this->assertSame('An-/Abreise zur Veranstaltung', $options[0]['label']);
$this->assertSame([false, false, false, true], array_column($options, 'requiresNote'));
}
/*
|--------------------------------------------------------------------------
| Was gespeichert wird
|--------------------------------------------------------------------------
*/
public function test_a_fixed_reason_is_stored_as_its_key(): void
{
$invoice = $this->submit(TravelReason::MATERIAL_TRANSPORT)->invoice;
$this->assertSame(TravelReason::MATERIAL_TRANSPORT, $invoice->travel_reason);
}
public function test_the_purpose_carries_the_name_not_the_key(): void
{
// In der Belegliste soll "Materialtransport" stehen, nicht "material_transport".
$invoice = $this->submit(TravelReason::MATERIAL_TRANSPORT)->invoice;
$this->assertSame('Materialtransport — Mika und Kim', $invoice->purpose);
}
public function test_another_reason_stores_the_text_instead_of_the_key(): void
{
$invoice = $this->submit(TravelReason::OTHER, 'Abholung der Ausrüstung')->invoice;
$this->assertSame('Abholung der Ausrüstung', $invoice->travel_reason);
$this->assertSame('Abholung der Ausrüstung — Mika und Kim', $invoice->purpose);
}
public function test_another_reason_without_a_text_creates_nothing(): void
{
// Sicherheitsnetz hinter der Oberfläche: Dort geht es ohne Erläuterung nicht weiter.
$response = $this->submit(TravelReason::OTHER, ' ');
$this->assertFalse($response->success);
$this->assertNull($response->invoice);
$this->assertSame(0, Invoice::count());
$this->assertNotNull($response->message);
}
public function test_an_unknown_value_passes_through_unchanged(): void
{
// Bestandsbelege und ihre Kopien bringen ihren Freitext schon mit.
$invoice = $this->submit('Fahrt zum Landeslager')->invoice;
$this->assertSame('Fahrt zum Landeslager', $invoice->travel_reason);
$this->assertSame('Fahrt zum Landeslager — Mika und Kim', $invoice->purpose);
}
/*
|--------------------------------------------------------------------------
| Was angezeigt wird
|--------------------------------------------------------------------------
*/
public function test_a_key_is_shown_as_its_name(): void
{
$invoice = $this->submit(TravelReason::EVENT_TRAVEL)->invoice;
$this->assertSame('An-/Abreise zur Veranstaltung', $invoice->travelReasonText());
$this->assertSame(
'An-/Abreise zur Veranstaltung',
new InvoiceResource($invoice)->toArray()['travelReason']
);
}
public function test_free_text_is_shown_as_itself(): void
{
$invoice = $this->submit('Fahrt zum Landeslager')->invoice;
$this->assertSame('Fahrt zum Landeslager', $invoice->travelReasonText());
}
}