---
# System prepended metadata

title: Histórico para PreAtendimento

---

### Histórico para PreAtendimento

Aqui está uma implementação completa de auditoria manual para a entidade `PreAtendimento`, seguindo a arquitetura hexagonal (Ports & Adapters).

---

### 1. Esquema SQL das Tabelas

```sql
-- Tabela de Revisões (equivalente à REVINFO)
-- Sintaxe para InterSystems IRIS/Caché
CREATE TABLE preatendimento.REVINFO (
    REV      BIGINT NOT NULL,
    REVTSTMP TIMESTAMP NOT NULL,
    Usuario  VARCHAR(100) NULL,
    CONSTRAINT PK_REVINFO PRIMARY KEY (REV)
);

-- Tabela de Auditoria do PreAtendimento (<ENTITY>_AUD)
CREATE TABLE preatendimento.PreAtendimento_AUD (
    ID                  BIGINT   NOT NULL,
    REV                 BIGINT   NOT NULL,
    REVTYPE             VARCHAR(50) NOT NULL, -- INSERT, UPDATE, DELETE
    IDExterno           VARCHAR(255) NULL,
    Status              VARCHAR(50) NULL,
    TelefoneOrigem      VARCHAR(50) NULL,
    TelefoneLaboratorio VARCHAR(50) NULL,
    DataCriacao         TIMESTAMP NULL,
    DataAtualizacao     TIMESTAMP NULL,
    PacienteId          BIGINT NULL,
    CONSTRAINT PK_PreAtendimento_AUD PRIMARY KEY (ID, REV),
    CONSTRAINT FK_PreAtendimento_AUD_REVINFO FOREIGN KEY (REV) REFERENCES preatendimento.REVINFO (REV)
);
```

---

### 2. Modelos de Domínio

#### 2.1 RevInfo (Domínio)

```java
package br.com.shift.preatendimento.core.domain.model;

import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class RevInfo {

  private Long rev;
  private LocalDateTime revtstmp;
  private String usuario;
}
```

#### 2.2 PreAtendimentoAudId (Domínio)

```java
public class PreAtendimentoAudId implements Serializable {

  @Column(name = "ID")
  private Long id;

  @Column(name = "REV")
  private Long rev;
}
```

#### 2.3 PreAtendimentoAud (Domínio)

```java
public class PreAtendimentoAud {

  private PreAtendimentoAudId id;

  private RevType revType;

  private String idExterno;

  private StatusEnum status;

  private String telefoneOrigem;

  private String telefoneLaboratorio;

  private LocalDateTime dataCriacao;

  private LocalDateTime dataAtualizacao;

  private Long pacienteId;

  private RevInfo revInfo;

  public static PreAtendimentoAud criar(PreAtendimento preAtendimento,
                                        RevInfo revInfo) {
    return audit(preAtendimento, revInfo, RevType.INSERT);
  }

  public static PreAtendimentoAud atualizar(PreAtendimento preAtendimento,
                                        RevInfo revInfo) {
    return audit(preAtendimento, revInfo, RevType.UPDATE);
  }

  public static PreAtendimentoAud remover(PreAtendimento preAtendimento,
                                            RevInfo revInfo) {
    return audit(preAtendimento, revInfo, RevType.DELETE);
  }

  public static PreAtendimentoAud audit(PreAtendimento preAtendimento, RevInfo revInfo, RevType revType) {
    return PreAtendimentoAud.builder()
        .id(PreAtendimentoAudId.builder().id(preAtendimento.getId()).rev(revInfo.getRev()).build())
        .revType(revType).idExterno(preAtendimento.getIdExterno()).status(preAtendimento.getStatus())
        .telefoneOrigem(preAtendimento.getTelefoneOrigem()).telefoneLaboratorio(preAtendimento.getTelefoneLaboratorio())
        .dataCriacao(preAtendimento.getDataCriacao()).dataAtualizacao(preAtendimento.getDataAtualizacao())
        .pacienteId(preAtendimento.getPacienteId()).revInfo(revInfo).build();
  }

}
```

---

### 3. Enum RevType

