Solucionado (ver solução)
Solucionado
(ver solução)
5
respostas

Duvida em API

Na minha API tenho entidades 'Receita' e 'Despesa' com controllers, repository, services e records e suas respectivas tabelas no DB.

Nesses controllers tenho o CRUD completo de cada uma delas, e uma requisição mais específica que é para buscar receitas/despesas com base no ano e mês.

@GetMapping("/{ano}/{mes}") public List findByData(@PathVariable("ano") String ano, @PathVariable("mes") String mes) {
    List<Receita> date = receitasService.findByData(ano, mes);
    return date;

}

Agora preciso criar um novo endpoint para /resumo/{ano}/{mes}. que retorna:

Valor total das receitas no mês
Valor total das despesas no mês
Saldo final no mês
Valor total gasto no mês em cada uma das categorias

Mas o que pensei foi que não pode estar em um dos meus dois controllers pois está relacionado as duas entidades e não só a uma. Sendo assim eu teria que criar outro controller, certo? Isso quer dizer outra entidade com repositórios, services e etc também?

Pensando nisso criei a entidade ResumoMensal, criei os controlllers e etc mas recebo ** Column 'id' not found** como erro.

Estou no caminho certo? Devo criar mesmo uma entidade e fazer esses relacionamentos OneToOne como fiz ? Ou devo criar uma classe DAO e usar jpql, entity manager etc?

Já tenho ideia da minha query:

```
@Query(value = "SELECT SUM(r.valor) AS total_receitas, SUM(d.valor) AS total_despesas, SUM(r.valor) - SUM(d.valor) AS saldo_final" +
        " FROM receitas r, despesas d WHERE d.ano = :ano and d.mes = :mes GROUP BY d.categoria", nativeQuery = true)
```
mas implementando ela num repository assim recebo erro.. não sei se deveria ir para o caminho de JPQL.

Devo mesmo ter uma terceira entidade relacionada as outras duas?

Como posso prosseguir??

package br.com.rfapi.rendafamiliarapi.model;

import br.com.rfapi.rendafamiliarapi.controller.ReceitasController;
import br.com.rfapi.rendafamiliarapi.service.DadosCadastraisReceitas;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import javax.validation.valueextraction.ExtractedValue;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.util.Date;

@Table(name = "receitas")
@Entity(name = "Receita")
@Setter
@Getter
@NoArgsConstructor
@EqualsAndHashCode(of = "receita_id")
public class Receita {

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

    private String descricao;
    private String valor;

    @JsonFormat(pattern = "dd/MM/yyyy")
    private LocalDate data;


    @JsonIgnore
    private String ano;
    @JsonIgnore
    private String mes;



@OneToOne
@JoinColumn(name = "resumo_mensal_id")
private ResumoMensal resumoMensal;

    public Receita(DadosCadastraisReceitas dados) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        this.descricao = dados.descricao();
        this.valor = dados.valor();
        this.data = dados.data();
        this.ano = dados.ano();
        this.mes = dados.mes();
    }

    public Receita(Receita receita) {

    }
}
```package br.com.rfapi.rendafamiliarapi.model;

import br.com.rfapi.rendafamiliarapi.controller.ReceitasController; import br.com.rfapi.rendafamiliarapi.infra.Categoria; import br.com.rfapi.rendafamiliarapi.service.DadosCadastraisDespesas; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*;

import javax.persistence.*; import java.time.LocalDate;

@Table(name = "despesas") @Getter @Setter @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(of = "despesa_id") @Entity(name = "Despesa") public class Despesa {

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


private String descricao;
private String valor;
@JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate data;

@Enumerated(EnumType.STRING)
private Categoria categoria;


@JsonIgnore
private String ano;

@JsonIgnore
private String mes;


@ManyToOne
@JoinColumn(name = "resumo_mensal_id")
private ResumoMensal resumoMensal;

public Despesa(DadosCadastraisDespesas dados) {
    this.descricao = dados.descricao();
    this.valor = dados.valor();
    this.data = dados.data();
    this.categoria = dados.categoria();
    this.ano = dados.ano();
    this.mes = dados.mes();
}

}

`

5 respostas

` package br.com.rfapi.rendafamiliarapi.controller;

import br.com.rfapi.rendafamiliarapi.infra.repo.ResumoRepository; //import br.com.rfapi.rendafamiliarapi.model.Resumo; import br.com.rfapi.rendafamiliarapi.model.ResumoMensal; //import br.com.rfapi.rendafamiliarapi.service.ResumoService; import br.com.rfapi.rendafamiliarapi.service.ResumoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

import java.util.List; @RestController @RequestMapping("resumo") public class ResumoMensalController {

@Autowired
private ResumoRepository repository;
private ResumoService resumoService;

public ResumoMensalController(ResumoService resumoService) {
    this.resumoService = resumoService;
}

@GetMapping("/{ano}/{mes}")
public List<ResumoMensal> resumoDoMes(@PathVariable("ano") String ano, @PathVariable("mes") String mes) {

    List<ResumoMensal> resumo = resumoService.fazerResumo(ano, mes);
    return resumo;

}

}


 ```package br.com.rfapi.rendafamiliarapi.controller;


