# Sistema de Resources (Output DTOs) - Framework OfficeGest

## Visão Geral

O **Sistema de Resources** do framework OfficeGest implementa o padrão **Resource/Transformer** para serialização consistente de dados em APIs e responses. Através da classe base `JsonResource` e um conjunto robusto de traits complementares, o sistema transforma dados brutos de entidades, base de dados e arrays complexos numa representação JSON estruturada, segura e previsível.

```mermaid
flowchart LR
    subgraph Input["Dados de Entrada"]
        DB[(Base de Dados)]
        Entity[Entidade/Model]
        Array[Array Bruto]
    end
    
    subgraph Resource["Output DTO"]
        DTO["**JsonResource**\n+ ConditionalAttributesTrait\n+ DateSerializerTrait\n+ DelegatesToResource\n+ InteractsWithType"]
    end
    
    subgraph Output["Saída JSON"]
        JSON["{\n  'id': 1,\n  'name': 'João',\n  'email': 'joao@ex.com'\n}"]
    end
    
    DB --> Resource
    Entity --> Resource
    Array --> Resource
    Resource --> Output
    
    style Resource fill:#e8f4fd,stroke:#0284c7
    style Output fill:#f0fdf4,stroke:#22c55e
```

---

## Arquitectura e Design Patterns

O sistema baseia-se em padrões de design modernos que garantem flexibilidade, extensibilidade e consistência:

```mermaid
classDiagram
    class Arrayable {
        <<interface>>
        +toArray() array
    }
    
    class JsonResource {
        +resource mixed
        +__construct(resource)
        +make(data) array
        +collection(data) array
        +toArray() array
        +resource() array
    }
    
    class ConditionalAttributesTrait {
        +whenHas(value, callback, default) mixed
        +whenExists(attribute, callback, default) mixed
        +when(condition, callback, default) mixed
        +merge(value) array
        +mergeWhen(condition, value) array
    }
    
    class DateSerializerTrait {
        +serializeValue(value) mixed
    }
    
    class DelegatesToResource {
        +__get(key) mixed
        +__isset(key) bool
        +__call(method, params) mixed
    }
    
    class InteractsWithType {
        +validateType(type, attr) mixed
        +isInteger(attr) mixed
        +isString(attr) mixed
    }
    
    class MissingValue {
        +getInstance() MissingValue
    }
    
    class TypedValue {
        +whenHas(attribute) mixed
    }
    
    Arrayable <|.. JsonResource
    JsonResource *-- ConditionalAttributesTrait
    JsonResource *-- DateSerializerTrait
    JsonResource *-- DelegatesToResource
    JsonResource *-- InteractsWithType
    ConditionalAttributesTrait ..> MissingValue : uses
    InteractsWithType ..> TypedValue : creates
```

### Padrões Implementados

| Padrão | Descrição | Implementação |
|--------|-----------|---------------|
| **Resource Pattern** | Define contrato para transformação de dados | Classe base `JsonResource` com método `toArray()` |
| **Factory Pattern** | Criação padronizada de instâncias | Métodos estáticos `make()` e `collection()` |
| **Transformer Pattern** | Encapsulamento de lógica de serialização | Cada DTO transforma um domínio específico |
| **Decorator Pattern** | Adição modular de funcionalidades | Traits adicionam comportamentos (validação, serialização) |
| **Singleton Pattern** | Instância única para valores especiais | `MissingValue::getInstance()` |

---

## Componentes do Sistema

### Estrutura de Ficheiros

```
Modules/Common/Resource/
├── JsonResource.php           # Classe base para todos os DTOs
├── Arrayable.php              # Interface de conversão para array
├── MissingValue.php           # Classe sentinel para campos ausentes
├── TypedValue.php             # Helper para validação de tipos
├── Exceptions/
│   └── InvalidResourceTypeException.php
└── Traits/
    ├── ConditionalAttributesTrait.php   # Campos condicionais
    ├── DateSerializerTrait.php          # Serialização de datas
    ├── DelegatesToResource.php          # Acesso a propriedades
    ├── InteractsWithType.php            # Validação de tipos
    ├── ValidatesPrimitiveTypes.php      # Tipos primitivos
    ├── ValidatesNullableTypes.php       # Tipos nullable
    ├── ValidatesClassTypes.php          # Tipos de classes
    └── ValidatesAdvancedTypes.php       # Tipos avançados
```

---

## Classe Base: JsonResource

A classe `JsonResource` é o coração do sistema, fornecendo a estrutura base para transformação de dados:

