Royalties fixo = * R$ 648,40 (0,40 x 1.621,00), batendo com a planilha. * * Os cenários de porte maior (100k-200k e acima de 200k) também confirmam que * Royalties e FNM são comparados de forma independente: nos meses 16-19 (GP) e * 16-29 (MGP) o FNM já vira "percentual do faturamento" enquanto os Royalties * continuam no fixo, exatamente como a planilha mostra. * * Cada linha testada é [mês de contrato, faturamento do mês, total esperado da * cobrança], exatamente como está na planilha do cliente. */ class TbrCalculationClientScenariosTest extends TestCase { use RefreshDatabase; private const TBR_VALUE = 1621.00; private TbrCalculationService $service; private Unit $unitPequenoPorte; private Unit $unitMedioPorte; private Unit $unitGrandePorte; private Unit $unitMuitoGrandePorte; protected function setUp(): void { parent::setUp(); Bus::fake(); $this->service = new TbrCalculationService; $country = Country::create(['name' => 'Brasil', 'code' => 'BR']); $state = State::create(['name' => 'Paraná', 'code' => 'PR', 'country_id' => $country->id]); $city = City::create(['name' => 'Foz do Iguaçu', 'country_id' => $country->id, 'state_id' => $state->id]); $sizePP = MunicipalitySize::create(['acronym' => 'PP', 'description' => 'Até 50 mil habitantes']); $sizeMP = MunicipalitySize::create(['acronym' => 'MP', 'description' => 'De 50 mil a 100 mil habitantes']); $sizeGP = MunicipalitySize::create(['acronym' => 'GP', 'description' => 'De 100 mil a 200 mil habitantes']); $sizeMGP = MunicipalitySize::create(['acronym' => 'MGP', 'description' => 'Acima de 200 mil habitantes']); // Faixas exatamente como nos cenários do cliente: 40%/60% (PP), 50%/75% (MP), // 75%/100% (GP), 100%/150% (MGP). $this->makeBrackets($sizePP->id, [[1, 3, 0.0], [4, 12, 0.40], [13, 60, 0.60]]); $this->makeBrackets($sizeMP->id, [[1, 3, 0.0], [4, 12, 0.50], [13, 60, 0.75]]); $this->makeBrackets($sizeGP->id, [[1, 3, 0.0], [4, 12, 0.75], [13, 60, 1.00]]); $this->makeBrackets($sizeMGP->id, [[1, 3, 0.0], [4, 12, 1.00], [13, 60, 1.50]]); // TBR e percentuais fixos de FNM/Manutenção iguais em todos os cenários da planilha. Tbr::create([ 'year' => 2026, 'tbr_value' => self::TBR_VALUE, 'royalties_percentage' => 0.08, 'fnm_percentage' => 0.20, 'maintenance_percentage' => 0.30, ]); $this->unitPequenoPorte = $this->makeUnit('Foz do Iguaçu (até 50k)', $city, $state, '00000000000191'); $this->unitMedioPorte = $this->makeUnit('Foz do Iguaçu (50k a 100k)', $city, $state, '00000000000272'); $this->unitGrandePorte = $this->makeUnit('Foz do Iguaçu (100k a 200k)', $city, $state, '00000000010316'); $this->unitMuitoGrandePorte = $this->makeUnit('Foz do Iguaçu (acima 200k)', $city, $state, '00000000020460'); $this->makeContract($this->unitPequenoPorte->id, $sizePP->id); $this->makeContract($this->unitMedioPorte->id, $sizeMP->id); $this->makeContract($this->unitGrandePorte->id, $sizeGP->id); $this->makeContract($this->unitMuitoGrandePorte->id, $sizeMGP->id); } private function makeUnit(string $name, City $city, State $state, string $cnpj): Unit { return Unit::create([ 'fantasy_name' => $name, 'social_reason' => $name.' LTDA', 'cnpj' => $cnpj, 'street' => 'Rua Teste', 'neighborhood' => 'Centro', 'postal_code' => '85850000', 'city_id' => $city->id, 'state_id' => $state->id, 'email' => strtolower(str_replace(' ', '.', $name)).'@teste.com', 'name_responsible' => 'Responsável Teste', ]); } /** @param array $brackets [start, end, percentual] */ private function makeBrackets(int $sizeId, array $brackets): void { foreach ($brackets as [$start, $end, $percentage]) { InhabitantClassification::create([ 'municipality_size_id' => $sizeId, 'description' => "Faixa {$start}-{$end}", 'start' => $start, 'end' => $end, 'tbr_percentage' => $percentage, 'is_renewal' => false, ]); } } private function makeContract(int $unitId, int $municipalitySizeId): FranchiseeContract { return FranchiseeContract::create([ 'unit_id' => $unitId, 'protocol' => $unitId, 'name' => 'Contrato de Franquia', 'description' => 'Contrato de teste', 'start_date' => '2026-01-01', 'end_date' => '2031-12-31', 'signature_date' => '2026-01-01', 'validity_months' => 60, 'invoice_due_date' => 10, 'municipality_size_id' => $municipalitySizeId, 'tbr_fixed_value' => self::TBR_VALUE, ]); } /** Competência (ano/mês) que faz o mês de contrato bater com $contractMonth, dado início em 01/2026. */ private function referenceFor(int $contractMonth): array { $date = Carbon::create(2026, 1, 1)->addMonthsNoOverflow($contractMonth - 1); return [$date->year, $date->month]; } #[DataProvider('cenario50kProvider')] public function test_cenario_50k_pequeno_porte(int $contractMonth, float $revenue, float $expectedTotal): void { [$year, $month] = $this->referenceFor($contractMonth); $result = $this->service->preview([ 'unit_id' => $this->unitPequenoPorte->id, 'reference_year' => $year, 'reference_month' => $month, 'revenue_value' => $revenue, ]); $this->assertSame($contractMonth, $result['contract_month_reference']); $this->assertEqualsWithDelta($expectedTotal, $result['final_value'], 0.01, "mês {$contractMonth}"); } #[DataProvider('cenario50kA100kProvider')] public function test_cenario_50k_a_100k_medio_porte(int $contractMonth, float $revenue, float $expectedTotal): void { [$year, $month] = $this->referenceFor($contractMonth); $result = $this->service->preview([ 'unit_id' => $this->unitMedioPorte->id, 'reference_year' => $year, 'reference_month' => $month, 'revenue_value' => $revenue, ]); $this->assertSame($contractMonth, $result['contract_month_reference']); $this->assertEqualsWithDelta($expectedTotal, $result['final_value'], 0.01, "mês {$contractMonth}"); } /** [mês de contrato, faturamento, total esperado] — planilha "Cenário 50K". */ public static function cenario50kProvider(): array { $rows = [ [1, 0, 486.30], [2, 0, 486.30], [3, 0, 486.30], [4, 5000, 1458.90], [5, 6000, 1458.90], [6, 7000, 1458.90], [7, 8000, 1458.90], [8, 9000, 1530.50], [9, 10000, 1610.50], [10, 11000, 1690.50], [11, 12000, 1770.50], [12, 13000, 1850.50], [13, 14000, 1930.50], [14, 15000, 2010.50], [15, 16000, 2090.50], ]; // A partir do mês 16, tanto Royalties quanto FNM já usam o percentual do // faturamento (8% + 2% = 10%), então o total é sempre faturamento x 10% + R$ 486,30 de manutenção. for ($month = 16; $month <= 60; $month++) { $revenue = 1000 * $month + 1000; $rows[] = [$month, $revenue, round($revenue * 0.10 + 486.30, 2)]; } return self::keyed($rows); } /** [mês de contrato, faturamento, total esperado] — planilha "Cenário 50K a 100K". */ public static function cenario50kA100kProvider(): array { $rows = [ [1, 0, 486.30], [2, 0, 486.30], [3, 0, 486.30], [4, 5000, 1621.00], [5, 6000, 1621.00], [6, 7000, 1621.00], [7, 8000, 1621.00], [8, 9000, 1621.00], [9, 10000, 1621.00], [10, 11000, 1690.50], [11, 12000, 1770.50], [12, 13000, 1850.50], [13, 14000, 2026.25], [14, 15000, 2026.25], [15, 16000, 2090.50], ]; // A partir do mês 16 o comportamento é idêntico ao cenário PP (ambos já // totalmente no percentual do faturamento). for ($month = 16; $month <= 60; $month++) { $revenue = 1000 * $month + 1000; $rows[] = [$month, $revenue, round($revenue * 0.10 + 486.30, 2)]; } return self::keyed($rows); } #[DataProvider('cenario100kA200kProvider')] public function test_cenario_100k_a_200k_grande_porte(int $contractMonth, float $revenue, float $expectedTotal): void { [$year, $month] = $this->referenceFor($contractMonth); $result = $this->service->preview([ 'unit_id' => $this->unitGrandePorte->id, 'reference_year' => $year, 'reference_month' => $month, 'revenue_value' => $revenue, ]); $this->assertSame($contractMonth, $result['contract_month_reference']); $this->assertEqualsWithDelta($expectedTotal, $result['final_value'], 0.01, "mês {$contractMonth}"); } #[DataProvider('cenarioAcima200kProvider')] public function test_cenario_acima_200k_porte_muito_grande(int $contractMonth, float $revenue, float $expectedTotal): void { [$year, $month] = $this->referenceFor($contractMonth); $result = $this->service->preview([ 'unit_id' => $this->unitMuitoGrandePorte->id, 'reference_year' => $year, 'reference_month' => $month, 'revenue_value' => $revenue, ]); $this->assertSame($contractMonth, $result['contract_month_reference']); $this->assertEqualsWithDelta($expectedTotal, $result['final_value'], 0.01, "mês {$contractMonth}"); } /** [mês de contrato, faturamento, total esperado] — planilha "100 a 200k". */ public static function cenario100kA200kProvider(): array { $rows = [ [1, 0, 486.30], [2, 0, 486.30], [3, 0, 486.30], // Royalties fixo (75% x 1.621 = 1.215,75) + FNM fixo (324,20) + TM (486,30). [4, 5000, 2026.25], [5, 6000, 2026.25], [6, 7000, 2026.25], [7, 8000, 2026.25], [8, 9000, 2026.25], [9, 10000, 2026.25], [10, 11000, 2026.25], [11, 12000, 2026.25], [12, 13000, 2026.25], // Mês 13: faixa muda pra 100% (1.621,00 fixo). Royalties e FNM ainda fixos. [13, 14000, 2431.50], [14, 15000, 2431.50], [15, 16000, 2431.50], // Meses 16-19: FNM já virou percentual do faturamento (2%), Royalties continua fixo em 1.621,00. [16, 17000, 2447.30], [17, 18000, 2467.30], [18, 19000, 2487.30], [19, 20000, 2507.30], ]; // A partir do mês 20, Royalties também vira percentual do faturamento (8%): total = 10% x faturamento + 486,30. for ($month = 20; $month <= 60; $month++) { $revenue = 1000 * $month + 1000; $rows[] = [$month, $revenue, round($revenue * 0.10 + 486.30, 2)]; } return self::keyed($rows); } /** [mês de contrato, faturamento, total esperado] — planilha "Acima de 200k". */ public static function cenarioAcima200kProvider(): array { $rows = [ [1, 0, 486.30], [2, 0, 486.30], [3, 0, 486.30], // Royalties fixo (100% x 1.621 = 1.621,00) + FNM fixo (324,20) + TM (486,30). [4, 5000, 2431.50], [5, 6000, 2431.50], [6, 7000, 2431.50], [7, 8000, 2431.50], [8, 9000, 2431.50], [9, 10000, 2431.50], [10, 11000, 2431.50], [11, 12000, 2431.50], [12, 13000, 2431.50], ]; // Mês 13: faixa muda pra 150% (2.431,50 fixo). Royalties e FNM ainda fixos até o mês 15. foreach ([13, 14, 15] as $month) { $revenue = 1000 * $month + 1000; $rows[] = [$month, $revenue, 3242.00]; } // Meses 16-29: FNM já virou percentual do faturamento (2%), Royalties continua fixo em 2.431,50. for ($month = 16; $month <= 29; $month++) { $revenue = 1000 * $month + 1000; $rows[] = [$month, $revenue, round(2431.50 + 486.30 + $revenue * 0.02, 2)]; } // A partir do mês 30, Royalties também vira percentual do faturamento (8%): total = 10% x faturamento + 486,30. for ($month = 30; $month <= 60; $month++) { $revenue = 1000 * $month + 1000; $rows[] = [$month, $revenue, round($revenue * 0.10 + 486.30, 2)]; } return self::keyed($rows); } /** @param array $rows */ private static function keyed(array $rows): array { $keyed = []; foreach ($rows as $row) { $keyed["mês {$row[0]}"] = $row; } return $keyed; } }