Teilnahmerechnungen erstellen
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Domains\Event\Actions\CreateEvent\CreateEventCommand;
|
||||
use App\Domains\Event\Actions\CreateEvent\CreateEventRequest;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpCommand;
|
||||
use App\Domains\Event\Actions\SignUp\SignUpRequest;
|
||||
use App\Enumerations\EatingHabit;
|
||||
use App\Enumerations\EfzStatus;
|
||||
use App\Enumerations\FirstAidPermission;
|
||||
use App\Enumerations\ParticipationFeeType;
|
||||
use App\Enumerations\ParticipationType;
|
||||
use App\Enumerations\SwimmingPermission;
|
||||
use App\Models\Event;
|
||||
use App\Models\PaymentMethod;
|
||||
use App\Models\Tenant;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Die Rechnungsnummer wird nirgends gespeichert, sondern aus `events.invoice_key` und
|
||||
* `event_participants.invoice_sequence` zusammengesetzt. Beide Bausteine müssen deshalb stabil sein.
|
||||
*/
|
||||
class InvoiceNumberingTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Tenant $wildeMoehre;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->wildeMoehre = $this->makeTenant('wm', 'Wilde Möhre');
|
||||
|
||||
DB::table('participation_fee_types')->insert(['slug' => 'fixed', 'name' => 'Fix']);
|
||||
EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_VEGAN, 'name' => 'Vegan']);
|
||||
EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_VEGETARIAN, 'name' => 'Vegetarisch']);
|
||||
ParticipationType::create(['slug' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, 'name' => 'Teilnehmende']);
|
||||
PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]);
|
||||
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']);
|
||||
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']);
|
||||
SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'name' => 'Darf baden', 'short' => 'Erteilt']);
|
||||
FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, 'name' => 'Zugestimmt', 'description' => '']);
|
||||
|
||||
app()->instance('tenant', $this->wildeMoehre);
|
||||
}
|
||||
|
||||
/**
|
||||
* `create()` liefert nur die übergebenen Attribute zurück; die DB-Defaults (u.a. `tax_liable`) stehen
|
||||
* erst nach dem Neuladen bereit -- so, wie der Tenant im Betrieb aus der IdentifyTenant-Middleware kommt.
|
||||
*/
|
||||
private function makeTenant(string $slug, string $name): Tenant
|
||||
{
|
||||
return Tenant::create([
|
||||
'slug' => $slug,
|
||||
'name' => $name,
|
||||
'address_1' => 'Musterweg 1',
|
||||
'email' => $slug . '@example.com',
|
||||
'email_finance' => $slug . '-f@example.com',
|
||||
'url' => $slug . '.test.local',
|
||||
'account_name' => $name,
|
||||
'account_iban' => 'DE00',
|
||||
'account_bic' => 'XY',
|
||||
'city' => 'Stadt',
|
||||
'postcode' => '00000',
|
||||
'invoice_prefix' => strtoupper($slug),
|
||||
'is_active_local_group' => true,
|
||||
'has_active_instance' => true,
|
||||
])->fresh();
|
||||
}
|
||||
|
||||
private function createEvent(string $begin, string $name = 'Lager'): Event
|
||||
{
|
||||
$response = new CreateEventCommand(new CreateEventRequest(
|
||||
name: $name,
|
||||
location: 'Ort',
|
||||
postalCode: '00000',
|
||||
email: 'e@example.com',
|
||||
begin: new DateTime($begin),
|
||||
end: new DateTime($begin),
|
||||
earlyBirdEnd: new DateTime('2026-01-01'),
|
||||
registrationFinalEnd: new DateTime('2026-01-02'),
|
||||
earlyBirdEndAmountIncrease: 0,
|
||||
participationFeeType: ParticipationFeeType::where('slug', 'fixed')->first(),
|
||||
accountOwner: 'Owner',
|
||||
accountIban: 'DE00',
|
||||
payPerDay: false,
|
||||
))->execute();
|
||||
|
||||
$this->assertTrue($response->success);
|
||||
|
||||
return $response->event->fresh();
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Event-Teil der Nummer
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
public function test_events_of_the_same_month_are_numbered_consecutively(): void
|
||||
{
|
||||
$this->assertSame('WM-V-20260701', $this->createEvent('2026-07-16')->invoice_key);
|
||||
$this->assertSame('WM-V-20260702', $this->createEvent('2026-07-24')->invoice_key);
|
||||
// Anderer Monat, eigene Zählung.
|
||||
$this->assertSame('WM-V-20260801', $this->createEvent('2026-08-03')->invoice_key);
|
||||
}
|
||||
|
||||
public function test_each_tenant_counts_for_itself(): void
|
||||
{
|
||||
$this->createEvent('2026-07-16');
|
||||
|
||||
app()->instance('tenant', $this->makeTenant('sn', 'Sachsen'));
|
||||
|
||||
$this->assertSame('SN-V-20260701', $this->createEvent('2026-07-18')->invoice_key);
|
||||
}
|
||||
|
||||
public function test_invoice_key_survives_moving_the_event_to_another_month(): void
|
||||
{
|
||||
$event = $this->createEvent('2026-07-16');
|
||||
|
||||
$event->update(['start_date' => '2026-11-05', 'end_date' => '2026-11-07']);
|
||||
|
||||
// Eingefroren: eine bereits herausgegebene Rechnung gehört sonst plötzlich zu einer anderen Nummer.
|
||||
$this->assertSame('WM-V-20260701', $event->fresh()->invoice_key);
|
||||
}
|
||||
|
||||
public function test_new_tenants_default_to_their_uppercased_slug(): void
|
||||
{
|
||||
$tenant = Tenant::create([
|
||||
'slug' => 'fen',
|
||||
'name' => 'Fen',
|
||||
'email' => 'fen@example.com',
|
||||
'email_finance' => 'fen-f@example.com',
|
||||
'url' => 'fen.test.local',
|
||||
'account_name' => 'Fen',
|
||||
'account_iban' => 'DE00',
|
||||
'account_bic' => 'XY',
|
||||
'city' => 'Stadt',
|
||||
'postcode' => '00000',
|
||||
'is_active_local_group' => true,
|
||||
'has_active_instance' => true,
|
||||
])->fresh();
|
||||
|
||||
// Ohne gesetztes Präfix greift der Migrations-Default: der großgeschriebene Slug.
|
||||
$this->assertSame('FEN', $tenant->invoice_prefix);
|
||||
|
||||
app()->instance('tenant', $tenant);
|
||||
|
||||
$this->assertSame('FEN-V-20260701', $this->createEvent('2026-07-16')->invoice_key);
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teilnehmer-Teil der Nummer
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
private function signUp(Event $event, string $firstname): void
|
||||
{
|
||||
$response = new SignUpCommand(new SignUpRequest(
|
||||
$event, null, $firstname, 'Muster', null, ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||||
$this->wildeMoehre, new DateTime('2000-01-01'), 'Weg 1', null, '00000', 'Stadt',
|
||||
strtolower($firstname) . '@example.com', '123', null, null, null, null, null, null, null,
|
||||
'vegan', null, null, false, false, false, false, false,
|
||||
new DateTime('2026-07-16'), new DateTime('2026-07-16'), 0, 0, null,
|
||||
new Amount(50.0, 'Euro'), PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION,
|
||||
))->execute();
|
||||
|
||||
$this->assertTrue($response->success, $response->message ?? '');
|
||||
}
|
||||
|
||||
public function test_sequences_are_handed_out_in_order(): void
|
||||
{
|
||||
$event = $this->createEvent('2026-07-16');
|
||||
|
||||
$this->signUp($event, 'Ada');
|
||||
$this->signUp($event, 'Bo');
|
||||
$this->signUp($event, 'Cem');
|
||||
|
||||
$this->assertSame([1, 2, 3], $event->participants()->orderBy('id')->pluck('invoice_sequence')->all());
|
||||
}
|
||||
|
||||
public function test_signing_off_does_not_shift_later_sequences(): void
|
||||
{
|
||||
$event = $this->createEvent('2026-07-16');
|
||||
|
||||
$this->signUp($event, 'Ada');
|
||||
$this->signUp($event, 'Bo');
|
||||
|
||||
// Abmeldung: der Datensatz bleibt bestehen, die Nummer damit vergeben.
|
||||
$event->participants()->orderBy('id')->first()->update(['unregistered_at' => '2026-07-01']);
|
||||
|
||||
$this->signUp($event, 'Cem');
|
||||
|
||||
$this->assertSame(3, $event->participants()->orderBy('id')->get()->last()->invoice_sequence);
|
||||
}
|
||||
|
||||
public function test_sequences_are_per_event(): void
|
||||
{
|
||||
$first = $this->createEvent('2026-07-16', 'Erstes');
|
||||
$second = $this->createEvent('2026-07-24', 'Zweites');
|
||||
|
||||
$this->signUp($first, 'Ada');
|
||||
$this->signUp($second, 'Bo');
|
||||
|
||||
$this->assertSame(1, $first->participants()->first()->invoice_sequence);
|
||||
$this->assertSame(1, $second->participants()->first()->invoice_sequence);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user