```php
<?php

namespace Og\Modules\Common\Resource;

class JsonResource implements Arrayable
{
    use DateSerializerTrait;
    use ConditionalAttributesTrait;
    use DelegatesToResource;
    use InteractsWithType;

    public function __construct(public $resource = null)
    {
    }

    public static function make(?array $data = null): array
    {
        if (!isset($data)) {
            return [];
        }
        return self::serializeValue((new static($data))->toArray());
    }

    public static function collection(?array $data = null): array
    {
        if (!isset($data)) {
            return [];
        }
        if (self::isPaginated($data)) {
            return [
                'items' => array_map(fn ($item) => is_array($item) ? static::make($item) : [], $data['items']),
                'meta' => [
                    'current_page' => $data['current_page'],
                    'from' => ($data['current_page'] - 1) * $data['per_page'] + 1,
                    'last_page' => $data['last_page'],
                    'per_page' => $data['per_page'],
                    'to' => min($data['current_page'] * $data['per_page'], $data['total']),
                    'total' => $data['total']
                ]
            ];
        };
        return array_map(fn ($item) => is_array($item) ? static::make($item) : [], $data);
    }

    public function toArray(): array
    {
        throw new BadMethodCallException('The subclass must implement a toArray method.');
    }

    private static function isPaginated(array $data): bool
    {
        return (isset($data['items']) || isset($data['data'])) 
            && isset($data['per_page']) 
            && isset($data['total']);
    }

    public function resource(): ?array
    {
        return $this->resource;
    }
}
```

### Lifecycle de Transformação

```mermaid
sequenceDiagram
    participant Client
    participant DTO as OutputDTO
    participant JsonResource
    participant Serializer as DateSerializerTrait
    participant Missing as MissingValue

    Client->>DTO: ::make($data)
    DTO->>JsonResource: new static($data)
    JsonResource->>DTO: toArray()
    DTO->>DTO: whenHas(), when(), etc.
    DTO-->>JsonResource: array com MissingValue
    JsonResource->>Serializer: serializeValue($array)
    Serializer->>Serializer: Formatar Carbon
    Serializer->>Serializer: Filtrar MissingValue
    Serializer-->>JsonResource: array limpo
    JsonResource-->>Client: JSON final
```

---

## Criando Output DTOs

### Exemplo Básico: UserOutputDTO

O exemplo mais simples de um Output DTO segue este padrão:

```php
<?php

namespace Og\Modules\Auth\Api\DTOs;

use Og\Modules\Common\Resource\JsonResource;

final class UserOutputDTO extends JsonResource
{
    public function toArray(): array
    {
        return [
            'id' => $this->codempr,
            'email' => $this->email,
            'name' => $this->nome,
            'short_name' => $this->nomeabreviado,
        ];
    }
}
```

**Uso:**

```php
// Dados brutos da base de dados
$userData = [
    'codempr' => 1,
    'email' => 'joao@empresa.com',
    'nome' => 'João Silva',
    'nomeabreviado' => 'João'
];

// Transformação simples
$result = UserOutputDTO::make($userData);
// Resultado: ['id' => 1, 'email' => 'joao@empresa.com', 'name' => 'João Silva', 'short_name' => 'João']
```

### Exemplo Intermediário: CrmAppointmentOutputDTO

DTOs podem ter construtores personalizados para normalizar dados legados:

```php
<?php

namespace Og\Modules\Crm\Appointments\DTOs;

use Og\Modules\Common\Resource\JsonResource;

final class CrmAppointmentOutputDTO extends JsonResource
{
    private int $id;
    private ?string $title;
    private ?string $start;
    private string $end;
    private ?bool $confirmed;
    private ?bool $completed;
    private ?int $employee_id;
    private ?string $priority;
    private ?string $location;

    public function __construct(array $data)
    {
        // Normalização de campos legados para padrão moderno
        $this->id = $data['NumRel'];
        $this->title = $data['Assunto'];
        $this->start = $data['DataHInicio'];
        $this->end = $data['DataHFim'];
        $this->confirmed = \Funcs::toBool($data['confirmed'] ?? 'false');
        $this->completed = \Funcs::toBool($data['Realizado'] ?? 'false');
        $this->employee_id = $data['CodEmpr'];
        $this->priority = $data['Prioridade'];
        $this->location = $data['Local'];
    }

    public function toArray(): array
    {
        return [
            'id'          => $this->id,
            'title'       => $this->title,
            'start'       => $this->start,
            'end'         => $this->end,
            'confirmed'   => $this->confirmed,
            'completed'   => $this->completed,
            'employee_id' => $this->employee_id,
            'priority'    => $this->priority,
            'location'    => $this->location,
        ];
    }
}
```

