'Brasil', 'code' => 'BR']); $state = State::create(['name' => 'Paraná', 'code' => 'PR', 'country_id' => $country->id]); $city = City::create(['name' => 'Maringá', 'country_id' => $country->id, 'state_id' => $state->id]); $this->unit = Unit::create([ 'fantasy_name' => 'Unidade Teste', 'social_reason' => 'Unidade Teste LTDA', 'cnpj' => '00000000000191', 'street' => 'Rua Teste', 'neighborhood' => 'Centro', 'postal_code' => '87000000', 'city_id' => $city->id, 'state_id' => $state->id, 'email' => 'unidade@teste.com', 'name_responsible' => 'Responsável Teste', ]); $this->size = MunicipalitySize::create(['acronym' => 'GP', 'description' => 'De 100 mil a 200 mil habitantes']); foreach ([[1, 3, 0.0], [4, 12, 0.75], [13, 60, 1.00]] as [$start, $end, $percentage]) { InhabitantClassification::create([ 'municipality_size_id' => $this->size->id, 'description' => "Faixa {$start}-{$end}", 'start' => $start, 'end' => $end, 'tbr_percentage' => $percentage, 'is_renewal' => false, ]); } Tbr::create([ 'year' => 2026, 'tbr_value' => 1000.00, 'royalties_percentage' => 0.08, 'fnm_percentage' => 0.20, 'maintenance_percentage' => 0.30, ]); FranchiseeContract::create([ 'unit_id' => $this->unit->id, 'protocol' => 1, 'name' => 'Contrato de Franquia', 'description' => 'Contrato de teste', 'start_date' => '2026-01-01', 'end_date' => '2031-01-01', 'signature_date' => '2026-01-01', 'validity_months' => 60, 'invoice_due_date' => 10, 'municipality_size_id' => $this->size->id, 'tbr_fixed_value' => 1000.00, ]); } /** Usuário ADMIN autenticado via Sanctum: hasPermission() libera qualquer scope. */ private function actingAsAdmin(): User { $userType = UserType::create([ 'slug' => 'ADMIN_TEST', 'label' => 'Admin Teste', 'is_system' => true, 'access_scope' => 'franchisor', 'system_key' => 'ADMIN', 'scope_key' => 'franchisor', ]); $user = User::create([ 'name' => 'Admin Teste', 'email' => 'admin.teste@example.com', 'password' => bcrypt('password'), 'franchisor_user_type_id' => $userType->id, ]); Sanctum::actingAs($user, ['access']); return $user; } public function test_rotas_de_calculo_exigem_autenticacao(): void { $this->getJson('/api/tbr-calculation')->assertStatus(401); $this->postJson('/api/tbr-calculation/preview-batch', [])->assertStatus(401); $this->postJson('/api/tbr-calculation/generate-batch', [])->assertStatus(401); } public function test_index_lista_calculos_ja_gravados(): void { $this->actingAsAdmin(); $this->postJson('/api/tbr-calculation/generate-batch', [ 'reference_year' => 2026, 'reference_month' => 4, ])->assertStatus(201); $response = $this->getJson('/api/tbr-calculation'); $response->assertOk(); $this->assertCount(1, $response->json('payload')); } public function test_preview_batch_retorna_calculo_por_unidade_sem_gravar_nada(): void { $this->actingAsAdmin(); $response = $this->postJson('/api/tbr-calculation/preview-batch', [ 'reference_year' => 2026, 'reference_month' => 4, ]); $response->assertOk(); $payload = $response->json('payload'); $this->assertCount(1, $payload); $this->assertSame($this->unit->id, $payload[0]['unit_id']); $this->assertEquals(750.0, $payload[0]['royalties_effective_value']); $this->assertEquals(300.0, $payload[0]['maintenance_effective_value']); $this->assertDatabaseCount('tbr_calculations', 0); } public function test_preview_batch_valida_ano_e_mes_obrigatorios(): void { $this->actingAsAdmin(); $this->postJson('/api/tbr-calculation/preview-batch', []) ->assertStatus(422) ->assertJsonValidationErrors(['reference_year', 'reference_month']); } public function test_generate_batch_gera_titulo_e_marca_calculo_como_gerado(): void { $this->actingAsAdmin(); $response = $this->postJson('/api/tbr-calculation/generate-batch', [ 'reference_year' => 2026, 'reference_month' => 4, ]); $response->assertStatus(201); $this->assertSame(1, $response->json('payload.generated_count')); $this->assertDatabaseHas('tbr_calculations', [ 'unit_id' => $this->unit->id, 'contract_month_reference' => 4, 'receivable_generated' => true, ]); $this->assertDatabaseHas('franchisee_account_receives', [ 'unit_id' => $this->unit->id, ]); Bus::assertDispatched(\App\Jobs\SyncFranchiseeChargeJob::class); } public function test_generate_batch_pula_unidade_que_ja_tem_titulo_no_mes(): void { $this->actingAsAdmin(); $this->postJson('/api/tbr-calculation/generate-batch', [ 'reference_year' => 2026, 'reference_month' => 4, ])->assertStatus(201); $response = $this->postJson('/api/tbr-calculation/generate-batch', [ 'reference_year' => 2026, 'reference_month' => 4, ]); $response->assertStatus(201); $this->assertSame(0, $response->json('payload.generated_count')); $this->assertSame(1, $response->json('payload.skipped_count')); } public function test_preview_calcula_uma_unidade_sem_gravar_nada(): void { $this->actingAsAdmin(); $response = $this->postJson('/api/tbr-calculation/preview', [ 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4, ]); $response->assertOk(); $this->assertEquals(750.0, $response->json('payload.royalties_effective_value')); $this->assertDatabaseCount('tbr_calculations', 0); } public function test_store_grava_o_calculo_de_uma_unidade(): void { $this->actingAsAdmin(); $response = $this->postJson('/api/tbr-calculation', [ 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4, ]); $response->assertStatus(201); $this->assertDatabaseHas('tbr_calculations', [ 'unit_id' => $this->unit->id, 'contract_month_reference' => 4, 'receivable_generated' => false, ]); } public function test_show_retorna_um_calculo_gravado(): void { $this->actingAsAdmin(); $id = $this->postJson('/api/tbr-calculation', [ 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4, ])->json('payload.id'); $response = $this->getJson("/api/tbr-calculation/{$id}"); $response->assertOk(); $this->assertSame($id, $response->json('payload.id')); } public function test_generate_receivable_gera_titulo_para_um_calculo_especifico(): void { $this->actingAsAdmin(); $id = $this->postJson('/api/tbr-calculation', [ 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4, ])->json('payload.id'); $response = $this->postJson("/api/tbr-calculation/{$id}/generate-receivable"); $response->assertStatus(201); $this->assertDatabaseHas('tbr_calculations', ['id' => $id, 'receivable_generated' => true]); $this->assertDatabaseHas('franchisee_account_receives', ['unit_id' => $this->unit->id]); Bus::assertDispatched(\App\Jobs\SyncFranchiseeChargeJob::class); } public function test_generate_receivable_bloqueia_segunda_geracao_para_o_mesmo_calculo(): void { $this->actingAsAdmin(); $id = $this->postJson('/api/tbr-calculation', [ 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4, ])->json('payload.id'); $this->postJson("/api/tbr-calculation/{$id}/generate-receivable")->assertStatus(201); $this->postJson("/api/tbr-calculation/{$id}/generate-receivable") ->assertStatus(422) ->assertJsonValidationErrors(['tbr_calculation_id']); } public function test_generate_batch_pode_ser_filtrado_por_unit_ids(): void { $this->actingAsAdmin(); $outraUnidade = Unit::create([ 'fantasy_name' => 'Outra Unidade', 'social_reason' => 'Outra Unidade LTDA', 'cnpj' => '00000000000272', 'street' => 'Rua Teste', 'neighborhood' => 'Centro', 'postal_code' => '87000000', 'city_id' => $this->unit->city_id, 'state_id' => $this->unit->state_id, 'email' => 'outra@teste.com', 'name_responsible' => 'Responsável Teste', ]); $response = $this->postJson('/api/tbr-calculation/generate-batch', [ 'reference_year' => 2026, 'reference_month' => 4, 'unit_ids' => [$outraUnidade->id], ]); // Outra Unidade não tem contrato cadastrado: filtro por ela não gera nada. $response->assertStatus(201); $this->assertSame(0, $response->json('payload.generated_count')); } }