فهرست منبع

feat: enhance UnitFinancialDashboardService to include linked accounts from franchisee in financial overview

alvesantos 1 روز پیش
والد
کامیت
fc5052e374
2فایلهای تغییر یافته به همراه68 افزوده شده و 18 حذف شده
  1. 28 7
      app/Services/UnitFinancialDashboardService.php
  2. 40 11
      tests/Unit/Financeiro/StudentBillingRevenueTest.php

+ 28 - 7
app/Services/UnitFinancialDashboardService.php

@@ -2,6 +2,7 @@
 
 
 namespace App\Services;
 namespace App\Services;
 
 
+use App\Models\FranchiseeAccountReceive;
 use App\Models\StudentContractInstallment;
 use App\Models\StudentContractInstallment;
 use App\Models\UnitAccountPayable;
 use App\Models\UnitAccountPayable;
 use App\Models\UnitAccountReceivable;
 use App\Models\UnitAccountReceivable;
@@ -9,7 +10,8 @@
 
 
 /**
 /**
  * Visão geral financeira da unidade: agrega Contas a Receber (parcelas de aluno
  * Visão geral financeira da unidade: agrega Contas a Receber (parcelas de aluno
- * + recebíveis avulsos manuais) e Contas a Pagar (unit_account_payables, que já
+ * + recebíveis avulsos manuais + contas que a franqueadora vinculou à unidade,
+ * ligadas ou não ao Asaas) e Contas a Pagar (unit_account_payables, que já
  * inclui os lançamentos manuais) do mês corrente.
  * inclui os lançamentos manuais) do mês corrente.
  */
  */
 class UnitFinancialDashboardService
 class UnitFinancialDashboardService
@@ -34,6 +36,19 @@ public function overview(int $unitId): array
         $manOverdueQ  = UnitAccountReceivable::where('unit_id', $unitId)
         $manOverdueQ  = UnitAccountReceivable::where('unit_id', $unitId)
             ->where('status', 'pending')->where('due_date', '<', $today);
             ->where('status', 'pending')->where('due_date', '<', $today);
 
 
+        // Contas que a franqueadora vinculou à unidade (origin=manual_unit) — mesma
+        // fonte que entra no faturamento do cálculo de TBR (TbrCalculationService).
+        // Conta independente de ter ou não cobrança Asaas gerada: o cliente reclama
+        // se faltar aqui, mesmo sem integração.
+        $linkedPendingQ  = FranchiseeAccountReceive::where('unit_id', $unitId)
+            ->where('origin', 'manual_unit')->where('status', 'pending');
+        $linkedReceivedQ = FranchiseeAccountReceive::where('unit_id', $unitId)
+            ->where('origin', 'manual_unit')
+            ->where('status', 'paid')->whereBetween('payment_date', [$start, $end]);
+        $linkedOverdueQ  = FranchiseeAccountReceive::where('unit_id', $unitId)
+            ->where('origin', 'manual_unit')
+            ->where('status', 'pending')->where('due_date', '<', $today);
+
         // Pagáveis (unit_account_payables já inclui os manuais, origin='manual')
         // Pagáveis (unit_account_payables já inclui os manuais, origin='manual')
         $payPendingQ  = UnitAccountPayable::where('unit_id', $unitId)->where('status', 'pending');
         $payPendingQ  = UnitAccountPayable::where('unit_id', $unitId)->where('status', 'pending');
         $payPaidQ     = UnitAccountPayable::where('unit_id', $unitId)
         $payPaidQ     = UnitAccountPayable::where('unit_id', $unitId)
@@ -42,28 +57,34 @@ public function overview(int $unitId): array
             ->where('status', 'pending')->where('due_date', '<', $today);
             ->where('status', 'pending')->where('due_date', '<', $today);
 
 
         $receivedMonth = (float) (clone $recReceivedQ)->sum('paid_value')
         $receivedMonth = (float) (clone $recReceivedQ)->sum('paid_value')
-            + (float) (clone $manReceivedQ)->sum('paid_value');
+            + (float) (clone $manReceivedQ)->sum('paid_value')
+            + (float) (clone $linkedReceivedQ)->sum('paid_value');
         $paidMonth     = (float) (clone $payPaidQ)->sum('paid_value');
         $paidMonth     = (float) (clone $payPaidQ)->sum('paid_value');
 
 
         $receivablePending = (float) (clone $recPendingQ)->sum('value')
         $receivablePending = (float) (clone $recPendingQ)->sum('value')
-            + (float) (clone $manPendingQ)->sum('value');
+            + (float) (clone $manPendingQ)->sum('value')
+            + (float) (clone $linkedPendingQ)->sum('value');
         $overdueReceivable = (float) (clone $recOverdueQ)->sum('value')
         $overdueReceivable = (float) (clone $recOverdueQ)->sum('value')
