TbrCalculationRoutesTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. namespace Tests\Feature\Financeiro;
  3. use App\Models\City;
  4. use App\Models\Country;
  5. use App\Models\FranchiseeContract;
  6. use App\Models\InhabitantClassification;
  7. use App\Models\MunicipalitySize;
  8. use App\Models\State;
  9. use App\Models\Tbr;
  10. use App\Models\Unit;
  11. use App\Models\User;
  12. use App\Models\UserType;
  13. use Illuminate\Foundation\Testing\RefreshDatabase;
  14. use Illuminate\Support\Facades\Bus;
  15. use Laravel\Sanctum\Sanctum;
  16. use Tests\TestCase;
  17. /**
  18. * Camada HTTP das rotas de cálculo de contrato (routes/authRoutes/franchisor_tbr.php).
  19. *
  20. * O frontend (src/api/tbr_calculation.js) já tem previewTbrCalculation(),
  21. * createTbrCalculation() e generateReceivable(id) prontos, mas as rotas
  22. * preview/store/{id}/generate-receivable não existiam — foram registradas
  23. * agora e são cobertas aqui junto com index/preview-batch/generate-batch.
  24. */
  25. class TbrCalculationRoutesTest extends TestCase
  26. {
  27. use RefreshDatabase;
  28. private Unit $unit;
  29. private MunicipalitySize $size;
  30. protected function setUp(): void
  31. {
  32. parent::setUp();
  33. Bus::fake();
  34. $country = Country::create(['name' => 'Brasil', 'code' => 'BR']);
  35. $state = State::create(['name' => 'Paraná', 'code' => 'PR', 'country_id' => $country->id]);
  36. $city = City::create(['name' => 'Maringá', 'country_id' => $country->id, 'state_id' => $state->id]);
  37. $this->unit = Unit::create([
  38. 'fantasy_name' => 'Unidade Teste',
  39. 'social_reason' => 'Unidade Teste LTDA',
  40. 'cnpj' => '00000000000191',
  41. 'street' => 'Rua Teste',
  42. 'neighborhood' => 'Centro',
  43. 'postal_code' => '87000000',
  44. 'city_id' => $city->id,
  45. 'state_id' => $state->id,
  46. 'email' => 'unidade@teste.com',
  47. 'name_responsible' => 'Responsável Teste',
  48. ]);
  49. $this->size = MunicipalitySize::create(['acronym' => 'GP', 'description' => 'De 100 mil a 200 mil habitantes']);
  50. foreach ([[1, 3, 0.0], [4, 12, 0.75], [13, 60, 1.00]] as [$start, $end, $percentage]) {
  51. InhabitantClassification::create([
  52. 'municipality_size_id' => $this->size->id,
  53. 'description' => "Faixa {$start}-{$end}",
  54. 'start' => $start,
  55. 'end' => $end,
  56. 'tbr_percentage' => $percentage,
  57. 'is_renewal' => false,
  58. ]);
  59. }
  60. Tbr::create([
  61. 'year' => 2026, 'tbr_value' => 1000.00,
  62. 'royalties_percentage' => 0.08, 'fnm_percentage' => 0.20, 'maintenance_percentage' => 0.30,
  63. ]);
  64. FranchiseeContract::create([
  65. 'unit_id' => $this->unit->id,
  66. 'protocol' => 1,
  67. 'name' => 'Contrato de Franquia',
  68. 'description' => 'Contrato de teste',
  69. 'start_date' => '2026-01-01',
  70. 'end_date' => '2031-01-01',
  71. 'signature_date' => '2026-01-01',
  72. 'validity_months' => 60,
  73. 'invoice_due_date' => 10,
  74. 'municipality_size_id' => $this->size->id,
  75. 'tbr_fixed_value' => 1000.00,
  76. ]);
  77. }
  78. /** Usuário ADMIN autenticado via Sanctum: hasPermission() libera qualquer scope. */
  79. private function actingAsAdmin(): User
  80. {
  81. $userType = UserType::create([
  82. 'slug' => 'ADMIN_TEST',
  83. 'label' => 'Admin Teste',
  84. 'is_system' => true,
  85. 'access_scope' => 'franchisor',
  86. 'system_key' => 'ADMIN',
  87. 'scope_key' => 'franchisor',
  88. ]);
  89. $user = User::create([
  90. 'name' => 'Admin Teste',
  91. 'email' => 'admin.teste@example.com',
  92. 'password' => bcrypt('password'),
  93. 'franchisor_user_type_id' => $userType->id,
  94. ]);
  95. Sanctum::actingAs($user, ['access']);
  96. return $user;
  97. }
  98. public function test_rotas_de_calculo_exigem_autenticacao(): void
  99. {
  100. $this->getJson('/api/tbr-calculation')->assertStatus(401);
  101. $this->postJson('/api/tbr-calculation/preview-batch', [])->assertStatus(401);
  102. $this->postJson('/api/tbr-calculation/generate-batch', [])->assertStatus(401);
  103. }
  104. public function test_index_lista_calculos_ja_gravados(): void
  105. {
  106. $this->actingAsAdmin();
  107. $this->postJson('/api/tbr-calculation/generate-batch', [
  108. 'reference_year' => 2026, 'reference_month' => 4,
  109. ])->assertStatus(201);
  110. $response = $this->getJson('/api/tbr-calculation');
  111. $response->assertOk();
  112. $this->assertCount(1, $response->json('payload'));
  113. }
  114. public function test_preview_batch_retorna_calculo_por_unidade_sem_gravar_nada(): void
  115. {
  116. $this->actingAsAdmin();
  117. $response = $this->postJson('/api/tbr-calculation/preview-batch', [
  118. 'reference_year' => 2026, 'reference_month' => 4,
  119. ]);
  120. $response->assertOk();
  121. $payload = $response->json('payload');
  122. $this->assertCount(1, $payload);
  123. $this->assertSame($this->unit->id, $payload[0]['unit_id']);
  124. // Sem faturamento (nenhuma parcela cadastrada), vence o fixo: 8% (base) x 75% (faixa) = 6% x TBR 1.000.
  125. $this->assertEquals(60.0, $payload[0]['royalties_effective_value']);
  126. $this->assertEquals(300.0, $payload[0]['maintenance_effective_value']);
  127. $this->assertDatabaseCount('tbr_calculations', 0);
  128. }
  129. public function test_preview_batch_valida_ano_e_mes_obrigatorios(): void
  130. {
  131. $this->actingAsAdmin();
  132. $this->postJson('/api/tbr-calculation/preview-batch', [])
  133. ->assertStatus(422)
  134. ->assertJsonValidationErrors(['reference_year', 'reference_month']);
  135. }
  136. public function test_generate_batch_gera_titulo_e_marca_calculo_como_gerado(): void
  137. {
  138. $this->actingAsAdmin();
  139. $response = $this->postJson('/api/tbr-calculation/generate-batch', [
  140. 'reference_year' => 2026, 'reference_month' => 4,
  141. ]);
  142. $response->assertStatus(201);
  143. $this->assertSame(1, $response->json('payload.generated_count'));
  144. $this->assertDatabaseHas('tbr_calculations', [
  145. 'unit_id' => $this->unit->id,
  146. 'contract_month_reference' => 4,
  147. 'receivable_generated' => true,
  148. ]);
  149. $this->assertDatabaseHas('franchisee_account_receives', [
  150. 'unit_id' => $this->unit->id,
  151. ]);
  152. Bus::assertDispatched(\App\Jobs\SyncFranchiseeChargeJob::class);
  153. }
  154. public function test_generate_batch_pula_unidade_que_ja_tem_titulo_no_mes(): void
  155. {
  156. $this->actingAsAdmin();
  157. $this->postJson('/api/tbr-calculation/generate-batch', [
  158. 'reference_year' => 2026, 'reference_month' => 4,
  159. ])->assertStatus(201);
  160. $response = $this->postJson('/api/tbr-calculation/generate-batch', [
  161. 'reference_year' => 2026, 'reference_month' => 4,
  162. ]);
  163. $response->assertStatus(201);
  164. $this->assertSame(0, $response->json('payload.generated_count'));
  165. $this->assertSame(1, $response->json('payload.skipped_count'));
  166. }
  167. public function test_preview_calcula_uma_unidade_sem_gravar_nada(): void
  168. {
  169. $this->actingAsAdmin();
  170. $response = $this->postJson('/api/tbr-calculation/preview', [
  171. 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4,
  172. ]);
  173. $response->assertOk();
  174. $this->assertEquals(60.0, $response->json('payload.royalties_effective_value'));
  175. $this->assertDatabaseCount('tbr_calculations', 0);
  176. }
  177. public function test_store_grava_o_calculo_de_uma_unidade(): void
  178. {
  179. $this->actingAsAdmin();
  180. $response = $this->postJson('/api/tbr-calculation', [
  181. 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4,
  182. ]);
  183. $response->assertStatus(201);
  184. $this->assertDatabaseHas('tbr_calculations', [
  185. 'unit_id' => $this->unit->id,
  186. 'contract_month_reference' => 4,
  187. 'receivable_generated' => false,
  188. ]);
  189. }
  190. public function test_show_retorna_um_calculo_gravado(): void
  191. {
  192. $this->actingAsAdmin();
  193. $id = $this->postJson('/api/tbr-calculation', [
  194. 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4,
  195. ])->json('payload.id');
  196. $response = $this->getJson("/api/tbr-calculation/{$id}");
  197. $response->assertOk();
  198. $this->assertSame($id, $response->json('payload.id'));
  199. }
  200. public function test_generate_receivable_gera_titulo_para_um_calculo_especifico(): void
  201. {
  202. $this->actingAsAdmin();
  203. $id = $this->postJson('/api/tbr-calculation', [
  204. 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4,
  205. ])->json('payload.id');
  206. $response = $this->postJson("/api/tbr-calculation/{$id}/generate-receivable");
  207. $response->assertStatus(201);
  208. $this->assertDatabaseHas('tbr_calculations', ['id' => $id, 'receivable_generated' => true]);
  209. $this->assertDatabaseHas('franchisee_account_receives', ['unit_id' => $this->unit->id]);
  210. Bus::assertDispatched(\App\Jobs\SyncFranchiseeChargeJob::class);
  211. }
  212. public function test_generate_receivable_bloqueia_segunda_geracao_para_o_mesmo_calculo(): void
  213. {
  214. $this->actingAsAdmin();
  215. $id = $this->postJson('/api/tbr-calculation', [
  216. 'unit_id' => $this->unit->id, 'reference_year' => 2026, 'reference_month' => 4,
  217. ])->json('payload.id');
  218. $this->postJson("/api/tbr-calculation/{$id}/generate-receivable")->assertStatus(201);
  219. $this->postJson("/api/tbr-calculation/{$id}/generate-receivable")
  220. ->assertStatus(422)
  221. ->assertJsonValidationErrors(['tbr_calculation_id']);
  222. }
  223. public function test_generate_batch_pode_ser_filtrado_por_unit_ids(): void
  224. {
  225. $this->actingAsAdmin();
  226. $outraUnidade = Unit::create([
  227. 'fantasy_name' => 'Outra Unidade', 'social_reason' => 'Outra Unidade LTDA',
  228. 'cnpj' => '00000000000272', 'street' => 'Rua Teste', 'neighborhood' => 'Centro',
  229. 'postal_code' => '87000000', 'city_id' => $this->unit->city_id, 'state_id' => $this->unit->state_id,
  230. 'email' => 'outra@teste.com', 'name_responsible' => 'Responsável Teste',
  231. ]);
  232. $response = $this->postJson('/api/tbr-calculation/generate-batch', [
  233. 'reference_year' => 2026, 'reference_month' => 4, 'unit_ids' => [$outraUnidade->id],
  234. ]);
  235. // Outra Unidade não tem contrato cadastrado: filtro por ela não gera nada.
  236. $response->assertStatus(201);
  237. $this->assertSame(0, $response->json('payload.generated_count'));
  238. }
  239. }