Add expense period total chart

This commit is contained in:
2019-08-19 20:59:27 +02:00
parent 7a73f45921
commit 6be58d6462
18 changed files with 340 additions and 12 deletions

View File

@@ -20,6 +20,14 @@
<groupId>javax.persistence</groupId> <groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId> <artifactId>javax.persistence-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -0,0 +1,43 @@
package de.financer.dto;
import de.financer.model.AccountType;
public class ExpensePeriodTotal {
private Long total;
private String periodShortCode;
private AccountType type;
public ExpensePeriodTotal() {
// nothing to do
}
public ExpensePeriodTotal(String periodShortCode, Long total, AccountType type) {
this.total = total;
this.periodShortCode = periodShortCode;
this.type = type;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public String getPeriodShortCode() {
return periodShortCode;
}
public void setPeriodShortCode(String periodShortCode) {
this.periodShortCode = periodShortCode;
}
public AccountType getType() {
return type;
}
public void setType(AccountType type) {
this.type = type;
}
}

View File

@@ -1,6 +1,13 @@
package de.financer.util; package de.financer.util;
import com.google.common.collect.Streams;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Month;
import java.time.Period;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ExpensePeriod { public class ExpensePeriod {
private LocalDate start; private LocalDate start;
@@ -24,27 +31,65 @@ public class ExpensePeriod {
// => 10 < 15 // => 10 < 15
// now - one month = 2019-05-10 // now - one month = 2019-05-10
// set the day = 2019-05-15 = start // set the day = 2019-05-15 = start
// end = start + one month = 2019-06-15 // end = start + one month - one day = 2019-06-14
// Period from 2019-05-15 to 2019-06-15 // Period from 2019-05-15 to 2019-06-14
periodStart = LocalDate.now().minusMonths(1).withDayOfMonth(periodStartDay); periodStart = LocalDate.now().minusMonths(1).withDayOfMonth(periodStartDay);
periodEnd = periodStart.plusMonths(1); periodEnd = periodStart.plusMonths(1).minusDays(1);
} } else {
else {
// Else, the current day of month is greater or equals the configured period // Else, the current day of month is greater or equals the configured period
// start day, just reset the day of month to the configured day, for example: // start day, just reset the day of month to the configured day, for example:
// configured start day of month for period = 15 // configured start day of month for period = 15
// now = 2019-06-26 // now = 2019-06-26
// => 26 > 15 // => 26 > 15
// set the day = 2019-06-15 = start // set the day = 2019-06-15 = start
// end = start + one month = 2019-07-15 // end = start + one month - one day = 2019-07-14
// Period from 2019-06-15 to 2019-07-15 // Period from 2019-06-15 to 2019-07-14
periodStart = LocalDate.now().withDayOfMonth(periodStartDay); periodStart = LocalDate.now().withDayOfMonth(periodStartDay);
periodEnd = periodStart.plusMonths(1); periodEnd = periodStart.plusMonths(1).minusDays(1);
} }
return new ExpensePeriod(periodStart, periodEnd); return new ExpensePeriod(periodStart, periodEnd);
} }
public static final List<ExpensePeriod> generateExpensePeriodsForYear(int periodStartDay, int year) {
Stream<LocalDate> localDateStreamStart;
Stream<LocalDate> localDateStreamEnd;
if (periodStartDay == 1) {
localDateStreamStart = LocalDate.of(year, Month.JANUARY, 1)
.datesUntil(LocalDate.of(year, Month.DECEMBER, 2), Period
.ofMonths(1));
localDateStreamEnd = LocalDate.of(year, Month.JANUARY, 31)
.datesUntil(LocalDate.of(year + 1, Month.JANUARY, 1), Period
.ofMonths(1));
}
// If the start of the period is not the 1st we need to look to the previous year as well
// because the period in January actually started in December last year
else {
localDateStreamStart = LocalDate.of(year, Month.JANUARY, periodStartDay).minusMonths(1)
.datesUntil(LocalDate.of(year, Month.DECEMBER, periodStartDay + 1).plusDays(1), Period
.ofMonths(1));
localDateStreamEnd = LocalDate.of(year, Month.JANUARY, periodStartDay - 1)
.datesUntil(LocalDate.of(year, Month.DECEMBER, periodStartDay + 1).plusDays(1), Period
.ofMonths(1));
}
return Streams
.zip(localDateStreamStart, localDateStreamEnd, (start, end) -> new ExpensePeriod(start, end))
.collect(Collectors.toList());
}
public String generatePeriodShortCode() {
return String.format("%s/%s", this.start.getYear(), this.start.getMonthValue());
}
@Override
public String toString() {
return String.format("Start[%s], End[%s], ShortCode[%s]", this.start, this.end, this.generatePeriodShortCode());
}
public LocalDate getStart() { public LocalDate getStart() {
return start; return start;
} }

