Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["POST /api/v1/faturas"] --> B["FaturamentoService::emitir()"]
    B --> C["DB::transaction"]
    C --> D["Fatura + ItemFatura persistidos"]
    C --> E["AuditoriaService::registrar()"]
    E --> F["Ledger append-only com hash encadeado"]
    D --> G["EmitirNotaFiscalJob (fila)"]
    G --> H["Notificação via WebSocket ao cliente"]

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style C fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
    style G fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px

🏛️ 1. Este Capítulo é um Projeto Integrador

Este capítulo não introduz uma API nova do Laravel — ele combina, num domínio contábil real (faturamento), quase tudo estudado nos capítulos anteriores: Eloquent com Enums nativos (Cap. 06), transações (Cap. 08), Service Layer (Cap. 17), filas assíncronas (Cap. 13), broadcasting (Cap. 18) e testes com Pest (Cap. 16). O fio condutor é um requisito de negócio real: toda fatura emitida precisa ser rastreável e imutável, por exigência contábil e fiscal.


🧾 2. Modelagem: Fatura, Itens e Status como Enum

<?php
namespace App\Models;

enum StatusFatura: string
{
    case PENDENTE = 'PENDENTE';
    case EMITIDA = 'EMITIDA';
    case CANCELADA = 'CANCELADA';
}
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Fatura extends Model
{
    protected $fillable = ['pedido_id', 'valor_bruto', 'impostos', 'valor_liquido', 'status'];

    protected $casts = [
        'status' => StatusFatura::class,
        'valor_bruto' => 'decimal:2',
        'impostos' => 'decimal:2',
        'valor_liquido' => 'decimal:2',
    ];
}

O cast para StatusFatura::class faz o Eloquent converter a coluna string automaticamente para o backed enum tanto na leitura quanto na escrita, eliminando comparações frágeis com strings soltas ($fatura->status === 'EMITIDA'). O cast decimal:2 é essencial para valores monetários: usar float puro para dinheiro introduz erros de arredondamento de ponto flutuante binário (ex: 0.1 + 0.2 !== 0.3), inaceitável em cálculo fiscal.


💰 3. Transação Atômica de Emissão

A emissão de uma fatura envolve múltiplas escritas (fatura + itens + registro de auditoria) que devem ser tudo-ou-nada: se qualquer etapa falhar, nada pode ficar persistido pela metade.

<?php
namespace App\Services;

use App\Models\Fatura;
use App\Models\StatusFatura;
use Illuminate\Support\Facades\DB;

class FaturamentoService
{
    public function __construct(private readonly AuditoriaService $auditoria) {}

    public function emitir(int $pedidoId, float $valor): Fatura
    {
        return DB::transaction(function () use ($pedidoId, $valor) {
            $impostos = round($valor * 0.15, 2);

            $fatura = Fatura::create([
                'pedido_id' => $pedidoId,
                'valor_bruto' => $valor,
                'impostos' => $impostos,
                'valor_liquido' => $valor - $impostos,
                'status' => StatusFatura::EMITIDA,
            ]);

            $this->auditoria->registrar('fatura.emitida', $fatura->toArray());

            return $fatura;
        });
    }
}

Se AuditoriaService::registrar() lançar uma exceção, o DB::transaction faz rollback automático da criação da Fatura — garantindo que nunca exista fatura emitida sem o registro de auditoria correspondente.


🔐 4. Ledger de Auditoria com Hash Encadeado

Uma trilha de auditoria comum (só INSERT com timestamp) protege contra sobrescrita acidental, mas não prova que um registro antigo não foi alterado depois do fato por acesso direto ao banco. Encadear os hashes resolve isso: cada novo registro incorpora o hash do anterior, formando uma cadeia semelhante a um blockchain simplificado — qualquer alteração retroativa quebra a cadeia de hashes a partir daquele ponto.

<?php
namespace App\Services;

use Illuminate\Support\Facades\DB;

class AuditoriaService
{
    public function registrar(string $acao, array $dados): void
    {
        $ultimoHash = DB::table('auditoria_ledger')->latest('id')->value('hash') ?? 'GENESIS_HASH';
        $payloadJson = json_encode($dados);
        $novoHash = hash('sha256', $ultimoHash . $payloadJson . now()->timestamp);

        DB::table('auditoria_ledger')->insert([
            'acao' => $acao,
            'payload' => $payloadJson,
            'prev_hash' => $ultimoHash,
            'hash' => $novoHash,
            'created_at' => now(),
        ]);
    }
}

A verificação de integridade percorre a tabela em ordem e recalcula cada hash a partir do payload e do prev_hash armazenados — se o hash recalculado não bater com o hash gravado, a cadeia foi violada.


🧪 5. Testando o Fluxo Completo com Pest

O teste de aceitação do projeto não valida uma unidade isolada, mas o efeito de borda a borda: requisição HTTP entra, fatura é criada, ledger é gravado.

<?php
use function Pest\Laravel\postJson;

it('emite fatura e grava auditoria encadeada', function () {
    $response = postJson('/api/v1/faturas', ['pedido_id' => 10, 'valor' => 1000.0]);

    $response->assertCreated()
        ->assertJsonPath('status', 'EMITIDA');

    $this->assertDatabaseCount('auditoria_ledger', 1);
});