Add chart report for expenses grouped by account

This commit is contained in:
2019-07-19 02:23:21 +02:00
parent f64788ca60
commit 97c65b1fe4
20 changed files with 309 additions and 34 deletions

View File

@@ -0,0 +1,33 @@
package de.financer.dto;
import de.financer.model.Account;
public class AccountExpense {
private Account account;
private Long expense;
public AccountExpense() {
// nothing to do
}
public AccountExpense(Account account, Long expense) {
this.account = account;
this.expense = expense;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public Long getExpense() {
return expense;
}
public void setExpense(Long expense) {
this.expense = expense;
}
}

View File

@@ -1,6 +1,7 @@
package de.financer.controller;
import de.financer.ResponseReason;
import de.financer.dto.AccountExpense;
import de.financer.model.Account;
import de.financer.service.AccountService;
import org.slf4j.Logger;
@@ -101,4 +102,13 @@ public class AccountController {
return response;
}
@RequestMapping("getAccountExpenses")
public Iterable<AccountExpense> getAccountExpenses(String periodStart, String periodEnd) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("/accounts/getAccountExpenses got parameters: %s|%s", periodStart, periodEnd));
}
return this.accountService.getAccountExpenses(periodStart, periodEnd);
}
}

View File

@@ -1,5 +1,6 @@
package de.financer.dba;
import de.financer.dto.AccountExpense;
import de.financer.model.Account;
import de.financer.model.AccountType;
import org.springframework.data.jpa.repository.Query;
@@ -7,10 +8,15 @@ 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 AccountRepository extends CrudRepository<Account, Long> {
Account findByKey(String key);
@Query("SELECT SUM(a.currentBalance) FROM Account a WHERE a.type IN :accountTypes")
Long getCurrentAssets(AccountType... accountTypes);
@Query("SELECT new de.financer.dto.AccountExpense(a, SUM(t.amount)) FROM Transaction t JOIN t.toAccount a WHERE a.type in :expenseTypes AND t.date BETWEEN :startDate AND :startEnd GROUP BY a")
Iterable<AccountExpense> getAccountExpenses(LocalDate startDate, LocalDate startEnd, AccountType... expenseTypes);
}

View File

