Files
voltcerto/app/Services/AnalyticalService.php
Luis Medon e488ae3342 Add feature tests, unit tests, and configuration for Laravel application
- Created ExampleTest and RoutesTest for basic application response and route testing.
- Implemented IsolatedDatabaseTestCase for isolated database testing.
- Added unit tests for various models and services including Contract, CPE, and LeadDetail.
- Developed request validation tests for CreateClientRequest, UpdateClientRequest, and ContractRequest.
- Introduced Vite configuration for asset management in the Laravel application.
2026-10-29 11:30:06 +00:00

284 lines
11 KiB
PHP

<?php
namespace App\Services;
use App\Models\Contract;
use App\Models\ContractHistory;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AnalyticalService
{
/**
* @author Luis Medon <luismedon@gmail.com>
* @version 2026/03
*/
public function index(Request $request)
{
$from = $this->parseDate($request->validity_from ?? $request->from ?? null);
$to = $this->parseDate($request->validity_to ?? $request->to ?? null);
$baseContractQuery = $this->applyFilters(Contract::query(), $request, 'contract_date');
$baseHistoryQuery = $this->applyFilters(ContractHistory::query(), $request, 'contract_date');
$contractTotals = (clone $baseContractQuery)
->selectRaw('COALESCE(SUM(contract_value), 0) as total_vendas, COALESCE(SUM(profit_value), 0) as total_lucro, COALESCE(SUM(commission_value), 0) as total_comissao, COUNT(*) as contagem, COUNT(DISTINCT client_id) as total_clients')
->first();
$historyTotals = (clone $baseHistoryQuery)
->selectRaw('COALESCE(SUM(contract_value), 0) as total_vendas, COALESCE(SUM(profit_value), 0) as total_lucro, COALESCE(SUM(commission_value), 0) as total_comissao, COUNT(*) as contagem, COUNT(DISTINCT contract_id) as total_clients')
->first();
$totalVendas = (float) ($contractTotals->total_vendas ?? 0) + (float) ($historyTotals->total_vendas ?? 0);
$totalLucro = (float) ($contractTotals->total_lucro ?? 0) + (float) ($historyTotals->total_lucro ?? 0);
$totalComissao = (float) ($contractTotals->total_comissao ?? 0) + (float) ($historyTotals->total_comissao ?? 0);
$contagem = (int) (($contractTotals->contagem ?? 0) + ($historyTotals->contagem ?? 0));
$totalClients = (int) max($contractTotals->total_clients ?? 0, $historyTotals->total_clients ?? 0);
$stats = [
'total_vendas' => $totalVendas,
'total_lucro' => $totalLucro,
'total_comissao' => $totalComissao,
'contagem' => $contagem,
'total_clients' => $totalClients,
];
$vendasMensais = $this->buildMonthlyGrowth($request);
$vendasPorTipo = $this->buildClientTypeBreakdown($request);
$totaisPorAno = $this->buildYearBreakdown($request);
$totaisPorComercializadora = $this->buildSupplierBreakdown($request);
return compact('stats', 'vendasMensais', 'vendasPorTipo', 'totaisPorAno', 'totaisPorComercializadora');
}
private function applyFilters($query, Request $request, string $dateField = 'contract_date')
{
if (!str_contains($dateField, '.')) {
$dateField = $query->getModel()->getTable() . '.' . $dateField;
}
$from = $this->parseDate($request->validity_from ?? $request->from ?? null);
$to = $this->parseDate($request->validity_to ?? $request->to ?? null);
if ($from) {
$query->whereDate($dateField, '>=', $from);
}
if ($to) {
$query->whereDate($dateField, '<=', $to);
}
foreach (['status_id', 'seller_id', 'supply_type', 'contract_type_id', 'year'] as $field) {
$value = $request->input($field);
if ($value === null || $value === '' || $value === 'all') {
continue;
}
switch ($field) {
case 'status_id':
$query->where('status_id', $value);
break;
case 'seller_id':
$query->where('seller_id', $value);
break;
case 'supply_type':
$query->where('supply_type', $value);
break;
case 'contract_type_id':
$query->where('contract_type_id', $value);
break;
case 'year':
$query->whereRaw($this->yearExpression($dateField) . ' = ?', [(int) $value]);
break;
}
}
if ($request->filled('client_type_id')) {
$query->whereHas('client', fn ($q) => $q->where('client_type_id', $request->client_type_id));
}
if ($request->filled('supplier_id')) {
$query->where(function ($q) use ($request) {
$q->where('new_supplier_id', $request->supplier_id)
->orWhere('previous_supplier_id', $request->supplier_id);
});
}
return $query;
}
private function buildMonthlyGrowth(Request $request)
{
$from = $this->parseDate($request->validity_from ?? $request->from ?? null);
$to = $this->parseDate($request->validity_to ?? $request->to ?? null);
$contractSeries = Contract::query();
$historySeries = ContractHistory::query();
$this->applyFilters($contractSeries, $request, 'contract_date');
$this->applyFilters($historySeries, $request, 'contract_date');
$monthExpression = $this->monthExpression('contract_date');
$contractTotals = (clone $contractSeries)
->selectRaw($monthExpression . ' as mes, SUM(contract_value) as total')
->whereNotNull('contract_date')
->groupByRaw($monthExpression)
->orderByRaw($monthExpression)
->get();
$historyTotals = (clone $historySeries)
->selectRaw($monthExpression . ' as mes, SUM(contract_value) as total')
->whereNotNull('contract_date')
->groupByRaw($monthExpression)
->orderByRaw($monthExpression)
->get();
$merged = [];
foreach ($contractTotals as $row) {
$merged[$row->mes] = (float) ($row->total ?? 0);
}
foreach ($historyTotals as $row) {
$merged[$row->mes] = ($merged[$row->mes] ?? 0) + (float) ($row->total ?? 0);
}
ksort($merged);
return collect(array_map(fn ($month, $total) => ['mes' => $month, 'total' => $total], array_keys($merged), array_values($merged)));
}
private function buildClientTypeBreakdown(Request $request)
{
$contractBreakdown = (clone $this->applyFilters(Contract::query(), $request, 'contract_date'))
->join('clients', 'contracts.client_id', '=', 'clients.id')
->join('client_types', 'clients.client_type_id', '=', 'client_types.id')
->selectRaw('client_types.description as tipo, SUM(contracts.contract_value) as total')
->groupBy('client_types.description')
->get();
$historyBreakdown = (clone $this->applyFilters(ContractHistory::query(), $request, 'contract_date'))
->join('contracts', 'contract_histories.contract_id', '=', 'contracts.id')
->join('clients', 'contracts.client_id', '=', 'clients.id')
->join('client_types', 'clients.client_type_id', '=', 'client_types.id')
->selectRaw('client_types.description as tipo, SUM(contract_histories.contract_value) as total')
->groupBy('client_types.description')
->get();
$merged = [];
foreach ($contractBreakdown as $row) {
$merged[$row->tipo] = (float) ($row->total ?? 0);
}
foreach ($historyBreakdown as $row) {
$key = $row->tipo ?? 'Sem tipo';
$merged[$key] = ($merged[$key] ?? 0) + (float) ($row->total ?? 0);
}
return collect(array_map(fn ($tipo, $total) => ['tipo' => $tipo, 'total' => $total], array_keys($merged), array_values($merged)));
}
private function buildYearBreakdown(Request $request)
{
$yearExpression = $this->yearExpression('contract_date');
$contractYears = (clone $this->applyFilters(Contract::query(), $request, 'contract_date'))
->selectRaw($yearExpression . ' as ano, SUM(contract_value) as total')
->whereNotNull('contract_date')
->groupByRaw($yearExpression)
->orderByRaw($yearExpression)
->get();
$historyYears = (clone $this->applyFilters(ContractHistory::query(), $request, 'contract_date'))
->selectRaw($yearExpression . ' as ano, SUM(contract_value) as total')
->whereNotNull('contract_date')
->groupByRaw($yearExpression)
->orderByRaw($yearExpression)
->get();
$merged = [];
foreach ($contractYears as $row) {
$merged[(string) $row->ano] = (float) ($row->total ?? 0);
}
foreach ($historyYears as $row) {
$key = (string) ($row->ano ?? '');
$merged[$key] = ($merged[$key] ?? 0) + (float) ($row->total ?? 0);
}
ksort($merged);
return collect(array_map(fn ($ano, $total) => ['ano' => $ano, 'total' => $total], array_keys($merged), array_values($merged)));
}
private function buildSupplierBreakdown(Request $request)
{
$contractSuppliers = (clone $this->applyFilters(Contract::query(), $request, 'contract_date'))
->join('suppliers', 'contracts.new_supplier_id', '=', 'suppliers.id')
->selectRaw('suppliers.name as fornecedor, SUM(contracts.contract_value) as total')
->groupBy('suppliers.name')
->orderBy('total', 'desc')
->get();
$historySuppliers = (clone $this->applyFilters(ContractHistory::query(), $request, 'contract_date'))
->join('suppliers', 'contract_histories.current_supplier_id', '=', 'suppliers.id')
->selectRaw('suppliers.name as fornecedor, SUM(contract_histories.contract_value) as total')
->groupBy('suppliers.name')
->orderBy('total', 'desc')
->get();
$merged = [];
foreach ($contractSuppliers as $row) {
$merged[$row->fornecedor] = (float) ($row->total ?? 0);
}
foreach ($historySuppliers as $row) {
$key = $row->fornecedor ?? 'Sem fornecedor';
$merged[$key] = ($merged[$key] ?? 0) + (float) ($row->total ?? 0);
}
return collect(array_map(fn ($fornecedor, $total) => ['fornecedor' => $fornecedor, 'total' => $total], array_keys($merged), array_values($merged)))
->sortByDesc('total')->values();
}
private function yearExpression(string $column): string
{
if (DB::getDriverName() === 'sqlite') {
return "CAST(strftime('%Y', {$column}) AS INTEGER)";
}
return "YEAR({$column})";
}
private function monthExpression(string $column): string
{
if (DB::getDriverName() === 'sqlite') {
return "strftime('%Y-%m', {$column})";
}
return "DATE_FORMAT({$column}, '%Y-%m')";
}
private function parseDate($date)
{
if ($date === null || $date === '') {
return null;
}
$date = trim((string) $date);
try {
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
return Carbon::createFromFormat('Y-m-d', $date)->format('Y-m-d');
}
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $date)) {
return Carbon::createFromFormat('d/m/Y', $date)->format('Y-m-d');
}
return Carbon::parse($date)->format('Y-m-d');
} catch (\Exception $e) {
return null;
}
}
}