Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ Os status disponíveis são `ABERTA`, `EM_ANDAMENTO`, `CONCLUIDA` e
}
```

Transições permitidas:

- `ABERTA` → `EM_ANDAMENTO` ou `CANCELADA`;
- `EM_ANDAMENTO` → `CONCLUIDA` ou `CANCELADA`;
- `CONCLUIDA` e `CANCELADA` são estados finais.

Uma transição não permitida retorna `409 Conflict`.

Cada ordem também informa `criadaEm` e `atualizadaEm` em UTC. Esses campos
são preenchidos automaticamente e não precisam ser enviados nas requisições.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,14 @@ public ProblemDetail tratarValidacao(
problema.setProperty("campos", campos);
return problema;
}

@ExceptionHandler(TransicaoStatusInvalidaException.class)
public ProblemDetail tratarTransicaoStatusInvalida(
TransicaoStatusInvalidaException exception) {
ProblemDetail problema = ProblemDetail.forStatusAndDetail(
HttpStatus.CONFLICT,
exception.getMessage());
problema.setTitle("Transição de status inválida");
return problema;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.exemplo.ordem_servico.exception;

import com.exemplo.ordem_servico.model.StatusOrdemServico;

public class TransicaoStatusInvalidaException extends RuntimeException {

public TransicaoStatusInvalidaException(
StatusOrdemServico statusAtual,
StatusOrdemServico novoStatus) {
super("Não é permitido alterar o status de "
+ statusAtual
+ " para "
+ novoStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,19 @@ public enum StatusOrdemServico {
ABERTA,
EM_ANDAMENTO,
CONCLUIDA,
CANCELADA
CANCELADA;

public boolean podeTransicionarPara(StatusOrdemServico proximoStatus) {
if (this == proximoStatus) {
return true;
}

return switch (this) {
case ABERTA -> proximoStatus == EM_ANDAMENTO
|| proximoStatus == CANCELADA;
case EM_ANDAMENTO -> proximoStatus == CONCLUIDA
|| proximoStatus == CANCELADA;
case CONCLUIDA, CANCELADA -> false;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.stereotype.Service;

import com.exemplo.ordem_servico.exception.OrdemServicoNaoEncontradaException;
import com.exemplo.ordem_servico.exception.TransicaoStatusInvalidaException;
import com.exemplo.ordem_servico.model.OrdemServico;
import com.exemplo.ordem_servico.model.StatusOrdemServico;
import com.exemplo.ordem_servico.repository.OrdemServicoRepository;
Expand Down Expand Up @@ -53,6 +54,13 @@ public OrdemServico atualizar(Long id, OrdemServico novosDados) {

public OrdemServico atualizarStatus(Long id, StatusOrdemServico status) {
OrdemServico ordem = buscarPorId(id);
StatusOrdemServico statusAtual = ordem.getStatus();
if (!statusAtual.podeTransicionarPara(status)) {
throw new TransicaoStatusInvalidaException(statusAtual, status);
}
if (statusAtual == status) {
return ordem;
}
ordem.setStatus(status);
return repository.save(ordem);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;

import com.exemplo.ordem_servico.model.StatusOrdemServico;

class ApiExceptionHandlerTests {

private final ApiExceptionHandler handler = new ApiExceptionHandler();
Expand Down Expand Up @@ -48,4 +50,18 @@ void deveInformarCamposComErroDeValidacao() {
Map.of("cliente", "O nome do cliente é obrigatório!"),
problema.getProperties().get("campos"));
}

@Test
void devePadronizarErroDeTransicaoDeStatus() {
ProblemDetail problema = handler.tratarTransicaoStatusInvalida(
new TransicaoStatusInvalidaException(
StatusOrdemServico.ABERTA,
StatusOrdemServico.CONCLUIDA));

assertEquals(HttpStatus.CONFLICT.value(), problema.getStatus());
assertEquals("Transição de status inválida", problema.getTitle());
assertEquals(
"Não é permitido alterar o status de ABERTA para CONCLUIDA",
problema.getDetail());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.mockito.junit.jupiter.MockitoExtension;

import com.exemplo.ordem_servico.exception.OrdemServicoNaoEncontradaException;
import com.exemplo.ordem_servico.exception.TransicaoStatusInvalidaException;
import com.exemplo.ordem_servico.model.OrdemServico;
import com.exemplo.ordem_servico.model.StatusOrdemServico;
import com.exemplo.ordem_servico.repository.OrdemServicoRepository;
Expand Down Expand Up @@ -114,12 +115,25 @@ void deveAtualizarStatusDaOrdem() {

OrdemServico atualizada = service.atualizarStatus(
1L,
StatusOrdemServico.CONCLUIDA);
StatusOrdemServico.EM_ANDAMENTO);

assertEquals(StatusOrdemServico.CONCLUIDA, atualizada.getStatus());
assertEquals(StatusOrdemServico.EM_ANDAMENTO, atualizada.getStatus());
verify(repository).save(ordem);
}

@Test
void deveRecusarTransicaoDeStatusInvalida() {
OrdemServico ordem = novaOrdem("Cliente", "Servico", "1", "0");
ordem.setId(1L);
when(repository.findById(1L)).thenReturn(Optional.of(ordem));

assertThrows(
TransicaoStatusInvalidaException.class,
() -> service.atualizarStatus(
1L,
StatusOrdemServico.CONCLUIDA));
}

private OrdemServico novaOrdem(
String cliente,
String descricao,
Expand Down
Loading