View File

@@ -1,6 +1,7 @@
package de.financer.controller; package de.financer.controller;
import de.financer.ResponseReason; import de.financer.ResponseReason;
import de.financer.dto.ExpensePeriodTotal;
import de.financer.model.Transaction; import de.financer.model.Transaction;
import de.financer.service.TransactionService; import de.financer.service.TransactionService;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -10,9 +11,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
@RestController @RestController
@RequestMapping("transactions") @RequestMapping("transactions")
public class TransactionController { public class TransactionController {
@@ -94,4 +92,20 @@ public class TransactionController {
return response; return response;
} }
@RequestMapping("getExpensePeriodTotals")
public Iterable<ExpensePeriodTotal> getExpensePeriodTotals(Integer monthPeriodStartDay, Integer year) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("/transactions/getExpensePeriodTotals got parameters: %s, %s", monthPeriodStartDay, year));
}
final Iterable<ExpensePeriodTotal> expensePeriodTotals = this.transactionService
.getExpensePeriodTotals(monthPeriodStartDay, year);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("/transactions/getExpensePeriodTotals returns with %s", expensePeriodTotals));
}
return expensePeriodTotals;
}
} }

View File

@@ -1,5 +1,6 @@
package de.financer.dba; package de.financer.dba;
import de.financer.dto.ExpensePeriodTotal;
import de.financer.model.Account; import de.financer.model.Account;
import de.financer.model.AccountType; import de.financer.model.AccountType;
import de.financer.model.Transaction; import de.financer.model.Transaction;
@@ -9,6 +10,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public interface TransactionRepository extends CrudRepository<Transaction, Long> { public interface TransactionRepository extends CrudRepository<Transaction, Long> {
@@ -16,4 +18,11 @@ public interface TransactionRepository extends CrudRepository<Transaction, Long>
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.toAccount a WHERE t.date BETWEEN :periodStart AND :periodEnd AND a.type IN :expenseTypes") @Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.toAccount a WHERE t.date BETWEEN :periodStart AND :periodEnd AND a.type IN :expenseTypes")
Long getExpensesCurrentPeriod(LocalDate periodStart, LocalDate periodEnd, AccountType... expenseTypes); Long getExpensesCurrentPeriod(LocalDate periodStart, LocalDate periodEnd, AccountType... expenseTypes);
// The HQL contains a hack because Hibernate can't resolve the alias of the CASE column in the GROUP BY clause
// That's why the generated alias is used directly in the HQL. It will break if the columns in the SELECT clause get reordered
// col_0_0_ instead of periodShortCode
// col_2_0_ instead of AccType
@Query("SELECT new de.financer.dto.ExpensePeriodTotal(CASE WHEN EXTRACT(DAY FROM t.date) >= :startDay THEN CONCAT(CAST(EXTRACT(YEAR FROM t.date) AS string), '/', CAST(EXTRACT(MONTH FROM t.date) AS string)) ELSE CASE WHEN EXTRACT(MONTH FROM t.date) = 1 THEN CONCAT(CAST(EXTRACT(YEAR FROM t.date) - 1 AS string), '/12') ELSE CONCAT(CAST(EXTRACT(YEAR FROM t.date) AS string), '/', CAST(EXTRACT(MONTH FROM t.date) - 1 AS string)) END END AS periodShortCode, SUM(t.amount), CASE WHEN fa.type = :incomeType THEN fa.type ELSE ta.type END AS AccType) FROM Transaction t JOIN t.toAccount ta JOIN t.fromAccount fa WHERE ((ta.type IN :expenseTypes AND fa.type <> :startType) OR (fa.type = :incomeType)) AND t.date BETWEEN :lowerBound AND :upperBound GROUP BY col_0_0_, col_2_0_ ORDER BY periodShortCode, AccType ASC")
List<ExpensePeriodTotal> getAccountExpenseTotals(int startDay, LocalDate lowerBound, LocalDate upperBound, AccountType incomeType, AccountType startType, AccountType... expenseTypes);
} }

