Trabalho final T21 Fundatec Detalhamento Parte 2

Vamos detalhar o restante do trabalho final nesse post.

Para o nossa API de estacionamento minima faltam as seguintes API's

Plano:

  • Criar um plano

  • Recarregar um plano

Tarifa

  • Calcular Tarifa

  • Entrar (entrada de veiculo)

  • Sair (Saída de Veículo)

Criando um plano

Para criar um plano basta o id do cliente e o valor do mesmo. O json pode ser algo do tipo:

{
    "idCliente": 1,
    "valor": 100
}

Poderiamos ter um POST em /api/plano para criar um novo plano.

Crie o controlador de planos e faça as anotações necessária. Faça um DTO para receber os dados do post.

Recarga do plano

A regarga do plano recebe o mesmo objeto (DTO), mas escuta no path /api/plano/recarga

Dentro do service será necessário procurar o plano pelo id do cliente usando-se o repository, se o plano existir somar o valor passado com o valor atual e salvar o objeto plano novamente.

O método no repository para buscar um plano pelo id do assinante (idCliente) seria:

    @Query("select c from Plano c join c.assinante a where a.id = :id")
    Plano findByAssinanteId(@Param("id") Long id);

Calcular uma tarifa

Para calcular tarifa podemos fazer usando o id do veiculo ou o id da entrada do veiculo. Se fizermos pelo primeiro teriamos que buscar o veiculo que possui uma entrada e não possui data de saida, pois esse veiculo está então estacionado no estacionamento. Lembre-se nossas entradas e saídas são controladas pela tabela/entidade Tarifa

Vamos ver a lógica alto nível para calcular uma tarifa pelo id do veiculo

O contrato (API) pode ser assim:

    @GetMapping(path = "/calcular")
    public CalculoTarifaDTO calcularTarifa(
            @RequestParam("idVeiculo") Long idVeiculo,
            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
            @RequestParam("saida") LocalDateTime saida
            ) {
        return service.calcularTarifa(idVeiculo, saida);
    }

Note que estamos recebendo como parametro o id e a data de saída. Para data de saída estamos dizendo ao spring qual o formato esperamos na requisição, infelizmente as formas de fazer isso globalmente não funcionaram até a presenta data. Nesse caso o formato é "2023-12-24 12:02:34"

Todos os @RequestParam se traduzem em parametros de query no insomia:

A lógica de negócio para calcular tarifa (feita no service) pode começar com varias validações:

  1. Verifica se veículo passado existe pelo seu id (findById)

  2. Verifica se uma tarifa por tipo de veiculo está cadastrada

  3. Busca a tarifa (entrada do veiculo) no banco e verifica se ela existe

Por fim o serviço passa para a tarifa os dados necessários para o calculo e retorna o resultado: A data de saída, se o cliente é assinante e as tarifas para aquele tipo

As queries que o service de tarifas tem que fazer para calcular a tarifa são as seguinte:

Busca o veiculo por seu id (os metodos findById são herdados do JpaRepository quando se cria o repository). Atentar-se ao uso do optional que nunca é nulo, ele tem metodos para verificar se o valor está vazio e o metodo get() para pegar o item opcional (no caso veiculo)

Optional<Veiculo> veiculo = veiculoRepository.findById(idVeiculo);

Busca a primeira tarifa por tipo salva no banco para um determinado tipo de veiculo (CARRO, MOTO). Note que o spring data vai gerar a query correta com base no nome do metodo

public interface TarifaPorTipoRepository extends JpaRepository<TarifaPorTipo, Long> {
    Optional<TarifaPorTipo> findFirstByTipoVeiculo(TipoVeiculo tipoVeiculo);
}

Busca a Tarifa (entrada do veiculo) dado o veiculo:

public interface TarifaRepository extends JpaRepository<Tarifa, Long> {

    Tarifa findFirstByVeiculoAndSaidaIsNull(Veiculo idVeiculo);
}

Repare que esse metodo retorna null se não encontrar registro. A query gerada pelo spring data vai verificar a tarifa para aquele veiculo tem uma saida nula (entrou e não saiu)

De posse dessas informações basta passarmos os dados para a propria tarifa retornada calcular (é possivel fazer isso no service também mas vamos separar para ter um pouco de modelos ricos)

return tarifa.calcularTarifa(saida, cliente.isAssinante(), tarifas.get());