@@ -1,7 +1,9 @@
package de.financer.service;
import de.financer.ResponseReason;
import de.financer.config.FinancerConfig;
import de.financer.dba.AccountRepository;
import de.financer.dto.AccountExpense;
import de.financer.model.Account;
import de.financer.model.AccountGroup;
import de.financer.model.AccountStatus;
@@ -15,8 +17,10 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Collections;
@Service
public class AccountService {
@@ -24,9 +28,13 @@ public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private AccountGroupService accountGroupService;
@Autowired
private FinancerConfig financerConfig;
/**
* This method returns the account identified by the given key.
*
@@ -152,4 +160,22 @@ public class AccountService {
public Long getCurrentAssets() {
return this.accountRepository.getCurrentAssets(AccountType.BANK, AccountType.CASH);
}
public Iterable<AccountExpense> getAccountExpenses(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.accountRepository.getAccountExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE);
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package de.financer.chart;
import de.financer.chart.impl.AccountGroupExpensesGenerator;
import de.financer.chart.impl.expense.AccountExpensesGenerator;
import de.financer.chart.impl.expense.AccountGroupExpensesGenerator;
import de.financer.config.FinancerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
@@ -19,9 +20,17 @@ public class FinancerChartFactory {
switch (chartType) {
case ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD:
// Fall through
case ACCOUNT_GROUP_EXPENSES_FOR_PERIOD:
// TODO WHY IS THIS CAST NECESSARY???
generator = (AbstractChartGenerator<P>) new AccountGroupExpensesGenerator();
break;
case ACCOUNT_EXPENSES_CURRENT_PERIOD:
// Fall through
case ACCOUNT_EXPENSES_FOR_PERIOD:
// TODO WHY IS THIS CAST NECESSARY???
generator = (AbstractChartGenerator<P>) new AccountExpensesGenerator();
break;
default:
generator = null;
}

View File

@@ -1,30 +1,24 @@
package de.financer.chart.impl;
package de.financer.chart.impl.expense;
import de.financer.chart.AbstractChartGenerator;
import de.financer.dto.AccountGroupExpense;
import de.financer.template.GetAccountGroupExpensesTemplate;
import de.financer.config.FinancerConfig;
import de.financer.util.ControllerUtils;
import org.apache.commons.collections4.IterableUtils;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.springframework.context.i18n.LocaleContextHolder;
import java.text.NumberFormat;
public class AccountGroupExpensesGenerator extends AbstractChartGenerator<AccountGroupExpensesParameter> {
public abstract class AbstractExpensesGenerator extends AbstractChartGenerator<ExpensesParameter> {
@Override
public JFreeChart generateChart(AccountGroupExpensesParameter parameter) {
public JFreeChart generateChart(ExpensesParameter parameter) {
final String start = ControllerUtils.formatDate(this.getFinancerConfig(), parameter.getPeriodStart());
final String end = ControllerUtils.formatDate(this.getFinancerConfig(), parameter.getPeriodEnd());
final Iterable<AccountGroupExpense> expenses = new GetAccountGroupExpensesTemplate()
.exchange(this.getFinancerConfig(), start, end).getBody();
final PieDataset dataSet = createDataSet(expenses);
final PieDataset dataSet = getDataset(start, end, this.getFinancerConfig());
final JFreeChart chart = ChartFactory
.createPieChart(this.getMessage(parameter.getTitle(), parameter.getArgsForTitle()), dataSet);
@@ -40,12 +34,19 @@ public class AccountGroupExpensesGenerator extends AbstractChartGenerator<Accoun
return chart;
}
private PieDataset createDataSet(Iterable<AccountGroupExpense> data) {
final DefaultPieDataset dataSet = new DefaultPieDataset();
IterableUtils.toList(data).stream()
.forEach((ex) -> dataSet.setValue(ex.getAccountGroup().getName(), (ex.getExpense() / 100D)));
return dataSet;
}
/**
* This method obtains the chart data via a REST call from the backend server application and transforms them into
* a compatible dataset.
*
* TODO Getting the data here seems kinda wrong as the data access is usually done in the controllers. However,
* TODO this would require to pass the data from the calling controller via the ExpensesParameter. To do this
* TODO the parameter class either needs to be extended into two subclasses that each declare the corresponding data
* TODO field or the data has to be generified.
*
* @param start - the start of the period
* @param end - the end of the period
* @param financerConfig - the financer config that holds the REST endpoint config
* @return the created dataset
*/
protected abstract PieDataset getDataset(String start, String end, FinancerConfig financerConfig);
}

View File

@@ -0,0 +1,25 @@
package de.financer.chart.impl.expense;
import de.financer.config.FinancerConfig;
import de.financer.dto.AccountExpense;
import de.financer.dto.AccountGroupExpense;
import de.financer.template.GetAccountExpensesTemplate;
import de.financer.template.GetAccountGroupExpensesTemplate;
import org.apache.commons.collections4.IterableUtils;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
public class AccountExpensesGenerator extends AbstractExpensesGenerator {
@Override
protected PieDataset getDataset(String start, String end, FinancerConfig financerConfig) {
final Iterable<AccountExpense> expenses = new GetAccountExpensesTemplate()
.exchange(financerConfig, start, end).getBody();
final DefaultPieDataset dataSet = new DefaultPieDataset();
IterableUtils.toList(expenses).stream()
.forEach((ex) -> dataSet.setValue(ex.getAccount().getKey(), (ex.getExpense() / 100D)));
return dataSet;
}
}

View File