View File

@@ -1,8 +1,10 @@
package de.financer.service; package de.financer.service;
import com.google.common.collect.Iterables;
import de.financer.ResponseReason; import de.financer.ResponseReason;
import de.financer.config.FinancerConfig; import de.financer.config.FinancerConfig;
import de.financer.dba.TransactionRepository; import de.financer.dba.TransactionRepository;
import de.financer.dto.ExpensePeriodTotal;
import de.financer.model.Account; import de.financer.model.Account;
import de.financer.model.AccountType; import de.financer.model.AccountType;
import de.financer.model.RecurringTransaction; import de.financer.model.RecurringTransaction;
@@ -21,6 +23,7 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Service @Service
@@ -239,4 +242,17 @@ public class TransactionService {
return Optional.ofNullable(expensesCurrentPeriod).orElse(Long.valueOf(0l)); return Optional.ofNullable(expensesCurrentPeriod).orElse(Long.valueOf(0l));
} }
public Iterable<ExpensePeriodTotal> getExpensePeriodTotals(Integer monthPeriodStartDay, Integer year) {
final List<ExpensePeriod> expensePeriods = ExpensePeriod.generateExpensePeriodsForYear(monthPeriodStartDay, year);
final ExpensePeriod lowerBound = Iterables.get(expensePeriods, 0);
final ExpensePeriod upperBound = Iterables.getLast(expensePeriods);
final List<ExpensePeriodTotal> expensePeriodTotals = this.transactionRepository
.getAccountExpenseTotals(monthPeriodStartDay, lowerBound.getStart(), upperBound
.getEnd(), AccountType.INCOME, AccountType.START, AccountType.EXPENSE, AccountType.LIABILITY);
return expensePeriodTotals;
}
} }

View File