> [!TIP]
> Use construtores personalizados quando precisar normalizar campos de sistemas legados (ex: `NumRel` → `id`, `CodEmpr` → `employee_id`).

### Exemplo Avançado: Faturação Electrónica SAFT Angola

Este exemplo do mundo real demonstra o uso completo de campos condicionais, DTOs aninhados e lógica complexa:

```php
<?php

declare(strict_types=1);

namespace Og\Modules\SaftAo\ElectronicInvoicing\DTOs;

use Og\Modules\Common\Resource\JsonResource;

final class SaftAoElectronicInvoicingDocumentOutputDTO extends JsonResource
{
    public function toArray(): array
    {
        return [
            // Campos simples com dot notation
            'documentNo' => $this->whenHas('document.documentnumber'),
            'documentStatus' => $this->whenHas('document.status'),
            
            // Campo condicional baseado em expressão lógica
            'documentCancelReason' => $this->when(
                $this->resource['document']['anulado'] === 'T',
                $this->resource['document']['document_cancel_reason']
            ),
            
            // Mais campos com dot notation
            'jwsDocumentSignature' => $this->whenHas('document.signature'),
            'documentDate' => $this->whenHas('document.data'),
            'documentType' => $this->whenHas('document.document_type'),
            'eacCode' => $this->whenHas('document.eaccode'),
            'systemEntryDate' => $this->whenHas('document.systementrydate'),
            'customerTaxID' => $this->whenHas('document.ncontrib'),
            'customerCountry' => $this->whenHas('document.codpais'),
            'companyName' => $this->whenHas('document.company_name'),
            
            // Colecção aninhada de linhas do documento
            'lines' => SaftAoElectronicInvoicingDocumentLineOutputDTO::collection(
                $this->resource['lines']
            ),
        ];
    }
}
```

**DTO Aninhado para Linhas do Documento:**

```php
<?php

namespace Og\Modules\SaftAo\ElectronicInvoicing\DTOs;

use Og\Modules\Common\Resource\JsonResource;

final class SaftAoElectronicInvoicingDocumentLineOutputDTO extends JsonResource
{
    public function toArray(): array
    {
        return [
            'lineNumber' => $this->whenHas('nlinha'),
            'productCode' => $this->whenHas('codartigo'),
            'productDescription' => $this->whenHas('designacao'),
            'quantity' => $this->whenHas('qtd'),
            
            // Campo condicional complexo com closure
            'referenceInfo' => $this->when(
                !empty($this->resource['document_operation_type']) 
                    && $this->resource['document_operation_type'] === 'NC',
                fn () => ['reference' => $this->resource['ndocori']]
            ),
        ];
    }
}
```

---

## Sistema de Campos Condicionais

O trait `ConditionalAttributesTrait` fornece um poderoso sistema para controlar quais campos aparecem na saída JSON, baseado em condições dinâmicas.

### whenHas() - Campo Existe e Tem Valor

O método mais utilizado: inclui o campo apenas se o atributo existir e tiver valor não-vazio.

```php
public function toArray(): array
{
    return [
        // Sintaxe simples - retorna o valor se existir
        'name' => $this->whenHas('name'),
        
        // Com callback - transforma o valor
        'created_at' => $this->whenHas('created_at', fn ($value) => Carbon::parse($value)->toIso8601String()),
        
        // Com dot notation para dados aninhados
        'city' => $this->whenHas('address.city'),
        
        // Com DTO aninhado (lazy loading)
        'customer' => $this->whenHas('customer', fn () => CustomerOutputDTO::make($this->customer)),
        
        // Com valor default quando não existe
        'status' => $this->whenHas('status', null, 'active'),
    ];
}
```

**Comportamento detalhado:**

| Entrada | Valor `whenHas('name')` | Campo na Saída? |
|---------|------------------------|-----------------|
| `['name' => 'João']` | `'João'` | ✅ Sim |
| `['name' => '']` | `MissingValue` | ❌ Não |
| `['name' => null]` | `MissingValue` | ❌ Não |
| `[]` (campo ausente) | `MissingValue` | ❌ Não |
| `['name' => []]` | `MissingValue` | ❌ Não |

### whenExists() - Campo Existe (Mesmo se Null)

Use quando precisar incluir o campo mesmo que o valor seja `null`:

```php
public function toArray(): array
{
    return [
        // Inclui mesmo se for null (diferente de whenHas)
        'deleted_at' => $this->whenExists('deleted_at'),
        
        // Com transformação
        'notes' => $this->whenExists('notes', fn ($value) => $value ?? 'Sem notas'),
    ];
}
```

### when() - Condição Booleana Livre

Para condições que não dependem da existência de um campo:

```php
public function toArray(): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        
        // Só aparece se o documento estiver anulado
        'cancelReason' => $this->when(
            $this->resource['anulado'] === 'T',
            $this->resource['cancel_reason']
        ),
        
        // Só aparece para usuários admin
        'adminNotes' => $this->when(
            auth()->user()?->isAdmin(),
            fn () => $this->fetchAdminNotes()
        ),
        
        // Com callback para lazy evaluation
        'premiumFeatures' => $this->when(
            $this->isPremium,
            fn () => PremiumFeaturesDTO::make($this->features)
        ),
        
        // Com valor default
        'badge' => $this->when($this->score >= 100, 'gold', 'standard'),
    ];
}
```

### merge() e mergeWhen() - Mesclagem de Arrays

Para adicionar múltiplos campos condicionalmente:

```php
public function toArray(): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        
        // Mescla incondicional (organização)
        ...$this->merge([
            'version' => '1.0',
            'api' => 'v2',
        ]),
        
        // Mescla condicional - múltiplos campos
        ...$this->mergeWhen($this->isAdmin, [
            'email' => $this->email,
            'permissions' => $this->permissions,
            'last_login' => $this->last_login,
        ]),
        
        // Com closure para lazy loading
        ...$this->mergeWhen($this->includeFinancials, fn () => [
            'total_spent' => $this->orders()->sum('total'),
            'credit_limit' => $this->credit_limit,
            'payment_history' => PaymentHistoryDTO::collection($this->payments),
        ]),
    ];
}
```

> [!WARNING]
> O operador spread (`...`) é **obrigatório** antes de `merge()` e `mergeWhen()` para mesclar os arrays no nível raiz. Sem ele, os campos serão aninhados incorrectamente.

### Dot Notation para Dados Aninhados

O sistema suporta acesso a propriedades aninhadas usando notação de ponto:

```php
public function toArray(): array
{
    return [
        // Acesso a arrays aninhados
        'doc_number' => $this->whenHas('document.header.number'),
        
        // Funciona com objetos e arrays misturados
        'author_email' => $this->whenHas('author.contact.email'),
        
        // O sistema trata null-safety automaticamente
        'deep_value' => $this->whenHas('level1.level2.level3.value'),
    ];
}
```

---

## Serialização de Tipos Especiais

O trait `DateSerializerTrait` processa automaticamente tipos especiais durante a serialização:

### Tipos Suportados

| Tipo | Conversão | Exemplo |
|------|-----------|---------|
| `Carbon` | Formato configurável | `2024-01-15 10:30:00` |
| `Collection` | Array serializado recursivamente | `[item1, item2, ...]` |
| `MissingValue` | **Removido** do output | Campo não aparece |
| Arrays | Processamento recursivo | Nested arrays |

### Serialização de Datas

```php
public function toArray(): array
{
    return [
        // Carbon é serializado automaticamente
        'entry_date' => $this->whenHas('entry_date', 
            fn () => Carbon::createFromDate($this->entry_date)
        ),
        
        // Formato pode ser customizado via config
        'created_at' => $this->whenHas('created_at', 
            fn () => Carbon::parse($this->created_at)
        ),
    ];
}
```

**Configuração global de formato de data:**

```php
// config/app.php
return [
    'date_format' => 'Y-m-d H:i:s', // Formato ISO padrão
    // ou: 'd/m/Y H:i' para formato português
];
```

---

## Sistema de Validação de Tipos

O sistema inclui um subsistema robusto de validação de tipos para garantir integridade de dados.

### Hierarquia de Traits de Validação

```mermaid
flowchart TD
    InteractsWithType["InteractsWithType\n(coordinador)"]
    
    Primitive["ValidatesPrimitiveTypes\nisInteger(), isString(),\nisBoolean(), isArray()..."]
    
    Nullable["ValidatesNullableTypes\nisNullOrInteger(),\nisNullOrString()..."]
    
    Class["ValidatesClassTypes\nisDateTime(), isCarbon(),\nisCollection(), isEnum()..."]
    
    Advanced["ValidatesAdvancedTypes\nisArrayOf(), isEmail(),\nisUrl(), isUuid()..."]
    
    InteractsWithType --> Primitive
    InteractsWithType --> Nullable
    InteractsWithType --> Class
    InteractsWithType --> Advanced
```

