/* * Copyright (C) 2016 Arthur Gregorio, AG.Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package br.com.webbudget.domain.model.service; import br.com.webbudget.domain.model.entity.entries.CostCenter; import br.com.webbudget.domain.model.entity.miscellany.FinancialPeriod; import br.com.webbudget.domain.model.entity.financial.Movement; import br.com.webbudget.domain.model.entity.entries.MovementClass; import br.com.webbudget.domain.model.entity.entries.MovementClassType; import br.com.webbudget.domain.model.entity.financial.MovementStateType; import br.com.webbudget.domain.model.entity.financial.MovementType; import br.com.webbudget.application.component.chart.donut.DonutChartDataset; import br.com.webbudget.application.component.chart.donut.DonutChartModel; import br.com.webbudget.application.component.chart.line.LineChartDatasetBuilder; import br.com.webbudget.application.component.chart.line.LineChartModel; import br.com.webbudget.application.component.Color; import br.com.webbudget.domain.model.repository.financial.IApportionmentRepository; import br.com.webbudget.domain.model.repository.entries.IMovementClassRepository; import br.com.webbudget.domain.model.repository.financial.IMovementRepository; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import org.apache.commons.collections.ListUtils; /** * Classe que representa a montagem da tela de detalhes do periodo financeiro * * @author Arthur Gregorio * * @version 1.0.0 * @since 2.2.0, 22/02/2016 */ @ApplicationScoped public class PeriodDetailService { @Inject private IMovementRepository movementRepository; @Inject private IApportionmentRepository apportionmentRepository; @Inject private IMovementClassRepository movementClassRepository; /** * Metodo que busca as classes de movimentacao e seu respectivo valor * movimento, ou seja, a somatoria de todos os rateios para a aquela classe * * @param period o periodo * @param direction qual tipo queremos, entrada ou saida * @return a lista de movimentos */ public List<MovementClass> fetchTopClassesAndValues( FinancialPeriod period, MovementClassType direction) { final List<MovementClass> withValues = new ArrayList<>(); // lista as classes sem pegar as bloqueadas final List<MovementClass> classes = this.movementClassRepository .listByTypeAndStatus(direction, Boolean.FALSE); // para cada classe pegamos o seu total em movimentacao classes.stream().forEach(clazz -> { final BigDecimal total = this.apportionmentRepository .totalMovementsPerClassAndPeriod(period, clazz); if (total != null) { clazz.setTotalMovements(total); withValues.add(clazz); } }); // ordena do maior para o menor withValues.sort((c1, c2) -> c2.getTotalMovements().compareTo(c1.getTotalMovements())); // retorna somente os 10 primeiros resultados return withValues.size() > 10 ? withValues.subList(0, 10) : withValues; } /** * Monta o model do grafico de consumo por centro de custo * * @param periods os periodos * @param direction a direcao que vamos montar no grafico, entrada ou saida * @return o modelo do grafico para a view */ public DonutChartModel buidCostCenterChart(List<FinancialPeriod> periods, MovementClassType direction) { // lista os movimentos final List<Movement> movements = this.listMovementsFrom(periods, direction); // mapeia para cada centro de custo, seus movimentos final Map<CostCenter, List<Movement>> costCentersAndMovements = this.mapCostCenterAndMovements(movements); final DonutChartModel donutChartModel = new DonutChartModel(); // para cada CC adiciona os dados do grafico costCentersAndMovements.keySet().stream().forEach(costCenter -> { final List<Movement> grouped = costCentersAndMovements.get(costCenter); BigDecimal total = grouped.stream() .map(Movement::getValue) .reduce(BigDecimal.ZERO, BigDecimal::add); // pega a cor para este CC caso tenha, senao randomiza uma cor final Color color = costCenter.getColor() != null ? costCenter.getColor() : Color.randomize(); donutChartModel.addData(new DonutChartDataset<>(total, color.lighter().toString(), color.toString(), costCenter.getName())); }); return donutChartModel; } /** * Metodo que monta o modelo do grafico de consumo por dia no periodo * * @param period o periodo * @return o model para a view */ public LineChartModel bulidDailyChart(FinancialPeriod period) { // lista receitas e despesas do periodo final List<Movement> revenues = this.listMovementsFrom( period, MovementClassType.IN); final List<Movement> expenses = this.listMovementsFrom( period, MovementClassType.OUT); // agrupamos pelas datas das despesas e receitas final List<LocalDate> payDates = this.groupPaymentDates( ListUtils.union(revenues, expenses)); // monta o grafico de linhas final LineChartModel model = new LineChartModel(); // dados de despesas final LineChartDatasetBuilder<BigDecimal> expensesBuilder = new LineChartDatasetBuilder<>() .filledByColor("rgba(255,153,153,0.2)") .withStrokeColor("rgba(255,77,77,1)") .withPointColor("rgba(204,0,0,1)") .withPointStrokeColor("#fff") .withPointHighlightFillColor("#fff") .withPointHighlightStroke("rgba(204,0,0,1)"); // dados de receitas final LineChartDatasetBuilder<BigDecimal> revenuesBuilder = new LineChartDatasetBuilder<>() .filledByColor("rgba(140,217,140,0.2)") .withStrokeColor("rgba(51,153,51,1)") .withPointColor("rgba(45,134,45,1)") .withPointStrokeColor("#fff") .withPointHighlightFillColor("#fff") .withPointHighlightStroke("rgba(45,134,45,1)"); // para cada data de pagamento, printa o valor no dataset payDates.stream().forEach(payDate -> { model.addLabel(DateTimeFormatter .ofPattern("dd/MM").format(payDate)); final BigDecimal expensesTotal = expenses.stream() .filter(movement -> movement.getPaymentDate().equals(payDate)) .map(Movement::getValue) .reduce(BigDecimal.ZERO, BigDecimal::add); final BigDecimal revenuesTotal = revenues.stream() .filter(movement -> movement.getPaymentDate().equals(payDate)) .map(Movement::getValue) .reduce(BigDecimal.ZERO, BigDecimal::add); expensesBuilder.andData(expensesTotal); revenuesBuilder.andData(revenuesTotal); }); // joga os datasets no model model.addDataset(revenuesBuilder.build()); model.addDataset(expensesBuilder.build()); return model; } /** * Agrupa todas as datas de pagamento dos movimentos pagos no periodo * * @param movements a lista de movimentos * @return a lista de datas */ private List<LocalDate> groupPaymentDates(List<Movement> movements) { final List<LocalDate> dates = new ArrayList<>(); movements.stream().forEach(movement -> { if (!dates.contains(movement.getPaymentDate())) { dates.add(movement.getPaymentDate()); } }); dates.sort((d1, d2) -> d1.compareTo(d2)); return dates; } /** * Lista todos os movimentos do periodo informado em uma lista * * @param periods os periodos * @param direction a direcao (entrada ou saida) * @return a lista de movimentos */ private List<Movement> listMovementsFrom(FinancialPeriod period, MovementClassType direction) { return this.listMovementsFrom(Arrays.asList(period), direction); } /** * Lista todos os movimentos dos periodos informados agrupados em uma lista * * @param periods os periodos * @param direction a direcao (entrada ou saida) * @return a lista de movimentos */ private List<Movement> listMovementsFrom(List<FinancialPeriod> periods, MovementClassType direction) { final List<Movement> movements = new ArrayList<>(); periods.stream().forEach(period -> { final MovementStateType state = period.isClosed() ? MovementStateType.CALCULATED : MovementStateType.PAID; movements.addAll(this.movementRepository .listByPeriodAndStateAndTypeAndDirection(period, state, MovementType.MOVEMENT, direction)); }); return movements; } /** * Mapeia os movimentos para dentro de seu respectivo centro de custo * * @param movements o movimentos * @return o mapa de CC X movimentos */ private Map<CostCenter, List<Movement>> mapCostCenterAndMovements(List<Movement> movements) { final Map<CostCenter, List<Movement>> result = new HashMap<>(); // mapeia apenas os centros de custo final List<CostCenter> costCenters = new ArrayList<>(); movements.stream().forEach(movement -> { movement.getCostCenters().stream().forEach(costCenter -> { if (!costCenters.contains(costCenter)) { costCenters.add(costCenter); } }); }); // mapeia os movimentos para cada centro de custo costCenters.stream().forEach(costCenter -> { final List<Movement> grouped = new ArrayList<>(); movements.stream().forEach(movement -> { if (movement.getCostCenters().contains(costCenter)) { grouped.add(movement); } }); result.put(costCenter, grouped); }); return result; } }