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']); foreach ([UserRole::USER_ROLE_ADMIN, UserRole::USER_ROLE_GROUP_LEADER, UserRole::USER_ROLE_USER] as $role) { UserRole::create(['slug' => $role, 'name' => $role]); } } public function test_details_liefern_den_abrechnungsschluss_im_format_des_datumsfelds(): void { $costUnit = $this->makeCostUnit('2026-09-11'); $this->actingAs($this->makeAdmin()); $response = $this->getJson('/api/v1/cost-unit/' . $costUnit->id . '/details'); $response->assertOk(); $this->assertSame('2026-09-11', $response->json('costUnit.billingDeadline')); } public function test_details_ohne_abrechnungsschluss_liefern_null(): void { $costUnit = $this->makeCostUnit(null); $this->actingAs($this->makeAdmin()); $response = $this->getJson('/api/v1/cost-unit/' . $costUnit->id . '/details'); $this->assertNull($response->json('costUnit.billingDeadline')); } public function test_speichern_uebernimmt_das_datum_aus_dem_formular(): void { $costUnit = $this->makeCostUnit('2026-09-11'); $this->actingAs($this->makeAdmin()); $this->postJson('/api/v1/cost-unit/' . $costUnit->id . '/details', ['formData' => [ 'billingDeadline' => '2026-09-30', 'mailOnNew' => true, 'distanceAllowance' => '0,30', ]])->assertOk()->assertJson(['status' => 'success']); $this->assertSame('2026-09-30', $costUnit->fresh()->billing_deadline); } public function test_ein_geleertes_datumsfeld_entfernt_den_abrechnungsschluss_ohne_fehler(): void { $costUnit = $this->makeCostUnit('2026-09-11'); $this->actingAs($this->makeAdmin()); $this->postJson('/api/v1/cost-unit/' . $costUnit->id . '/details', ['formData' => [ 'billingDeadline' => '', 'mailOnNew' => false, 'distanceAllowance' => '0,25', ]])->assertOk()->assertJson(['status' => 'success']); $this->assertNull($costUnit->fresh()->billing_deadline); } public function test_eine_uhrzeit_im_request_wird_nicht_gespeichert(): void { $costUnit = $this->makeCostUnit(null); new ChangeCostUnitDetailsCommand(new ChangeCostUnitDetailsRequest( $costUnit, Amount::fromString('0,25'), false, new DateTime('2026-09-11 14:23:45'), ))->execute(); $this->assertSame('2026-09-11', $costUnit->fresh()->billing_deadline); } public function test_am_stichtag_um_2359_ist_die_kostenstelle_noch_offen(): void { $costUnit = $this->makeCostUnit('2026-09-11'); $this->travelTo(new DateTime('2026-09-11 23:59:00')); new CloseCostUnit()->handle(); $this->assertTrue((bool) $costUnit->fresh()->allow_new); } public function test_am_folgetag_schliesst_der_cron_die_kostenstelle(): void { $costUnit = $this->makeCostUnit('2026-09-11'); $this->travelTo(new DateTime('2026-09-12 00:05:00')); new CloseCostUnit()->handle(); $this->assertFalse((bool) $costUnit->fresh()->allow_new); } public function test_ohne_abrechnungsschluss_setzt_der_cron_sechs_wochen_und_laesst_offen(): void { $costUnit = $this->makeCostUnit(null); $this->travelTo(new DateTime('2026-09-12 00:05:00')); new CloseCostUnit()->handle(); $costUnit = $costUnit->fresh(); $this->assertSame('2026-10-24', $costUnit->billing_deadline); $this->assertTrue((bool) $costUnit->allow_new); } private function makeCostUnit(?string $billingDeadline): CostUnit { return CostUnit::create([ 'tenant' => $this->tenant->slug, 'name' => 'Sommerlager', 'type' => CostUnitType::COST_UNIT_TYPE_EVENT, 'billing_deadline' => $billingDeadline, 'distance_allowance' => 0.25, 'mail_on_new' => false, 'allow_new' => true, 'archived' => false, ]); } private function makeAdmin(): User { return User::create([ 'username' => 'admin-' . uniqid() . '@example.com', 'email' => 'admin-' . uniqid() . '@example.com', 'firstname' => 'Test', 'lastname' => 'Person', 'password' => bcrypt('secret'), 'local_group' => $this->tenant->slug, 'user_role_main' => UserRole::USER_ROLE_USER, 'user_role_local_group' => UserRole::USER_ROLE_ADMIN, 'active' => true, ]); } }