import br.com.rfapi.rendafamiliarapi.infra.exceptions.ReceitaNaoEncontradaException;
import br.com.rfapi.rendafamiliarapi.infra.repo.ReceitasCustomRepository;
import br.com.rfapi.rendafamiliarapi.infra.repo.ReceitasRepository;
import br.com.rfapi.rendafamiliarapi.model.Receita;
import br.com.rfapi.rendafamiliarapi.service.DadosCadastraisReceitas;
import br.com.rfapi.rendafamiliarapi.service.DadosListagemReceita;
import br.com.rfapi.rendafamiliarapi.service.ReceitasService;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.time.*;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("receitas")
public class ReceitasController {

    @Autowired
    private ReceitasRepository repository;

    private ReceitasService receitasService;
    private ReceitasCustomRepository receitasCustomRepository;

    public ReceitasController(ReceitasService receitasService) {
        this.receitasService = receitasService;
    }

    @PostMapping
    @Transactional
    public void cadastrar(@RequestBody DadosCadastraisReceitas dados) {

        repository.save(new Receita(dados));

    }


    @GetMapping
    public Page<DadosListagemReceita> listar(@PageableDefault(size = 10, page = 0, sort = {"descricao"}) Pageable paginacao) {
        return repository.findAll(paginacao).map(DadosListagemReceita::new);

//      old ->  return repository.findAll(paginacao).stream().map(DadosListagemReceita::new).toList();
    }

    @GetMapping("/{id}")
    public Receita detalharReceita(@PathVariable Long id) {

        return repository.findById(id)
                .orElseThrow(() -> new ReceitaNaoEncontradaException(id));

    }

    @PutMapping("/{id}")
    public Receita atualizar(@RequestBody Receita novaReceita, @PathVariable Long id) {

        return repository.findById(id).map(receita -> {
                    receita.setDescricao(novaReceita.getDescricao());
                    receita.setValor(novaReceita.getValor());
                    receita.setData(novaReceita.getData());
                    return repository.save(receita);
                })
                .orElseGet(() -> {
                    novaReceita.setReceita_id(id);
                    return repository.save(novaReceita);
                });
    }

    @DeleteMapping("/{id}")
    void excluirReceita(@PathVariable Long id) {

        repository.deleteById(id);

    }

    @GetMapping(params = "descricao")
    public ResponseEntity<List<Receita>> buscarDescricao(@RequestParam("descricao") String descricao) {
        return ResponseEntity.ok(receitasService.buscarDescricao(descricao));
    }

    @GetMapping("/{ano}/{mes}")
    public List<Receita> findByData(@PathVariable("ano") String ano, @PathVariable("mes") String mes) {

        List<Receita> date = receitasService.findByData(ano, mes);
        return date;

    }


}


package br.com.rfapi.rendafamiliarapi.model;

import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter;

import javax.persistence.*;

@Getter @Setter @NoArgsConstructor @Entity public class ResumoMensal {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;


@OneToOne
@JoinColumn(name = "receita_id")
private Receita receita;
@ManyToOne
@JoinColumn(name = "despesa_id")
private Despesa despesa;

}

Repositorio do projeto completo: https://github.com/matthewmatheus/renda-familiar-api

solução!

Oii Matheus, tudo bem?

Com relação a criar uma terceira classe/entidade, eu criaria apenas um novo controller, por questão de organização de código mesmo. Porque pelo que eu entendi, daria para fazer um resumo sem que ele fosse uma entidade, não?

Você criaria um ReceitaMensalController e usaria as services tanto de receita quanto de despesa nele. Daí, poderia fazer alguns métodos auxiliares:

total de receitas, usando a service de despesas total de despesas, usando a service de receitas saldo do mes, usando as duas, e assim por diante.

Depois, o método resumo chamaria essas pequenas partes e as retornaria na requisição.

Com relação a usar ou não o JPQL com DAOS ao invés do repository, eu acho o repository bem mais tranquilo para praticar e percebo que ele é mais comum atualmente. Outro ponto é que isso tá diferente do padrão que já está no seu projeto, então não fica muito legal ter as duas coisas juntas. Caso queira ver mais sobre as queries no repository, tem um curso de JPA aqui na alura que pode te ajudar: Persistência com JPA: Hibernate. Qualquer dúvida, manda aqui no fórum também :)

Espero ter contribuído um pouquinho, geralmente a modelagem envolve várias discussões mesmo, acho que você está no caminho certo!

Abraços e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓. Bons Estudos!

Pô ajudou demais, era exatamente o que eu precisava, implementei ontem e deu super certo!