### Tipos Primitivos (ValidatesPrimitiveTypes)

```php
public function toArray(): array
{
    return [
        // Validação directa - lança excepção se tipo incorreto
        'user_id' => $this->isInteger('user_id'),
        
        // Encadeamento com whenHas - valida apenas se existir
        'age' => $this->isInteger()->whenHas('age'),
        
        // Outros tipos primitivos
        'name' => $this->isString('name'),
        'active' => $this->isBoolean('active'),
        'settings' => $this->isArray('settings'),
        'score' => $this->isFloat('score'),
        'value' => $this->isNumeric('value'),  // int ou float
        'data' => $this->isScalar('data'),      // int, float, string, bool
    ];
}
```

### Tipos Nullable (ValidatesNullableTypes)

```php
public function toArray(): array
{
    return [
        // Permite null ou integer
        'parent_id' => $this->isNullOrInteger('parent_id'),
        
        // Permite null ou string
        'nickname' => $this->isNullOrString('nickname'),
        
        // Com encadeamento
        'optional_count' => $this->isNullOrInteger()->whenHas('optional_count'),
    ];
}
```

### Tipos de Classes (ValidatesClassTypes)

```php
public function toArray(): array
{
    return [
        // DateTime e Carbon
        'created_at' => $this->isDateTime('created_at'),
        'updated_at' => $this->isCarbon('updated_at'),
        'deleted_at' => $this->isNullOrDateTime('deleted_at'),
        
        // Collections
        'items' => $this->isCollection('items'),
        
        // Enums (PHP 8.1+)
        'status' => $this->isEnum(OrderStatus::class, 'status'),
        
        // Com nullable
        'type' => $this->isNullOrEnum(DocumentType::class, 'type'),
    ];
}
```

### Tipos Avançados (ValidatesAdvancedTypes)

```php
public function toArray(): array
{
    return [
        // Arrays tipados
        'tag_ids' => $this->isArrayOf('integer', 'tag_ids'),
        'names' => $this->isArrayOf('string', 'names'),
        
        // Nullable arrays tipados
        'category_ids' => $this->isNullOrArrayOf('integer', 'category_ids'),
        
        // Integers positivos
        'quantity' => $this->isPositiveInteger('quantity'),
        'count' => $this->isNullOrPositiveInteger('count'),
        
        // Strings não vazias
        'title' => $this->isNonEmptyString('title'),
        'description' => $this->isNullOrNonEmptyString('description'),
        
        // Formatos específicos
        'email' => $this->isEmail('email'),
        'website' => $this->isNullOrUrl('website'),
        'external_id' => $this->isUuid('external_id'),
    ];
}
```

### Classe TypedValue para Encadeamento

A classe `TypedValue` permite encadear validação de tipos com campos condicionais:

```php
// Validação complexa com encadeamento
'user_id' => $this->isInteger()->whenHas('user_id'),

// Equivale a:
'user_id' => $this->whenHas('user_id', function ($value) {
    if (!is_int($value)) {
        throw new InvalidResourceTypeException("...");
    }
    return $value;
}),
```

---

## Delegação de Propriedades

O trait `DelegatesToResource` permite acesso transparente às propriedades do resource:

```php
final class ProductOutputDTO extends JsonResource
{
    public function toArray(): array
    {
        return [
            // Acesso via __get() magic method
            'id' => $this->id,           // Tenta: $this->resource['id']
            'name' => $this->name,       // Tenta: $this->resource['name']
            'price' => $this->price,     // Tenta: $this->resource->getPrice()
        ];
    }
}
```

### Ordem de Resolução

```mermaid
flowchart TD
    A["$this->propriedade"] --> B{Propriedade local?}
    B -->|Sim| C["Retorna propriedade da classe"]
    B -->|Não| D{Resource é array?}
    D -->|Sim| E["Retorna resource['propriedade']"]
    D -->|Não| F{Resource é objecto?}
    F -->|Sim| G{Propriedade existe?}
    G -->|Sim| H["Retorna resource->propriedade"]
    G -->|Não| I{Método getter existe?}
    I -->|Sim| J["Retorna resource->getPropriedade()"]
    I -->|Não| K["Retorna null"]
    F -->|Não| K
```

---

## Colecções e Paginação

### Transformar Colecções Simples