@@ -8,7 +8,8 @@ public enum ChartType {
ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD, ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD,
ACCOUNT_GROUP_EXPENSES_FOR_PERIOD, ACCOUNT_GROUP_EXPENSES_FOR_PERIOD,
ACCOUNT_EXPENSES_CURRENT_PERIOD, ACCOUNT_EXPENSES_CURRENT_PERIOD,
ACCOUNT_EXPENSES_FOR_PERIOD; ACCOUNT_EXPENSES_FOR_PERIOD,
EXPENSE_PERIOD_TOTALS_CURRENT_YEAR;
public static List<String> valueList() { public static List<String> valueList() {
return Arrays.stream(ChartType.values()).map(ChartType::name).collect(Collectors.toList()); return Arrays.stream(ChartType.values()).map(ChartType::name).collect(Collectors.toList());

View File

@@ -2,6 +2,7 @@ package de.financer.chart;
import de.financer.chart.impl.expense.AccountExpensesGenerator; import de.financer.chart.impl.expense.AccountExpensesGenerator;
import de.financer.chart.impl.expense.AccountGroupExpensesGenerator; import de.financer.chart.impl.expense.AccountGroupExpensesGenerator;
import de.financer.chart.impl.total.PeriodTotalGenerator;
import de.financer.config.FinancerConfig; import de.financer.config.FinancerConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
@@ -31,6 +32,10 @@ public class FinancerChartFactory {
// TODO WHY IS THIS CAST NECESSARY??? // TODO WHY IS THIS CAST NECESSARY???
generator = (AbstractChartGenerator<P>) new AccountExpensesGenerator(); generator = (AbstractChartGenerator<P>) new AccountExpensesGenerator();
break; break;
case EXPENSE_PERIOD_TOTALS_CURRENT_YEAR:
// TODO WHY IS THIS CAST NECESSARY???
generator = (AbstractChartGenerator<P>) new PeriodTotalGenerator();
break;
default: default:
generator = null; generator = null;
} }

View File

@@ -0,0 +1,74 @@
package de.financer.chart.impl.total;
import de.financer.chart.AbstractChartGenerator;
import de.financer.dto.ExpensePeriodTotal;
import de.financer.template.GetExpensePeriodTotalsTemplate;
import de.financer.util.ControllerUtils;
import de.financer.util.ExpensePeriod;
import org.apache.commons.collections4.IterableUtils;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.springframework.context.i18n.LocaleContextHolder;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;
public class PeriodTotalGenerator extends AbstractChartGenerator<PeriodTotalParameter> {
@Override
public JFreeChart generateChart(PeriodTotalParameter parameter) {
final CategoryDataset dataSet = getDataset(parameter);
final JFreeChart chart = ChartFactory
.createBarChart(this.getMessage(parameter.getTitle(), parameter.getArgsForTitle()),
this.getMessage(parameter.getyAxis()),
this.getMessage(parameter.getxAxis()),
dataSet, PlotOrientation.VERTICAL, true, true, false);
final NumberAxis axis = (NumberAxis) chart.getCategoryPlot().getRangeAxis();
axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits(LocaleContextHolder
.getLocale()));
final NumberFormat currencyInstance = NumberFormat.getCurrencyInstance(LocaleContextHolder.getLocale());
currencyInstance.setCurrency(this.getFinancerConfig().getCurrency());
axis.setNumberFormatOverride(currencyInstance);
return chart;
}
private CategoryDataset getDataset(PeriodTotalParameter parameter) {
final DefaultCategoryDataset result = new DefaultCategoryDataset();
final List<ExpensePeriod> expensePeriods = ExpensePeriod
.generateExpensePeriodsForYear(this.getFinancerConfig().getMonthPeriodStartDay(), parameter.getYear());
final Iterable<ExpensePeriodTotal> totalData = new GetExpensePeriodTotalsTemplate()
.exchange(this.getFinancerConfig(), parameter.getYear()).getBody();
IterableUtils.toList(totalData).stream()
.forEach((ept) -> result.addValue((ept.getTotal() / 100D),
this.getMessage("financer.account-type." + ept.getType()),
expensePeriods.stream()
.filter((ep) -> ep.generatePeriodShortCode()
.equals(ept.getPeriodShortCode()))
.map((ep) -> formatDateY(ep))
.findFirst().get()));
return result;
}
private String formatDateY(ExpensePeriod ep) {
return String.format("%s - %s",
ep.getStart().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(LocaleContextHolder.getLocale())),
ep.getEnd().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(LocaleContextHolder.getLocale())));
}
}

View File