-            + (float) (clone $manOverdueQ)->sum('value');
+            + (float) (clone $manOverdueQ)->sum('value')
+            + (float) (clone $linkedOverdueQ)->sum('value');
 
 
         return [
         return [
             'received_month'           => round($receivedMonth, 2),
             'received_month'           => round($receivedMonth, 2),
-            'received_month_count'     => (clone $recReceivedQ)->count() + (clone $manReceivedQ)->count(),
+            'received_month_count'     => (clone $recReceivedQ)->count() + (clone $manReceivedQ)->count()
+                + (clone $linkedReceivedQ)->count(),
             'paid_month'               => round($paidMonth, 2),
             'paid_month'               => round($paidMonth, 2),
             'paid_month_count'         => (clone $payPaidQ)->count(),
             'paid_month_count'         => (clone $payPaidQ)->count(),
             'result_month'             => round($receivedMonth - $paidMonth, 2),
             'result_month'             => round($receivedMonth - $paidMonth, 2),
 
 
             'receivable_pending'       => round($receivablePending, 2),
             'receivable_pending'       => round($receivablePending, 2),
-            'receivable_pending_count' => (clone $recPendingQ)->count() + (clone $manPendingQ)->count(),
+            'receivable_pending_count' => (clone $recPendingQ)->count() + (clone $manPendingQ)->count()
+                + (clone $linkedPendingQ)->count(),
             'payable_pending'          => round((float) (clone $payPendingQ)->sum('value'), 2),
             'payable_pending'          => round((float) (clone $payPendingQ)->sum('value'), 2),
             'payable_pending_count'    => (clone $payPendingQ)->count(),
             'payable_pending_count'    => (clone $payPendingQ)->count(),
 
 
             'overdue_receivable'       => round($overdueReceivable, 2),
             'overdue_receivable'       => round($overdueReceivable, 2),
-            'overdue_receivable_count' => (clone $recOverdueQ)->count() + (clone $manOverdueQ)->count(),
+            'overdue_receivable_count' => (clone $recOverdueQ)->count() + (clone $manOverdueQ)->count()
+                + (clone $linkedOverdueQ)->count(),
             'overdue_payable'          => round((float) (clone $payOverdueQ)->sum('value'), 2),
             'overdue_payable'          => round((float) (clone $payOverdueQ)->sum('value'), 2),
             'overdue_payable_count'    => (clone $payOverdueQ)->count(),
             'overdue_payable_count'    => (clone $payOverdueQ)->count(),
         ];
         ];

+ 40 - 11
tests/Unit/Financeiro/StudentBillingRevenueTest.php

@@ -119,17 +119,13 @@ public function test_entrada_com_vencimento_em_mes_diferente_entra_no_faturament
     }
     }
 
 
     /**
     /**
-     * Atenção (achado da investigação, não é uma regra de negócio pedida):
-     * O dashboard financeiro da unidade (UnitFinancialDashboardService::overview,
-     * campo receivable_pending) e o faturamento usado pelo cálculo de royalties
-     * (TbrCalculationService::resolveRevenueBetween) NÃO somam as mesmas fontes.
-     * O dashboard não inclui FranchiseeAccountReceive com origin=manual_unit
-     * (contas que a franqueadora vincula à unidade); o cálculo de TBR inclui.
-     *
-     * Este teste documenta o comportamento atual — não afirma qual dos dois
-     * está "certo". Se um dia precisarem bater, um dos dois serviços muda.
+     * Regra: O dashboard financeiro da unidade (UnitFinancialDashboardService)
+     * e o faturamento usado pelo cálculo de royalties (TbrCalculationService)
+     * precisam bater — inclusive contas que a franqueadora vinculou à unidade
+     * (origin=manual_unit) sem cobrança Asaas nenhuma. Antes dessa correção o
+     * dashboard ignorava essa fonte; o cliente reclamava do número não bater.
      */
      */
-    public function test_atencao_dashboard_da_unidade_diverge_do_faturamento_usado_no_tbr(): void
+    public function test_dashboard_da_unidade_soma_contas_vinculadas_pela_franqueadora_mesmo_sem_asaas(): void
     {
     {
         FranchiseeAccountReceive::create([
         FranchiseeAccountReceive::create([
             'unit_id'  => $this->unit->id,
             'unit_id'  => $this->unit->id,
@@ -149,6 +145,39 @@ public function test_atencao_dashboard_da_unidade_diverge_do_faturamento_usado_n
         $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id);
         $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id);
 
 
         $this->assertSame(1000.0, $tbrRevenueBetween);
         $this->assertSame(1000.0, $tbrRevenueBetween);
-        $this->assertSame(0.0, $dashboard['receivable_pending']);
+        $this->assertSame(1000.0, $dashboard['receivable_pending']);
+        $this->assertSame(1, $dashboard['receivable_pending_count']);
+    }
+
+    /**
+     * Regra: Conta vinculada pela franqueadora paga dentro do mês entra em
+     * "recebido no mês"; vencida (não paga) entra em "vencido".
+     */
+    public function test_dashboard_conta_recebido_e_vencido_das_contas_vinculadas_pela_franqueadora(): void
+    {
+        FranchiseeAccountReceive::create([
+            'unit_id'      => $this->unit->id,
+            'origin'       => 'manual_unit',
+            'history'      => 'Paga no mês',
+            'value'        => 200,
+            'paid_value'   => 200,
+            'due_date'     => now()->startOfMonth()->toDateString(),
+            'payment_date' => now()->toDateString(),
+            'status'       => 'paid',
+        ]);
+
+        FranchiseeAccountReceive::create([
+            'unit_id'  => $this->unit->id,
+            'origin'   => 'manual_unit',
+            'history'  => 'Vencida',
+            'value'    => 300,
+            'due_date' => now()->subDays(5)->toDateString(),
+            'status'   => 'pending',
+        ]);
+
+        $dashboard = (new UnitFinancialDashboardService())->overview($this->unit->id);
+
+        $this->assertSame(200.0, $dashboard['received_month']);
+        $this->assertSame(300.0, $dashboard['overdue_receivable']);
     }
     }
 }
 }