```php
$users = [
    ['codempr' => 1, 'nome' => 'João', 'email' => 'joao@ex.com'],
    ['codempr' => 2, 'nome' => 'Maria', 'email' => 'maria@ex.com'],
    ['codempr' => 3, 'nome' => 'Pedro', 'email' => 'pedro@ex.com'],
];

$result = UserOutputDTO::collection($users);

// Resultado:
// [
//     ['id' => 1, 'name' => 'João', 'email' => 'joao@ex.com'],
//     ['id' => 2, 'name' => 'Maria', 'email' => 'maria@ex.com'],
//     ['id' => 3, 'name' => 'Pedro', 'email' => 'pedro@ex.com'],
// ]
```

### Dados Paginados com Metadata

```php
$paginatedData = [
    'items' => [
        ['codempr' => 1, 'nome' => 'João', 'email' => 'joao@ex.com'],
        ['codempr' => 2, 'nome' => 'Maria', 'email' => 'maria@ex.com'],
    ],
    'per_page' => 10,
    'total' => 50,
    'current_page' => 1,
    'last_page' => 5,
];

$result = UserOutputDTO::collection($paginatedData);

// Resultado:
// {
//     "items": [
//         {"id": 1, "name": "João", "email": "joao@ex.com"},
//         {"id": 2, "name": "Maria", "email": "maria@ex.com"}
//     ],
//     "meta": {
//         "current_page": 1,
//         "from": 1,
//         "last_page": 5,
//         "per_page": 10,
//         "to": 2,
//         "total": 50
//     }
// }
```

> [!NOTE]
> A detecção de paginação é automática. Basta que os dados incluam as chaves `items` (ou `data`), `per_page` e `total`.

---

## DTOs Aninhados (Nested Resources)

Para relações complexas, use DTOs aninhados com lazy loading:

```php
<?php

namespace Og\Modules\Ams\Handling\Handling\DTOs;

use Og\Modules\Common\Resource\JsonResource;

final class AmsHandlingOutputDTO extends JsonResource
{
    public function toArray(): array
    {
        return [
            'id' => $this->whenHas('id'),
            'number' => $this->whenHas('number'),
            'customer_id' => $this->whenHas('customer_id'),
            
            // Datas com Carbon
            'entry_date' => $this->whenHas('entry_date', 
                fn () => Carbon::createFromDate($this->entry_date)
            ),
            'departure_date' => $this->whenHas('departure_date', 
                fn () => Carbon::createFromDate($this->departure_date)
            ),
            
            // Relações aninhadas com lazy loading
            'customer' => $this->whenHas('customer', 
                fn () => EntitiesCustomerOutputDTO::make($this->customer)
            ),
            'airport' => $this->whenHas('airport', 
                fn () => AmsSupportAirportOutputDTO::make($this->airport)
            ),
            'aircraft' => $this->whenHas('aircraft', 
                fn () => AmsSupportHandlingAirCraftOutputDTO::make($this->aircraft)
            ),
            'status' => $this->whenHas('status', 
                fn () => AmsSupportHandlingStatusOutputDTO::make($this->status)
            ),
            
            // Múltiplas relações do mesmo tipo
            'created_by' => $this->whenHas('created_by', 
                fn () => EmployeeOutputDTO::make($this->created_by)
            ),
            'updated_by' => $this->whenHas('updated_by', 
                fn () => EmployeeOutputDTO::make($this->updated_by)
            ),
            'responsible' => $this->whenHas('responsible', 
                fn () => EmployeeOutputDTO::make($this->responsible)
            ),
            
            // Aeroportos múltiplos
            'origin_airport' => $this->whenHas('origin_airport', 
                fn () => AmsSupportAirportOutputDTO::make($this->origin_airport)
            ),
            'destination_airport' => $this->whenHas('destination_airport', 
                fn () => AmsSupportAirportOutputDTO::make($this->destination_airport)
            ),
        ];
    }
}
```

### Benefícios do Lazy Loading

1. **Performance**: DTOs aninhados são criados apenas quando o campo existe
2. **Memória**: Evita instanciação desnecessária de objectos
3. **Flexibilidade**: Permite carregar relações opcional e dinamicamente

---

## Padrões Avançados

### DTO com Normalização de Dados Legados

