First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
SysCMS Commercial License
Copyright (c) 2026 Marco Costa. All rights reserved.
This software is licensed, not sold. Permission is granted to use this software subject to the terms of the SysCMS Commercial License.
Redistribution, sublicensing, resale, or disclosure of this software, in whole or in part, is strictly prohibited without prior written permission from the author.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
# Fluxo Caixa Pro Module for SysCMS
Advanced cash flow management module.
## Features
- Cash flow projection
- Bank reconciliation
- Payment scheduling
- Receivable tracking
- Financial reporting
## Installation
1. Upload the `fluxocaixapro.zip` file through the module manager or extract it to the `modules/` directory.
2. Run the migrations to register the module.
3. Configure cash flow settings in the admin panel.
## Routes
- Web routes: `routes/web.php`
## License
Copyright (c) 2026 Marco Costa. All rights reserved.
Licensed under the SysCMS Commercial License.
@@ -0,0 +1,282 @@
<?php
namespace Modules\FluxoCaixaPro\Controllers;
use App\Controllers\BaseAdminController;
use Kernel\View;
use Symfony\Component\HttpFoundation\Request;
use Modules\FluxoCaixaPro\Models\Transaction;
use Modules\FluxoCaixaPro\Models\Category;
use Modules\FluxoCaixaPro\Models\CashFlowAnalysis;
use Modules\FluxoCaixaPro\Services\CashFlowService;
use Modules\FluxoCaixaPro\Services\CashFlowPredictionService;
use Carbon\Carbon;
/**
* Class CashFlowController
*
* Main controller for Cash Flow management and Dashboard.
*
* @package Modules\FluxoCaixaPro\Controllers
*/
class CashFlowController extends BaseAdminController
{
protected $cashFlowService;
protected $predictionService;
public function __construct(View $view, CashFlowService $cashFlowService, CashFlowPredictionService $predictionService)
{
parent::__construct($view);
$this->cashFlowService = $cashFlowService;
$this->predictionService = $predictionService;
}
/**
* checkPermission - Check if user has permission
*
* @param string $permission
* @return void
*/
protected function checkPermission(string $permission)
{
$user = $this->getCurrentUser();
if (!$user || !$user->hasPermission($permission)) {
$this->addFlash('error', 'Você não tem permissão para acessar esta área.');
$this->redirect('admin/dashboard')->send();
exit;
}
}
/**
* Display the Cash Flow Dashboard.
*/
public function index(Request $request)
{
$this->checkPermission('fluxocaixapro.view');
$startDate = $request->query->get('start_date', Carbon::now()->startOfMonth()->toDateString());
$endDate = $request->query->get('end_date', Carbon::now()->endOfMonth()->toDateString());
$statusFilter = $request->query->get('status');
$typeFilter = $request->query->get('type');
$entityFilter = $request->query->get('entity');
$summary = $this->cashFlowService->getSummary($startDate, $endDate);
$monthlyBreakdown = $this->cashFlowService->getMonthlyBreakdown();
$categoryDistribution = $this->cashFlowService->getCategoryDistribution('expense');
$projections = $this->cashFlowService->getProjections();
$latestAnalysis = CashFlowAnalysis::orderBy('created_at', 'desc')->first();
$query = Transaction::with('category');
// The global scope 'company' already handles idbus filtering
if ($startDate) {
$query->where('transaction_date', '>=', $startDate);
}
if ($endDate) {
$query->where('transaction_date', '<=', $endDate);
}
if ($statusFilter) {
$query->where('status', $statusFilter);
}
if ($typeFilter) {
$query->where('type', $typeFilter);
}
if ($entityFilter !== null && $entityFilter !== '') {
$query->where('is_personal', $entityFilter == 'personal');
}
$latestTransactions = $query->orderBy('transaction_date', 'desc')
->limit(50)
->get();
$categoriesAll = Category::where('is_active', true)->orderBy('name')->get();
return $this->renderAdminView('@fluxocaixapro/admin/index.twig', [
'title' => 'Dashboard Fluxo de Caixa Pró',
'summary' => $summary,
'monthlyBreakdown' => json_encode($monthlyBreakdown),
'categoryDistribution' => json_encode($categoryDistribution),
'projections' => json_encode($projections),
'latestAnalysis' => $latestAnalysis,
'latestTransactions' => $latestTransactions,
'categories_all' => $categoriesAll,
'startDate' => $startDate,
'endDate' => $endDate,
'filters' => [
'status' => $statusFilter,
'type' => $typeFilter,
'entity' => $entityFilter
]
]);
}
/**
* storeTransaction - Store a new transaction
*
* @return Response
*/
public function storeTransaction(Request $request)
{
$this->checkPermission('fluxocaixapro.manage');
$data = $request->request->all();
// idbus and iduser are handled by Transaction::booted() static::creating
Transaction::create([
'category_id' => $data['category_id'],
'description' => $data['description'],
'amount' => str_replace(',', '.', $data['amount']),
'type' => $data['type'],
'status' => $data['status'] ?? 'pending',
'transaction_date' => $data['transaction_date'] ?? date('Y-m-d'),
'due_date' => !empty($data['due_date']) ? $data['due_date'] : null,
'is_personal' => isset($data['is_personal']) && $data['is_personal'] == '1',
'notes' => $data['notes'] ?? null
]);
$this->addFlash('success', 'Transação registrada com sucesso!');
return $this->redirect('/admin/fluxo-caixa');
}
/**
* Update an existing transaction.
*/
public function updateTransaction(Request $request, $id)
{
$this->checkPermission('fluxocaixapro.manage');
$transaction = Transaction::findOrFail($id);
$data = $request->request->all();
$transaction->update([
'category_id' => $data['category_id'],
'description' => $data['description'],
'amount' => str_replace(',', '.', $data['amount']),
'transaction_date' => $data['transaction_date'],
'due_date' => !empty($data['due_date']) ? $data['due_date'] : null,
'payment_date' => ($data['status'] === 'paid') ? (!empty($data['payment_date']) ? $data['payment_date'] : $data['transaction_date']) : null,
'type' => $data['type'],
'status' => $data['status'] ?? 'paid',
'is_personal' => isset($data['is_personal']) && $data['is_personal'] == '1',
'notes' => $data['notes'] ?? null
]);
$this->addFlash('success', 'Transação atualizada com sucesso!');
return $this->redirect('/admin/fluxo-caixa');
}
/**
* Delete a transaction.
*/
public function deleteTransaction(Request $request, $id)
{
$this->checkPermission('fluxocaixapro.manage');
$transaction = Transaction::findOrFail($id);
$transaction->delete();
$this->addFlash('success', 'Transação excluída com sucesso!');
return $this->redirect('/admin/fluxo-caixa');
}
/**
* Mark a transaction as paid.
*/
public function markAsPaid(Request $request, $id)
{
$this->checkPermission('fluxocaixapro.manage');
$transaction = Transaction::findOrFail($id);
$transaction->update([
'status' => 'paid',
'payment_date' => Carbon::now()->toDateString()
]);
$this->addFlash('success', 'Transação marcada como paga!');
return $this->redirect('/admin/fluxo-caixa');
}
/**
* Export cash flow to Excel.
*/
public function exportToExcel(Request $request)
{
$this->checkPermission('fluxocaixapro.manage');
$transactions = Transaction::with('category')->orderBy('transaction_date', 'desc')->get();
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Fluxo de Caixa');
// Header
$headers = ['Data', 'Vencimento', 'Pagamento', 'Descrição', 'Categoria', 'Valor', 'Tipo', 'Status', 'Empresa/Pessoal'];
foreach ($headers as $col => $text) {
$sheet->setCellValue([$col + 1, 1], $text);
}
// Data
$row = 2;
foreach ($transactions as $t) {
$sheet->setCellValue([1, $row], $t->transaction_date ? $t->transaction_date->format('d/m/Y') : '');
$sheet->setCellValue([2, $row], $t->due_date ? $t->due_date->format('d/m/Y') : '');
$sheet->setCellValue([3, $row], $t->payment_date ? $t->payment_date->format('d/m/Y') : '');
$sheet->setCellValue([4, $row], $t->description);
$sheet->setCellValue([5, $row], $t->category->name ?? 'N/A');
$sheet->setCellValue([6, $row], $t->amount);
$sheet->setCellValue([7, $row], $t->type === 'income' ? 'Entrada' : 'Saída');
$sheet->setCellValue([8, $row], $t->status === 'paid' ? 'Pago' : 'Pendente');
$sheet->setCellValue([9, $row], $t->is_personal ? 'Pessoal' : 'Empresa');
$row++;
}
// Style header
$sheet->getStyle('A1:I1')->getFont()->setBold(true);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$filename = 'fluxo_caixa_' . date('Y-m-d') . '.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$writer->save('php://output');
exit;
}
/**
* Run a new AI analysis.
*/
public function runAnalysis(Request $request)
{
$this->checkPermission('fluxocaixapro.manage');
try {
$this->predictionService->generateAnalysis();
$this->addFlash('success', 'Análise inteligente concluída com sucesso!');
} catch (\Exception $e) {
$this->addFlash('error', 'Erro ao gerar análise: ' . $e->getMessage());
}
return $this->redirect('/admin/fluxo-caixa');
}
/**
* Perform bank reconciliation.
*/
public function reconcile(Request $request)
{
$this->checkPermission('fluxocaixapro.manage');
$bankBalance = (float) $request->request->get('bank_balance', 0);
$summary = $this->cashFlowService->getSummary();
$systemBalance = $summary['net_balance'];
$difference = $bankBalance - $systemBalance;
return $this->renderAdminView('@fluxocaixapro/admin/reconciliation_result.twig', [
'title' => 'Resultado da Conciliação Bancária',
'bankBalance' => $bankBalance,
'systemBalance' => $systemBalance,
'difference' => $difference,
'summary' => $summary
]);
}
}
@@ -0,0 +1,106 @@
<?php
namespace Modules\FluxoCaixaPro\Controllers;
use App\Controllers\BaseAdminController;
use Symfony\Component\HttpFoundation\Request;
use Modules\FluxoCaixaPro\Models\Category;
/**
* Class CategoryController
*
* Manages financial categories.
*
* @package Modules\FluxoCaixaPro\Controllers
*/
class CategoryController extends BaseAdminController
{
/**
* checkPermission - Check if user has permission
*
* @param string $permission
* @return void
*/
protected function checkPermission(string $permission)
{
$user = $this->getCurrentUser();
if (!$user || !$user->hasPermission($permission)) {
$this->addFlash('error', 'Você não tem permissão para acessar esta área.');
$this->redirect('admin/dashboard')->send();
exit;
}
}
/**
* List all categories.
*/
public function index(Request $request)
{
$this->checkPermission('fluxocaixapro.view');
$categories = Category::orderBy('name')->get();
return $this->renderAdminView('@fluxocaixapro/admin/categories/index.twig', [
'title' => 'Categorias de Fluxo de Caixa',
'categories' => $categories
]);
}
/**
* Store a new category.
*/
public function store(Request $request)
{
$this->checkPermission('fluxocaixapro.manage');
$data = $request->request->all();
Category::create([
'name' => $data['name'],
'type' => $data['type'],
'color' => $data['color'] ?? '#6c757d',
'icon' => $data['icon'] ?? 'bi bi-tag',
'is_active' => true
]);
$this->addFlash('success', 'Categoria criada com sucesso!');
return $this->redirect('/admin/fluxo-caixa/categories');
}
/**
* Update an existing category.
*/
public function update(Request $request, $id)
{
$this->checkPermission('fluxocaixapro.manage');
$category = Category::findOrFail($id);
$data = $request->request->all();
$category->update([
'name' => $data['name'],
'type' => $data['type'],
'color' => $data['color'],
'icon' => $data['icon']
]);
$this->addFlash('success', 'Categoria atualizada com sucesso!');
return $this->redirect('/admin/fluxo-caixa/categories');
}
/**
* Toggle category status.
*/
public function delete(Request $request, $id)
{
$this->checkPermission('fluxocaixapro.manage');
$category = Category::findOrFail($id);
$category->is_active = !$category->is_active;
$category->save();
$status = $category->is_active ? 'ativada' : 'desativada';
$this->addFlash('success', "Categoria {$status} com sucesso!");
return $this->redirect('/admin/fluxo-caixa/categories');
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace Modules\FluxoCaixaPro\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Class CashFlowAnalysis
*
* Represents an AI-generated cash flow analysis in the Fluxo de Caixa Pró module.
*
* @package Modules\FluxoCaixaPro\Models
*/
class CashFlowAnalysis extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'cash_flow_analysis';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'idbus',
'iduser',
'analysis_date',
'summary_data',
'recommendations',
'projections'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'idbus' => 'integer',
'iduser' => 'integer',
'analysis_date' => 'date',
'summary_data' => 'array',
'projections' => 'array'
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope('company', function ($query) {
if (isset($_SESSION['user_idbus']) && $_SESSION['user_idbus'] !== null && (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'super_admin')) {
$query->where('idbus', $_SESSION['user_idbus']);
}
});
static::creating(function ($model) {
if (isset($_SESSION['user_idbus'])) {
$model->idbus = $_SESSION['user_idbus'];
}
if (isset($_SESSION['user_id'])) {
$model->iduser = $_SESSION['user_id'];
}
});
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 SysCMS Team
* All rights reserved.
*
* Author: SysCMS Team
* Project: SysCMS
*/
namespace Modules\FluxoCaixaPro\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Category - Model for cash flow categories
*
* @package Modules\FluxoCaixaPro\Models
*/
class Category extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'cash_flow_categories';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'idbus',
'iduser',
'name',
'type',
'color',
'icon',
'is_active'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'idbus' => 'integer',
'iduser' => 'integer',
'is_active' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope('company', function ($query) {
if (isset($_SESSION['user_idbus']) && $_SESSION['user_idbus'] !== null && (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'super_admin')) {
$query->where('idbus', $_SESSION['user_idbus']);
}
});
static::creating(function ($model) {
if (isset($_SESSION['user_idbus'])) {
$model->idbus = $_SESSION['user_idbus'];
}
if (isset($_SESSION['user_id'])) {
$model->iduser = $_SESSION['user_id'];
}
});
}
/**
* transactions - Get transactions for this category
*
* @return HasMany
*/
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class, 'category_id');
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace Modules\FluxoCaixaPro\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class Transaction
*
* Represents a financial transaction in the Fluxo de Caixa Pró module.
*
* @package Modules\FluxoCaixaPro\Models
*/
class Transaction extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'cash_flow_transactions';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'idbus',
'iduser',
'category_id',
'description',
'amount',
'transaction_date',
'due_date',
'payment_date',
'type',
'status',
'is_personal',
'notes',
'metadata'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'idbus' => 'integer',
'iduser' => 'integer',
'amount' => 'decimal:2',
'transaction_date' => 'date',
'due_date' => 'date',
'payment_date' => 'date',
'is_personal' => 'boolean',
'metadata' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope('company', function ($query) {
if (isset($_SESSION['user_idbus']) && $_SESSION['user_idbus'] !== null && (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'super_admin')) {
$query->where('idbus', $_SESSION['user_idbus']);
}
});
static::creating(function ($model) {
if (isset($_SESSION['user_idbus'])) {
$model->idbus = $_SESSION['user_idbus'];
}
if (isset($_SESSION['user_id'])) {
$model->iduser = $_SESSION['user_id'];
}
});
}
/**
* Get the category that owns the transaction.
*
* @return BelongsTo
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
}
@@ -0,0 +1,37 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 SysCMS Team
* All rights reserved.
*
* Author: SysCMS Team
* Project: SysCMS
*/
namespace Modules\FluxoCaixaPro\Providers;
/**
* FluxoCaixaProServiceProvider - Service Provider for the FluxoCaixaPro module
*/
class FluxoCaixaProServiceProvider
{
/**
* register - Register module services in the container
*
* @return void
*/
public function register()
{
}
/**
* boot - Boot module resources
*
* @return void
*/
public function boot()
{
}
}
@@ -0,0 +1,180 @@
<?php
namespace Modules\FluxoCaixaPro\Services;
use Modules\FluxoCaixaPro\Models\Transaction;
use Modules\FluxoCaixaPro\Models\CashFlowAnalysis;
use Carbon\Carbon;
/**
* Class CashFlowPredictionService
*
* Handles intelligent predictions and analysis for cash flow.
*
* @package Modules\FluxoCaixaPro\Services
*/
class CashFlowPredictionService
{
/**
* Generate a new cash flow analysis based on historical data.
*
* @return CashFlowAnalysis
*/
public function generateAnalysis(): CashFlowAnalysis
{
$lastSixMonths = Carbon::now()->subMonths(6);
// Use only business transactions for core metrics
$transactions = Transaction::where('transaction_date', '>=', $lastSixMonths)
->where('is_personal', false)
->get();
$count = $transactions->count();
$monthlyAvgIncome = $count > 0 ? ($transactions->where('type', 'income')->sum('amount') / 6) : 0;
$monthlyAvgExpense = $count > 0 ? ($transactions->where('type', 'expense')->sum('amount') / 6) : 0;
$predictedBalance = $monthlyAvgIncome - $monthlyAvgExpense;
// Pending items analysis
$pendingIncome = Transaction::where('type', 'income')->where('status', 'pending')->sum('amount');
$pendingExpense = Transaction::where('type', 'expense')->where('status', 'pending')->sum('amount');
// Personal mix analysis
$personalExpense = Transaction::where('is_personal', true)
->where('transaction_date', '>=', $lastSixMonths)
->sum('amount');
$recommendations = $this->generateDetailedRecommendations([
'income' => $monthlyAvgIncome,
'expense' => $monthlyAvgExpense,
'pending_income' => $pendingIncome,
'pending_expense' => $pendingExpense,
'personal_expense' => $personalExpense,
'balance' => $predictedBalance
]);
return CashFlowAnalysis::create([
'analysis_date' => Carbon::now(),
'summary_data' => [
'period_analyzed' => $lastSixMonths->format('Y-m') . ' to ' . Carbon::now()->format('Y-m'),
'predicted_balance' => $predictedBalance,
'avg_monthly_income' => $monthlyAvgIncome,
'avg_monthly_expense' => $monthlyAvgExpense,
'growth_rate' => $this->calculateGrowthRate(),
'risk_level' => $this->calculateRiskLevel($predictedBalance, $pendingExpense, $pendingIncome),
'personal_mix_ratio' => $monthlyAvgExpense > 0 ? ($personalExpense / ($monthlyAvgExpense * 6)) : 0
],
'recommendations' => $recommendations,
'projections' => $this->getProjections()
]);
}
/**
* Generate detailed recommendations based on financial management principles.
*/
private function generateDetailedRecommendations(array $data): string
{
$rec = [];
// 1. Separation of accounts (Core management principle)
if ($data['personal_expense'] > 0) {
$rec[] = "⚠️ SEPARAÇÃO DE CONTAS: Identificamos gastos pessoais misturados aos da empresa. Recomenda-se que o empresário tenha um 'Pró-labore' fixo e nunca use o caixa da empresa para despesas pessoais.";
}
// 2. Operational Health
if ($data['balance'] < 0) {
$rec[] = "🔴 DÉFICIT OPERACIONAL: Suas despesas empresariais superam as receitas. Revise seus custos fixos e tente aumentar o volume de vendas ou reajustar preços.";
} elseif ($data['balance'] < ($data['income'] * 0.15)) {
$rec[] = "🟡 MARGEM ESTREITA: Seu saldo operacional está muito próximo de zero. Qualquer imprevisto pode gerar necessidade de capital de giro externo (empréstimos).";
}
// 3. Accounts Payable vs Receivable
if ($data['pending_expense'] > $data['pending_income']) {
$gap = $data['pending_expense'] - $data['pending_income'];
$rec[] = "📉 GAP DE CAIXA: Você tem R$ " . number_format($gap, 2, ',', '.') . " a mais em contas a pagar do que a receber nos próximos períodos. Antecipe recebíveis ou renegocie prazos com fornecedores.";
}
// 4. Working Capital Reserve
$reserveNeeded = $data['expense'] * 3; // 3 months of reserve
$rec[] = "🏦 RESERVA DE EMERGÊNCIA: Recomenda-se manter uma reserva de 3 a 6 meses de despesas. Sua meta de reserva deve ser de aproximadamente R$ " . number_format($reserveNeeded, 2, ',', '.') . ".";
if (empty($rec)) {
$rec[] = "✅ SAÚDE FINANCEIRA: Seu fluxo de caixa está equilibrado. Continue registrando todas as movimentações diariamente para manter a precisão das projeções.";
}
return implode("\n\n", $rec);
}
/**
* Calculate risk level.
*/
private function calculateRiskLevel(float $balance, float $pendingExp, float $pendingInc): string
{
if ($balance < 0 || ($pendingExp > $pendingInc * 1.5)) {
return 'High';
}
if ($balance < 500 || $pendingExp > $pendingInc) {
return 'Medium';
}
return 'Low';
}
/**
* Calculate growth rate based on last two months.
*
* @return float
*/
/**
* Calculate growth rate based on last two months.
*
* @return float
*/
private function calculateGrowthRate(): float
{
$currentMonth = Carbon::now()->startOfMonth();
$lastMonth = Carbon::now()->subMonth()->startOfMonth();
$currentIncome = Transaction::where('type', 'income')
->whereBetween('transaction_date', [$currentMonth, Carbon::now()])
->sum('amount');
$lastMonthIncome = Transaction::where('type', 'income')
->whereBetween('transaction_date', [$lastMonth, $currentMonth->subDay()])
->sum('amount');
if ($lastMonthIncome == 0) {
return 0;
}
return (($currentIncome - $lastMonthIncome) / $lastMonthIncome) * 100;
}
/**
* getProjections - Generate basic future projections for the analysis
*
* @return array
*/
private function getProjections(): array
{
$lastSixMonths = Carbon::now()->subMonths(6);
$transactions = Transaction::where('transaction_date', '>=', $lastSixMonths)
->where('is_personal', false)
->get();
$monthlyAvgIncome = $transactions->where('type', 'income')->sum('amount') / 6;
$monthlyAvgExpense = $transactions->where('type', 'expense')->sum('amount') / 6;
$projections = [];
for ($i = 1; $i <= 3; $i++) {
$month = Carbon::now()->addMonths($i)->format('Y-m');
$projections[] = [
'month' => $month,
'predicted_income' => round($monthlyAvgIncome, 2),
'predicted_expense' => round($monthlyAvgExpense, 2),
'predicted_balance' => round($monthlyAvgIncome - $monthlyAvgExpense, 2)
];
}
return $projections;
}
}
+178
View File
@@ -0,0 +1,178 @@
<?php
namespace Modules\FluxoCaixaPro\Services;
use Modules\FluxoCaixaPro\Models\Transaction;
use Modules\FluxoCaixaPro\Models\Category;
use Illuminate\Database\Capsule\Manager as DB;
use Carbon\Carbon;
/**
* Class CashFlowService
*
* Handles business logic for cash flow management.
*
* @package Modules\FluxoCaixaPro\Services
*/
class CashFlowService
{
/**
* Get summary of cash flow for a specific period.
*
* @param string|null $startDate
* @param string|null $endDate
* @return array
*/
public function getSummary(?string $startDate = null, ?string $endDate = null): array
{
$query = Transaction::query();
if ($startDate) {
$query->where('transaction_date', '>=', $startDate);
}
if ($endDate) {
$query->where('transaction_date', '<=', $endDate);
}
$transactions = $query->get();
// Total amounts (including personal)
$totalIncome = $transactions->where('type', 'income')->sum('amount');
$totalExpense = $transactions->where('type', 'expense')->sum('amount');
$netBalance = $totalIncome - $totalExpense;
// Operational amounts (Business only)
$businessTransactions = $transactions->where('is_personal', false);
$opIncome = $businessTransactions->where('type', 'income')->sum('amount');
$opExpense = $businessTransactions->where('type', 'expense')->sum('amount');
$opBalance = $opIncome - $opExpense;
// Personal amounts
$personalTransactions = $transactions->where('is_personal', true);
$personalIncome = $personalTransactions->where('type', 'income')->sum('amount');
$personalExpense = $personalTransactions->where('type', 'expense')->sum('amount');
// Accounts Payable/Receivable
$pendingIncome = Transaction::where('type', 'income')->where('status', 'pending')->sum('amount');
$pendingExpense = Transaction::where('type', 'expense')->where('status', 'pending')->sum('amount');
return [
'total_income' => $totalIncome,
'total_expense' => $totalExpense,
'net_balance' => $netBalance,
'operational_income' => $opIncome,
'operational_expense' => $opExpense,
'operational_balance' => $opBalance,
'personal_income' => $personalIncome,
'personal_expense' => $personalExpense,
'pending_income' => $pendingIncome,
'pending_expense' => $pendingExpense,
'transaction_count' => $transactions->count()
];
}
/**
* Get monthly breakdown of cash flow.
*
* @param int $months
* @return array
*/
public function getMonthlyBreakdown(int $months = 6): array
{
$startDate = Carbon::now()->subMonths($months)->startOfMonth();
$data = Transaction::where('transaction_date', '>=', $startDate)
->select(
\Illuminate\Database\Capsule\Manager::raw('DATE_FORMAT(transaction_date, "%Y-%m") as month'),
\Illuminate\Database\Capsule\Manager::raw('SUM(CASE WHEN type = "income" THEN amount ELSE 0 END) as income'),
\Illuminate\Database\Capsule\Manager::raw('SUM(CASE WHEN type = "expense" THEN amount ELSE 0 END) as expense')
)
->groupBy('month')
->orderBy('month', 'asc')
->get();
return $data->toArray();
}
/**
* Get distribution by category.
*
* @param string $type 'income' or 'expense'
* @return array
*/
public function getCategoryDistribution(string $type = 'expense'): array
{
return Transaction::where('type', $type)
->with('category')
->select('category_id', \Illuminate\Database\Capsule\Manager::raw('SUM(amount) as total'))
->groupBy('category_id')
->get()
->map(function ($item) {
return [
'category' => $item->category->name ?? 'Sem Categoria',
'total' => $item->total,
'color' => $item->category->color ?? '#6c757d'
];
})
->toArray();
}
/**
* Get long term projections with scenarios.
*
* @param int $months
* @return array
*/
public function getProjections(int $months = 12): array
{
$startDate = Carbon::now()->subMonths(12);
$history = Transaction::where('transaction_date', '>=', $startDate)
->where('is_personal', false)
->get();
if ($history->isEmpty()) {
return [];
}
$avgMonthlyIncome = $history->where('type', 'income')->sum('amount') / 12;
$avgMonthlyExpense = $history->where('type', 'expense')->sum('amount') / 12;
$projections = [];
for ($i = 1; $i <= $months; $i++) {
$month = Carbon::now()->addMonths($i)->format('Y-m');
// Realistic Scenario (Current average + slight growth)
$realisticIncome = $avgMonthlyIncome * (1 + ($i * 0.01));
$realisticExpense = $avgMonthlyExpense * (1 + ($i * 0.005));
// Optimistic Scenario (+5% income, -2% expense vs realistic)
$optimisticIncome = $realisticIncome * 1.05;
$optimisticExpense = $realisticExpense * 0.98;
// Pessimistic Scenario (-10% income, +5% expense vs realistic)
$pessimisticIncome = $realisticIncome * 0.90;
$pessimisticExpense = $realisticExpense * 1.05;
$projections[] = [
'month' => $month,
'realistic' => [
'income' => $realisticIncome,
'expense' => $realisticExpense,
'balance' => $realisticIncome - $realisticExpense
],
'optimistic' => [
'income' => $optimisticIncome,
'expense' => $optimisticExpense,
'balance' => $optimisticIncome - $optimisticExpense
],
'pessimistic' => [
'income' => $pessimisticIncome,
'expense' => $pessimisticExpense,
'balance' => $pessimisticIncome - $pessimisticExpense
]
];
}
return $projections;
}
}
@@ -0,0 +1,83 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 SysCMS Team
* All rights reserved.
*/
use Illuminate\Database\Capsule\Manager as Capsule;
class InsertFluxoCaixaProModule
{
public function up()
{
$moduleName = 'fluxocaixapro';
$moduleData = [
'modules_name' => $moduleName,
'modules_display_name' => 'Fluxo de Caixa Pró',
'modules_description' => 'Módulo avançado de gestão de fluxo de caixa com previsões inteligentes.',
'modules_version' => '1.0.0',
'modules_author' => 'SysCMS Team',
'modules_ative' => 1,
'modules_public' => 1,
'modules_blocked' => 0,
'modules_dependencies' => json_encode(['auth']),
'modules_permissions' => json_encode([
"fluxocaixapro.view",
"fluxocaixapro.manage"
]),
'modules_settings' => json_encode([
"menu" => [
"dashboard" => [
"icon" => "bi bi-cash-stack",
"label" => "Fluxo de Caixa Pró",
"route" => "admin/fluxo-caixa",
"order" => 70,
"permission" => "fluxocaixapro.view"
]
]
]),
'modules_path' => 'modules/fluxocaixapro',
'modules_namespace' => 'Modules\\FluxoCaixaPro',
'modules_icon' => 'bi bi-cash-stack',
'menu_side_bar' => 1,
'menu_nav_bar' => 0,
'modules_order' => 70,
'modules_install_date' => date('Y-m-d H:i:s'),
'modules_update_date' => date('Y-m-d H:i:s')
];
$existingModule = Capsule::table('modules')->where('modules_name', $moduleName)->first();
if (!$existingModule) {
$moduleId = Capsule::table('modules')->insertGetId($moduleData);
// Associar a todas as empresas existentes
if (Capsule::schema()->hasTable('bus')) {
$companies = Capsule::table('bus')->get();
foreach ($companies as $company) {
Capsule::table('bus_modules')->insert([
'bus_modules_bus_id' => $company->bus_id,
'bus_modules_module_id' => $moduleId,
'bus_modules_is_enabled' => 1,
'bus_modules_show_public_nav' => 0,
'bus_modules_show_dashboard_nav' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
}
}
public function down()
{
$module = Capsule::table('modules')->where('modules_name', 'fluxocaixapro')->first();
if ($module) {
Capsule::table('bus_modules')->where('bus_modules_module_id', $module->modules_id)->delete();
Capsule::table('modules')->where('modules_id', $module->modules_id)->delete();
}
}
}
@@ -0,0 +1,113 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
/**
* Migration: Create Cash Flow Tables
*/
class CreateCashFlowTables
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
// Categorias de Fluxo de Caixa
if (!Capsule::schema()->hasTable('cash_flow_categories')) {
Capsule::schema()->create('cash_flow_categories', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('idbus')->nullable();
$table->unsignedBigInteger('iduser')->nullable();
$table->string('name');
$table->enum('type', ['income', 'expense']);
$table->string('color')->default('#6c757d');
$table->string('icon')->default('bi bi-tag');
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->index('idbus');
$table->index('iduser');
});
}
// Transações de Fluxo de Caixa
if (!Capsule::schema()->hasTable('cash_flow_transactions')) {
Capsule::schema()->create('cash_flow_transactions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('idbus')->nullable();
$table->unsignedBigInteger('iduser')->nullable();
$table->unsignedBigInteger('category_id');
$table->string('description');
$table->decimal('amount', 15, 2);
$table->enum('type', ['income', 'expense']);
$table->enum('status', ['pending', 'paid', 'cancelled'])->default('pending');
$table->date('transaction_date');
$table->date('due_date')->nullable();
$table->date('payment_date')->nullable();
$table->boolean('is_personal')->default(false);
$table->text('notes')->nullable();
$table->json('metadata')->nullable();
$table->timestamps();
$table->index('idbus');
$table->index('iduser');
$table->index('category_id');
$table->index('transaction_date');
});
}
// Análises e Previsões (IA)
if (!Capsule::schema()->hasTable('cash_flow_analysis')) {
Capsule::schema()->create('cash_flow_analysis', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('idbus')->nullable();
$table->unsignedBigInteger('iduser')->nullable();
$table->date('analysis_date');
$table->json('summary_data');
$table->text('recommendations')->nullable();
$table->json('projections')->nullable();
$table->timestamps();
$table->index('idbus');
$table->index('iduser');
$table->index('analysis_date');
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Capsule::schema()->dropIfExists('cash_flow_analysis');
Capsule::schema()->dropIfExists('cash_flow_transactions');
Capsule::schema()->dropIfExists('cash_flow_categories');
}
}
@@ -0,0 +1,49 @@
<?php
/**
* Migration: Seed Default Categories for Cash Flow
*/
use Illuminate\Database\Capsule\Manager as Capsule;
class SeedDefaultCategories
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
$categories = [
['name' => 'Vendas', 'type' => 'income', 'color' => '#198754', 'icon' => 'bi bi-cart-check'],
['name' => 'Serviços', 'type' => 'income', 'color' => '#0dcaf0', 'icon' => 'bi bi-tools'],
['name' => 'Aluguel', 'type' => 'expense', 'color' => '#dc3545', 'icon' => 'bi bi-house'],
['name' => 'Salários', 'type' => 'expense', 'color' => '#fd7e14', 'icon' => 'bi bi-people'],
['name' => 'Marketing', 'type' => 'expense', 'color' => '#6f42c1', 'icon' => 'bi bi-megaphone'],
['name' => 'Impostos', 'type' => 'expense', 'color' => '#6c757d', 'icon' => 'bi bi-bank'],
['name' => 'Outros', 'type' => 'expense', 'color' => '#adb5bd', 'icon' => 'bi bi-three-dots'],
];
foreach ($categories as $cat) {
if (Capsule::table('cash_flow_categories')->where('name', $cat['name'])->count() === 0) {
Capsule::table('cash_flow_categories')->insert(array_merge($cat, [
'is_active' => true,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]));
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
// No need to delete seeds on rollback usually, but if needed:
// Capsule::table('cash_flow_categories')->truncate();
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"module_name": "Fluxo de Caixa Pró",
"dashboard": "Dashboard",
"transactions": "Transações",
"categories": "Categorias",
"income": "Receita",
"expense": "Despesa",
"balance": "Saldo",
"total_income": "Total de Receitas",
"total_expense": "Total de Despesas",
"current_balance": "Saldo Atual",
"latest_transactions": "Últimas Transações",
"add_transaction": "Adicionar Transação",
"description": "Descrição",
"amount": "Valor",
"date": "Data",
"category": "Categoria",
"status": "Status",
"paid": "Pago",
"pending": "Pendente",
"notes": "Observações",
"save": "Salvar",
"cancel": "Cancelar",
"ai_analysis": "Análise Inteligente",
"run_analysis": "Gerar Nova Análise",
"predicted_balance": "Saldo Previsto",
"recommendations": "Recomendações",
"risk_level": "Nível de Risco"
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "fluxocaixapro",
"display_name": "Fluxo de Caixa Pró",
"description": "Módulo avançado de gestão de fluxo de caixa com previsões inteligentes.",
"version": "1.0.0",
"author": "SysCMS Team",
"dependencies": [
"auth"
],
"permissions": [
"fluxocaixapro.view",
"fluxocaixapro.manage"
],
"providers": [
"Modules\\FluxoCaixaPro\\Providers\\FluxoCaixaProServiceProvider"
],
"routes": {
"web": "routes/web.php"
},
"menu": {
"admin": [
{
"id": "FluxoCaixaPro",
"label": "Fluxo de Caixa Pró",
"url": "/admin/fluxo-caixa",
"icon": "bi bi-cash-stack",
"permission": "fluxocaixapro.view"
}
]
},
"views": "views",
"locales": "locales",
"module_active": true,
"module_public": true,
"module_blocked": false,
"ready": true,
"tables": [
"cash_flow_analysis",
"cash_flow_categories",
"cash_flow_transactions"
]
}
+50
View File
@@ -0,0 +1,50 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
use Kernel\Application;
use Modules\FluxoCaixaPro\Controllers\CashFlowController;
use Modules\FluxoCaixaPro\Controllers\CategoryController;
/**
* Register FluxoCaixaPro module routes
*
* @param Application $app
* @return void
*/
return function (Application $app) {
// Dashboard and Main Actions
$app->addRoute('GET', '/admin/fluxo-caixa', [CashFlowController::class, 'index']);
$app->addRoute('GET', '/admin/fluxo-caixa/export', [CashFlowController::class, 'exportToExcel']);
$app->addRoute('POST', '/admin/fluxo-caixa/transaction', [CashFlowController::class, 'storeTransaction']);
$app->addRoute('POST', '/admin/fluxo-caixa/transaction/update/{id}', [CashFlowController::class, 'updateTransaction']);
$app->addRoute('POST', '/admin/fluxo-caixa/transaction/delete/{id}', [CashFlowController::class, 'deleteTransaction']);
$app->addRoute('POST', '/admin/fluxo-caixa/transaction/pay/{id}', [CashFlowController::class, 'markAsPaid']);
$app->addRoute('POST', '/admin/fluxo-caixa/analysis', [CashFlowController::class, 'runAnalysis']);
$app->addRoute('POST', '/admin/fluxo-caixa/reconcile', [CashFlowController::class, 'reconcile']);
// Categories
$app->addRoute('GET', '/admin/fluxo-caixa/categories', [CategoryController::class, 'index']);
$app->addRoute('POST', '/admin/fluxo-caixa/categories/store', [CategoryController::class, 'store']);
$app->addRoute('POST', '/admin/fluxo-caixa/categories/update/{id}', [CategoryController::class, 'update']);
$app->addRoute('POST', '/admin/fluxo-caixa/categories/delete/{id}', [CategoryController::class, 'delete']);
};
+172
View File
@@ -0,0 +1,172 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mt-4">{{ title }}</h1>
<div>
<a href="{{ url('/admin/fluxo-caixa') }}" class="btn btn-outline-secondary me-2">
<i class="bi bi-arrow-left me-1"></i> Voltar ao Fluxo
</a>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newCategoryModal">
<i class="bi bi-plus-circle me-1"></i> Nova Categoria
</button>
</div>
</div>
{% if flash_messages.success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ flash_messages.success }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="card shadow mb-4 border-0">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Nome</th>
<th>Tipo Padrão</th>
<th>Cor</th>
<th>Ícone</th>
<th>Status</th>
<th class="text-center">Ações</th>
</tr>
</thead>
<tbody>
{% for cat in categories %}
<tr>
<td class="ps-4 fw-bold">{{ cat.name }}</td>
<td>
{% if cat.type == 'income' %}
<span class="text-success small"><i class="bi bi-plus-circle me-1"></i> Receita</span>
{% else %}
<span class="text-danger small"><i class="bi bi-dash-circle me-1"></i> Despesa</span>
{% endif %}
</td>
<td>
<div class="d-flex align-items-center">
<div style="width: 20px; height: 20px; border-radius: 50%; background-color: {{ cat.color }}; margin-right: 8px;"></div>
<small class="text-muted">{{ cat.color }}</small>
</div>
</td>
<td><i class="{{ cat.icon }} fs-5"></i></td>
<td>
{% if cat.is_active %}
<span class="badge bg-success">Ativa</span>
{% else %}
<span class="badge bg-secondary">Inativa</span>
{% endif %}
</td>
<td class="text-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-primary border-0"
data-bs-toggle="modal" data-bs-target="#editCategoryModal{{ cat.id }}">
<i class="bi bi-pencil"></i>
</button>
<form action="{{ url('/admin/fluxo-caixa/categories/delete/' ~ cat.id) }}" method="POST">
<button type="submit" class="btn btn-sm btn-outline-{{ cat.is_active ? 'warning' : 'success' }} border-0">
<i class="bi bi-power"></i>
</button>
</form>
</div>
</td>
</tr>
<!-- Modal Editar Categoria -->
<div class="modal fade" id="editCategoryModal{{ cat.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Editar Categoria: {{ cat.name }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url('/admin/fluxo-caixa/categories/update/' ~ cat.id) }}" method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Nome da Categoria</label>
<input type="text" name="name" class="form-control" value="{{ cat.name }}" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Tipo Padrão</label>
<select name="type" class="form-select">
<option value="income" {{ cat.type == 'income' ? 'selected' : '' }}>Receita</option>
<option value="expense" {{ cat.type == 'expense' ? 'selected' : '' }}>Despesa</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Cor</label>
<input type="color" name="color" class="form-control form-control-color w-100" value="{{ cat.color }}">
</div>
</div>
<div class="mb-3">
<label class="form-label">Ícone (Bootstrap Icon)</label>
<input type="text" name="icon" class="form-control" value="{{ cat.icon }}" placeholder="bi bi-tag">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Salvar Alterações</button>
</div>
</form>
</div>
</div>
</div>
{% else %}
<tr>
<td colspan="6" class="text-center py-4 text-muted">Nenhuma categoria cadastrada.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal Nova Categoria -->
<div class="modal fade" id="newCategoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Nova Categoria</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url('/admin/fluxo-caixa/categories/store') }}" method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Nome da Categoria</label>
<input type="text" name="name" class="form-control" required placeholder="Ex: Alimentação, Vendas...">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Tipo Padrão</label>
<select name="type" class="form-select">
<option value="income">Receita</option>
<option value="expense" selected>Despesa</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Cor</label>
<input type="color" name="color" class="form-control form-control-color w-100" value="#0d6efd">
</div>
</div>
<div class="mb-3">
<label class="form-label">Ícone (Bootstrap Icon)</label>
<input type="text" name="icon" class="form-control" value="bi bi-tag" placeholder="bi bi-tag">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Criar Categoria</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
+660
View File
@@ -0,0 +1,660 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block head %}
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.card-metric { transition: transform 0.2s; cursor: pointer; }
.card-metric:hover { transform: translateY(-5px); }
.border-income { border-left: 5px solid #198754 !important; }
.border-expense { border-left: 5px solid #dc3545 !important; }
.border-balance { border-left: 5px solid #0dcaf0 !important; }
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mt-4">{{ title }}</h1>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#reconcileModal">
<i class="bi bi-bank me-1"></i> Conciliação
</button>
<a href="{{ url('/admin/fluxo-caixa/export') }}" class="btn btn-outline-success">
<i class="bi bi-file-earmark-excel me-1"></i> Exportar
</a>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#newTransactionModal">
<i class="bi bi-plus-circle me-1"></i> Nova Transação
</button>
<form action="{{ url('/admin/fluxo-caixa/analysis') }}" method="POST">
<button type="submit" class="btn btn-info text-white">
<i class="bi bi-cpu me-1"></i> Análise IA
</button>
</form>
<a href="{{ url('/admin/fluxo-caixa/categories') }}" class="btn btn-secondary">
<i class="bi bi-tags me-1"></i> Categorias
</a>
</div>
</div>
{% if flash_messages.success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ flash_messages.success }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<!-- Filtros de Período e Status -->
<div class="card mb-4 shadow-sm border-0">
<div class="card-body">
<form method="GET" class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label small fw-bold">Período:</label>
<div class="input-group input-group-sm">
<input type="date" name="start_date" class="form-control" value="{{ startDate }}">
<span class="input-group-text">até</span>
<input type="date" name="end_date" class="form-control" value="{{ endDate }}">
</div>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold">Status:</label>
<select name="status" class="form-select form-select-sm">
<option value="">Todos</option>
<option value="paid" {{ filters.status == 'paid' ? 'selected' : '' }}>Pago</option>
<option value="pending" {{ filters.status == 'pending' ? 'selected' : '' }}>Pendente</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold">Tipo:</label>
<select name="type" class="form-select form-select-sm">
<option value="">Todos</option>
<option value="income" {{ filters.type == 'income' ? 'selected' : '' }}>Entrada</option>
<option value="expense" {{ filters.type == 'expense' ? 'selected' : '' }}>Saída</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold">Entidade:</label>
<select name="entity" class="form-select form-select-sm">
<option value="">Todos</option>
<option value="business" {{ filters.entity == 'business' ? 'selected' : '' }}>Empresa</option>
<option value="personal" {{ filters.entity == 'personal' ? 'selected' : '' }}>Pessoal</option>
</select>
</div>
<div class="col-md-3">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary btn-sm flex-grow-1">
<i class="bi bi-filter me-1"></i> Filtrar
</button>
<a href="{{ url('/admin/fluxo-caixa') }}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-x-circle"></i>
</a>
</div>
</div>
</form>
</div>
</div>
<!-- Cards de Métricas Principais -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6">
<div class="card card-metric shadow-sm border-0 border-income h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Receitas (Operacional)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">R$ {{ summary.operational_income|number_format(2, ',', '.') }}</div>
<small class="text-muted">Total: R$ {{ summary.total_income|number_format(2, ',', '.') }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card card-metric shadow-sm border-0 border-expense h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1">Despesas (Operacional)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">R$ {{ summary.operational_expense|number_format(2, ',', '.') }}</div>
<small class="text-muted">Total: R$ {{ summary.total_expense|number_format(2, ',', '.') }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card card-metric shadow-sm border-0 border-info h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Saldo Operacional</div>
<div class="h5 mb-0 font-weight-bold {{ summary.operational_balance >= 0 ? 'text-info' : 'text-danger' }}">
R$ {{ summary.operational_balance|number_format(2, ',', '.') }}
</div>
<small class="text-muted">Líquido: R$ {{ summary.net_balance|number_format(2, ',', '.') }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card card-metric shadow-sm border-0 border-warning h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Contas Pendentes</div>
<div class="small">A Receber: <span class="text-success fw-bold">R$ {{ summary.pending_income|number_format(2, ',', '.') }}</span></div>
<div class="small">A Pagar: <span class="text-danger fw-bold">R$ {{ summary.pending_expense|number_format(2, ',', '.') }}</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<!-- Gráfico Mensal -->
<div class="col-lg-6">
<div class="card shadow-sm border-0 h-100">
<div class="card-header bg-white border-0 py-3">
<h6 class="m-0 font-weight-bold text-primary">Fluxo Mensal (Histórico)</h6>
</div>
<div class="card-body">
<canvas id="monthlyChart" style="min-height: 300px;"></canvas>
</div>
</div>
</div>
<!-- Projeções -->
<div class="col-lg-6">
<div class="card shadow-sm border-0 h-100">
<div class="card-header bg-white border-0 py-3">
<h6 class="m-0 font-weight-bold text-primary">Projeção de Saldo (12 Meses)</h6>
</div>
<div class="card-body">
<canvas id="projectionChart" style="min-height: 300px;"></canvas>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<!-- IA Insights -->
<div class="col-lg-12">
<div class="card shadow-sm border-0 h-100 bg-light">
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-info"><i class="bi bi-robot me-2"></i>Insights da Inteligência Artificial</h6>
{% if latestAnalysis %}
<small class="text-muted">Última análise: {{ latestAnalysis.created_at|date('d/m/Y H:i') }}</small>
{% endif %}
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
{% if latestAnalysis %}
<div class="mb-3">
<small class="text-muted d-block mb-1">Previsão de Saldo Próximo Mês:</small>
<span class="h4 fw-bold text-primary">R$ {{ latestAnalysis.summary_data.predicted_balance|number_format(2, ',', '.') }}</span>
</div>
<div class="mb-3">
<small class="text-muted d-block mb-1">Nível de Risco Financeiro:</small>
{% set risk = latestAnalysis.summary_data.risk_level %}
<span class="badge fs-6 bg-{{ risk == 'High' ? 'danger' : (risk == 'Medium' ? 'warning' : 'success') }}">
{{ risk == 'High' ? 'Alto Risco' : (risk == 'Medium' ? 'Risco Médio' : 'Baixo Risco') }}
</span>
</div>
{% else %}
<div class="text-center py-4">
<i class="bi bi-cpu fs-1 text-muted opacity-25"></i>
<p class="text-muted mt-2 small">Sem análise recente.</p>
</div>
{% endif %}
</div>
<div class="col-md-9">
{% if latestAnalysis %}
<div class="p-3 bg-white rounded border">
<h6 class="fw-bold mb-2">Recomendações Estratégicas:</h6>
<p class="text-dark mb-0" style="white-space: pre-line;">{{ latestAnalysis.recommendations }}</p>
</div>
{% else %}
<div class="alert alert-info mb-0">
<i class="bi bi-info-circle me-2"></i> Clique no botão <strong>Análise IA</strong> no topo para que nosso sistema examine seu fluxo de caixa e forneça recomendações personalizadas baseadas em melhores práticas de gestão financeira.
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Últimas Transações -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-0 py-3">
<h6 class="m-0 font-weight-bold text-primary">Últimas Transações</h6>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Data</th>
<th>Descrição</th>
<th>Categoria</th>
<th>Tipo</th>
<th>Valor</th>
<th>Status</th>
<th class="text-end pe-4">Ações</th>
</tr>
</thead>
<tbody>
{% for trans in latestTransactions %}
<tr>
<td class="ps-4 small">
<span class="text-muted d-block">{{ trans.transaction_date|date('d/m/Y') }}</span>
{% if trans.due_date %}
<small class="text-info">Venc: {{ trans.due_date|date('d/m/Y') }}</small>
{% endif %}
</td>
<td>
{{ trans.description }}
{% if trans.is_personal %}
<span class="badge bg-secondary ms-1" title="Despesa/Receita Pessoal">Pessoal</span>
{% endif %}
</td>
<td>
<span class="badge" style="background-color: {{ trans.category.color }};">
<i class="{{ trans.category.icon }} me-1"></i>{{ trans.category.name }}
</span>
</td>
<td>
<span class="text-{{ trans.type == 'income' ? 'success' : 'danger' }} small fw-bold">
{{ trans.type == 'income' ? 'Entrada' : 'Saída' }}
</span>
</td>
<td class="fw-bold {{ trans.type == 'income' ? 'text-success' : 'text-danger' }}">
{{ trans.type == 'income' ? '+' : '-' }} R$ {{ trans.amount|abs|number_format(2, ',', '.') }}
</td>
<td>
<span class="badge bg-{{ trans.status == 'paid' ? 'success' : 'warning' }}">
{{ trans.status == 'paid' ? 'Pago' : 'Pendente' }}
</span>
</td>
<td class="text-end pe-4">
<div class="btn-group btn-group-sm">
{% if trans.status == 'pending' %}
<form action="{{ url('/admin/fluxo-caixa/transaction/pay/' ~ trans.id) }}" method="POST" class="d-inline">
<button type="submit" class="btn btn-outline-success" title="Marcar como Pago">
<i class="bi bi-check2-circle"></i>
</button>
</form>
{% endif %}
<button type="button" class="btn btn-outline-primary"
data-bs-toggle="modal"
data-bs-target="#editTransactionModal{{ trans.id }}"
title="Editar">
<i class="bi bi-pencil"></i>
</button>
<button type="button" class="btn btn-outline-danger"
data-bs-toggle="modal"
data-bs-target="#deleteTransactionModal{{ trans.id }}"
title="Excluir">
<i class="bi bi-trash"></i>
</button>
</div>
<!-- Modal Editar Transação -->
<div class="modal fade" id="editTransactionModal{{ trans.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog text-start">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Editar Transação</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url('/admin/fluxo-caixa/transaction/update/' ~ trans.id) }}" method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Descrição</label>
<input type="text" name="description" class="form-control" value="{{ trans.description }}" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Valor (R$)</label>
<input type="number" step="0.01" name="amount" class="form-control" value="{{ trans.amount }}" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Data Lançamento</label>
<input type="date" name="transaction_date" class="form-control" value="{{ trans.transaction_date|date('Y-m-d') }}" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Data Vencimento</label>
<input type="date" name="due_date" class="form-control" value="{{ trans.due_date ? trans.due_date|date('Y-m-d') : '' }}">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Data Pagamento</label>
<input type="date" name="payment_date" class="form-control" value="{{ trans.payment_date ? trans.payment_date|date('Y-m-d') : '' }}">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Tipo</label>
<select name="type" class="form-select">
<option value="income" {{ trans.type == 'income' ? 'selected' : '' }}>Receita</option>
<option value="expense" {{ trans.type == 'expense' ? 'selected' : '' }}>Despesa</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Categoria</label>
<select name="category_id" class="form-select" required>
{% for cat in categories_all %}
<option value="{{ cat.id }}" {{ cat.id == trans.category_id ? 'selected' : '' }}>{{ cat.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row align-items-center mb-3">
<div class="col-md-6">
<label class="form-label">Status</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="status" value="paid" {{ trans.status == 'paid' ? 'checked' : '' }}>
<label class="form-check-label">Pago</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="status" value="pending" {{ trans.status == 'pending' ? 'checked' : '' }}>
<label class="form-check-label">Pendente</label>
</div>
</div>
</div>
<div class="col-md-6 pt-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_personal" value="1" id="editIsPersonal{{ trans.id }}" {{ trans.is_personal ? 'checked' : '' }}>
<label class="form-check-label" for="editIsPersonal{{ trans.id }}">Gasto Pessoal?</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Observações</label>
<textarea name="notes" class="form-control" rows="2">{{ trans.notes }}</textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Salvar Alterações</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal Excluir Transação -->
<div class="modal fade" id="deleteTransactionModal{{ trans.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog text-start">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Excluir Transação</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Tem certeza que deseja excluir a transação <strong>"{{ trans.description }}"</strong>?</p>
<p class="text-danger small"><i class="bi bi-exclamation-triangle me-1"></i> Esta ação não pode ser desfeita.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<form action="{{ url('/admin/fluxo-caixa/transaction/delete/' ~ trans.id) }}" method="POST">
<button type="submit" class="btn btn-danger">Excluir</button>
</form>
</div>
</div>
</div>
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center py-4 text-muted">Nenhuma transação encontrada.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal Nova Transação -->
<div class="modal fade" id="newTransactionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Nova Transação</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url('/admin/fluxo-caixa/transaction') }}" method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Descrição</label>
<input type="text" name="description" class="form-control" required placeholder="Ex: Pagamento Fornecedor X">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Valor (R$)</label>
<input type="number" step="0.01" name="amount" class="form-control" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Data Lançamento</label>
<input type="date" name="transaction_date" class="form-control" value="{{ "now"|date("Y-m-d") }}" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Data Vencimento</label>
<input type="date" name="due_date" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Data Pagamento</label>
<input type="date" name="payment_date" class="form-control">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Tipo</label>
<select name="type" class="form-select" id="trans_type">
<option value="income">Receita</option>
<option value="expense" selected>Despesa</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Categoria</label>
<select name="category_id" class="form-select" required>
{% for cat in categories_all %}
<option value="{{ cat.id }}" data-type="{{ cat.type }}">{{ cat.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row align-items-center mb-3">
<div class="col-md-6">
<label class="form-label">Status</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="status" value="paid" checked>
<label class="form-check-label">Pago</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="status" value="pending">
<label class="form-check-label">Pendente</label>
</div>
</div>
</div>
<div class="col-md-6 pt-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_personal" value="1" id="isPersonalSwitch">
<label class="form-check-label" for="isPersonalSwitch">Gasto Pessoal?</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Observações</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Salvar Transação</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal Conciliação Bancária -->
<div class="modal fade" id="reconcileModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Conciliação Bancária</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ url('/admin/fluxo-caixa/reconcile') }}" method="POST">
<div class="modal-body">
<p class="small text-muted mb-3">
Insira o saldo atual que aparece no extrato do seu banco para comparar com o saldo registrado no sistema.
</p>
<div class="mb-3">
<label class="form-label">Saldo Atual no Banco (R$)</label>
<input type="number" step="0.01" name="bank_balance" class="form-control" required placeholder="0,00">
</div>
<div class="alert alert-info py-2 small mb-0">
<i class="bi bi-info-circle me-1"></i> O sistema utilizará o <strong>Saldo Líquido Total</strong> (Empresa + Pessoal) para esta conferência.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Conciliar Agora</button>
</div>
</form>
</div>
</div>
</div>
{% block scripts %}
<script>
const monthlyData = {{ monthlyBreakdown|raw }};
const projectionsData = {{ projections|raw }};
// Configuração comum para tooltips de moeda
const currencyTooltip = {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) label += ': ';
if (context.parsed.y !== null) {
label += 'R$ ' + context.parsed.y.toLocaleString('pt-BR', {minimumFractionDigits: 2});
}
return label;
}
}
};
// Gráfico Histórico
const ctxMonthly = document.getElementById('monthlyChart').getContext('2d');
new Chart(ctxMonthly, {
type: 'bar',
data: {
labels: monthlyData.map(d => d.month),
datasets: [
{
label: 'Receitas',
data: monthlyData.map(d => d.income),
backgroundColor: 'rgba(25, 135, 84, 0.7)',
borderColor: 'rgb(25, 135, 84)',
borderWidth: 1
},
{
label: 'Despesas',
data: monthlyData.map(d => d.expense),
backgroundColor: 'rgba(220, 53, 69, 0.7)',
borderColor: 'rgb(220, 53, 69)',
borderWidth: 1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return 'R$ ' + value.toLocaleString('pt-BR');
}
}
}
},
plugins: {
tooltip: currencyTooltip
}
}
});
// Gráfico de Projeções
const ctxProjection = document.getElementById('projectionChart').getContext('2d');
new Chart(ctxProjection, {
type: 'line',
data: {
labels: projectionsData.map(d => d.month),
datasets: [
{
label: 'Cenário Otimista',
data: projectionsData.map(d => d.optimistic.balance),
borderColor: 'rgba(25, 135, 84, 0.8)',
backgroundColor: 'rgba(25, 135, 84, 0.1)',
borderDash: [5, 5],
fill: false,
tension: 0.4
},
{
label: 'Cenário Realista',
data: projectionsData.map(d => d.realistic.balance),
borderColor: 'rgba(13, 202, 240, 1)',
backgroundColor: 'rgba(13, 202, 240, 0.1)',
fill: true,
tension: 0.4,
borderWidth: 3
},
{
label: 'Cenário Pessimista',
data: projectionsData.map(d => d.pessimistic.balance),
borderColor: 'rgba(220, 53, 69, 0.8)',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
borderDash: [5, 5],
fill: false,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
ticks: {
callback: function(value) {
return 'R$ ' + value.toLocaleString('pt-BR');
}
}
}
},
plugins: {
tooltip: currencyTooltip
}
}
});
</script>
{% endblock %}
{% endblock %}
@@ -0,0 +1,77 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<div class="row mt-4">
<div class="col-lg-6">
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-0 py-3">
<h6 class="m-0 font-weight-bold text-primary">Resumo da Conciliação</h6>
</div>
<div class="card-body">
<div class="d-flex justify-content-between mb-3">
<span>Saldo no Banco:</span>
<span class="fw-bold">R$ {{ bankBalance|number_format(2, ',', '.') }}</span>
</div>
<div class="d-flex justify-content-between mb-3">
<span>Saldo no Sistema:</span>
<span class="fw-bold">R$ {{ systemBalance|number_format(2, ',', '.') }}</span>
</div>
<hr>
<div class="d-flex justify-content-between">
<span class="h5">Diferença:</span>
<span class="h5 fw-bold {{ difference == 0 ? 'text-success' : 'text-danger' }}">
R$ {{ difference|number_format(2, ',', '.') }}
</span>
</div>
</div>
</div>
{% if difference != 0 %}
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<strong>Atenção!</strong> Existe uma diferença de R$ {{ difference|abs|number_format(2, ',', '.') }} entre o banco e o sistema.
<br><br>
<strong>Sugestões de gestão para resolver:</strong>
<ul class="mt-2">
<li>Verifique se todas as tarifas bancárias foram lançadas.</li>
<li>Confira se algum cheque ou transferência ainda não compensou.</li>
<li>Verifique se houve algum erro de digitação nos valores.</li>
<li>Certifique-se de que não houve mistura de gastos pessoais (que não foram lançados).</li>
</ul>
</div>
{% else %}
<div class="alert alert-success">
<i class="bi bi-check-circle-fill me-2"></i>
<strong>Tudo certo!</strong> O saldo do banco coincide perfeitamente com o saldo do sistema. Sua gestão está excelente!
</div>
{% endif %}
<a href="{{ url('/admin/fluxo-caixa') }}" class="btn btn-primary mt-3">
<i class="bi bi-arrow-left me-1"></i> Voltar ao Dashboard
</a>
</div>
<div class="col-lg-6">
<div class="card shadow-sm border-0 bg-light">
<div class="card-body">
<h6 class="fw-bold mb-3">O que é Conciliação Bancária?</h6>
<p class="small text-muted">
A conciliação bancária consiste em conferir se o saldo do controle interno da empresa bate com o saldo do extrato bancário.
</p>
<p class="small text-muted">
Este processo deve ser feito diariamente ou, no máximo, semanalmente para evitar que erros se acumulem e distorçam a realidade financeira da sua empresa.
</p>
<div class="text-center mt-4">
<i class="bi bi-bank fs-1 text-primary opacity-25"></i>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}