Add charts (based on JFreeChart)
Currently only an expense report is supported, but more will come. Also moved rest templates into own toplevel package.
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package de.financer.dto;
|
||||
|
||||
import de.financer.model.AccountGroup;
|
||||
|
||||
public class AccountGroupExpense {
|
||||
private AccountGroup accountGroup;
|
||||
private Long expense;
|
||||
|
||||
public AccountGroupExpense() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public AccountGroupExpense(AccountGroup accountGroup, Long expense) {
|
||||
this.accountGroup = accountGroup;
|
||||
this.expense = expense;
|
||||
}
|
||||
|
||||
public AccountGroup getAccountGroup() {
|
||||
return accountGroup;
|
||||
}
|
||||
|
||||
public void setAccountGroup(AccountGroup accountGroup) {
|
||||
this.accountGroup = accountGroup;
|
||||
}
|
||||
|
||||
public Long getExpense() {
|
||||
return expense;
|
||||
}
|
||||
|
||||
public void setExpense(Long expense) {
|
||||
this.expense = expense;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.model.AccountGroup;
|
||||
import de.financer.service.AccountGroupService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -50,4 +51,13 @@ public class AccountGroupController {
|
||||
|
||||
return responseReason.toResponseEntity();
|
||||
}
|
||||
|
||||
@RequestMapping("getAccountGroupExpenses")
|
||||
public Iterable<AccountGroupExpense> getAccountGroupExpenses(String periodStart, String periodEnd) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/accountGroups/getAccountGroupExpenses got parameters: %s|%s", periodStart, periodEnd));
|
||||
}
|
||||
|
||||
return this.accountGroupService.getAccountGroupExpenses(periodStart, periodEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package de.financer.dba;
|
||||
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.model.AccountGroup;
|
||||
import de.financer.model.AccountType;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public interface AccountGroupRepository extends CrudRepository<AccountGroup, Long> {
|
||||
AccountGroup findByName(String name);
|
||||
|
||||
@Query("SELECT new de.financer.dto.AccountGroupExpense(ag, SUM(t.amount)) FROM Transaction t JOIN t.toAccount a JOIN a.accountGroup ag WHERE a.type in :expenseTypes AND t.date BETWEEN :startDate AND :startEnd GROUP BY ag")
|
||||
Iterable<AccountGroupExpense> getAccountGroupExpenses(LocalDate startDate, LocalDate startEnd, AccountType... expenseTypes);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package de.financer.service;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dba.AccountGroupRepository;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.model.AccountGroup;
|
||||
import de.financer.model.AccountType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -11,6 +14,11 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Collections;
|
||||
|
||||
@Service
|
||||
public class AccountGroupService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountGroupService.class);
|
||||
@@ -18,6 +26,9 @@ public class AccountGroupService {
|
||||
@Autowired
|
||||
private AccountGroupRepository accountGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
/**
|
||||
* @return all existing account groups
|
||||
*/
|
||||
@@ -29,6 +40,7 @@ public class AccountGroupService {
|
||||
* This method returns the account group with the given name.
|
||||
*
|
||||
* @param name the name to get the account group for
|
||||
*
|
||||
* @return the account group or <code>null</code> if no account group with the given name can be found
|
||||
*/
|
||||
public AccountGroup getAccountGroupByName(String name) {
|
||||
@@ -39,10 +51,10 @@ public class AccountGroupService {
|
||||
* This method creates a new account group with the given name.
|
||||
*
|
||||
* @param name the name of the new account group
|
||||
* @return {@link ResponseReason#DUPLICATE_ACCOUNT_GROUP_NAME} if an account group with the given name already exists,
|
||||
* {@link ResponseReason#UNKNOWN_ERROR} if an unknown error occurs,
|
||||
* {@link ResponseReason#OK} if the operation completed successfully.
|
||||
* Never returns <code>null</code>.
|
||||
*
|
||||
* @return {@link ResponseReason#DUPLICATE_ACCOUNT_GROUP_NAME} if an account group with the given name already
|
||||
* exists, {@link ResponseReason#UNKNOWN_ERROR} if an unknown error occurs, {@link ResponseReason#OK} if the
|
||||
* operation completed successfully. Never returns <code>null</code>.
|
||||
*/
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public ResponseReason createAccountGroup(String name) {
|
||||
@@ -52,13 +64,11 @@ public class AccountGroupService {
|
||||
|
||||
try {
|
||||
this.accountGroupRepository.save(accountGroup);
|
||||
}
|
||||
catch (DataIntegrityViolationException dive) {
|
||||
} catch (DataIntegrityViolationException dive) {
|
||||
LOGGER.error(String.format("Duplicate account group name! %s", name), dive);
|
||||
|
||||
return ResponseReason.DUPLICATE_ACCOUNT_GROUP_NAME;
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Could not save account group %s", name), e);
|
||||
|
||||
return ResponseReason.UNKNOWN_ERROR;
|
||||
@@ -66,4 +76,22 @@ public class AccountGroupService {
|
||||
|
||||
return ResponseReason.OK;
|
||||
}
|
||||
|
||||
public Iterable<AccountGroupExpense> getAccountGroupExpenses(String periodStart, String periodEnd) {
|
||||
LocalDate startDate;
|
||||
LocalDate endDate;
|
||||
|
||||
try {
|
||||
startDate = LocalDate.parse(periodStart, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
endDate = LocalDate.parse(periodEnd, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
|
||||
} catch (DateTimeParseException dtpe) {
|
||||
LOGGER.error(String
|
||||
.format("Could not parse periodStart (%s) or periodEnd (%s): %s", periodStart, periodEnd, dtpe
|
||||
.getMessage()));
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return this.accountGroupRepository.getAccountGroupExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE account SET account_group_id = (SELECT id FROM account_group WHERE name = 'Car') WHERE "key" IN ('Car', 'Gas')
|
||||
|
||||
UPDATE account SET account_group_id = (SELECT id FROM account_group WHERE name = 'Housing') WHERE "key" IN ('Rent', 'FVS', 'Electricity/Water')
|
||||
@@ -12,7 +12,8 @@
|
||||
|
||||
<artifactId>financer-web-client</artifactId>
|
||||
<packaging>${packaging.type}</packaging>
|
||||
<description>The web client part of the financer application - a simple app to manage your personal finances</description>
|
||||
<description>The web client part of the financer application - a simple app to manage your personal finances
|
||||
</description>
|
||||
<name>financer-web-client</name>
|
||||
|
||||
<properties>
|
||||
@@ -47,6 +48,11 @@
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
<!-- Misc dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.jfree</groupId>
|
||||
<artifactId>jfreechart</artifactId>
|
||||
<version>1.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Financer dependencies -->
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.financer.chart;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.template.GetAccountGroupExpensesTemplate;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.data.general.DefaultPieDataset;
|
||||
import org.jfree.data.general.PieDataset;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class AccountGroupExpensesChartGenerator {
|
||||
public JFreeChart generateChart(FinancerConfig financerConfig, LocalDate periodStart, LocalDate periodEnd, MessageSource messageSource) {
|
||||
final String start = ControllerUtils.formatDate(financerConfig, periodStart);
|
||||
final String end = ControllerUtils.formatDate(financerConfig, periodEnd);
|
||||
|
||||
final Iterable<AccountGroupExpense> expenses = new GetAccountGroupExpensesTemplate()
|
||||
.exchange(financerConfig, start, end).getBody();
|
||||
|
||||
final PieDataset dataSet = createDataSet(expenses);
|
||||
|
||||
return ChartFactory.createPieChart(messageSource
|
||||
.getMessage("financer.chart.account-group-expenses-current-period.title", null, LocaleContextHolder
|
||||
.getLocale()), dataSet);
|
||||
}
|
||||
|
||||
private PieDataset createDataSet(Iterable<AccountGroupExpense> data) {
|
||||
final DefaultPieDataset dataSet = new DefaultPieDataset();
|
||||
|
||||
IterableUtils.toList(data).stream()
|
||||
.forEach((ex) -> dataSet.setValue(ex.getAccountGroup().getName(), ex.getExpense()));
|
||||
|
||||
return dataSet;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.template.*;
|
||||
import de.financer.template.*;
|
||||
import de.financer.form.NewAccountForm;
|
||||
import de.financer.model.*;
|
||||
import de.financer.util.ControllerUtils;
|
||||
@@ -19,7 +19,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.template.StringTemplate;
|
||||
import de.financer.template.StringTemplate;
|
||||
import de.financer.form.NewAccountGroupForm;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.financer.controller;
|
||||
|
||||
import de.financer.chart.AccountGroupExpensesChartGenerator;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.util.ExpensePeriod;
|
||||
import org.jfree.chart.ChartUtils;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Controller
|
||||
public class ChartController {
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@Autowired
|
||||
private MessageSource messageSource;
|
||||
|
||||
@GetMapping("/getAccountGroupExpensesCurrentPeriod")
|
||||
public void getAccountGroupExpensesCurrentPeriod(HttpServletResponse response) {
|
||||
final ExpensePeriod period = ExpensePeriod
|
||||
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
|
||||
|
||||
JFreeChart chart = new AccountGroupExpensesChartGenerator()
|
||||
.generateChart(this.financerConfig, period.getStart(), period.getEnd(), this.messageSource);
|
||||
|
||||
response.setContentType("image/png");
|
||||
|
||||
try {
|
||||
ChartUtils.writeChartAsPNG(response.getOutputStream(), chart, 1024, 768);
|
||||
} catch (IOException ioe) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ public enum Function {
|
||||
|
||||
ACC_GP_CREATE_ACCOUNT_GROUP("accountGroups/createAccountGroup"),
|
||||
ACC_GP_GET_ALL("accountGroups/getAll"),
|
||||
ACC_GP_GET_ACC_GP_EXPENSES("accountGroups/getAccountGroupExpenses"),
|
||||
|
||||
TR_GET_ALL("transactions/getAll"),
|
||||
TR_GET_ALL_FOR_ACCOUNT("transactions/getAllForAccount"),
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.template.*;
|
||||
import de.financer.template.*;
|
||||
import de.financer.form.NewRecurringTransactionForm;
|
||||
import de.financer.form.RecurringToTransactionWithAmountForm;
|
||||
import de.financer.model.Account;
|
||||
|
||||
@@ -2,10 +2,10 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.template.GetAccountByKeyTemplate;
|
||||
import de.financer.controller.template.GetAllAccountsTemplate;
|
||||
import de.financer.controller.template.GetAllTransactionsForAccountTemplate;
|
||||
import de.financer.controller.template.StringTemplate;
|
||||
import de.financer.template.GetAccountByKeyTemplate;
|
||||
import de.financer.template.GetAllAccountsTemplate;
|
||||
import de.financer.template.GetAllTransactionsForAccountTemplate;
|
||||
import de.financer.template.StringTemplate;
|
||||
import de.financer.form.NewTransactionForm;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountStatus;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.Transaction;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class GetAccountGroupExpensesTemplate {
|
||||
public ResponseEntity<Iterable<AccountGroupExpense>> exchange(FinancerConfig financerConfig, String start, String end) {
|
||||
final UriComponentsBuilder expensesBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.ACC_GP_GET_ACC_GP_EXPENSES))
|
||||
.queryParam("periodStart", start)
|
||||
.queryParam("periodEnd", end);
|
||||
|
||||
return new FinancerRestTemplate<Iterable<AccountGroupExpense>>()
|
||||
.exchange(expensesBuilder.toUriString(), new ParameterizedTypeReference<Iterable<AccountGroupExpense>>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
@@ -1,8 +1,7 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.model.Transaction;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.financer.controller.template;
|
||||
package de.financer.template;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -136,6 +136,8 @@ financer.heading.recurring-to-transaction-with-amount=financer\: create transact
|
||||
|
||||
financer.cancel-back-to-overview=Cancel and back to overview
|
||||
|
||||
financer.chart.account-group-expenses-current-period.title=Expenses of the current period grouped by account group
|
||||
|
||||
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.FROM_ACCOUNT_NOT_FOUND=The specified from account has not been found!
|
||||
|
||||
@@ -135,3 +135,5 @@ financer.heading.recurring-transaction-list.all=financer\: alle wiederkehrende B
|
||||
financer.heading.recurring-to-transaction-with-amount=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen
|
||||
|
||||
financer.cancel-back-to-overview=Abbrechen und zur \u00DCbersicht
|
||||
|
||||
financer.chart.account-group-expenses-current-period.title=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe
|
||||
@@ -16,7 +16,9 @@
|
||||
</div>
|
||||
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${#temporals.format(periodStart)}, ${#temporals.format(periodEnd)})}">
|
||||
<span th:text="#{financer.account-overview.status.current-expenses}"/>
|
||||
<a th:href="@{/getAccountGroupExpensesCurrentPeriod}">
|
||||
<span th:text="${#numbers.formatDecimal(currentExpenses/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}" />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span th:text="#{financer.account-overview.status.recurring-transaction-due-today}"/>
|
||||
|
||||
Reference in New Issue
Block a user