```java
public enum RevType {

  INSERT,
  UPDATE,
  DELETE

}
```

---

### 4. Entidades JPA (Adapter Out)

#### 4.1 Classe de Chave Composta (PreAtendimentoAudIdEntity)

```java
public class PreAtendimentoAudIdEntity implements Serializable {

  @Column(name = "ID")
  private Long id;

  @Column(name = "REV")
  private Long rev;

  public static PreAtendimentoAudIdEntity fromDomain(PreAtendimentoAudId preAtendimentoAudId) {
    return new PreAtendimentoAudIdEntity(preAtendimentoAudId.getId(), preAtendimentoAudId.getRev());
  }

  public PreAtendimentoAudId toDomain() {
    return new PreAtendimentoAudId(this.id, this.rev);
  }

}
```

#### 4.2 Entidade de Auditoria (PreAtendimentoAudEntity)

```java
public class PreAtendimentoAudEntity {

  @EmbeddedId
  private PreAtendimentoAudIdEntity id;

  @Column(name = "REVTYPE")
  @Enumerated(EnumType.STRING)
  private RevType revType;

  @Column(name = "IDExterno")
  private String idExterno;

  @Column(name = "Status")
  @Enumerated(EnumType.STRING)
  private StatusEnum status;

  @Column(name = "TelefoneOrigem")
  private String telefoneOrigem;

  @Column(name = "TelefoneLaboratorio")
  private String telefoneLaboratorio;

  @Column(name = "DataCriacao")
  private LocalDateTime dataCriacao;

  @Column(name = "DataAtualizacao")
  private LocalDateTime dataAtualizacao;

  @Column(name = "PacienteId")
  private Long pacienteId;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "REV", insertable = false, updatable = false,
      foreignKey = @ForeignKey(name = "FK_PreAtendimento_AUD_REVINFO"))
  private RevInfoEntity revInfo;

  public static PreAtendimentoAudEntity fromDomain(PreAtendimentoAud preAtendimentoAud) {
    return PreAtendimentoAudEntity.builder()
        .id(PreAtendimentoAudIdEntity.fromDomain(preAtendimentoAud.getId()))
        .revType(preAtendimentoAud.getRevType())
        .idExterno(preAtendimentoAud.getIdExterno())
        .status(preAtendimentoAud.getStatus())
        .telefoneOrigem(preAtendimentoAud.getTelefoneOrigem())
        .telefoneLaboratorio(preAtendimentoAud.getTelefoneLaboratorio())
        .dataCriacao(preAtendimentoAud.getDataCriacao())
        .dataAtualizacao(preAtendimentoAud.getDataAtualizacao())
        .pacienteId(preAtendimentoAud.getPacienteId())
        .build();
  }

  public PreAtendimentoAud toDomain() {
    return PreAtendimentoAud.builder()
        .id(this.id.toDomain())
        .revType(this.revType)
        .idExterno(this.idExterno)
        .status(this.status)
        .telefoneOrigem(this.telefoneOrigem)
        .telefoneLaboratorio(this.telefoneLaboratorio)
        .dataCriacao(this.dataCriacao)
        .dataAtualizacao(this.dataAtualizacao)
        .pacienteId(this.pacienteId)
        .build();
  }
}
```

#### 4.3 Entidade RevInfo (RevInfoEntity)

```java
public class RevInfoEntity {

  @Id
  @Column(name = "REV")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long rev;

  @CreationTimestamp
  @Column(name = "REVTSTMP")
  private LocalDateTime revtstmp;

  @Column(name = "Usuario")
  private String usuario;

  public static RevInfoEntity fromDomain(RevInfo revInfo) {
    return RevInfoEntity.builder().rev(revInfo.getRev()).revtstmp(revInfo.getRevtstmp()).usuario(revInfo.getUsuario())
        .build();
  }

  public RevInfo toDomain() {
    return RevInfo.builder().rev(this.rev).revtstmp(this.revtstmp).usuario(this.usuario).build();
  }

}
```

---

### 5. Ports (Interfaces de Saída)

#### 5.1 RevInfoRepositoryPort