Lembre-se: A saida foi passada pelo controlador ao chamar o service, e o metodo isAssinante vem do cliente que por sua vez vem de dentro do veiculo (o jpa vai buscar o cliente com base na relação feita entre eles @ManyToOne

Para calcular uma tarifa verifique o codigo em aulas-fundatec-estacionamento-exercicio1/Veiculo.java at master · giovannicandido/aulas-fundatec-estacionamento-exercicio1 (github.com)

A logica é a mesma porém as tarifas agora são passadas como parametro, e o tempo é calculado conforme a data de saida (a data de entrada já temos salva).

O código para calcular o tempo em minutos entre duas datas é:

long tempoEstacionamento = ChronoUnit.MINUTES.between(entrada, saida);

Não se esqueça de que se ele é um assinate ele deve ter um desconto de 15%

Entrar - Entrada de um veículo

Podemos colocar em TarifaCtrl o contrato de entrada: POST - /api/tarifa/entrar

{
    "idVeiculo": 1,
    "entrada": "2023-02-01 16:00:00",
    "nomeEstacionamento": "estacionamento1"
}

EntradaDTO

public class EntradaDTO {
    private Long idVeiculo;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime entrada;
    private String nomeEstacionamento;

    public Long getIdVeiculo() {
        return idVeiculo;
    }

    public void setIdVeiculo(Long idVeiculo) {
        this.idVeiculo = idVeiculo;
    }

    public LocalDateTime getEntrada() {
        return entrada;
    }

    public void setEntrada(LocalDateTime entrada) {
        this.entrada = entrada;
    }

    public String getNomeEstacionamento() {
        return nomeEstacionamento;
    }

    public void setNomeEstacionamento(String nomeEstacionamento) {
        this.nomeEstacionamento = nomeEstacionamento;
    }
}

Para fazer a entrada no serviço, podemos verificar:

  1. Se o veiculo existe

  2. Se o estacionamento existe pelo nome (estacionamentoRepository.findById)

  3. Se o veiculo já possui uma entrada pois ele não pode entrar duas vezes (método findFirstByVeiculoAndSaidaIsNull listado acima)

Caso esses items passem na validação crie uma tarifa sete os dados de veiculo, entrada com a hora local (LocalDateTime.now()) e salve.

Sair - Saida de veiculo

A saida do veiculo segue o contrato: POST - /api/tarifa/saida

{
    "idVeiculo": 1,
    "saida": "2023-02-01 17:00:00",
    "valorPago": 10.00
}

Com SaidaDTO:

public class SaidaDTO {
    private Long idVeiculo;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime saida;
    private Double valorPago;

    public Long getIdVeiculo() {
        return idVeiculo;
    }

    public void setIdVeiculo(Long idVeiculo) {
        this.idVeiculo = idVeiculo;
    }

    public LocalDateTime getSaida() {
        return saida;
    }

    public void setSaida(LocalDateTime saida) {
        this.saida = saida;
    }

    public Double getValorPago() {
        return valorPago;
    }

    public void setValorPago(Double valorPago) {
        this.valorPago = valorPago;
    }
}

Para calcular a saida podemos validar:

  1. Se o veiculo existe

  2. Se existe uma entrada ou seja se o metodo tarifaRepository.findFirstByVeiculoAndSaidaIsNull(veiculo.get()) retorna alguma tarifa

Dai podemos salvar a Tarifa localizada setando-se o valor pago.

Até aqui está conforme o modelo de dados apresentado na sala de aula.

Um incremento que seria interessante é o seguinte:

Alterar o modelo de Tarifa para conter qual a tarifa calculada no momento da saida (para fins históricos) e se teve desconto ou não:

  private Double taxaCobrada;
  private Boolean comDesconto;

Dai antes de salvar calcular a tarifa novamente e salvar, deve-se reaproveitar o metodo do service para calcular a tarifa.

Agora como saber se teve descontro ou não?

Para isso é possível retonar em calcularTarifa um DTO com o resultado e um booleano para saber se a tarifa deve o desconto.

Confira o arquivo API do insomia em: https://github.com/giovannicandido/fundatec-trabalho-final/raw/59f92a3eadf25be49ccec58a46f0955e98e474ab/api%20trabalho%20final.json

Caso o insomia não importe a API use o postman com esse arquivo: https://github.com/giovannicandido/fundatec-trabalho-final/raw/main/TFinal.postman_collection.json