@extends('layouts.app') @section('title', 'Lançamentos — '.config('app.name')) {{-- LANÇAMENTOS — a tela única do financeiro. A tela inteira gira em torno de UMA distinção, e ela precisa ficar visível o tempo todo: PREVISTO é pelo vencimento ("quanto o período deveria movimentar"), REALIZADO é pela baixa ("quanto entrou e saiu de fato"). Uma conta de junho paga em julho aparece nos dois, em meses diferentes — e as duas afirmações estão certas. AS AÇÕES MORAM NA LINHA, não numa segunda tela: clicar no lançamento abre a edição, o polegar dá e desfaz a baixa, e o "+" do cabeçalho cadastra receita ou despesa. Todas postam para as rotas de escrita de sempre — esta tela não tem regra de dinheiro própria. --}} @php $money = fn (int $cents) => App\Support\Money::format($cents); $signed = fn (int $cents) => ($cents < 0 ? '− ' : '').App\Support\Money::format(abs($cents)); $income = App\Enums\FinancialOperation::In; $expense = App\Enums\FinancialOperation::Out; @endphp @section('content')

Lançamentos

O que a arena movimentou no período.

{{-- O "+" abre um menu em vez de um modal direto porque a escolha receita/despesa é a PRIMEIRA decisão do cadastro e define o lado do caixa — que vem da rota (o `action` de cada formulário), nunca de um campo. Um seletor dentro do modal reintroduziria pelo formulário exatamente o que a rota existe para impedir. --}} @can('create', App\Models\FinancialEntry::class) {{-- `data-hover-dropdown`: o menu abre ao passar o mouse, sem exigir o clique. O clique CONTINUA valendo — é o que mantém o botão utilizável no celular, que não tem hover, e no teclado. Ver resources/js/cashflow.js. --}} @endcan
@include('partials.status')
@php $previous = $period->shiftMonths(-1); $next = $period->shiftMonths(1); @endphp
@if ($filters->isActive()) @endif {{-- TIPO: lado do caixa e situação num controle só, porque é assim que a pergunta nasce ("o que eu já recebi?"). Ver App\Enums\TransactionFilter para a diferença entre "receitas previstas" (ainda não recebidas) e a leitura "Previsto" da tabela (pelo vencimento). --}} {{-- Contas e tags são MÚLTIPLAS e somam em "ou": marcar duas contas pergunta "o que passou por estas contas?". UMA ESCOLHIDA MOSTRA O NOME; VÁRIAS, A CONTAGEM. "1 conta" escondia justamente o que o usuário acabara de escolher — qual conta. O JS repete esta regra ao marcar e desmarcar; ver resources/js/cashflow.js. --}} {{-- O rótulo do botão sai daqui, e não de um encadeamento de ternários dentro do echo: a regra tem três casos e fica ilegível em uma linha só. ⚠️ DUAS ARMADILHAS DO BLADE que já custaram caro neste arquivo, e que valem para qualquer view: 1. Use o bloco PHP com abertura e fechamento em linhas próprias, NUNCA a forma curta com a expressão entre parênteses. Quando a expressão tem parênteses aninhados (o whereIn abaixo), a forma curta não é reconhecida e sai crua no compilado, como uma abertura de PHP que nunca fecha. 2. NÃO escreva diretivas do Blade dentro de um comentário como este, nem entre crases. O compilador processa as diretivas ANTES de remover os comentários, então a diretiva citada é compilada de verdade, quebra o par de marcadores do comentário e o resto da tela para de renderizar — sem erro nenhum, só conteúdo faltando. --}} @php $selectedAccounts = $bankAccounts->whereIn('id', $filters->bankAccountIds); $accountsLabel = match (true) { $selectedAccounts->count() === 1 => $selectedAccounts->first()->name, $filters->bankAccountIds === [] => 'Contas', default => count($filters->bankAccountIds).' contas', }; @endphp {{-- Uma tag mostra o nome; várias, a contagem — a mesma regra das contas, logo acima. --}} {{-- Mesmas duas cautelas registradas no bloco das contas, acima. --}} @php $selectedTags = $tags->whereIn('id', $filters->tagIds); $tagsLabel = match (true) { $selectedTags->count() === 1 => $selectedTags->first()->name, $filters->tagIds === [] => 'Tags', default => count($filters->tagIds).' tags', }; @endphp
{{-- Inadimplência: o que venceu no período e ninguém pagou. Fica em destaque próprio porque é a única linha do relatório sobre a qual dá para AGIR hoje. --}} @if ($overdue['in'] > 0 || $overdue['out'] > 0)
Vencido e em aberto A receber: {{ $money($overdue['in']) }} A pagar: {{ $money($overdue['out']) }} {{-- Antes este botão levava para a tela de Contas a Receber filtrada. Agora ele FILTRA A PRÓPRIA TELA: a lista das receitas em aberto já está aqui, e mandar o usuário embora para ver um recorte do que ele está olhando era a duplicação de tela que esta sprint eliminou. --}} Ver receitas em aberto
@endif {{-- LANÇAMENTOS DO MÊS. Ficou no lugar da antiga tabela "mês a mês", que era só resumo. Com os lançamentos à vista, o efeito de cada filtro é visível LINHA A LINHA, em vez de ter de ser deduzido de um total que mudou — era o que fazia parecer que os filtros não estavam sendo aplicados. Tudo aqui vem do servidor, inclusive a troca de mês e de leitura: um caminho de código só para a tela e para o filtro elimina a classe inteira de defeito em que a lista e os totais discordam. --}}
{{-- A lista é AGRUPADA POR DIA, não uma tabela com uma coluna de data: o dia repetido em vinte linhas seguidas é ruído, e o separador responde "o que aconteceu no dia 10?" de relance. `groupBy` sobre a PÁGINA já carregada, e não uma consulta por dia: os lançamentos estão em memória, e agrupar aqui não custa ida ao banco nenhuma. --}} {{-- O FECHAMENTO DO MÊS, como na referência. Repete a distinção da tela inteira em quatro linhas: realizado é o que passou pelo caixa, previsto é o que o mês deveria movimentar. Os dois saldos aparecem juntos e NUNCA somados entre si. --}}
Receita realizada {{ $money($periodTotals['realized']['in']) }}
Receita prevista {{ $money($periodTotals['expected']['in']) }}
Despesa realizada − {{ $money($periodTotals['realized']['out']) }}
Despesa prevista − {{ $money($periodTotals['expected']['out']) }}
{{-- AS TRANSFERÊNCIAS aparecem sempre que houve alguma no período, mesmo quando o efeito é ZERO — que é o caso sem filtro de conta, porque as duas pontas são da arena e o dinheiro só mudou de lugar. Esconder a linha nesse caso faria parecer que não houve transferência nenhuma. Filtrando por uma conta, aqui aparece quanto saiu (ou entrou) DELA, e o saldo abaixo pode ficar negativo. --}} @if ($periodTotals['has_transfers'])
Transferências {{ $signed($periodTotals['transfers']) }}
@endif
{{-- "Saldo atual" e não "realizado": é o dinheiro que de fato está no caixa neste recorte — as baixas mais o efeito das transferências. "Realizado" formava par com "previsto" e sugeria duas projeções do mesmo mês; são coisas diferentes. --}}
Saldo atual {{ $signed($periodTotals['realized']['balance']) }}
Saldo previsto {{ $signed($periodTotals['expected']['balance']) }}
@if ($entries->hasPages())
{{ $entries->links() }}
@endif
{{-- Sugestões de tag, compartilhadas por TODOS os modais da página: um `` por modal repetiria a mesma lista dezenas de vezes no HTML sem nenhum ganho. --}} @foreach ($tagSuggestions as $tagName) @endforeach {{-- OS MODAIS FICAM AQUI, no fim e fora da lista — um
dentro de outro é HTML inválido e o navegador desmonta silenciosamente o de dentro. A barra de filtros no topo é um , e a lista inteira vive depois dela; deixar os modais aqui embaixo mantém a garantia. DOIS modais de cadastro, um por lado do caixa: cada um posta para a sua rota (`financeiro/receber`, `financeiro/pagar`) e recebe só as categorias daquele lado. É o que impede escolher "Energia" numa receita sem nenhuma validação condicional. --}} @can('create', App\Models\FinancialEntry::class) @include('financial._form', [ 'modalId' => 'financial-entry-create-in', 'entry' => null, 'operation' => $income, 'month' => $period->from->copy()->startOfMonth(), 'categories' => $formCategories[$income->value], 'bankAccounts' => $formBankAccounts, ]) @include('financial._form', [ 'modalId' => 'financial-entry-create-out', 'entry' => null, 'operation' => $expense, 'month' => $period->from->copy()->startOfMonth(), 'categories' => $formCategories[$expense->value], 'bankAccounts' => $formBankAccounts, ]) @endcan @can('create', App\Models\FinancialTransfer::class) @include('financial._transfer', [ 'modalId' => 'financial-transfer-create', 'transfer' => null, 'month' => $period->from->copy()->startOfMonth(), 'bankAccounts' => $formBankAccounts, ]) @endcan @foreach ($entries as $entry) {{-- A lista mistura lançamentos e transferências (ver o controller), e cada um tem o seu próprio modal de edição. O salto adiante evita que a transferência caia nas autorizações de lançamento logo abaixo, onde a policy errada seria consultada. --}} @if ($entry instanceof App\Models\FinancialTransfer) @can('update', $entry) @include('financial._transfer', [ 'modalId' => 'financial-transfer-'.$entry->id, 'transfer' => $entry, 'month' => $period->from->copy()->startOfMonth(), 'bankAccounts' => $formBankAccounts, ]) @endcan {{-- O formulário de exclusão, fora do modal — ver o do lançamento logo abaixo para o porquê do `form=`. --}} @can('delete', $entry) @csrf @method('DELETE')
@endcan @continue @endif {{-- O CONSUMO DA COMANDA DE ORIGEM, só leitura. FORA de qualquer `@can` de escrita, de propósito: é consulta, e quem tem `finance.view` já vê o valor e a contraparte na linha — esconder a lista de itens protegeria menos do que a própria linha já revela. O `@if` casa com o botão da linha lá em cima: os dois leem o mesmo `$sourceOrders`, então ou existem juntos ou não existe nenhum. --}} @if ($order = $sourceOrders[$entry->id] ?? null) @include('financial._order', ['entry' => $entry, 'order' => $order]) @endif {{-- O RESUMO DA RESERVA DE ORIGEM, só leitura. Fora de qualquer `@can` de escrita pela mesma razão do bloco da comanda acima: é consulta, e a linha já revela cliente e valor a quem tem `finance.view`. O `@if` casa com o botão da linha lá em cima: os dois leem o mesmo `$sourceReservations`. --}} @if ($reservationSummary = $sourceReservations[$entry->id] ?? null) @include('financial._reservation', ['entry' => $entry, 'summary' => $reservationSummary]) @endif {{-- Na edição a operação vem DO LANÇAMENTO, não de uma escolha da tela: editar não muda o lado do caixa. É o que garante que o `action` do formulário e a lista de categorias continuem falando do mesmo lado. --}} @can('update', $entry) @include('financial._form', [ 'modalId' => 'financial-entry-'.$entry->id, 'entry' => $entry, 'operation' => $entry->operation, 'month' => $period->from->copy()->startOfMonth(), 'categories' => $formCategories[$entry->operation->value], 'bankAccounts' => $formBankAccounts, ]) @endcan @can('delete', $entry) @if ($entry->financial_recurrence_id) @include('financial._delete', ['entry' => $entry, 'operation' => $entry->operation]) @else {{-- O FORMULÁRIO DE EXCLUSÃO DO LANÇAMENTO SOLTO, fora de qualquer modal. Quem o dispara é o botão "Excluir" do rodapé do modal de edição, por `form="..."` — um
dentro de outro é HTML inválido e o navegador desmonta o de dentro em silêncio. `d-none` porque ele não tem nada a mostrar: existe só para carregar o CSRF, o método e a confirmação. --}} @csrf @method('DELETE')
@endif @endcan {{-- Só para quem está em aberto: reabrir não pergunta data. --}} @unless ($entry->isPaid()) @can('pay', $entry) @include('financial._payment', ['entry' => $entry]) @endcan @endunless @endforeach @endsection