Bladeren bron

feat: add royalty calculation documentation and audit table requirements

alvesantos 2 dagen geleden
bovenliggende
commit
e746a62cca
2 gewijzigde bestanden met toevoegingen van 76 en 305 verwijderingen
  1. 76 0
      docs/calculo-royalties.md
  2. 0 305
      docs/regras-financeiras-contrato-unidade.md

+ 76 - 0
docs/calculo-royalties.md

@@ -0,0 +1,76 @@
+# Calculo de Royalties
+
+O Calculo é baseado em 3 faixas de tempo de contrato (Mês sequencial desde a abertura) e 3 Componentes de cobrança.
+
+## Royalties
+
+- Meses 1 a 3: Isento
+- Meses 4 a 12: Maior entre % do TBR Fixo do ano (Atribuido em Configurações do TBR) ou 8%  to total do faturamento da unidade.
+- Meses 13 a 60: Maior entre % fixa da tbr anual ou 8% de faturamento total da unidade
+
+## FNM
+
+- Meses 1 a 3: Isento
+- Meses 4 a 12: Maior entre: Porcentagem Fixa do TBR Anual ou 2% do faturamento total da unidade
+- Meses 13 a 60: Maior entre: Porcentagem Fixa do TBR Anual ou 2% do faturamento total da unidade
+
+## Taxa de Manutenção:
+
+- Meses 1 a 60: 30% do Fixo da TBR
+
+`Devemos guardar no banco de dados qual o criterio que foi aplicado tbr_fixo (tbr_fixed) ou porcentual_faturamento (revenue_percentage)`
+
+# Classificação de Habitantes
+
+Define categorias de porte da região (Pequeno, Médio, Grande Porte) que determinam qual faixa de royalties se aplica ao franqueado.
+
+--
+
+## Tabela de Auditoria
+
+Devemos ter uma tabela. Todos os valores intermediários devem ser gravados para garantir auditoria completa de qualquer cálculo.
+
+Campos esperados:
+
+- Referencia de unidade (unidade_id)
+- Valor do Faturamento Bruto do Mês em questão da unidade
+- Referencia do mês de contrato da unidade. Ex: Mes 15 em sequencia, deve ser guardado como 15.
+- Valor da TBR no momento do calculo
+- Referencia a faixa FNM utilizada
+- Valor Percentual da faixa de FNM Aplicada (Snapshot)
+- Valor calculado pela faixa de FNM (% da TBR)
+- Referencia para a Faixa da Manutenção utilizada
+- Percentual da Faixa de Manutenção (Snapshot)
+- Valor calculado pela faixa de manutenção (% da TBR)
+- Referencia para a faixa de Royalties da unidade
+- Percentual da faixa de royalties (Snapshot)
+- Valor calculado de royalties pela faixa (% da TBR)
+- Percentual efetivo do FNM cobrado na cobrança final
+-  Valor efetivo do FNM cobrado
+- Percentual efetivo do royalties cobrado na cobrança final
+- Valor efetivo dos royalties cobrado
+- Percentual efetivo da manutenção cobrado na cobrança final
+- Valor efetivo da manutenção cobrado
+- Subtotal calculado pelas faixas (antes da comparação com % de faturamento)
+- Subtotal após aplicar a regra "maior valor" (Faixa vs % Faturamento)
+- Valor total final a cobrar da unidade
+- Usuário que executou ou confirmou o calculo (Nullable, pode ser automático)
+- Referencia ao criterio aplicado
+- Booleano se conta a receber foi gerada
+- Booleana se conta a pagar para unidae foi gerado
+- timestamps
+
+---
+
+Também devemos ter tabela de contas a receber
+- Uma tabela de contas a receber da unidade
+- Outra tabela de contas a receber da franqueadora
+- Uma tabela de contas a pagar da unidade também
+
+Todas essas tabelas devem ter status de aguardando pagamento, pago, identificador se já foi gerado e etc
+
+Na tab de financeiro, em UnitActionPage, a gente pode definir se FNM e Royalties serão cobrados ou não. Se tiver desativado, não cobraremos para a unidade em questão esses valores.
+
+
+
+

+ 0 - 305
docs/regras-financeiras-contrato-unidade.md