```java
public interface RevInfoRepositoryPort {

  RevInfo salvar(RevInfo revInfo);

}
```

#### 5.2 PreAtendimentoAudRepositoryPort

```java
public interface PreAtendimentoAudRepositoryPort {

  void persistir(PreAtendimentoAud preAtendimentoAud);

}
```

---

### 6. Adapters (Implementações dos Ports)

#### 6.1 RevInfoAdapter

```java
@ApplicationScoped
public class RevInfoAdapter implements RevInfoRepositoryPort, PanacheRepositoryBase<RevInfoEntity, Long> {

  @Override
  public RevInfo salvar(RevInfo revInfo) {
    RevInfoEntity entity = RevInfoEntity.fromDomain(revInfo);
    persist(entity);
    return entity.toDomain();
  }

}
```

#### 6.2 PreAtendimentoAudDbAdapter

```java
@ApplicationScoped
public class PreAtendimentoAudDbAdapter implements PreAtendimentoAudRepositoryPort, PanacheRepositoryBase<PreAtendimentoAudEntity, PreAtendimentoAudIdEntity> {

  public List<PreAtendimentoAud> findById(Long entityId) {
    return list("id.id", entityId).stream().map(PreAtendimentoAudEntity::toDomain).toList();
  }

  public List<PreAtendimentoAud> findByRev(Long rev) {
    return list("id.rev", rev).stream().map(PreAtendimentoAudEntity::toDomain).toList();
  }

  public List<PreAtendimentoAud> findByIdOrderByRevDesc(Long entityId) {
    return list("id.id = ?1 ORDER BY id.rev DESC", entityId).stream().map(PreAtendimentoAudEntity::toDomain).toList();
  }

  @Override
  public void persistir(PreAtendimentoAud preAtendimento) {
    persist(PreAtendimentoAudEntity.fromDomain(preAtendimento));
  }

}
```

---

### 7. AuditService (Serviço de Aplicação)

```java
@ApplicationScoped
public class PreAtendimentoAuditService {

  @Inject
  RevInfoRepositoryPort revInfoRepository;

  @Inject
  PreAtendimentoAudRepositoryPort preAtendimentoAudRepository;

  @Transactional
  public void criar(PreAtendimento preAtendimento, String usuario) {
    RevInfo revInfo = revInfoRepository.salvar(RevInfo.builder().usuario(usuario).build());
    PreAtendimentoAud preAtendimentoAud = PreAtendimentoAud.criar(preAtendimento, revInfo);
    preAtendimentoAudRepository.persistir(preAtendimentoAud);
  }

  @Transactional
  public void atualizar(PreAtendimento preAtendimento, String usuario) {
    RevInfo revInfo = revInfoRepository.salvar(RevInfo.builder().usuario(usuario).build());
    PreAtendimentoAud preAtendimentoAud = PreAtendimentoAud.atualizar(preAtendimento, revInfo);
    preAtendimentoAudRepository.persistir(preAtendimentoAud);
  }

  @Transactional
  public void remover(PreAtendimento preAtendimento, String usuario) {
    RevInfo revInfo = revInfoRepository.salvar(RevInfo.builder().usuario(usuario).build());
    PreAtendimentoAud preAtendimentoAud = PreAtendimentoAud.remover(preAtendimento, revInfo);
    preAtendimentoAudRepository.persistir(preAtendimentoAud);
  }

}
```

---

### 8. Exemplo de Uso no Serviço de Negócio (PreAtendimentoService)