```php
<?php

namespace Og\Modules\Entidade\Common\DTOs;

use Og\Modules\Common\Resource\JsonResource;

class EntitiesEntityOutputDTO extends JsonResource
{
    // Propriedades tipadas para normalização
    public ?int $id = null;
    public ?string $name;
    public ?string $tax_id = null;
    public ?string $address = null;
    public ?string $city = null;
    public ?int $district_id = null;
    public ?array $district = null;
    public ?string $postal_code = null;
    public ?string $country_code = null;
    public ?array $country = null;
    public ?string $phone = null;
    public ?string $email = null;
    public ?bool $active = null;

    public function __construct(array $data)
    {
        // Normalização: suporta campos em CamelCase e snake_case
        $this->id = $data['CodTerc'] ?? $data['codterc'] ?? null;
        $this->name = $data['Nome'] ?? $data['nome'] ?? null;
        $this->tax_id = $data['NContrib'] ?? $data['ncontrib'] ?? null;
        
        $this->address = $data['Morada'] ?? $data['morada'] ?? null;
        $this->city = $data['Localidade'] ?? $data['localidade'] ?? null;
        $this->district_id = $data['Distrito'] ?? $data['distrito'] ?? null;
        $this->district = $data['district'] ?? null;
        $this->postal_code = $data['CodPostal'] ?? $data['codpostal'] ?? null;
        $this->country_code = $data['CodPais'] ?? $data['codpais'] ?? null;
        $this->country = $data['country'] ?? null;
        
        $this->phone = $data['Telefone1'] ?? $data['telefone1'] ?? null;
        $this->email = $data['EMail'] ?? $data['email'] ?? null;
        
        // Conversão de tipo com helper
        $this->active = isset($data['Activo']) 
            ? \Funcs::toBool($data['Activo'])
            : (isset($data['active']) ? \Funcs::toBool($data['active']) : null);
    }

    public function toArray(): array
    {
        return [
            'id'     => $this->whenHas('id'),
            'name'   => $this->whenHas('name'),
            'tax_id' => $this->whenHas('tax_id'),

            'address'      => $this->whenHas('address'),
            'city'         => $this->whenHas('city'),
            'district_id'  => $this->whenHas('district_id'),
            'district'     => $this->whenHas('district', 
                fn() => TablesGeographicalDistrictOutputDTO::make($this->district)
            ),
            'postal_code'  => $this->whenHas('postal_code'),
            'country_code' => $this->whenHas('country_code'),
            'country'      => $this->whenHas('country', 
                fn() => TablesGeographicalCountryOutputDTO::make($this->country)
            ),

            'phone'  => $this->whenHas('phone'),
            'email'  => $this->whenHas('email'),

            'active' => $this->whenHas('active'),
        ];
    }
}
```

### DTO com Herança

```php
<?php

namespace Og\Modules\Entidade\Customers\DTOs;

use Og\Modules\Entidade\Common\DTOs\EntitiesEntityOutputDTO;

class EntitiesCustomerOutputDTO extends EntitiesEntityOutputDTO
{
    public ?float $credit_limit = null;
    public ?string $payment_terms = null;

    public function __construct(array $data)
    {
        parent::__construct($data);
        
        $this->credit_limit = $data['LimiteCredito'] ?? $data['credit_limit'] ?? null;
        $this->payment_terms = $data['CondPagamento'] ?? $data['payment_terms'] ?? null;
    }

    public function toArray(): array
    {
        return [
            ...parent::toArray(),
            'credit_limit' => $this->whenHas('credit_limit'),
            'payment_terms' => $this->whenHas('payment_terms'),
        ];
    }
}
```

---

## Integração com Controllers

### Uso Básico em APIs

```php
<?php

namespace Og\Modules\Auth\Api\Controllers;

class UserController
{
    public function index()
    {
        $users = $this->userService->getUsers();
        return response()->json(UserOutputDTO::collection($users));
    }

    public function show(int $id)
    {
        $user = $this->userService->getUser($id);
        return response()->json(UserOutputDTO::make($user));
    }
}
```

### Com Paginação

```php
public function index(Request $request)
{
    $paginatedUsers = $this->userService->getPaginatedUsers(
        page: $request->get('page', 1),
        perPage: $request->get('per_page', 15)
    );
    
    // O método collection() detecta automaticamente paginação
    return response()->json(UserOutputDTO::collection($paginatedUsers));
}
```

### Com Status Code Customizado

```php
public function store(Request $request)
{
    $user = $this->userService->createUser($request->validated());
    
    return response()->json(
        UserOutputDTO::make($user), 
        Response::HTTP_CREATED
    );
}
```

---

## Boas Práticas

### Convenções de Nomenclatura

