Add FQL and rework /transaction endpoint
This commit is contained in:
@@ -4,8 +4,6 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
@SpringBootApplication
|
||||
public class FinancerApplication extends SpringBootServletInitializer {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package de.financer;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
public enum ResponseReason {
|
||||
OK(HttpStatus.OK),
|
||||
UNKNOWN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_ACCOUNT_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
FROM_ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
TO_ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
FROM_AND_TO_ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_DATE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_DATE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
AMOUNT_ZERO(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_AMOUNT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_BOOKING_ACCOUNTS(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_HOLIDAY_WEEKEND_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_HOLIDAY_WEEKEND_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_INTERVAL_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_INTERVAL_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_FIRST_OCCURRENCE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_FIRST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_LAST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_RECURRING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_RECURRING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
RECURRING_TRANSACTION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
TRANSACTION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
DUPLICATE_ACCOUNT_KEY(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
DUPLICATE_ACCOUNT_GROUP_NAME(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
ACCOUNT_GROUP_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
|
||||
private HttpStatus httpStatus;
|
||||
|
||||
ResponseReason(HttpStatus httpStatus) {
|
||||
this.httpStatus = httpStatus;
|
||||
}
|
||||
|
||||
public ResponseEntity toResponseEntity() {
|
||||
return new ResponseEntity<>(this.name(), this.httpStatus);
|
||||
}
|
||||
|
||||
public static ResponseReason fromResponseEntity(ResponseEntity<String> entity) {
|
||||
for (ResponseReason reason : values()) {
|
||||
if (reason.name().equals(entity.getBody())) {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,18 @@ public enum ChartType {
|
||||
EXPENSE_PERIOD_TOTALS_CURRENT_YEAR(false),
|
||||
EXPENSES_ALL_PERIODS_INLINE(true);
|
||||
|
||||
private boolean inline;
|
||||
private final boolean inline;
|
||||
|
||||
ChartType(boolean inline) {
|
||||
this.inline = inline;
|
||||
}
|
||||
|
||||
public static List<String> valueList(boolean filterInline) {
|
||||
return Arrays.stream(ChartType.values()).filter((ct) -> filterInline ? !ct.inline : true).map(ChartType::name)
|
||||
return Arrays.stream(ChartType.values()).filter((ct) -> !filterInline || !ct.inline).map(ChartType::name)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static ChartType getByValue(String value) {
|
||||
return Arrays.asList(values()).stream().filter((ct) -> ct.name().equals(value)).findFirst().get();
|
||||
return Arrays.stream(values()).filter((ct) -> ct.name().equals(value)).findFirst().get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ 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.GetAccountExpensesCurrentExpensePeriodTemplate;
|
||||
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;
|
||||
@@ -24,7 +22,7 @@ public class AccountExpensesGenerator extends AbstractExpensesGenerator {
|
||||
|
||||
final DefaultPieDataset dataSet = new DefaultPieDataset();
|
||||
|
||||
IterableUtils.toList(expenses).stream()
|
||||
IterableUtils.toList(expenses)
|
||||
.forEach((ex) -> dataSet.setValue(ex.getAccount().getKey(), (ex.getExpense() / 100D)));
|
||||
|
||||
return dataSet;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class AccountGroupExpensesGenerator extends AbstractExpensesGenerator {
|
||||
|
||||
final DefaultPieDataset dataSet = new DefaultPieDataset();
|
||||
|
||||
IterableUtils.toList(expenses).stream()
|
||||
IterableUtils.toList(expenses)
|
||||
.forEach((ex) -> dataSet.setValue(ex.getAccountGroup().getName(), (ex.getExpense() / 100D)));
|
||||
|
||||
return dataSet;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ExpensesAllPeriodsGenerator extends AbstractChartGenerator<EmptyPar
|
||||
final List<Long> totalData = new GetExpensesAllPeriodsTemplate().exchange(this.getFinancerConfig()).getBody();
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
totalData.stream().forEach((l) -> result.addValue(l, "c", "r" + counter.incrementAndGet()));
|
||||
totalData.forEach((l) -> result.addValue(l, "c", "r" + counter.incrementAndGet()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class PeriodTotalGenerator extends AbstractChartGenerator<PeriodTotalPara
|
||||
|
||||
IterableUtils.toList(totalData).stream()
|
||||
.sorted(Comparator.comparing((ExpensePeriodTotal ept) -> ept.getPeriod().getStart())
|
||||
.thenComparing((ExpensePeriodTotal ept) -> ept.getType()))
|
||||
.thenComparing(ExpensePeriodTotal::getType))
|
||||
.forEach((ept) -> result.addValue((ept.getTotal() / 100D),
|
||||
this.getMessage("financer.account-type." + ept.getType()),
|
||||
formatDateY(ept.getPeriod())));
|
||||
|
||||
@@ -3,12 +3,14 @@ package de.financer.controller;
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.decorator.AccountDecorator;
|
||||
import de.financer.template.*;
|
||||
import de.financer.dto.Order;
|
||||
import de.financer.dto.SearchTransactionsResponseDto;
|
||||
import de.financer.form.NewAccountForm;
|
||||
import de.financer.model.*;
|
||||
import de.financer.template.*;
|
||||
import de.financer.template.exception.FinancerRestException;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import de.financer.util.TransactionUtils;
|
||||
import de.financer.util.comparator.TransactionByDateByIdDescComparator;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -57,7 +59,7 @@ public class AccountController {
|
||||
}
|
||||
|
||||
private Iterable<AccountDecorator> decorateAccounts(List<Account> accounts) {
|
||||
return accounts.stream().map((a) -> new AccountDecorator(a)).collect(Collectors.toList());
|
||||
return accounts.stream().map(AccountDecorator::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/newAccount")
|
||||
@@ -102,21 +104,40 @@ public class AccountController {
|
||||
return "redirect:/accountOverview";
|
||||
}
|
||||
|
||||
public static void _accountDetails(String key, Model model, FinancerConfig financerConfig) {
|
||||
try {
|
||||
final Iterable<SearchTransactionsResponseDto> response =
|
||||
new SearchTransactionsTemplate()
|
||||
.exchangeGet(financerConfig, key, key, null, null,
|
||||
Order.TRANSACTIONS_BY_DATE_DESC, null, false);
|
||||
|
||||
final List<SearchTransactionsResponseDto> transactions = IterableUtils.toList(response);
|
||||
final Account account = IterableUtils.toList(response).stream()
|
||||
.filter(t -> t.getFromAccount().getKey().equals(key))
|
||||
.findFirst()
|
||||
.map(SearchTransactionsResponseDto::getFromAccount)
|
||||
.orElseGet(() -> transactions
|
||||
.stream()
|
||||
.filter(t -> t.getToAccount().getKey().equals(key))
|
||||
.findFirst()
|
||||
.map(SearchTransactionsResponseDto::getToAccount).get());
|
||||
|
||||
transactions.forEach((t) -> TransactionUtils.adjustAmount(t, account));
|
||||
|
||||
model.addAttribute("account", account);
|
||||
model.addAttribute("transactions", transactions);
|
||||
model.addAttribute("showActions", true);
|
||||
model.addAttribute("isClosed", AccountStatus.CLOSED.equals(account.getStatus()));
|
||||
}
|
||||
catch(FinancerRestException e) {
|
||||
// TODO probably leads to follow-up exceptions during thymeleaf parsing as relevant information are missing
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/accountDetails")
|
||||
public String accountDetails(String key, Model model) {
|
||||
final ResponseEntity<Account> response = new GetAccountByKeyTemplate().exchange(this.financerConfig, key);
|
||||
final Account account = response.getBody();
|
||||
final ResponseEntity<Iterable<Transaction>> transactionResponse = new GetAllTransactionsForAccountTemplate()
|
||||
.exchange(this.financerConfig, account.getKey());
|
||||
_accountDetails(key, model, this.financerConfig);
|
||||
|
||||
List<Transaction> transactions = IterableUtils.toList(transactionResponse.getBody());
|
||||
|
||||
transactions.sort(new TransactionByDateByIdDescComparator());
|
||||
transactions.stream().forEach((t) -> TransactionUtils.adjustAmount(t, account));
|
||||
|
||||
model.addAttribute("account", account);
|
||||
model.addAttribute("transactions", transactions);
|
||||
model.addAttribute("isClosed", AccountStatus.CLOSED.equals(account.getStatus()));
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
@@ -134,19 +155,8 @@ public class AccountController {
|
||||
final ResponseReason responseReason = ResponseReason.fromResponseEntity(closeResponse);
|
||||
|
||||
if (!ResponseReason.OK.equals(responseReason)) {
|
||||
final ResponseEntity<Account> response = new GetAccountByKeyTemplate().exchange(this.financerConfig, key);
|
||||
final Account account = response.getBody();
|
||||
final ResponseEntity<Iterable<Transaction>> transactionResponse = new GetAllTransactionsForAccountTemplate()
|
||||
.exchange(this.financerConfig, account.getKey());
|
||||
_accountDetails(key, model, this.financerConfig);
|
||||
|
||||
List<Transaction> transactions = IterableUtils.toList(transactionResponse.getBody());
|
||||
|
||||
transactions.sort(new TransactionByDateByIdDescComparator());
|
||||
transactions.stream().forEach((t) -> TransactionUtils.adjustAmount(t, account));
|
||||
|
||||
model.addAttribute("account", account);
|
||||
model.addAttribute("transactions", transactions);
|
||||
model.addAttribute("isClosed", AccountStatus.CLOSED.equals(account.getStatus()));
|
||||
model.addAttribute("errorMessage", responseReason.name());
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
@@ -168,19 +178,8 @@ public class AccountController {
|
||||
final ResponseReason responseReason = ResponseReason.fromResponseEntity(closeResponse);
|
||||
|
||||
if (!ResponseReason.OK.equals(responseReason)) {
|
||||
final ResponseEntity<Account> response = new GetAccountByKeyTemplate().exchange(this.financerConfig, key);
|
||||
final Account account = response.getBody();
|
||||
final ResponseEntity<Iterable<Transaction>> transactionResponse = new GetAllTransactionsForAccountTemplate()
|
||||
.exchange(this.financerConfig, account.getKey());
|
||||
_accountDetails(key, model, this.financerConfig);
|
||||
|
||||
List<Transaction> transactions = IterableUtils.toList(transactionResponse.getBody());
|
||||
|
||||
transactions.sort(new TransactionByDateByIdDescComparator());
|
||||
transactions.stream().forEach((t) -> TransactionUtils.adjustAmount(t, account));
|
||||
|
||||
model.addAttribute("account", account);
|
||||
model.addAttribute("transactions", transactions);
|
||||
model.addAttribute("isClosed", AccountStatus.CLOSED.equals(account.getStatus()));
|
||||
model.addAttribute("errorMessage", responseReason.name());
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
@@ -15,10 +15,10 @@ public enum Function {
|
||||
ACC_GP_GET_ACC_GP_EXPENSES("accountGroups/getAccountGroupExpenses"),
|
||||
ACC_GP_GET_ACC_GP_EXPENSES_CURRENT_EXPENSE_PERIOD("accountGroups/getAccountGroupExpensesCurrentExpensePeriod"),
|
||||
|
||||
TR_GET_ALL("transactions/getAll"),
|
||||
TR_GET_ALL_FOR_ACCOUNT("transactions/getAllForAccount"),
|
||||
TR_CREATE_TRANSACTION("transactions/createTransaction"),
|
||||
TR_DELETE_TRANSACTION("transactions/deleteTransaction"),
|
||||
TR_SEARCH("transactions"),
|
||||
TR_SEARCH_BY_FQL("transactionsByFql"),
|
||||
TR_CREATE_TRANSACTION("transactions"),
|
||||
TR_DELETE_TRANSACTION("transactions/"),
|
||||
TR_EXPENSES_CURRENT_PERIOD("transactions/getExpensesCurrentPeriod"),
|
||||
TR_EXPENSE_PERIOD_TOTALS("transactions/getExpensePeriodTotals"),
|
||||
TR_EXPENSES_ALL_PERIODS("transactions/getExpensesAllPeriods"),
|
||||
@@ -34,7 +34,7 @@ public enum Function {
|
||||
P_GET_CURRENT_EXPENSE_PERIOD("periods/getCurrentExpensePeriod"),
|
||||
P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod");
|
||||
|
||||
private String path;
|
||||
private final String path;
|
||||
|
||||
Function(String path) {
|
||||
this.path = path;
|
||||
|
||||
@@ -2,21 +2,17 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dto.SaveTransactionRequestDto;
|
||||
import de.financer.dto.SearchTransactionsResponseDto;
|
||||
import de.financer.form.SearchTransactionsForm;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.template.GetAccountByKeyTemplate;
|
||||
import de.financer.template.GetAllAccountsTemplate;
|
||||
import de.financer.template.GetAllTransactionsForAccountTemplate;
|
||||
import de.financer.template.StringTemplate;
|
||||
import de.financer.template.*;
|
||||
import de.financer.form.NewTransactionForm;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountStatus;
|
||||
import de.financer.model.Transaction;
|
||||
import de.financer.template.exception.FinancerRestException;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import de.financer.util.TransactionUtils;
|
||||
import de.financer.util.comparator.TransactionByDateByIdDescComparator;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -24,6 +20,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@@ -32,20 +29,77 @@ public class TransactionController {
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@GetMapping("/searchTransactions")
|
||||
public String searchTransaction(Model model) {
|
||||
model.addAttribute("form", new SearchTransactionsForm());
|
||||
model.addAttribute("showActions", false);
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
return "transaction/searchTransactions";
|
||||
}
|
||||
|
||||
@PostMapping("/searchTransactions")
|
||||
public String searchTransactions(SearchTransactionsForm form, Model model) {
|
||||
try {
|
||||
final UriComponentsBuilder transactionBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.TR_SEARCH_BY_FQL));
|
||||
|
||||
transactionBuilder.queryParam("fql", form.getFql());
|
||||
|
||||
final Iterable<SearchTransactionsResponseDto> trxs = FinancerRestTemplate.exchangeGet(transactionBuilder,
|
||||
new ParameterizedTypeReference<Iterable<SearchTransactionsResponseDto>>() {
|
||||
});
|
||||
|
||||
|
||||
model.addAttribute("transactions", trxs);
|
||||
}
|
||||
catch(FinancerRestException e) {
|
||||
// TODO
|
||||
model.addAttribute("errorMessage", e.getResponseReason().name());
|
||||
}
|
||||
|
||||
model.addAttribute("form", form);
|
||||
model.addAttribute("showActions", false);
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
return "transaction/searchTransactions";
|
||||
}
|
||||
|
||||
@GetMapping("/newTransaction")
|
||||
public String newTransaction(Model model) {
|
||||
final ResponseEntity<Iterable<Account>> response = new GetAllAccountsTemplate().exchange(this.financerConfig);
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(response.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(response.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
return _newTransaction(model, new NewTransactionForm(), Optional.empty());
|
||||
}
|
||||
|
||||
private String _newTransaction(Model model, NewTransactionForm form, Optional<ResponseReason> responseReason) {
|
||||
try {
|
||||
final Iterable<Account> allAccounts =
|
||||
FinancerRestTemplate.exchangeGet(this.financerConfig,
|
||||
Function.ACC_GET_ALL,
|
||||
new ParameterizedTypeReference<Iterable<Account>>() {});
|
||||
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(allAccounts).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(allAccounts).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
}
|
||||
catch(FinancerRestException e) {
|
||||
// Nothing to do
|
||||
// This is very unlikely to happen and if it happens the account selection stays empty, so the user
|
||||
// cannot create a transaction anyway and is forced to reload the page or navigate back
|
||||
}
|
||||
|
||||
model.addAttribute("form", form);
|
||||
|
||||
responseReason.ifPresent(rr -> model.addAttribute("errorMessage", rr.name()));
|
||||
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
model.addAttribute("form", new NewTransactionForm());
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
@@ -54,36 +108,20 @@ public class TransactionController {
|
||||
|
||||
@PostMapping("/saveTransaction")
|
||||
public String saveTransaction(NewTransactionForm form, Model model) {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.TR_CREATE_TRANSACTION))
|
||||
.queryParam("fromAccountKey", form.getFromAccountKey())
|
||||
.queryParam("toAccountKey", form.getToAccountKey())
|
||||
.queryParam("amount", form.getAmount())
|
||||
.queryParam("date", ControllerUtils.formatDate(this.financerConfig, form.getDate()))
|
||||
.queryParam("description", form.getDescription())
|
||||
.queryParam("taxRelevant", form.getTaxRelevant());
|
||||
final SaveTransactionRequestDto requestDto = new SaveTransactionRequestDto();
|
||||
|
||||
final ResponseEntity<String> response = new StringTemplate().exchange(builder);
|
||||
final ResponseReason responseReason = ResponseReason.fromResponseEntity(response);
|
||||
requestDto.setFromAccountKey(form.getFromAccountKey());
|
||||
requestDto.setToAccountKey(form.getToAccountKey());
|
||||
requestDto.setAmount(form.getAmount());
|
||||
requestDto.setDate(ControllerUtils.formatDate(this.financerConfig, form.getDate()));
|
||||
requestDto.setDescription(form.getDescription());
|
||||
requestDto.setTaxRelevant(form.getTaxRelevant());
|
||||
|
||||
if (!ResponseReason.OK.equals(responseReason)) {
|
||||
final ResponseEntity<Iterable<Account>> accRes = new GetAllAccountsTemplate().exchange(this.financerConfig);
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(accRes.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(accRes.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
final ResponseReason responseReason = FinancerRestTemplate
|
||||
.exchangePost(this.financerConfig, Function.TR_CREATE_TRANSACTION, requestDto);
|
||||
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
model.addAttribute("form", form);
|
||||
model.addAttribute("errorMessage", responseReason.name());
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
return "transaction/newTransaction";
|
||||
if (!ResponseReason.CREATED.equals(responseReason)) {
|
||||
return _newTransaction(model, form, Optional.of(responseReason));
|
||||
}
|
||||
|
||||
return "redirect:/accountOverview";
|
||||
@@ -91,34 +129,16 @@ public class TransactionController {
|
||||
|
||||
@GetMapping("/deleteTransaction")
|
||||
public String deleteTransaction(String transactionId, String accountKey, Model model) {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.TR_DELETE_TRANSACTION))
|
||||
.queryParam("transactionId", transactionId);
|
||||
final ResponseReason responseReason = FinancerRestTemplate
|
||||
.exchangeDelete(this.financerConfig, Function.TR_DELETE_TRANSACTION, transactionId);
|
||||
|
||||
final ResponseEntity<String> response = new StringTemplate().exchange(builder);
|
||||
final ResponseReason responseReason = ResponseReason.fromResponseEntity(response);
|
||||
AccountController._accountDetails(accountKey, model, this.financerConfig);
|
||||
|
||||
final ResponseEntity<Account> accountResponse = new GetAccountByKeyTemplate()
|
||||
.exchange(this.financerConfig, accountKey);
|
||||
final Account account = accountResponse.getBody();
|
||||
final ResponseEntity<Iterable<Transaction>> transactionResponse = new GetAllTransactionsForAccountTemplate()
|
||||
.exchange(this.financerConfig, account.getKey());
|
||||
|
||||
List<Transaction> transactions = IterableUtils.toList(transactionResponse.getBody());
|
||||
|
||||
transactions.sort(new TransactionByDateByIdDescComparator());
|
||||
transactions.stream().forEach((t) -> TransactionUtils.adjustAmount(t, account));
|
||||
|
||||
model.addAttribute("account", account);
|
||||
model.addAttribute("transactions", transactions);
|
||||
model.addAttribute("isClosed", AccountStatus.CLOSED.equals(account.getStatus()));
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
if (!ResponseReason.OK.equals(responseReason)) {
|
||||
model.addAttribute("errorMessage", responseReason.name());
|
||||
|
||||
return "account/accountDetails";
|
||||
}
|
||||
|
||||
return "account/accountDetails";
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package de.financer.controller.handler;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NoExceptionResponseErrorHandler implements ResponseErrorHandler {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse response) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class AccountDecorator {
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
AccountStatistic as = new AccountStatistic();
|
||||
as.setTransactionCountTo(0l);
|
||||
as.setTransactionCountTo(0L);
|
||||
return as;
|
||||
})
|
||||
.getSpendingTotalTo();
|
||||
@@ -55,9 +55,9 @@ public class AccountDecorator {
|
||||
return Math.round(this.account.getAccountStatistics().stream()
|
||||
.filter((as) -> as.getPeriod().getType().equals(PeriodType.EXPENSE) && as.getPeriod()
|
||||
.getEnd() != null)
|
||||
.mapToLong((as) -> as.getSpendingTotalTo())
|
||||
.mapToLong(AccountStatistic::getSpendingTotalTo)
|
||||
.average()
|
||||
.orElseGet(() -> Double.valueOf(0d)));
|
||||
.orElseGet(() -> 0d));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.financer.form;
|
||||
|
||||
public class SearchTransactionsForm {
|
||||
private String fql;
|
||||
|
||||
public String getFql() {
|
||||
return fql;
|
||||
}
|
||||
|
||||
public void setFql(String fql) {
|
||||
this.fql = fql;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,133 @@
|
||||
package de.financer.template;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.financer.controller.handler.NoExceptionResponseErrorHandler;
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.template.exception.FinancerRestException;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class FinancerRestTemplate<T> {
|
||||
public ResponseEntity<T> exchange(String url, ParameterizedTypeReference<T> type) {
|
||||
// Requests that result in the return of a String allow simplified error handling
|
||||
|
||||
private static <R, B> ResponseEntity<R> _exchangePost(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
B body,
|
||||
Class<R> type) throws FinancerRestException {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, endpoint));
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
restTemplate.setErrorHandler(new NoExceptionResponseErrorHandler());
|
||||
try {
|
||||
return restTemplate.exchange(builder.toUriString(), HttpMethod.POST, new HttpEntity<>(body), type);
|
||||
} catch (HttpStatusCodeException e) {
|
||||
final ResponseEntity<String> exceptionResponse = new ResponseEntity<>(e.getResponseBodyAsString(), e
|
||||
.getStatusCode());
|
||||
|
||||
throw new FinancerRestException(ResponseReason.fromResponseEntity(exceptionResponse));
|
||||
}
|
||||
}
|
||||
|
||||
public static <R, B> R exchangePost(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
B body,
|
||||
Class<R> type) throws FinancerRestException {
|
||||
return FinancerRestTemplate._exchangePost(financerConfig, endpoint, body, type).getBody();
|
||||
}
|
||||
|
||||
public static <B> ResponseReason exchangePost(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
B body) {
|
||||
ResponseReason response;
|
||||
|
||||
try {
|
||||
final ResponseEntity<String> responseEntity = FinancerRestTemplate
|
||||
._exchangePost(financerConfig, endpoint, body, String.class);
|
||||
|
||||
response = ResponseReason.fromResponseEntity(responseEntity);
|
||||
} catch (FinancerRestException e) {
|
||||
response = e.getResponseReason();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static ResponseReason exchangeDelete(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
String id
|
||||
) {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, endpoint, id));
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
ResponseEntity<String> response;
|
||||
|
||||
try {
|
||||
response = restTemplate.exchange(builder.toUriString(), HttpMethod.DELETE, null, String.class);
|
||||
} catch (HttpStatusCodeException e) {
|
||||
response = new ResponseEntity<>(e.getResponseBodyAsString(), e.getStatusCode());
|
||||
}
|
||||
|
||||
return ResponseReason.fromResponseEntity(response);
|
||||
}
|
||||
|
||||
public static <R> R exchangeGet(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
Class<R> type) throws FinancerRestException {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, endpoint));
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
try {
|
||||
return restTemplate.exchange(builder.toUriString(), HttpMethod.GET, null, type).getBody();
|
||||
} catch (HttpStatusCodeException e) {
|
||||
final ResponseEntity<String> exceptionResponse = new ResponseEntity<>(e.getResponseBodyAsString(), e
|
||||
.getStatusCode());
|
||||
|
||||
throw new FinancerRestException(ResponseReason.fromResponseEntity(exceptionResponse));
|
||||
}
|
||||
}
|
||||
|
||||
public static <R> R exchangeGet(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
ParameterizedTypeReference<R> type) throws FinancerRestException {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, endpoint));
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
try {
|
||||
return restTemplate.exchange(builder.toUriString(), HttpMethod.GET, null, type).getBody();
|
||||
} catch (HttpStatusCodeException e) {
|
||||
final ResponseEntity<String> exceptionResponse = new ResponseEntity<>(e.getResponseBodyAsString(), e
|
||||
.getStatusCode());
|
||||
|
||||
throw new FinancerRestException(ResponseReason.fromResponseEntity(exceptionResponse));
|
||||
}
|
||||
}
|
||||
|
||||
public static <R> R exchangeGet(UriComponentsBuilder builder,
|
||||
ParameterizedTypeReference<R> type) throws FinancerRestException {
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
try {
|
||||
return restTemplate.exchange(builder.toUriString(), HttpMethod.GET, null, type).getBody();
|
||||
} catch (HttpStatusCodeException e) {
|
||||
final ResponseEntity<String> exceptionResponse = new ResponseEntity<>(e.getResponseBodyAsString(), e
|
||||
.getStatusCode());
|
||||
|
||||
throw new FinancerRestException(ResponseReason.fromResponseEntity(exceptionResponse));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------- LEGACY
|
||||
|
||||
public ResponseEntity<T> exchange(String url, ParameterizedTypeReference<T> type) {
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
return restTemplate.exchange(url, HttpMethod.GET, null, type);
|
||||
}
|
||||
@@ -21,22 +135,6 @@ public class FinancerRestTemplate<T> {
|
||||
public ResponseEntity<T> exchange(String url, Class<T> type) {
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
restTemplate.setErrorHandler(new NoExceptionResponseErrorHandler());
|
||||
|
||||
return restTemplate.exchange(url, HttpMethod.GET, null, type);
|
||||
}
|
||||
|
||||
// The spring.jackson. properties are not picked up by the RestTemplate and its converter,
|
||||
// so we need to overwrite it here
|
||||
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
|
||||
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
jsonConverter.setObjectMapper(objectMapper);
|
||||
|
||||
return jsonConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
|
||||
@@ -3,8 +3,6 @@ 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;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class GetAllTransactionsForAccountTemplate {
|
||||
public ResponseEntity<Iterable<Transaction>> exchange(FinancerConfig financerConfig, String accountKey) {
|
||||
final UriComponentsBuilder transactionBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_GET_ALL_FOR_ACCOUNT))
|
||||
.queryParam("accountKey", accountKey);
|
||||
|
||||
return new FinancerRestTemplate<Iterable<Transaction>>()
|
||||
.exchange(transactionBuilder.toUriString(), new ParameterizedTypeReference<Iterable<Transaction>>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.dto.Order;
|
||||
import de.financer.dto.SearchTransactionsResponseDto;
|
||||
import de.financer.template.exception.FinancerRestException;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class SearchTransactionsTemplate {
|
||||
public Iterable<SearchTransactionsResponseDto> exchangeGet(FinancerConfig financerConfig,
|
||||
String fromAccountKey,
|
||||
String toAccountKey,
|
||||
Long periodId,
|
||||
Integer limit,
|
||||
Order order,
|
||||
Boolean taxRelevant,
|
||||
boolean accountsAnd) throws FinancerRestException {
|
||||
final UriComponentsBuilder transactionBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_SEARCH));
|
||||
|
||||
if (fromAccountKey != null) {
|
||||
transactionBuilder.queryParam("fromAccountKey", fromAccountKey);
|
||||
}
|
||||
|
||||
if (fromAccountKey != null) {
|
||||
transactionBuilder.queryParam("toAccountKey", toAccountKey);
|
||||
}
|
||||
|
||||
if (periodId != null) {
|
||||
transactionBuilder.queryParam("periodId", periodId);
|
||||
}
|
||||
|
||||
if (limit != null) {
|
||||
transactionBuilder.queryParam("limit", limit);
|
||||
}
|
||||
|
||||
if (order != null) {
|
||||
transactionBuilder.queryParam("order", order.name());
|
||||
}
|
||||
|
||||
if (taxRelevant != null) {
|
||||
transactionBuilder.queryParam("taxRelevant", taxRelevant);
|
||||
}
|
||||
|
||||
transactionBuilder.queryParam("accountsAnd", accountsAnd);
|
||||
|
||||
return FinancerRestTemplate.exchangeGet(transactionBuilder, new ParameterizedTypeReference<Iterable<SearchTransactionsResponseDto>>() {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.financer.template.exception;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
|
||||
public class FinancerRestException extends Exception {
|
||||
private final ResponseReason responseReason;
|
||||
|
||||
public FinancerRestException(ResponseReason responseReason) {
|
||||
this.responseReason = responseReason;
|
||||
}
|
||||
|
||||
public ResponseReason getResponseReason() {
|
||||
return this.responseReason;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,10 @@ public class ControllerUtils {
|
||||
return String.format("%s%s", financerConfig.getServerUrl(), function.getPath());
|
||||
}
|
||||
|
||||
public static String buildUrl(FinancerConfig financerConfig, Function function, String id) {
|
||||
return String.format("%s%s%s", financerConfig.getServerUrl(), function.getPath(), id);
|
||||
}
|
||||
|
||||
public static List<Account> filterAndSortAccounts(Iterable<Account> accounts) {
|
||||
return filterAndSortAccounts(accounts, false);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package de.financer.util;
|
||||
|
||||
import de.financer.dto.SearchTransactionsResponseDto;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.Transaction;
|
||||
|
||||
import static de.financer.model.AccountType.*;
|
||||
|
||||
public class TransactionUtils {
|
||||
public static final void adjustAmount(Transaction t, Account account) {
|
||||
public static void adjustAmount(SearchTransactionsResponseDto t, Account account) {
|
||||
boolean isFrom = t.getFromAccount().getKey().equals(account.getKey());
|
||||
|
||||
if (AccountType.START.equals(t.getFromAccount().getType()) && AccountType.LIABILITY.equals(t.getToAccount().getType())) {
|
||||
|
||||
@@ -4,6 +4,7 @@ financer.account-overview.available-actions.show-closed=Show closed accounts
|
||||
financer.account-overview.available-actions.hide-closed=Hide closed accounts
|
||||
financer.account-overview.available-actions.create-account=Create new account
|
||||
financer.account-overview.available-actions.create-transaction=Create new transaction
|
||||
financer.account-overview.available-actions.search-transactions=Search transactions
|
||||
financer.account-overview.available-actions.create-recurring-transaction=Create new recurring transaction
|
||||
financer.account-overview.available-actions.recurring-transaction-all=Show all recurring transactions
|
||||
financer.account-overview.available-actions.create-account-group=Create new account group
|
||||
@@ -98,28 +99,34 @@ financer.account-details.available-actions.close-account=Close account
|
||||
financer.account-details.available-actions.open-account=Open account
|
||||
financer.account-details.available-actions.back-to-overview=Back to overview
|
||||
financer.account-details.available-actions.create-transaction=Create new transaction
|
||||
financer.account-details.table-header.id=ID
|
||||
financer.account-details.table-header.fromAccount=From account
|
||||
financer.account-details.table-header.toAccount=To account
|
||||
financer.account-details.table-header.date=Date
|
||||
financer.account-details.table-header.amount=Amount
|
||||
financer.account-details.table-header.description=Description
|
||||
financer.account-details.table-header.byRecurring=Recurring
|
||||
financer.account-details.table-header.taxRelevant=Tax relevant
|
||||
financer.transaction-list.table-header.id=ID
|
||||
financer.transaction-list.table-header.fromAccount=From account
|
||||
financer.transaction-list.table-header.toAccount=To account
|
||||
financer.transaction-list.table-header.date=Date
|
||||
financer.transaction-list.table-header.amount=Amount
|
||||
financer.transaction-list.table-header.description=Description
|
||||
financer.transaction-list.table-header.byRecurring=Recurring
|
||||
financer.transaction-list.table-header.taxRelevant=Tax relevant
|
||||
financer.account-details.details.type=Type\:
|
||||
financer.account-details.details.balance=Current balance\:
|
||||
financer.account-details.details.group=Group\:
|
||||
financer.account-details.table-header.actions=Actions
|
||||
financer.account-details.table.actions.deleteTransaction=Delete
|
||||
financer.account-details.table.recurring.yes=Yes
|
||||
financer.account-details.table.recurring.no=No
|
||||
financer.account-details.table.taxRelevant.true=Yes
|
||||
financer.account-details.table.taxRelevant.false=No
|
||||
financer.transaction-list.table-header.actions=Actions
|
||||
financer.transaction-list.table.actions.deleteTransaction=Delete
|
||||
financer.transaction-list.table.recurring.yes=Yes
|
||||
financer.transaction-list.table.recurring.no=No
|
||||
financer.transaction-list.table.taxRelevant.true=Yes
|
||||
financer.transaction-list.table.taxRelevant.false=No
|
||||
|
||||
financer.recurring-to-transaction-with-amount.title=financer\: create transaction from recurring with amount
|
||||
financer.recurring-to-transaction-with-amount.label.amount=Amount\:
|
||||
financer.recurring-to-transaction-with-amount.submit=Create
|
||||
|
||||
financer.search-transactions.title=financer\: search transactions
|
||||
financer.search-transactions.back-to-overview=Back to overview
|
||||
financer.search-transactions.label.fql=FQL expression\:
|
||||
financer.search-transactions.submit=Search
|
||||
financer.search-transactions.show-query-options=Show query options
|
||||
|
||||
financer.chart-select.title=Select a chart to generate
|
||||
financer.chart-select.submit=Select
|
||||
|
||||
@@ -166,6 +173,7 @@ financer.heading.recurring-to-transaction-with-amount=financer\: create transact
|
||||
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.heading.search-transactions=financer\: search transactions
|
||||
|
||||
financer.cancel-back-to-overview=Cancel and back to overview
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ financer.account-overview.available-actions.show-closed=Zeige auch geschlossene
|
||||
financer.account-overview.available-actions.hide-closed=Verstecke geschlossene Konten
|
||||
financer.account-overview.available-actions.create-account=Neues Konto erstellen
|
||||
financer.account-overview.available-actions.create-transaction=Neue Buchung erstellen
|
||||
financer.account-overview.available-actions.search-transactions=Buchungen suchen
|
||||
financer.account-overview.available-actions.create-recurring-transaction=Neue wiederkehrende Buchung erstellen
|
||||
financer.account-overview.available-actions.recurring-transaction-all=Zeige alle wiederkehrende Buchungen
|
||||
financer.account-overview.available-actions.create-account-group=Neue Konto-Gruppe erstellen
|
||||
@@ -18,6 +19,8 @@ financer.account-overview.table-header.id=ID
|
||||
financer.account-overview.table-header.key=Konto
|
||||
financer.account-overview.table-header.group=Gruppe
|
||||
financer.account-overview.table-header.balance=Kontostand
|
||||
financer.account-overview.table-header.spending-current-period=Ausgaben aktuelle Periode
|
||||
financer.account-overview.table-header.average-spending-period=Durchschnittliche Ausgaben
|
||||
financer.account-overview.table-header.type=Typ
|
||||
financer.account-overview.table-header.status=Status
|
||||
financer.account-overview.tooltip.status.current-expenses=Periode ab {0}. Durch Klicken des Betrags kann eine grafische \u00DCbersicht über die Ausgaben gruppiert nach Konto-Gruppe angezeigt werden
|
||||
@@ -96,28 +99,34 @@ financer.account-details.available-actions.close-account=Konto schlie\u00DFen
|
||||
financer.account-details.available-actions.open-account=Konto \u00F6ffnen
|
||||
financer.account-details.available-actions.back-to-overview=Zur\u00FCck zur \u00DCbersicht
|
||||
financer.account-details.available-actions.create-transaction=Neue Buchung erstellen
|
||||
financer.account-details.table-header.id=ID
|
||||
financer.account-details.table-header.fromAccount=Von Konto
|
||||
financer.account-details.table-header.toAccount=An Konto
|
||||
financer.account-details.table-header.date=Datum
|
||||
financer.account-details.table-header.amount=Betrag
|
||||
financer.account-details.table-header.description=Beschreibung
|
||||
financer.account-details.table-header.byRecurring=Wiederkehrend
|
||||
financer.account-details.table-header.taxRelevant=Relevant f\u00FCr Steuererkl\u00E4rung
|
||||
financer.transaction-list.table-header.id=ID
|
||||
financer.transaction-list.table-header.fromAccount=Von Konto
|
||||
financer.transaction-list.table-header.toAccount=An Konto
|
||||
financer.transaction-list.table-header.date=Datum
|
||||
financer.transaction-list.table-header.amount=Betrag
|
||||
financer.transaction-list.table-header.description=Beschreibung
|
||||
financer.transaction-list.table-header.byRecurring=Wiederkehrend
|
||||
financer.transaction-list.table-header.taxRelevant=Relevant f\u00FCr Steuererkl\u00E4rung
|
||||
financer.account-details.details.type=Typ\:
|
||||
financer.account-details.details.balance=Kontostand\:
|
||||
financer.account-details.details.group=Gruppe\:
|
||||
financer.account-details.table-header.actions=Aktionen
|
||||
financer.account-details.table.actions.deleteTransaction=L\u00F6schen
|
||||
financer.account-details.table.recurring.yes=Ja
|
||||
financer.account-details.table.recurring.no=Nein
|
||||
financer.account-details.table.taxRelevant.true=Ja
|
||||
financer.account-details.table.taxRelevant.false=Nein
|
||||
financer.transaction-list.table-header.actions=Aktionen
|
||||
financer.transaction-list.table.actions.deleteTransaction=L\u00F6schen
|
||||
financer.transaction-list.table.recurring.yes=Ja
|
||||
financer.transaction-list.table.recurring.no=Nein
|
||||
financer.transaction-list.table.taxRelevant.true=Ja
|
||||
financer.transaction-list.table.taxRelevant.false=Nein
|
||||
|
||||
financer.recurring-to-transaction-with-amount.title=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen
|
||||
financer.recurring-to-transaction-with-amount.label.amount=Betrag\:
|
||||
financer.recurring-to-transaction-with-amount.submit=Erstellen
|
||||
|
||||
financer.search-transactions.title=financer\: Buchungen suchen
|
||||
financer.search-transactions.back-to-overview=Zur\u00FCck zur \u00DCbersicht
|
||||
financer.search-transactions.label.fql=FQL Ausdruck\:
|
||||
financer.search-transactions.submit=Suchen
|
||||
financer.search-transactions.show-query-options=Suchoptionen anzeigen
|
||||
|
||||
financer.chart-select.title=Ein Diagramm zum Erzeugen ausw\u00E4hlen
|
||||
financer.chart-select.submit=Ausw\u00E4hlen
|
||||
|
||||
@@ -157,12 +166,13 @@ financer.heading.account-new=financer\: Neues Konto erstellen
|
||||
financer.heading.account-group-new=financer\: Neue Konto-Gruppe erstellen
|
||||
financer.heading.account-overview=financer\: \u00DCbersicht
|
||||
financer.heading.account-details=financer\: Kontodetails f\u00FCr {0}
|
||||
financer.heading.recurring-transaction-list.dueToday=financer\: wiederkehrende Buchungen heute f\u00E4llig
|
||||
financer.heading.recurring-transaction-list.active=financer\: aktive wiederkehrende Buchungen
|
||||
financer.heading.recurring-transaction-list.all=financer\: alle wiederkehrende Buchungen
|
||||
financer.heading.recurring-transaction-list.dueToday=financer\: Wiederkehrende Buchungen heute f\u00E4llig
|
||||
financer.heading.recurring-transaction-list.active=financer\: Aktive wiederkehrende Buchungen
|
||||
financer.heading.recurring-transaction-list.all=financer\: Alle wiederkehrende Buchungen
|
||||
financer.heading.recurring-to-transaction-with-amount=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen
|
||||
financer.heading.chart-select=financer\: ein Diagramm zum Erzeugen ausw\u00E4hlen
|
||||
financer.heading.chart-config-account-group-expenses-for-period=financer\: konfigurieren von Ausgaben f\u00FCr Periode gruppiert nach Konto-Gruppe Diagramm
|
||||
financer.heading.chart-select=financer\: Ein Diagramm zum Erzeugen ausw\u00E4hlen
|
||||
financer.heading.chart-config-account-group-expenses-for-period=financer\: Konfigurieren von Ausgaben f\u00FCr Periode gruppiert nach Konto-Gruppe Diagramm
|
||||
financer.heading.search-transactions=financer\: Buchungen suchen
|
||||
|
||||
financer.cancel-back-to-overview=Abbrechen und zur \u00DCbersicht
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ v26 -> v27:
|
||||
- Changed sort order of accounts in overview page. The accounts are now sorted by the account type first (BCILES), then
|
||||
by the account group name and then by the account ID, leading to an overall more organic order of accounts
|
||||
- Add tax relevance flag to transaction and recurring transaction creation. This flag denotes whether a transaction or
|
||||
the instances of a recurring transaction are relevant for a tax declaration. This is preparation for extended reports.
|
||||
the instances of a recurring transaction are relevant for a tax declaration. This is preparation for extended reports
|
||||
- Add searching of transactions via FQL (Financer Query Language)
|
||||
- Rework /transaction end point to better adhere to REST API requirements (proper HTTP return codes and HTTP method
|
||||
usage)
|
||||
|
||||
v25 -> v26:
|
||||
- Close of the current expense period now creates null statistic entries for accounts that have not been used in
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/* --------------------- */
|
||||
|
||||
#account-overview-table,
|
||||
#account-transaction-table,
|
||||
#transaction-table,
|
||||
#recurring-transaction-list-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
#account-overview-table th,
|
||||
#account-overview-table td,
|
||||
#account-transaction-table th,
|
||||
#account-transaction-table td,
|
||||
#transaction-table th,
|
||||
#transaction-table td,
|
||||
#recurring-transaction-list-table th,
|
||||
#recurring-transaction-list-table td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
#account-overview-table th,
|
||||
#account-transaction-table th,
|
||||
#transaction-table th,
|
||||
#recurring-transaction-list-table th {
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
@@ -107,13 +107,18 @@ tr:hover {
|
||||
#recurring-to-transaction-with-amount-form *,
|
||||
#new-account-group-form *,
|
||||
#chart-config-account-group-expenses-for-period-form *,
|
||||
#chart-config-account-expenses-for-period-form * {
|
||||
#chart-config-account-expenses-for-period-form *,
|
||||
#search-transactions-form * {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
width: 20em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#search-transactions-form > input[type=text] {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#chart-select-form div {
|
||||
width: 20em;
|
||||
margin-top: 1em;
|
||||
@@ -187,4 +192,15 @@ input[type=submit] {
|
||||
#type-row {
|
||||
width: var(--type-row-width);
|
||||
padding: 0px !important
|
||||
}
|
||||
|
||||
#search-transactions-fql-detail {
|
||||
border: 1px solid grey;
|
||||
border-radius: 0.5em;
|
||||
padding-inline: 0.3em;
|
||||
background-color: lightgrey;
|
||||
}
|
||||
|
||||
#search-transactions-fql-detail > * {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
@@ -14,8 +14,9 @@
|
||||
7. Transactions
|
||||
8. Recurring transactions
|
||||
9. Reporting
|
||||
10. Setup
|
||||
11. Planned features
|
||||
10. FQL
|
||||
11. Setup
|
||||
12. Planned features
|
||||
|
||||
1. About
|
||||
========
|
||||
@@ -170,12 +171,15 @@
|
||||
9. Reporting
|
||||
============
|
||||
|
||||
10. Setup
|
||||
10. FQL
|
||||
=======
|
||||
|
||||
11. Setup
|
||||
=========
|
||||
This chapter explains how to setup a financer instance. It requires PostgreSQL as a database backend and a Java
|
||||
Servlet Container (e.g. Apache Tomcat) as a runtime environment.
|
||||
|
||||
10.1 Database setup
|
||||
11.1 Database setup
|
||||
-------------------
|
||||
First install PostgreSQL. Then create a user for financer:
|
||||
sudo -iu postgres
|
||||
@@ -191,7 +195,7 @@
|
||||
\q
|
||||
exit
|
||||
|
||||
11. Planned features
|
||||
12. Planned features
|
||||
====================
|
||||
This chapter lists planned features. The list is in no particular order:
|
||||
- Transaction import from online banking (file based)
|
||||
|
||||
@@ -35,36 +35,7 @@
|
||||
<a th:href="@{/accountOverview}"
|
||||
th:text="#{financer.account-details.available-actions.back-to-overview}"/>
|
||||
</div>
|
||||
<table id="account-transaction-table">
|
||||
<tr>
|
||||
<th class="hideable-column" th:text="#{financer.account-details.table-header.id}"/>
|
||||
<th th:text="#{financer.account-details.table-header.fromAccount}"/>
|
||||
<th th:text="#{financer.account-details.table-header.toAccount}"/>
|
||||
<th th:text="#{financer.account-details.table-header.date}"/>
|
||||
<th th:text="#{financer.account-details.table-header.amount}"/>
|
||||
<th th:text="#{financer.account-details.table-header.description}"/>
|
||||
<th th:text="#{financer.account-details.table-header.byRecurring}"/>
|
||||
<th th:text="#{financer.account-details.table-header.taxRelevant}"/>
|
||||
<th th:text="#{financer.account-details.table-header.actions}"/>
|
||||
</tr>
|
||||
<tr th:each="transaction : ${transactions}">
|
||||
<td class="hideable-column" th:text="${transaction.id}"/>
|
||||
<td th:text="${transaction.fromAccount.key}" />
|
||||
<td th:text="${transaction.toAccount.key}" />
|
||||
<td th:text="${#temporals.format(transaction.date)}" />
|
||||
<td th:text="${#numbers.formatDecimal(transaction.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
<td th:text="${transaction.description}" />
|
||||
<td th:if="${transaction.recurringTransaction != null}" th:text="#{financer.account-details.table.recurring.yes}" />
|
||||
<td th:if="${transaction.recurringTransaction == null}" th:text="#{financer.account-details.table.recurring.no}" />
|
||||
<td th:text="#{'financer.account-details.table.taxRelevant.' + ${transaction.taxRelevant}}" />
|
||||
<td>
|
||||
<div id="account-transaction-table-actions-container">
|
||||
<a th:href="@{/deleteTransaction(transactionId=${transaction.id}, accountKey=${account.key})}"
|
||||
th:text="#{financer.account-details.table.actions.deleteTransaction}"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div th:replace="transaction/transactionList :: transaction-list"/>
|
||||
<div th:replace="includes/footer :: footer"/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -49,6 +49,7 @@
|
||||
</div>
|
||||
<div id="action-container-sub-transactions">
|
||||
<a th:href="@{/newTransaction}" th:text="#{financer.account-overview.available-actions.create-transaction}"/>
|
||||
<a th:href="@{/searchTransactions}" th:text="#{financer.account-overview.available-actions.search-transactions}"/>
|
||||
</div>
|
||||
<div id="action-container-sub-recurring-transactions">
|
||||
<a th:href="@{/newRecurringTransaction}"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="#{financer.search-transactions.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}">
|
||||
<link rel="shortcut icon" th:href="@{/favicon.ico}" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 th:text="#{financer.heading.search-transactions}" />
|
||||
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
|
||||
<a th:href="@{/accountOverview}" th:text="#{financer.search-transactions.back-to-overview}"/>
|
||||
<form id="search-transactions-form" action="#" th:action="@{/searchTransactions}" th:object="${form}"
|
||||
method="post">
|
||||
<label for="inputFql" th:text="#{financer.search-transactions.label.fql}"/>
|
||||
<input type="text" id="inputFql" th:field="*{fql}"/>
|
||||
<input type="submit" th:value="#{financer.search-transactions.submit}"/>
|
||||
</form>
|
||||
<details>
|
||||
<summary th:text="#{financer.search-transactions.show-query-options}"/>
|
||||
<div id="search-transactions-fql-detail">
|
||||
<p>
|
||||
<div>Available fields:</div>
|
||||
<ul>
|
||||
<li>amount: the amount of a transaction</li>
|
||||
<li>fromAccount: the key of the from account</li>
|
||||
<li>toAccount: the key of the to account</li>
|
||||
<li>fromAccountGroup: the name of the account group of the from account</li>
|
||||
<li>toAccountGroup: the name of the account group of the to account</li>
|
||||
<li>date: the date of the transaction in the format yyyy-mm-dd</li>
|
||||
<li>recurring: whether the transaction has been created from a recurring transaction</li>
|
||||
<li>taxRelevant: whether the transaction is relevant for tax declaration</li>
|
||||
<li>period: the period the transaction is assigned to
|
||||
<ul>
|
||||
<li>CURRENT: denotes the current expense period</li>
|
||||
<li>LAST: denotes the last expense period</li>
|
||||
<li>CURRENT_YEAR: denotes the current year period</li>
|
||||
<li>LAST_YEAR: denotes the last year period</li>
|
||||
<li>GRAND_TOTAL: denotes the grand total period</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<div>General</div>
|
||||
<ul>
|
||||
<li>Conjunctions with OR, AND</li>
|
||||
<li>Grouping of expressions with parenthesis</li>
|
||||
<li>Strings enclosed in single quotes</li>
|
||||
<li>Case-insensitive</li>
|
||||
<li>Possible operators =, >, >=, <, <=, !=</li>
|
||||
<li>Ordering for a single field ASC, DESC via ORDER BY</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
<div th:replace="transaction/transactionList :: transaction-list"/>
|
||||
<div th:replace="includes/footer :: footer"/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<div id="transaction-list-container" th:fragment="transaction-list">
|
||||
<table id="transaction-table">
|
||||
<tr>
|
||||
<th class="hideable-column" th:text="#{financer.transaction-list.table-header.id}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.fromAccount}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.toAccount}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.date}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.amount}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.description}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.byRecurring}"/>
|
||||
<th th:text="#{financer.transaction-list.table-header.taxRelevant}"/>
|
||||
<th th:if="${showActions}" th:text="#{financer.transaction-list.table-header.actions}"/>
|
||||
</tr>
|
||||
<tr th:each="transaction : ${transactions}">
|
||||
<td class="hideable-column" th:text="${transaction.id}"/>
|
||||
<td th:text="${transaction.fromAccount.key}" />
|
||||
<td th:text="${transaction.toAccount.key}" />
|
||||
<td th:text="${#temporals.format(transaction.date)}" />
|
||||
<td th:text="${#numbers.formatDecimal(transaction.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
<td th:text="${transaction.description}" />
|
||||
<td th:if="${transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.yes}" />
|
||||
<td th:if="${!transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.no}" />
|
||||
<td th:text="#{'financer.transaction-list.table.taxRelevant.' + ${transaction.taxRelevant}}" />
|
||||
<td th:if="${showActions}">
|
||||
<div id="account-transaction-table-actions-container">
|
||||
<a th:href="@{/deleteTransaction(transactionId=${transaction.id}, accountKey=${account.key})}"
|
||||
th:text="#{financer.transaction-list.table.actions.deleteTransaction}"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
Reference in New Issue
Block a user