@@ -0,0 +1,51 @@
package de.financer.chart.impl.total;
import de.financer.chart.ChartParameter;
public class PeriodTotalParameter implements ChartParameter {
private String title;
private Object[] argsForTitle;
private String yAxis;
private String xAxis;
private int year;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Object[] getArgsForTitle() {
return argsForTitle;
}
public void setArgsForTitle(Object[] argsForTitle) {
this.argsForTitle = argsForTitle;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String getyAxis() {
return yAxis;
}
public void setyAxis(String yAxis) {
this.yAxis = yAxis;
}
public String getxAxis() {
return xAxis;
}
public void setxAxis(String xAxis) {
this.xAxis = xAxis;
}
}

View File

@@ -4,6 +4,7 @@ import de.financer.chart.impl.expense.ExpensesParameter;
import de.financer.chart.ChartGenerator; import de.financer.chart.ChartGenerator;
import de.financer.chart.ChartType; import de.financer.chart.ChartType;
import de.financer.chart.FinancerChartFactory; import de.financer.chart.FinancerChartFactory;
import de.financer.chart.impl.total.PeriodTotalParameter;
import de.financer.config.FinancerConfig; import de.financer.config.FinancerConfig;
import de.financer.form.ConfigAccountExpenseForPeriodForm; import de.financer.form.ConfigAccountExpenseForPeriodForm;
import de.financer.form.ConfigAccountGroupExpenseForPeriodForm; import de.financer.form.ConfigAccountGroupExpenseForPeriodForm;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate;
@Controller @Controller
public class ChartController { public class ChartController {
@@ -104,6 +106,23 @@ public class ChartController {
writeChart(response, chart); writeChart(response, chart);
} }
@GetMapping("/getExpensePeriodTotalCurrentYear")
public void getExpensePeriodTotalCurrentYear(HttpServletResponse response) {
PeriodTotalParameter parameter = new PeriodTotalParameter();
parameter.setYear(LocalDate.now().getYear());
parameter.setTitle("financer.chart.expense-period-totals-current-year.title");
parameter.setxAxis("financer.chart.expense-period-totals-current-year.x");
parameter.setyAxis("financer.chart.expense-period-totals-current-year.y");
final ChartGenerator<PeriodTotalParameter> generator =
this.financerChartFactory.getGenerator(ChartType.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR);
final JFreeChart chart = generator.generateChart(parameter);
writeChart(response, chart);
}
private void writeChart(HttpServletResponse response, JFreeChart chart) { private void writeChart(HttpServletResponse response, JFreeChart chart) {
response.setContentType("image/png"); response.setContentType("image/png");

View File

@@ -18,6 +18,7 @@ public enum Function {
TR_CREATE_TRANSACTION("transactions/createTransaction"), TR_CREATE_TRANSACTION("transactions/createTransaction"),
TR_DELETE_TRANSACTION("transactions/deleteTransaction"), TR_DELETE_TRANSACTION("transactions/deleteTransaction"),
TR_EXPENSES_CURRENT_PERIOD("transactions/getExpensesCurrentPeriod"), TR_EXPENSES_CURRENT_PERIOD("transactions/getExpensesCurrentPeriod"),
TR_EXPENSE_PERIOD_TOTALS("transactions/getExpensePeriodTotals"),
RT_GET_ALL("recurringTransactions/getAll"), RT_GET_ALL("recurringTransactions/getAll"),
RT_GET_ALL_ACTIVE("recurringTransactions/getAllActive"), RT_GET_ALL_ACTIVE("recurringTransactions/getAllActive"),

View File

@@ -67,6 +67,10 @@ public class ReportController {
ControllerUtils.addCurrencySymbol(model, this.financerConfig); ControllerUtils.addCurrencySymbol(model, this.financerConfig);
return "report/configureAccountExpensesForPeriod"; return "report/configureAccountExpensesForPeriod";
case EXPENSE_PERIOD_TOTALS_CURRENT_YEAR:
// Special case: this chart does not require parameters, so we can
// directly redirect to the actual chart instead of the config page
return "redirect:/getExpensePeriodTotalCurrentYear";
default: default:
// Cannot happen // Cannot happen
throw new IllegalStateException("Unexpected value: " + selectedChartType); throw new IllegalStateException("Unexpected value: " + selectedChartType);

View File

@@ -0,0 +1,22 @@
package de.financer.template;
import de.financer.config.FinancerConfig;
import de.financer.controller.Function;
import de.financer.dto.ExpensePeriodTotal;
import de.financer.util.ControllerUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriComponentsBuilder;
public class GetExpensePeriodTotalsTemplate {
public ResponseEntity<Iterable<ExpensePeriodTotal>> exchange(FinancerConfig financerConfig, int year) {
final UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_EXPENSE_PERIOD_TOTALS))
.queryParam("monthPeriodStartDay", financerConfig.getMonthPeriodStartDay())
.queryParam("year", year);
return new FinancerRestTemplate<Iterable<ExpensePeriodTotal>>()
.exchange(builder.toUriString(), new ParameterizedTypeReference<Iterable<ExpensePeriodTotal>>() {
});
}
}

View File

@@ -160,11 +160,16 @@ financer.chart.account-group-expenses-current-period.title=Expenses of the curre
financer.chart.account-group-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account group financer.chart.account-group-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account group
financer.chart.account-expenses-current-period.title=Expenses of the current period grouped by account financer.chart.account-expenses-current-period.title=Expenses of the current period grouped by account
financer.chart.account-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account financer.chart.account-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account
financer.chart.expense-period-totals-current-year.title=Expense period totals for the current year
financer.chart.expense-period-totals-current-year.x=Amount
financer.chart.expense-period-totals-current-year.y=Period
financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account group (pie chart) financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account group (pie chart)
financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account group (pie chart) financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account group (pie chart)
financer.chart.name.ACCOUNT_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account (pie chart) financer.chart.name.ACCOUNT_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account (pie chart)
financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account (pie chart) financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account (pie chart)
financer.chart.name.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR=Overview about the income and expenses in the current year (bar chart)
financer.chart.name.EXPENSE_PERIOD_TOTALS_FOR_YEAR=Overview about the income and expenses for a configurable year (bar chart)
financer.error-message.UNKNOWN_ERROR=An unknown error occurred! financer.error-message.UNKNOWN_ERROR=An unknown error occurred!
financer.error-message.INVALID_ACCOUNT_TYPE=The selected account type is not valid! financer.error-message.INVALID_ACCOUNT_TYPE=The selected account type is not valid!

View File

@@ -159,11 +159,16 @@ financer.chart.account-group-expenses-current-period.title=Ausgaben in der aktue
financer.chart.account-group-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto-Gruppe financer.chart.account-group-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto-Gruppe
financer.chart.account-expenses-current-period.title=Ausgaben in der aktuellen Periode gruppiert nach Konto financer.chart.account-expenses-current-period.title=Ausgaben in der aktuellen Periode gruppiert nach Konto
financer.chart.account-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto financer.chart.account-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto
financer.chart.expense-period-totals-current-year.title=Gesamtbetr\u00E4ge gruppiert nach Periode für das aktuelle Jahr
financer.chart.expense-period-totals-current-year.x=Betrag
financer.chart.expense-period-totals-current-year.y=Periode
financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe (Kuchendiagramm) financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe (Kuchendiagramm)
financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto-Gruppe (Kuchendiagramm) financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto-Gruppe (Kuchendiagramm)
financer.chart.name.ACCOUNT_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto (Kuchendiagramm) financer.chart.name.ACCOUNT_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto (Kuchendiagramm)
financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto (Kuchendiagramm) financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto (Kuchendiagramm)
financer.chart.name.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR=\u00DCbersicht über das Einkommen und die Ausgaben im laufenden Jahr (Balkendiagramm)
financer.chart.name.EXPENSE_PERIOD_TOTALS_FOR_YEAR=\u00DCbersicht über das Einkommen und die Ausgaben für ein konfigurierbares Jahr (Balkendiagramm)
financer.error-message.UNKNOWN_ERROR=Ein unbekannter Fehler ist aufgetreten! financer.error-message.UNKNOWN_ERROR=Ein unbekannter Fehler ist aufgetreten!
financer.error-message.INVALID_ACCOUNT_TYPE=Der ausgew\u00E4hlte Kontotyp ist ung\u00FCltig! financer.error-message.INVALID_ACCOUNT_TYPE=Der ausgew\u00E4hlte Kontotyp ist ung\u00FCltig!

View File

@@ -2,6 +2,7 @@ v17 -> v18:
- Add readme to the footer - Add readme to the footer
- Translate error messages to German - Translate error messages to German
- Add chart type to chart description in chart type selection page - Add chart type to chart description in chart type selection page
- Add expense period total chart
v16 -> v17: v16 -> v17:
- Add this changelog to the footer - Add this changelog to the footer

View File

@@ -66,6 +66,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.12</version> <version>4.12</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<dependency> <dependency>
<groupId>de.77zzcx7.financer</groupId> <groupId>de.77zzcx7.financer</groupId>
<artifactId>financer-common</artifactId> <artifactId>financer-common</artifactId>