@@ -1,305 +0,0 @@
-# Regras financeiras do contrato da unidade (TBR / Royalties / FNM / Manutenção)
-
-Documento simples e direto sobre **como o sistema cobra a unidade franqueada**.
-Base do código: `app/Services/TbrCalculationService.php`.
-
----
-
-## 1. O que é criado junto com a unidade
-
-Ao criar uma unidade (`UnitService::create`), o sistema já cria:
-
-| Registro | Tabela | Para quê |
-|---|---|---|
-| Unidade | `units` | cadastro |
-| Franqueado + vínculo | `franchisees`, `franchisee_units` | dono da unidade |
-| **Financeiro da unidade** | `unit_financials` | nasce com `charge_roi = true` e `charge_fnm = true` |
-| Perfis de acesso | `user_types` | ADMIN_FRANCHISEE + templates |
-| Pacotes de aula | `class_package_units` | réplica dos pacotes base |
-
-O **contrato** é um cadastro à parte (`franchisee_contracts`), criado depois pela franqueadora.
-Uma unidade pode ter vários contratos; o sistema sempre usa **o de `start_date` mais recente**.
-
-> Atenção ao nome: a tabela `unit_contracts` **não** tem regra de cobrança — é só upload de PDF.
-> Quem carrega as regras é `franchisee_contracts`.
-
----
-
-## 2. O contrato (`franchisee_contracts`)
-
-Campos que importam para a cobrança:
-
-| Campo | Papel |
-|---|---|
-| `unit_id` | unidade dona do contrato |
-| `start_date` | **marco zero**: define o "mês de contrato" (mês 1, 2, 3...) |
-| `end_date` | fim da vigência (nulo = sem fim) |
-| `municipality_size_id` | **porte do município** (PP / MP / GP / MGP) → define o % de royalties |
-| `tbr_fixed_value` | TBR de reserva: só é usada se o ano de referência não estiver cadastrado em `tbrs` |
-| `invoice_due_date` | dia do vencimento da fatura (1 a 28; padrão 10) |
-
-Regras de cadastro (`FranchiseeContractService`):
-
-- `protocol` = maior protocolo daquela unidade + 1 (sequência **por unidade**).
-- `signature_date` = `start_date`.
-- `validity_months` = meses entre `start_date` e `end_date`.
-- Ao alterar campos de imposto (`municipality_size_id`, `tbr_fixed_value`, `tbr_fixed_value_percentage`,
-  `marketing_fund_percentage`, `maintance_tax_percentage`) o sistema grava
-  `franchisee_contract_tax_histories`: na **primeira** alteração grava o "antes" e o "depois";
-  nas seguintes, só o "depois".
-
----
-
-## 3. Os três valores cobrados todo mês
-
-Toda competência (mês/ano) a franqueadora cobra da unidade:
-
-1. **Royalties (ROI)**
-2. **FNM** — Fundo Nacional de Marketing
-3. **Taxa de Manutenção**
-
-Total do título = soma dos três (`final_value`). Não há desconto/juros embutidos no cálculo.
-
----
-
-## 4. Passo a passo do cálculo
-
-### 4.1. Achar o valor da TBR
-
-```
-TBR = tbrs.tbr_value do ano de referência   (se > 0)
-    senão contrato.tbr_fixed_value
-    senão → erro "TBR não definida para o ano de referência nem para o contrato."
-```
-
-**A tabela anual `tbrs` manda.** É a configuração que o admin cadastra por ano e vale para
-a rede inteira; se o ano de referência estiver cadastrado, o valor do contrato é ignorado.
-O `tbr_fixed_value` do contrato só é usado quando aquele ano não tem configuração
-(ou está com valor zerado).
-
-Exemplo: `tbrs` 2026 = R$ 1.621,00 e contrato com `tbr_fixed_value` = R$ 1.400,00
-→ competência de 2026 calcula sobre **R$ 1.621,00**.
-
-### 4.2. Achar o "mês de contrato"
-
-```
-mês_de_contrato = meses entre (início do mês de start_date) e (início do mês de referência) + 1
-mínimo = 1
-```
-
-Exemplo: contrato começa em **jan/2026**.
-- competência jan/2026 → mês 1
-- competência mar/2026 → mês 3
-- competência abr/2026 → mês 4
-
-Se o contrato não tiver `start_date`, o mês vira 1.
-
-### 4.3. Isenção dos meses 1 a 3
-
-**Meses 1, 2 e 3: Royalties = 0 e FNM = 0.**
-O critério registrado é `tbr_fixo`.
-
-> A **Taxa de Manutenção continua sendo cobrada** nos meses 1 a 3. A isenção é só de ROI e FNM.
-
-### 4.4. Do mês 4 em diante — a regra do "maior valor"
-
-Cada componente tem dois candidatos: um valor **fixo sobre a TBR** e um **percentual do faturamento**.
-Vence o **maior**. Em caso de empate, vence o fixo da TBR.
-
-**Royalties**
-
-```
-fixo      = % da faixa do porte  ×  TBR
-faturamento = 8% × faturamento do mês
-Royalties  = max(fixo, faturamento)
-critério   = 'tbr_fixo' se fixo >= faturamento, senão 'percentual_faturamento'
-```
-
-**FNM**
-
-```
-fixo      = tbrs.fnm_percentage do ano  ×  TBR    (fallback do código: 20%)
-faturamento = 2% × faturamento do mês
-FNM        = max(fixo, faturamento)
-```
-
-**Taxa de Manutenção** — não tem comparação e não tem isenção:
-
-```
-Manutenção = tbrs.maintenance_percentage do ano × TBR   (fallback do código: 30%)
-```
-
-### 4.5. Faixas de royalties por porte do município
-
-Vêm de `inhabitant_classifications`, filtrando `is_renewal = false` e
-`start <= mês_de_contrato <= end`. Valores atuais do seeder:
-
-| Porte | Habitantes | Meses 1–3 | Meses 4–12 | Meses 13–60 |
-|---|---|---|---|---|
-| PP  | até 50 mil       | 0% | **40%** da TBR | **60%** da TBR |
-| MP  | 50 mil–100 mil   | 0% | **50%** | **75%** |
-| GP  | 100 mil–200 mil  | 0% | **75%** | **100%** |
-| MGP | acima de 200 mil | 0% | **100%** | **150%** |
-
-Se não achar faixa para o mês → erro
-"Não foi encontrada faixa de royalties para o porte e mês de contrato informados."
-
-### 4.6. Chaves liga/desliga da unidade (`unit_financials`)
-
-| Flag | Efeito quando `false` |
-|---|---|
-| `charge_roi` | Royalties = 0, critério vira `nao_cobrado` e o sistema **nem procura a faixa** |
-| `charge_fnm` | FNM = 0 |
-
-Se a unidade **não tiver** registro em `unit_financials`, o sistema assume **cobra os dois**.
-A Taxa de Manutenção não tem flag: é sempre cobrada.
-
----
-
-## 5. Como o faturamento do mês é apurado
-
-Só entra o que **vence dentro do mês de referência** e **não está `cancelled`** (nem soft-deleted):
-
-| Fonte | Tabela | Filtro |
-|---|---|---|
-| Parcelas de aluno | `student_contract_installments` | `unit_id`, `due_date` no mês |
-| Recebíveis da unidade | `unit_account_receivables` | `unit_id`, `due_date` no mês |
-| Contas que a matriz lançou na unidade | `franchisee_account_receives` | `origin = 'manual_unit'` e `unit_id` preenchido |
-
-**Fora da base:** recebíveis de TBR (`origin = 'tbr'`) — senão a cobrança realimentaria a própria base.
-
-Duas formas de informar o faturamento:
-
-- **Automático**: soma acima, do próprio mês de referência
-  (a constante `REVENUE_FROM_PREVIOUS_MONTH` está `false`).
-- **Manual**: quem calcula manda `revenue_value` > 0 e esse valor **substitui** a soma automática.
-
----
-
-## 6. Da conta ao título
-
-### 6.1. Cálculo (`preview` / `calculate`)
-
-- `preview` só simula; `calculate` grava um registro em `tbr_calculations`.
-- O contrato usado é o mais recente com `start_date` preenchido.
-- Sem contrato → "Unidade não possui contrato cadastrado."
-- Contrato sem `municipality_size_id` → "O contrato da unidade não tem a faixa de habitantes definida."
-
-### 6.2. Geração do título (`generateReceivable`)
-
-Bloqueios:
-- cálculo já usado (`receivable_generated = true`) → erro;
-- já existe outro título gerado para **a mesma unidade no mesmo mês de contrato** → erro.
-
-O que é criado:
-
-| Registro | Conteúdo |
-|---|---|
-| `franchisee_account_receives` | valor total, `status = 'pending'`, `order` = mês de contrato, histórico "Royalties / FNM / Manutenção — MM/AAAA" |
-| 3× `franchisee_account_receive_details` | Royalties, FNM e Taxa Manutenção separados |
-| `unit_account_payables` | espelho da dívida no financeiro da unidade, `origin = 'tbr'` |
-| `SyncFranchiseeChargeJob` | tenta emitir a cobrança no Asaas (sem chave configurada, fica para baixa manual) |
-
-**Vencimento:**
-
-```
-vencimento = (mês de referência + 1 mês) no dia invoice_due_date
-dia limitado entre 1 e 28; padrão 10 quando o contrato não informa
-```
-
-Ex.: competência 03/2026, `invoice_due_date = 15` → vence **15/04/2026**.
-
-### 6.3. Geração em lote (`generateBatch`)
-
-Pega os contratos **ativos** na competência: `start_date <= último dia do mês`,
-`end_date` nulo ou `>= primeiro dia do mês`, `municipality_size_id` preenchido,
-**um contrato por unidade** (o de `start_date` mais recente).
-Retorna `generated` / `skipped` (já gerado) / `errors`.
-
----
-
-## 7. Exemplo numérico
-
-Contrato: porte **GP**, `start_date = 01/01/2026`, TBR = R$ 1.621,00, `invoice_due_date = 10`.
-Tabela `tbrs` 2026: FNM 20%, Manutenção 30%.
-
-**Competência 02/2026 (mês 2) — faturamento R$ 40.000**
-
-| Componente | Conta | Valor |
-|---|---|---|
-| Royalties | isento (mês ≤ 3) | R$ 0,00 |
-| FNM | isento (mês ≤ 3) | R$ 0,00 |
-| Manutenção | 30% × 1.621,00 | R$ 486,30 |
-| **Total** | | **R$ 486,30** |
-
-**Competência 05/2026 (mês 5) — faturamento R$ 40.000**
-
-| Componente | Fixo TBR | % faturamento | Vence | Valor |
-|---|---|---|---|---|
-| Royalties | 75% × 1.621 = 1.215,75 | 8% × 40.000 = 3.200,00 | faturamento | R$ 3.200,00 |
-| FNM | 20% × 1.621 = 324,20 | 2% × 40.000 = 800,00 | faturamento | R$ 800,00 |
-| Manutenção | 30% × 1.621 = 486,30 | — | — | R$ 486,30 |
-| **Total** | | | | **R$ 4.486,30** |
-
-Vencimento: **10/06/2026**.
-
-**Competência 05/2026 (mês 5) — faturamento R$ 5.000**
-
-| Componente | Fixo TBR | % faturamento | Vence | Valor |
-|---|---|---|---|---|
-| Royalties | 1.215,75 | 400,00 | fixo TBR | R$ 1.215,75 |
-| FNM | 324,20 | 100,00 | fixo TBR | R$ 324,20 |
-| Manutenção | — | — | — | R$ 486,30 |
-| **Total** | | | | **R$ 2.026,25** |
-
----
-
-## 8. Pontos de atenção encontrados na varredura
-
-Itens que **divergem entre si** no código atual — vale decidir qual é o certo:
-
-0. **Contratos com `tbr_fixed_value` diferente da tabela anual passam a ser ignorados.**
-   Com a tabela do ano no comando, qualquer TBR negociada e gravada no contrato deixa de
-   valer enquanto aquele ano estiver cadastrado em `tbrs`. Se existirem contratos com TBR
-   negociada, eles precisam de outro campo/regra.
-
-1. **`tbrs.fnm_percentage` semeado como 0,02.**
-   O `TbrSeeder` grava `fnm_percentage = 0.0200`, mas o cálculo usa esse campo como
-   *percentual sobre a TBR* (o fallback do código é `0.20`). Com o dado semeado, o FNM fixo
-   de uma TBR de R$ 1.621 vira **R$ 32,42** em vez de **R$ 324,20**. O 2% parece ter sido
-   pensado para o percentual sobre faturamento, que já é constante no código.
-
-2. **`tbrs.royalties_percentage` nunca é lido.** Os 8% sobre faturamento são a constante
-   `ROYALTIES_REVENUE_RATE` no serviço.
-
-3. **`TbrBillingPreviewService` tem uma segunda versão das regras.** Ele usa
-   `contrato.tbr_fixed_value_percentage` / `marketing_fund_percentage`, ignora o faturamento
-   e ignora `charge_roi` / `charge_fnm`. Resultado diferente do `TbrCalculationService`.
-
-4. **Duplicidade é checada só pelo mês de contrato**, sem olhar ano/mês de competência
-   (`existingReceivable(unit_id, contract_month)`).
-
-5. **Faixas de renovação não são usadas.** A busca fixa `is_renewal = false`, e as faixas
-   terminam no mês 60 — a partir do mês 61 o cálculo estoura com "faixa não encontrada".
-
-6. **Campos do contrato sem uso no cálculo**: `marketing_fund_fixed_value`,
-   `maintance_tax_fixed_value`, `maintance_tax_percentage`, `cancellation_fine`,
-   `discount_until_due_date`.
-
----
-
-## 9. Onde estão os testes
-
-`tests/Unit/Financeiro/TbrCalculationRulesTest.php` cobre as regras acima.
-Rodar com:
-
-```bash
-php artisan test --filter=TbrCalculationRulesTest
-```
-
-Os testes usam o banco `db_test_ginastica_cerebro`, apontado em `phpunit.xml`.
-Na primeira vez, crie o banco (o `RefreshDatabase` cuida das migrations):
-
-```bash
-createdb -h localhost -U postgres db_test_ginastica_cerebro
-```