@@ -0,0 +1,23 @@
package de.financer.chart.impl.expense;
import de.financer.config.FinancerConfig;
import de.financer.dto.AccountGroupExpense;
import de.financer.template.GetAccountGroupExpensesTemplate;
import org.apache.commons.collections4.IterableUtils;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
public class AccountGroupExpensesGenerator extends AbstractExpensesGenerator {
@Override
protected PieDataset getDataset(String start, String end, FinancerConfig financerConfig) {
final Iterable<AccountGroupExpense> expenses = new GetAccountGroupExpensesTemplate()
.exchange(financerConfig, start, end).getBody();
final DefaultPieDataset dataSet = new DefaultPieDataset();
IterableUtils.toList(expenses).stream()
.forEach((ex) -> dataSet.setValue(ex.getAccountGroup().getName(), (ex.getExpense() / 100D)));
return dataSet;
}
}

View File

@@ -1,10 +1,10 @@
package de.financer.chart.impl;
package de.financer.chart.impl.expense;
import de.financer.chart.ChartParameter;
import java.time.LocalDate;
public class AccountGroupExpensesParameter implements ChartParameter {
public class ExpensesParameter implements ChartParameter {
private LocalDate periodStart;
private LocalDate periodEnd;
private String title;

View File

@@ -1,10 +1,11 @@
package de.financer.controller;
import de.financer.chart.impl.AccountGroupExpensesParameter;
import de.financer.chart.impl.expense.ExpensesParameter;
import de.financer.chart.ChartGenerator;
import de.financer.chart.ChartType;
import de.financer.chart.FinancerChartFactory;
import de.financer.config.FinancerConfig;
import de.financer.form.ConfigAccountExpenseForPeriodForm;
import de.financer.form.ConfigAccountGroupExpenseForPeriodForm;
import de.financer.util.ControllerUtils;
import de.financer.util.ExpensePeriod;
@@ -31,13 +32,13 @@ public class ChartController {
public void getAccountGroupExpensesCurrentPeriod(HttpServletResponse response) {
final ExpensePeriod period = ExpensePeriod
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
final AccountGroupExpensesParameter parameter = new AccountGroupExpensesParameter();
final ExpensesParameter parameter = new ExpensesParameter();
parameter.setPeriodStart(period.getStart());
parameter.setPeriodEnd(period.getEnd());
parameter.setTitle("financer.chart.account-group-expenses-current-period.title");
final ChartGenerator<AccountGroupExpensesParameter> generator =
final ChartGenerator<ExpensesParameter> generator =
this.financerChartFactory.getGenerator(ChartType.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD);
final JFreeChart chart = generator.generateChart(parameter);
@@ -47,7 +48,7 @@ public class ChartController {
@PostMapping("/getAccountGroupExpensesForPeriod")
public void getAccountGroupExpensesForPeriod(HttpServletResponse response, ConfigAccountGroupExpenseForPeriodForm form) {
final AccountGroupExpensesParameter parameter = new AccountGroupExpensesParameter();
final ExpensesParameter parameter = new ExpensesParameter();
parameter.setPeriodStart(ControllerUtils.parseDate(this.financerConfig, form.getFromDate()));
parameter.setPeriodEnd(ControllerUtils.parseDate(this.financerConfig, form.getToDate()));
@@ -57,8 +58,46 @@ public class ChartController {
.getLocale()), ControllerUtils.formatDate(this.financerConfig, form.getToDate(), LocaleContextHolder
.getLocale())});
final ChartGenerator<AccountGroupExpensesParameter> generator =
this.financerChartFactory.getGenerator(ChartType.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD);
final ChartGenerator<ExpensesParameter> generator =
this.financerChartFactory.getGenerator(ChartType.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD);
final JFreeChart chart = generator.generateChart(parameter);
writeChart(response, chart);
}
@GetMapping("/getAccountExpensesCurrentPeriod")
public void getAccountExpensesCurrentPeriod(HttpServletResponse response) {
final ExpensePeriod period = ExpensePeriod
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
final ExpensesParameter parameter = new ExpensesParameter();
parameter.setPeriodStart(period.getStart());
parameter.setPeriodEnd(period.getEnd());
parameter.setTitle("financer.chart.account-expenses-current-period.title");
final ChartGenerator<ExpensesParameter> generator =
this.financerChartFactory.getGenerator(ChartType.ACCOUNT_EXPENSES_CURRENT_PERIOD);
final JFreeChart chart = generator.generateChart(parameter);
writeChart(response, chart);
}
@PostMapping("/getAccountExpensesForPeriod")
public void getAccountExpensesForPeriod(HttpServletResponse response, ConfigAccountExpenseForPeriodForm form) {
final ExpensesParameter parameter = new ExpensesParameter();
parameter.setPeriodStart(ControllerUtils.parseDate(this.financerConfig, form.getFromDate()));
parameter.setPeriodEnd(ControllerUtils.parseDate(this.financerConfig, form.getToDate()));
parameter.setTitle("financer.chart.account-expenses-for-period.title");
parameter.setArgsForTitle(new Object[]{ControllerUtils.formatDate(this.financerConfig, form
.getFromDate(), LocaleContextHolder
.getLocale()), ControllerUtils.formatDate(this.financerConfig, form.getToDate(), LocaleContextHolder
.getLocale())});
final ChartGenerator<ExpensesParameter> generator =
this.financerChartFactory.getGenerator(ChartType.ACCOUNT_EXPENSES_FOR_PERIOD);
final JFreeChart chart = generator.generateChart(parameter);

View File

@@ -7,6 +7,7 @@ public enum Function {
ACC_CLOSE_ACCOUNT("accounts/closeAccount"),
ACC_OPEN_ACCOUNT("accounts/openAccount"),
ACC_CURRENT_ASSETS("accounts/getCurrentAssets"),
ACC_GET_ACC_EXPENSES("accounts/getAccountExpenses"),
ACC_GP_CREATE_ACCOUNT_GROUP("accountGroups/createAccountGroup"),
ACC_GP_GET_ALL("accountGroups/getAll"),

View File

@@ -2,6 +2,7 @@ package de.financer.controller;
import de.financer.chart.ChartType;
import de.financer.config.FinancerConfig;
import de.financer.form.ConfigAccountExpenseForPeriodForm;
import de.financer.form.ConfigAccountGroupExpenseForPeriodForm;
import de.financer.form.SelectChartForm;
import de.financer.util.ControllerUtils;
@@ -56,6 +57,16 @@ public class ReportController {
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
return "report/configureAccountGroupExpensesForPeriod";
case ACCOUNT_EXPENSES_CURRENT_PERIOD:
// Special case: this chart does not require parameters, so we can
// directly redirect to the actual chart instead of the config page
return "redirect:/getAccountExpensesCurrentPeriod";
case ACCOUNT_EXPENSES_FOR_PERIOD:
model.addAttribute("form", new ConfigAccountExpenseForPeriodForm());
ControllerUtils.addVersionAttribute(model, this.financerConfig);
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
return "report/configureAccountExpensesForPeriod";
default:
// Cannot happen
throw new IllegalStateException("Unexpected value: " + selectedChartType);

View File

@@ -0,0 +1,22 @@
package de.financer.form;
public class ConfigAccountExpenseForPeriodForm {
private String fromDate;
private String toDate;
public String getFromDate() {
return fromDate;
}
public void setFromDate(String fromDate) {
this.fromDate = fromDate;
}
public String getToDate() {
return toDate;
}
public void setToDate(String toDate) {
this.toDate = toDate;
}
}

View File

@@ -0,0 +1,23 @@
package de.financer.template;
import de.financer.config.FinancerConfig;
import de.financer.controller.Function;
import de.financer.dto.AccountExpense;
import de.financer.dto.AccountGroupExpense;
import de.financer.util.ControllerUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriComponentsBuilder;
public class GetAccountExpensesTemplate {
public ResponseEntity<Iterable<AccountExpense>> exchange(FinancerConfig financerConfig, String start, String end) {
final UriComponentsBuilder expensesBuilder = UriComponentsBuilder
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.ACC_GET_ACC_EXPENSES))
.queryParam("periodStart", start)
.queryParam("periodEnd", end);
return new FinancerRestTemplate<Iterable<AccountExpense>>()
.exchange(expensesBuilder.toUriString(), new ParameterizedTypeReference<Iterable<AccountExpense>>() {
});
}
}

View File

@@ -115,6 +115,11 @@ financer.chart-config-account-group-expenses-for-period.label.from-date=From dat
financer.chart-config-account-group-expenses-for-period.label.to-date=To date\:
financer.chart-config-account-group-expenses-for-period.submit=Generate
financer.chart-config-account-expenses-for-period.title=Configure account expenses for period chart
financer.chart-config-account-expenses-for-period.label.from-date=From date\:
financer.chart-config-account-expenses-for-period.label.to-date=To date\:
financer.chart-config-account-expenses-for-period.submit=Generate
financer.interval-type.DAILY=Daily
financer.interval-type.WEEKLY=Weekly
financer.interval-type.MONTHLY=Monthly
@@ -147,14 +152,19 @@ financer.heading.recurring-transaction-list.all=financer\: all recurring transac
financer.heading.recurring-to-transaction-with-amount=financer\: create transaction from recurring with amount
financer.heading.chart-select=financer\: select a chart to generate
financer.heading.chart-config-account-group-expenses-for-period=financer\: configure account group expenses for period chart
financer.heading.chart-config-account-expenses-for-period=financer\: configure account expenses for period chart
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.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-for-period.title=Expenses for period from {0} to {1} grouped by account
financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account group
financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account group
financer.chart.name.ACCOUNT_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account
financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account
financer.error-message.UNKNOWN_ERROR=An unknown error occurred!
financer.error-message.INVALID_ACCOUNT_TYPE=The selected account type is not valid!

View File

@@ -115,6 +115,11 @@ financer.chart-config-account-group-expenses-for-period.label.from-date=Von Datu
financer.chart-config-account-group-expenses-for-period.label.to-date=Bis Datum\:
financer.chart-config-account-group-expenses-for-period.submit=Erzeugen
financer.chart-config-account-expenses-for-period.title=Konfigurieren von Ausgaben f\u00FCr Periode gruppiert nach Konto Diagramm
financer.chart-config-account-expenses-for-period.label.from-date=Von Datum\:
financer.chart-config-account-expenses-for-period.label.to-date=Bis Datum\:
financer.chart-config-account-expenses-for-period.submit=Erzeugen
financer.interval-type.DAILY=T\u00E4glich
financer.interval-type.WEEKLY=W\u00F6chentlich
financer.interval-type.MONTHLY=Monatlich
@@ -152,6 +157,10 @@ 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
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-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto
financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe
financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto-Gruppe
financer.chart.name.ACCOUNT_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto
financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto

View File

@@ -2,3 +2,4 @@ v16 -> v17:
- Add this changelog to the footer
- Locale of the recurring transaction reminder email is now configurable
- Recurring transaction indicator in transaction overview (account details) is now properly translated
- Add chart report to visualize the expenses of the current/a configurable period grouped by account

View File

@@ -68,7 +68,8 @@ tr:hover {
#new-recurring-transaction-form *,
#recurring-to-transaction-with-amount-form *,
#new-account-group-form *,
#chart-config-account-group-expenses-for-period-form * {
#chart-config-account-group-expenses-for-period-form *,
#chart-config-account-expenses-for-period-form * {
display: block;
margin-top: 1em;
width: 20em;

View File

@@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{financer.chart-config-account-expenses-for-period.title}"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" th:href="@{/css/main.css}">
</head>
<body>
<h1 th:text="#{financer.heading.chart-config-account-expenses-for-period}"/>
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<form id="chart-config-account-expenses-for-period-form" action="#"
th:action="@{/getAccountExpensesForPeriod}" th:object="${form}" method="post">
<label for="fromDate" th:text="#{financer.chart-config-account-expenses-for-period.label.from-date}"/>
<input type="date" id="fromDate" th:field="*{fromDate}"/>
<label for="toDate" th:text="#{financer.chart-config-account-expenses-for-period.label.to-date}"/>
<input type="date" id="toDate" th:field="*{toDate}"/>
<input type="submit" th:value="#{financer.chart-config-account-expenses-for-period.submit}"/>
</form>
<div th:replace="includes/footer :: footer" />
</body>
</html>