| Item | Convenção | Exemplo |
|------|-----------|---------|
| Nome da classe | `{Domain}{Entity}OutputDTO` | `CrmAppointmentOutputDTO` |
| Namespace | `Og\Modules\{Module}\{Domain}\DTOs` | `Og\Modules\Crm\Appointments\DTOs` |
| Campos de saída | `snake_case` | `employee_id`, `created_at` |
| Ficheiro | `{ClassName}.php` | `CrmAppointmentOutputDTO.php` |

### Checklist para Novos DTOs

- [ ] Estender `JsonResource`
- [ ] Implementar `toArray(): array`
- [ ] Usar `whenHas()` para campos opcionais
- [ ] Usar `when()` para lógica condicional
- [ ] DTOs aninhados com callbacks para lazy loading
- [ ] Validação de tipos para dados críticos
- [ ] Normalização de nomes de campos legados
- [ ] Testes unitários

### O Que Evitar

```php
// ❌ EVITAR: Acesso directo sem verificação
public function toArray(): array
{
    return [
        'name' => $this->resource['name'],  // Pode lançar erro se não existir
    ];
}

// ✅ CORRECTO: Usar whenHas() para segurança
public function toArray(): array
{
    return [
        'name' => $this->whenHas('name'),  // Retorna MissingValue se não existir
    ];
}
```

```php
// ❌ EVITAR: DTO aninhado sem lazy loading
public function toArray(): array
{
    return [
        'customer' => CustomerOutputDTO::make($this->customer),  // Falha se customer for null
    ];
}

// ✅ CORRECTO: Usar callback para lazy loading
public function toArray(): array
{
    return [
        'customer' => $this->whenHas('customer', 
            fn () => CustomerOutputDTO::make($this->customer)
        ),
    ];
}
```

---

## Referência de Ficheiros

| Ficheiro | Caminho | Descrição |
|----------|---------|-----------|
| JsonResource | [JsonResource.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/JsonResource.php) | Classe base |
| MissingValue | [MissingValue.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/MissingValue.php) | Sentinel para valores ausentes |
| TypedValue | [TypedValue.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/TypedValue.php) | Helper para validação |
| ConditionalAttributesTrait | [ConditionalAttributesTrait.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/Traits/ConditionalAttributesTrait.php) | Campos condicionais |
| DateSerializerTrait | [DateSerializerTrait.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/Traits/DateSerializerTrait.php) | Serialização de datas |
| DelegatesToResource | [DelegatesToResource.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/Traits/DelegatesToResource.php) | Delegação de propriedades |
| InteractsWithType | [InteractsWithType.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Resource/Traits/InteractsWithType.php) | Sistema de validação |

### Exemplos do Sistema

| DTO | Caminho | Padrão Demonstrado |
|-----|---------|-------------------|
| UserOutputDTO | [UserOutputDTO.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Auth/Api/DTOs/UserOutputDTO.php) | Básico |
| CrmAppointmentOutputDTO | [CrmAppointmentOutputDTO.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Crm/Appointments/DTOs/CrmAppointmentOutputDTO.php) | Constructor personalizado |
| EntitiesEntityOutputDTO | [EntitiesEntityOutputDTO.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Entidade/Common/DTOs/EntitiesEntityOutputDTO.php) | Normalização legado |
| SaftAoElectronicInvoicingDocumentOutputDTO | [SaftAoElectronicInvoicingDocumentOutputDTO.php](file:///home/paulo/www/guisoft/ogdevel/Modules/SaftAo/ElectronicInvoicing/DTOs/SaftAoElectronicInvoicingDocumentOutputDTO.php) | Dot notation |
| AmsHandlingOutputDTO | [AmsHandlingOutputDTO.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Ams/Handling/Handling/DTOs/AmsHandlingOutputDTO.php) | DTOs aninhados avançado |

---

## Conclusão

O Sistema de Resources do OfficeGest oferece uma solução robusta e flexível para transformação de dados em APIs. Os principais benefícios incluem:

- **Consistência**: Formato padronizado em todas as APIs
- **Segurança**: Validação de tipos e tratamento seguro de campos ausentes
- **Flexibilidade**: Campos condicionais e merge dinâmico
- **Performance**: Lazy loading de relações e serialização eficiente
- **Manutenibilidade**: Separação clara entre dados brutos e representação API
- **Extensibilidade**: Sistema de traits modular para funcionalidades adicionais

A arquitectura modular, baseada em padrões de design comprovados, torna o sistema adequado tanto para APIs simples como para cenários enterprise complexos com múltiplos domínios de negócio.