```java
@Slf4j
@ApplicationScoped
public class PreAtendimentoService implements IdentificacaoUseCase, ConfirmacaoPreAtendimentoUseCase {

  @Inject
  PreAtendimentoRepositoryPort preAtendimentoRepositoryPort;

  @Inject
  PacienteRepositoryPort pacienteRepositoryPort;

  @Inject
  PreAtendimentoAuditService preAtendimentoAuditService;

  @Override
  @WithSpan
  public void registrarIdentificacao(IdentificacaoPreAtendimento identificacaoPreAtendimento) {
    Optional<Long> pacienteId = pacienteRepositoryPort
        .findByDocumentoIdentificacaoAndDataNascimento(
            identificacaoPreAtendimento.getDocumentoIdentificacaoPaciente(),
            identificacaoPreAtendimento.getDataNascimentoPaciente());
    LocalDateTime dataHoraAgora = LocalDateTime.now();
    PreAtendimento preAtendimento =
        PreAtendimento.criar(identificacaoPreAtendimento, pacienteId, dataHoraAgora);
    preAtendimentoRepositoryPort.salvarPreAtendimento(preAtendimento);
    preAtendimentoAuditService.criar(preAtendimento, "username");
  }

  @Override
  @WithSpan
  @Transactional
  public void confirmar(String idExterno) {
    PreAtendimento preAtendimento = buscarPorIdExterno(idExterno);
    preAtendimento.confirmar();
    preAtendimentoRepositoryPort.atualizar(preAtendimento);
    preAtendimentoAuditService.atualizar(preAtendimento, "username");
  }

  private PreAtendimento buscarPorIdExterno(String idExterno) {
    return preAtendimentoRepositoryPort
        .buscarPorIdExterno(idExterno)
        .orElseThrow(() -> {
          log.error("Pré-atendimento não encontrado pelo idExterno: {}", idExterno);
          return new PreAtendimentoInexistenteException();
        });
  }

}
```

---

### 9. Como Estender para Outras Entidades

Para adicionar auditoria a uma nova entidade (ex: `Paciente`), siga estes passos:

#### Passo 1: Criar a tabela de auditoria SQL

```sql
CREATE TABLE preatendimento.Paciente_AUD (
    ID                  BIGINT   NOT NULL,
    REV                 BIGINT   NOT NULL,
    REVTYPE             VARCHAR(50) NOT NULL, -- INSERT, UPDATE, DELETE
    -- Adicione aqui as colunas da entidade Paciente
    Nome VARCHAR(255) NULL,
    CPF VARCHAR(14) NULL,
    DataNascimento DATE NULL,
    -- ... outras colunas
    CONSTRAINT PK_Paciente_AUD PRIMARY KEY (ID, REV),
    CONSTRAINT FK_Paciente_AUD_REVINFO FOREIGN KEY (REV) REFERENCES preatendimento.REVINFO(REV)
);
```

#### Passo 2: Criar os modelos de domínio

```java
// PacienteAudId.java (domínio)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Builder(toBuilder = true)
public class PacienteAudId implements Serializable {
    private Long id;
    private Long rev;
}

// PacienteAud.java (domínio)
@Data
@Builder(toBuilder = true)
public class PacienteAud {
    private PacienteAudId id;
    private RevType revType;
    private String nome;
    private String cpf;
    private LocalDate dataNascimento;
    private RevInfo revInfo;

    public static PacienteAud criar(Paciente paciente, RevInfo revInfo) {
        return audit(paciente, revInfo, RevType.INSERT);
    }

    public static PacienteAud atualizar(Paciente paciente, RevInfo revInfo) {
        return audit(paciente, revInfo, RevType.UPDATE);
    }

    public static PacienteAud remover(Paciente paciente, RevInfo revInfo) {
        return audit(paciente, revInfo, RevType.DELETE);
    }

    private static PacienteAud audit(Paciente paciente, RevInfo revInfo, RevType revType) {
        return PacienteAud.builder()
            .id(PacienteAudId.builder().id(paciente.getId()).rev(revInfo.getRev()).build())
            .revType(revType)
            .nome(paciente.getNome())
            .cpf(paciente.getCpf())
            .dataNascimento(paciente.getDataNascimento())
            .revInfo(revInfo)
            .build();
    }
}
```

#### Passo 3: Criar as entidades JPA

