diff --git a/CLAUDE.md b/CLAUDE.md index 5db148a..fcafba8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,6 +36,12 @@ UI-Bezeichnungen. `/admin/settings`. Gelesen über `ParticipantGroupType::label()/pluralLabel()/labels()`. Texte ohne Artikel formulieren („Bitte bei „{label}" eine Auswahl treffen."), weil der Artikel vom Wort abhängt. +## eFZ-Prüfung pro Veranstaltung + +`events.coc_check_required` (Default `true`, Checkbox in `CommonSettings.vue`, Resource-Feld `cocCheckRequired`). Ist +sie aus, liefert `CertificateOfConductionCheckCommand` ohne API-Aufruf `not_required`, und `SignUpCommand` belegt +`efz_status` mit `not_required` vor. Wirkt nur auf neue Anmeldungen, bestehende Status bleiben. + ## Umsatzsteuer / Steuerinformationen Rechtlich korrekter Begriff ist **Umsatzsteuer (USt)**, nicht MwSt. Ziel: tagesbasierte Anmelde-Rechnungen („Teilnahme diff --git a/app/Domains/Event/Actions/CertificateOfConductionCheck/CertificateOfConductionCheckCommand.php b/app/Domains/Event/Actions/CertificateOfConductionCheck/CertificateOfConductionCheckCommand.php index 1824432..aad8a37 100644 --- a/app/Domains/Event/Actions/CertificateOfConductionCheck/CertificateOfConductionCheckCommand.php +++ b/app/Domains/Event/Actions/CertificateOfConductionCheck/CertificateOfConductionCheckCommand.php @@ -12,6 +12,12 @@ class CertificateOfConductionCheckCommand { public function execute() : CertificateOfConductionCheckResponse { $response = new CertificateOfConductionCheckResponse(); + // Verlangt die Veranstaltung kein eFZ, entfällt die Prüfung -- ohne Aufruf der Prüf-API. + if (!$this->request->participant->event()->first()?->coc_check_required) { + $response->status = EfzStatus::EFZ_STATUS_NOT_REQUIRED; + return $response; + } + // Der Stamm ist optional (die Kurzanmeldung erhebt ihn nicht, in der Verwaltung kann er entfernt werden). $localGroup = str_replace('Stamm ', '', $this->request->participant->localGroup()->first()?->name ?? ''); diff --git a/app/Domains/Event/Actions/SignUp/SignUpCommand.php b/app/Domains/Event/Actions/SignUp/SignUpCommand.php index f933bfb..e764bca 100644 --- a/app/Domains/Event/Actions/SignUp/SignUpCommand.php +++ b/app/Domains/Event/Actions/SignUp/SignUpCommand.php @@ -95,7 +95,9 @@ class SignUpCommand { 'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname, 'payment_method' => $this->request->paymentMethod, 'payment_options' => $paymentOptions, - 'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED, + 'efz_status' => $participantAge->isfullAged() && $this->request->event->coc_check_required + ? EfzStatus::EFZ_STATUS_NOT_CHECKED + : EfzStatus::EFZ_STATUS_NOT_REQUIRED, ] ); diff --git a/app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php b/app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php index 1e216a3..b361ec4 100644 --- a/app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php +++ b/app/Domains/Event/Actions/UpdateEvent/UpdateEventCommand.php @@ -28,6 +28,7 @@ class UpdateEventCommand { $this->request->event->registration_allowed = $this->request->registrationAllowed; $this->request->event->short_registration = $this->request->shortRegistration; $this->request->event->swimming_permission_required = $this->request->swimmingPermissionRequired; + $this->request->event->coc_check_required = $this->request->cocCheckRequired; // Umstellen zwischen Local und Parent Group nur ohne Anmeldungen -- sonst stünden in einer Veranstaltung // beide Arten von Antworten nebeneinander. diff --git a/app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php b/app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php index 5c9ad36..3e4ab74 100644 --- a/app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php +++ b/app/Domains/Event/Actions/UpdateEvent/UpdateEventRequest.php @@ -22,6 +22,7 @@ class UpdateEventRequest { public bool $registrationAllowed; public bool $shortRegistration; public bool $swimmingPermissionRequired; + public bool $cocCheckRequired; public Amount $flatSupport; public Amount $supportPerPerson; public array $contributingLocalGroups; @@ -29,9 +30,10 @@ class UpdateEventRequest { /** Wonach bei der Anmeldung gefragt wird; null = unverändert. Greift nur, solange es keine Anmeldungen gibt. */ public ?ParticipantGroupType $participantGroupType; - public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits, bool $shortRegistration = false, bool $swimmingPermissionRequired = true, ?string $street = null, ?string $houseNumber = null, ?ParticipantGroupType $participantGroupType = null) + public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits, bool $shortRegistration = false, bool $swimmingPermissionRequired = true, ?string $street = null, ?string $houseNumber = null, ?ParticipantGroupType $participantGroupType = null, bool $cocCheckRequired = true) { $this->participantGroupType = $participantGroupType; + $this->cocCheckRequired = $cocCheckRequired; $this->event = $event; $this->eventName = $eventName; $this->eventLocation = $eventLocation; diff --git a/app/Domains/Event/Controllers/DetailsController.php b/app/Domains/Event/Controllers/DetailsController.php index 0db288a..97f3cde 100644 --- a/app/Domains/Event/Controllers/DetailsController.php +++ b/app/Domains/Event/Controllers/DetailsController.php @@ -70,6 +70,7 @@ class DetailsController extends CommonController { $request->input('street'), $request->input('houseNumber'), ParticipantGroupType::tryFrom((string) $request->input('participantGroupType')), + (bool)$request->input('cocCheckRequired', true), ); $eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest); diff --git a/app/Domains/Event/Views/Partials/CommonSettings.vue b/app/Domains/Event/Views/Partials/CommonSettings.vue index 568ec84..0ffcf86 100644 --- a/app/Domains/Event/Views/Partials/CommonSettings.vue +++ b/app/Domains/Event/Views/Partials/CommonSettings.vue @@ -44,6 +44,7 @@ const emit = defineEmits(['close']) registrationAllowed: props.event.registrationAllowed, shortRegistration: props.event.shortRegistration, swimmingPermissionRequired: props.event.swimmingPermissionRequired, + cocCheckRequired: props.event.cocCheckRequired, flatSupport: props.event.flatSupportEdit, supportPerson: props.event.supportPersonIndex, participantGroupType: props.event.participantGroupType ?? 'local_group', @@ -78,6 +79,7 @@ const emit = defineEmits(['close']) registrationAllowed: formData.registrationAllowed, shortRegistration: formData.shortRegistration, swimmingPermissionRequired: formData.swimmingPermissionRequired, + cocCheckRequired: formData.cocCheckRequired, flatSupport: formData.flatSupport, supportPerson: formData.supportPerson, contributingLocalGroups: contributingLocalGroups.value, @@ -234,6 +236,16 @@ const emit = defineEmits(['close']) + + + + +
+ + Ist die Prüfung deaktiviert, wird das eFZ bei neuen Anmeldungen als „Nicht erforderlich" hinterlegt. + + + diff --git a/app/Models/Event.php b/app/Models/Event.php index 708048f..ef4f06a 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -51,6 +51,7 @@ use Illuminate\Support\Collection; * @property boolean $archived * @property boolean $short_registration * @property boolean $swimming_permission_required + * @property boolean $coc_check_required * @property string|null $invoice_key * @property string $participant_group_type */ @@ -102,6 +103,7 @@ class Event extends InstancedModel 'short_registration', 'swimming_permission_required', + 'coc_check_required', 'invoice_key', 'participant_group_type', @@ -137,6 +139,7 @@ class Event extends InstancedModel 'short_registration' => 'boolean', 'swimming_permission_required' => 'boolean', + 'coc_check_required' => 'boolean', ]; /** diff --git a/app/Resources/EventResource.php b/app/Resources/EventResource.php index ba565fd..d6e27db 100644 --- a/app/Resources/EventResource.php +++ b/app/Resources/EventResource.php @@ -85,6 +85,7 @@ class EventResource extends JsonResource{ // Steuert im Frontend, welcher Anmelde-Wizard gerendert wird, und ob die Badeerlaubnis abgefragt wird. $returnArray['shortRegistration'] = (bool)$this->event->short_registration; $returnArray['swimmingPermissionRequired'] = (bool)$this->event->swimming_permission_required; + $returnArray['cocCheckRequired'] = (bool)$this->event->coc_check_required; $returnArray['siblingReduction'] = $this->event->sibling_reduction ?? true; $returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true); diff --git a/database/migrations/2026_09_29_140020_add_coc_check_required_to_events.php b/database/migrations/2026_09_29_140020_add_coc_check_required_to_events.php new file mode 100644 index 0000000..7feb008 --- /dev/null +++ b/database/migrations/2026_09_29_140020_add_coc_check_required_to_events.php @@ -0,0 +1,24 @@ +boolean('coc_check_required')->default(true); + }); + } + + public function down(): void + { + Schema::table('events', function (Blueprint $table) { + $table->dropColumn('coc_check_required'); + }); + } +}; diff --git a/tests/Feature/ShortSignUpTest.php b/tests/Feature/ShortSignUpTest.php index b35eeeb..a90f344 100644 --- a/tests/Feature/ShortSignUpTest.php +++ b/tests/Feature/ShortSignUpTest.php @@ -2,6 +2,8 @@ namespace Tests\Feature; +use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckCommand; +use App\Domains\Event\Actions\CertificateOfConductionCheck\CertificateOfConductionCheckRequest; use App\Domains\Event\Actions\ShortSignUp\ShortSignUpCommand; use App\Domains\Event\Actions\ShortSignUp\ShortSignUpRequest; use App\Enumerations\EatingHabit; @@ -18,6 +20,7 @@ use App\Models\Tenant; use App\RelationModels\EventParticipationFee; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Mail; use Tests\TestCase; @@ -57,6 +60,7 @@ class ShortSignUpTest extends TestCase bool $swimmingPermissionRequired = true, ?float $amountReduced = 30.0, ?float $amountSolidarity = 70.0, + bool $cocCheckRequired = true, ): Event { $event = Event::create([ @@ -66,6 +70,7 @@ class ShortSignUpTest extends TestCase 'account_owner' => 'Owner', 'account_iban' => 'DE00', 'participation_fee_type' => 'FIXED', 'pay_per_day' => false, 'pay_direct' => false, 'short_registration' => true, 'swimming_permission_required' => $swimmingPermissionRequired, + 'coc_check_required' => $cocCheckRequired, ]); if ($withFee) { @@ -217,6 +222,43 @@ class ShortSignUpTest extends TestCase $this->assertSame('maria@example.com', $response->participant->email_2); } + public function test_volljaehrige_ohne_efz_pruefung_brauchen_kein_efz(): void + { + Http::fake(); + $event = $this->createEvent(cocCheckRequired: false); + + $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); + + $this->assertTrue($response->success, $response->message ?? ''); + $this->assertSame(EfzStatus::EFZ_STATUS_NOT_REQUIRED, $response->participant->efz_status); + + $cocResponse = new CertificateOfConductionCheckCommand( + new CertificateOfConductionCheckRequest($response->participant) + )->execute(); + + $this->assertSame(EfzStatus::EFZ_STATUS_NOT_REQUIRED, $cocResponse->status); + Http::assertNothingSent(); + } + + public function test_volljaehrige_mit_efz_pruefung_werden_geprueft(): void + { + putenv('COC_CHECK_URL=https://coc.test/check'); + Http::fake(['*' => Http::response(['status' => '200/ok', 'efzStatus' => 'CHECKED_VALID'])]); + $event = $this->createEvent(); + + $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); + + $this->assertTrue($response->success, $response->message ?? ''); + $this->assertSame(EfzStatus::EFZ_STATUS_NOT_CHECKED, $response->participant->efz_status); + + $cocResponse = new CertificateOfConductionCheckCommand( + new CertificateOfConductionCheckRequest($response->participant) + )->execute(); + + $this->assertSame(EfzStatus::EFZ_STATUS_CHECKED_VALID, $cocResponse->status); + Http::assertSentCount(1); + } + public function test_u18_uebernimmt_gewaehlte_badeerlaubnis(): void { $event = $this->createEvent(); diff --git a/version b/version index 2da4316..ad96464 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.10.0 +4.10.1