@extends('layouts.docs') @section('title', 'Padrões e Casos de Uso - OG Framework') @section('body')
@include('docs.partials.sidebar')
Voltar para Documentação

Apêndice

Padrões e Casos de Uso

Este apêndice demonstra como os componentes do framework trabalham em conjunto para resolver problemas reais de um ERP. Cada exemplo vem de código em produção.

{{-- Introduction --}}

Porque Mostrar Padrões?

A documentação de componentes individuais explica o "o quê" — o que cada classe faz e que métodos tem. Esta secção mostra o "como" — como combinar os componentes para resolver problemas reais.

Cada padrão inclui também anti-patterns (o que NÃO fazer), para ajudar a evitar erros comuns.

{{-- Async Processing --}}

Processamento Assíncrono de Documentos

Quando o utilizador pede para gerar um ficheiro SAF-T (que pode demorar 5-10 minutos para clientes grandes), não podemos fazê-lo esperar. A solução é processar em background e notificar quando estiver pronto.

✅ Solução: Queue + Notificações

O controller enfileira o trabalho e devolve imediatamente. Quando o Job termina, envia uma notificação ao utilizador.

// No Controller
public function exportSaft(SaftExportRequest $request)
{
    $data = $request->validated();

    // Enfileira o trabalho pesado
    SaftExportJob::dispatch($data, auth()->id())
        ->onQueue('saft-export');

    // Devolve imediatamente
    return response()->json([
        'message' => 'O ficheiro está a ser gerado. Serás notificado quando estiver pronto.'
    ]);
}

// No Job
class SaftExportJob implements ShouldQueue
{
    public function handle(): void
    {
        // Processamento pesado aqui...
        $filePath = $this->generateSaftFile($this->data);

        // Notificar quando terminar
        Notification::send([
            'codempr' => $this->userId,
            'subject' => 'Ficheiro SAF-T Pronto',
            'text' => "O teu ficheiro está disponível para download.",
            'color' => 'success',
            'action_url' => route('saft.download', $filePath)
        ]);
    }
}

❌ Anti-Pattern: Processamento Síncrono

Este código chama o browser do utilizador a esperar, resulta em timeouts, e impossibilita mostrar progresso.

// ❌ NÃO FAÇAS ISTO
public function exportSaft(Request $request)
{
    // Utilizador fica a olhar para um ecrã branco durante 10 minutos
    $saftData = $this->generateSaft($request->all());

    // Pode nem chegar aqui (timeout)
    return response()->download($saftData['file']);
}
{{-- DTO Validation --}}

DTOs com Validação Contextual

DTOs (Data Transfer Objects) são classes imutáveis que representam dados estruturados. No OfficeGest, usamo-los para garantir que os dados estão válidos antes de chegarem à lógica de negócio — falham rápido no constructor.

Exemplo: DTO de Exportação

Este DTO valida os dados no constructor e adapta a lógica baseada no tipo de exportação. Se algo estiver errado, lança excepção imediatamente.

final class ExportInputDTO
{
    public readonly int $dateMonthFrom;
    public readonly int $dateMonthTo;
    public readonly string $type;
    public readonly bool $includeDetails;

    public function __construct(array $data)
    {
        // Validação falha rápido
        $this->validate($data);

        $this->type = $data['tipo'];
        $this->includeDetails = $data['detalhes'] ?? false;

        // Lógica contextual: campos diferentes por tipo
        $dateField = match($this->type) {
            'accounting' => 'data_mes_ini_ctb',
            'invoicing' => 'data_mes_ini',
            default => throw new InvalidArgumentException("Tipo inválido: {$this->type}")
        };

        $this->dateMonthFrom = (int) $data[$dateField];
        $this->dateMonthTo = (int) $data[str_replace('_ini', '_fim', $dateField)];
    }

    private function validate(array $data): void
    {
        $required = ['tipo'];
        foreach ($required as $field) {
            if (empty($data[$field])) {
                throw new ValidationException("Campo obrigatório: {$field}");
            }
        }
    }
}
{{-- Legacy Integration --}}

Integração com Código Legacy

O OfficeGest tem código de 15+ anos. Em vez de reescrever tudo, criamos "bridges" — adaptadores que permitem que código novo use funcionalidades legacy de forma limpa e testável.

✅ Solução: Adapter via ServiceProvider

Registamos o código legacy no container com uma interface moderna. O código novo depende da interface, não do legacy.

// ServiceProvider regista o adapter
class LegacyBridgeServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Encapsula o $u global num adapter moderno
        $this->app->singleton(UserInterface::class, function() {
            global $u;
            return new LegacyUserAdapter($u);
        });
    }
}

// O adapter implementa uma interface limpa
class LegacyUserAdapter implements UserInterface
{
    public function __construct(private $legacyUser) {}

    public function hasPermission(string $permission): bool
    {
        // Mapeia nomes modernos para o formato legacy
        $legacyName = $this->mapPermission($permission);
        return $this->legacyUser->permissao($legacyName);
    }
}

// Código novo usa a interface (testável, limpo)
class StockService
{
    public function __construct(private UserInterface $user) {}

    public function update(Stock $stock): void
    {
        if (!$this->user->hasPermission('stocks:update')) {
            throw new AuthorizationException();
        }
        // ...
    }
}

❌ Anti-Pattern: Dependências Globais

// ❌ NÃO FAÇAS ISTO
class StockService
{
    public function update(Stock $stock): void
    {
        global $u; // Impossível testar, dependência escondida

        if (!$u->permissao('ver_stocks')) {
            // ...
        }
    }
}
{{-- Security Patterns --}}

Autorização Granular com Policies

Nem todas as verificações de permissão são simples "pode ou não pode". Muitas vezes dependem do recurso específico — por exemplo, um funcionário pode ver stocks do seu sector, mas não de outros.

Policy com Lógica Contextual

class StockPolicy
{
    public function view(User $user, Stock $stock): bool
    {
        // Managers vêem tudo
        if ($user->hasPermission('stocks.manage')) {
            return true;
        }

        // Funcionários só vêem stocks do seu sector
        if ($user->hasPermission('stocks.view')) {
            return $user->sectors->contains($stock->sector_id);
        }

        return false;
    }

    public function update(User $user, Stock $stock): bool
    {
        // Só o responsável do sector pode editar
        return $stock->sector->manager_id === $user->getId()
            || $user->hasPermission('stocks.manage');
    }
}

// Uso num controller
public function show(int $id)
{
    $stock = Stock::find($id);

    if (!$this->policy->view(auth()->user(), $stock)) {
        abort(403, 'Não tens permissão para ver este stock');
    }

    return view('stocks.show', compact('stock'));
}
{{-- Navigation --}}
@include('docs.partials.toc', ['sections' => [ 'introducao' => 'Porquê Padrões?', 'async' => 'Async Processing', 'dto' => 'DTOs', 'legacy' => 'Legacy Integration', 'security' => 'Autorização', ]])
@endsection