```java
// PacienteAudIdEntity.java
@Embeddable
public class PacienteAudIdEntity implements Serializable {
    @Column(name = "ID")
    private Long id;
    
    @Column(name = "REV")
    private Long rev;

    public static PacienteAudIdEntity fromDomain(PacienteAudId pacienteAudId) {
        return new PacienteAudIdEntity(pacienteAudId.getId(), pacienteAudId.getRev());
    }

    public PacienteAudId toDomain() {
        return new PacienteAudId(this.id, this.rev);
    }
}

// PacienteAudEntity.java
@Entity
@Table(schema = "preatendimento", name = "Paciente_AUD")
public class PacienteAudEntity {
    @EmbeddedId
    private PacienteAudIdEntity id;
    
    @Column(name = "REVTYPE")
    @Enumerated(EnumType.STRING)
    private RevType revType;
    
    private String nome;
    private String cpf;
    private LocalDate dataNascimento;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "REV", insertable = false, updatable = false)
    private RevInfoEntity revInfo;
    
    public static PacienteAudEntity fromDomain(PacienteAud pacienteAud) {
        // ... conversão de domínio para entidade
    }

    public PacienteAud toDomain() {
        // ... conversão de entidade para domínio
    }
}
```

#### Passo 4: Criar o Port (interface)

```java
public interface PacienteAudRepositoryPort {
    void persistir(PacienteAud pacienteAud);
}
```

#### Passo 5: Criar o Adapter (implementação)

```java
@ApplicationScoped
public class PacienteAudDbAdapter implements PacienteAudRepositoryPort, PanacheRepositoryBase<PacienteAudEntity, PacienteAudIdEntity> {
    
    public List<PacienteAud> findById(Long entityId) {
        return list("id.id", entityId).stream().map(PacienteAudEntity::toDomain).toList();
    }

    @Override
    public void persistir(PacienteAud pacienteAud) {
        persist(PacienteAudEntity.fromDomain(pacienteAud));
    }
}
```

#### Passo 6: Criar o AuditService específico

```java
@ApplicationScoped
public class PacienteAuditService {

    @Inject
    RevInfoRepositoryPort revInfoRepository;

    @Inject
    PacienteAudRepositoryPort pacienteAudRepository;

    @Transactional
    public void criar(Paciente paciente, String usuario) {
        RevInfo revInfo = revInfoRepository.salvar(RevInfo.builder().usuario(usuario).build());
        PacienteAud pacienteAud = PacienteAud.criar(paciente, revInfo);
        pacienteAudRepository.persistir(pacienteAud);
    }

    @Transactional
    public void atualizar(Paciente paciente, String usuario) {
        RevInfo revInfo = revInfoRepository.salvar(RevInfo.builder().usuario(usuario).build());
        PacienteAud pacienteAud = PacienteAud.atualizar(paciente, revInfo);
        pacienteAudRepository.persistir(pacienteAud);
    }

    @Transactional
    public void remover(Paciente paciente, String usuario) {
        RevInfo revInfo = revInfoRepository.salvar(RevInfo.builder().usuario(usuario).build());
        PacienteAud pacienteAud = PacienteAud.remover(paciente, revInfo);
        pacienteAudRepository.persistir(pacienteAud);
    }
}
```

#### Passo 7: Chamar no serviço de negócio

```java
@Transactional
public Paciente criar(Paciente paciente, String usuario) {
    pacienteRepositoryPort.salvar(paciente);
    pacienteAuditService.criar(paciente, usuario);
    return paciente;
}
```

---

### Exemplos de Saída JSON

#### Exemplo 1: RevInfo

```json
{
  "rev": 1,
  "revtstmp": "2025-12-10T01:00:00",
  "usuario": "admin"
}
```

#### Exemplo 2: PreAtendimentoAudId

```json
{
  "id": 100,
  "rev": 1
}
```

#### Exemplo 3: PreAtendimentoAud (INSERT)

```json
{
  "id": {
    "id": 100,
    "rev": 1
  },
  "revType": "INSERT",
  "idExterno": "ext-12345",
  "status": "PENDENTE",
  "telefoneOrigem": "11999998888",
  "telefoneLaboratorio": "1133334444",
  "dataCriacao": "2025-12-10T01:00:00",
  "dataAtualizacao": "2025-12-10T01:00:00",
  "pacienteId": 50,
  "revInfo": {
    "rev": 1,
    "revtstmp": "2025-12-10T01:00:00",
    "usuario": "admin"
  }
}
```

#### Exemplo 4: PreAtendimentoAud (UPDATE)

```json
{
  "id": {
    "id": 100,
    "rev": 2
  },
  "revType": "UPDATE",
  "idExterno": "ext-12345",
  "status": "CONFIRMADO",
  "telefoneOrigem": "11999998888",
  "telefoneLaboratorio": "1133334444",
  "dataCriacao": "2025-12-10T01:00:00",
  "dataAtualizacao": "2025-12-10T01:05:00",
  "pacienteId": 50,
  "revInfo": {
    "rev": 2,
    "revtstmp": "2025-12-10T01:05:00",
    "usuario": "admin"
  }
}
```

#### Exemplo 5: Lista de Histórico (obterHistorico)

Retorno do método `obterHistorico(Long entityId)` ordenado por revisão decrescente:

```json
[
  {
    "id": {
      "id": 100,
      "rev": 2
    },
    "revType": "UPDATE",
    "idExterno": "ext-12345",
    "status": "CONFIRMADO",
    "telefoneOrigem": "11999998888",
    "telefoneLaboratorio": "1133334444",
    "dataCriacao": "2025-12-10T01:00:00",
    "dataAtualizacao": "2025-12-10T01:05:00",
    "pacienteId": 50,
    "revInfo": {
      "rev": 2,
      "revtstmp": "2025-12-10T01:05:00",
      "usuario": "admin"
    }
  },
  {
    "id": {
      "id": 100,
      "rev": 1
    },
    "revType": "INSERT",
    "idExterno": "ext-12345",
    "status": "PENDENTE",
    "telefoneOrigem": "11999998888",
    "telefoneLaboratorio": "1133334444",
    "dataCriacao": "2025-12-10T01:00:00",
    "dataAtualizacao": "2025-12-10T01:00:00",
    "pacienteId": 50,
    "revInfo": {
      "rev": 1,
      "revtstmp": "2025-12-10T01:00:00",
      "usuario": "admin"
    }
  }
]
```

---

### Resumo da Estrutura de Pastas

```
src/main/java/br/com/shift/preatendimento/core/
├── adapter/out/db/
│   ├── entity/
│   │   ├── PreAtendimentoEntity.java
│   │   ├── PreAtendimentoAudEntity.java
│   │   ├── PreAtendimentoAudIdEntity.java
│   │   └── RevInfoEntity.java
│   ├── PreAtendimentoAudDbAdapter.java
│   └── RevInfoAdapter.java
├── application/
│   ├── port/
│   │   └── out/
│   │       ├── PreAtendimentoAudRepositoryPort.java
│   │       └── RevInfoRepositoryPort.java
│   └── service/
│       ├── PreAtendimentoService.java
│       └── PreAtendimentoAuditService.java
└── domain/model/
    ├── PreAtendimento.java
    ├── PreAtendimentoAud.java
    ├── PreAtendimentoAudId.java
    ├── RevInfo.java
    └── enums/
        ├── StatusEnum.java
        └── RevType.java

src/main/resources/db/migrations/
└── 8.6/
    ├── PROD-91621-create-table-revinfo.sql
    └── PROD-91621-create-table-preatendimento_aud.sql
```

---

### Vantagens desta Abordagem

1. **Arquitetura Hexagonal**: Separação clara entre domínio, aplicação e infraestrutura
2. **Controle total**: Você decide exatamente quando e o que auditar
3. **Flexibilidade**: Pode adicionar campos extras (usuário, IP, etc.)
4. **Performance**: Sem overhead de interceptors automáticos
5. **Testabilidade**: Fácil de mockar os ports para testes unitários
6. **Desacoplamento**: O domínio não conhece detalhes de persistência

### Desvantagens

1. **Código manual**: Precisa lembrar de chamar o audit em cada operação
2. **Manutenção**: Ao alterar a entidade, deve alterar também a tabela _AUD e os modelos de domínio/entidade

---

Este código está pronto para ser usado no seu projeto Quarkus com Panache seguindo arquitetura hexagonal!
