Add current expenses to the overview

The period start is configurable.
This commit is contained in:
2019-06-24 22:44:05 +02:00
parent 6ec073d08e
commit 6009592b61
14 changed files with 125 additions and 8 deletions

View File

@@ -21,6 +21,7 @@ public class FinancerConfig {
private String dateFormat; private String dateFormat;
private Collection<String> mailRecipients; private Collection<String> mailRecipients;
private String fromAddress; private String fromAddress;
private Integer monthPeriodStartDay;
/** /**
* @return the raw country code, mostly an uppercase ISO 3166 2-letter code * @return the raw country code, mostly an uppercase ISO 3166 2-letter code
@@ -109,4 +110,32 @@ public class FinancerConfig {
public void setFromAddress(String fromAddress) { public void setFromAddress(String fromAddress) {
this.fromAddress = fromAddress; this.fromAddress = fromAddress;
} }
/**
* The day of month that indicates a start of an expense period. Valid values range from 1-28. There is no special
* handling for months with more days.
* If the value is 15 for example an expense period is always from the 15th of the current month to the 15th of the
* next month:
* <ul>
* <li>15.01.2019 - 15.02.2019</li>
* <li>15.02.2019 - 15.03.2019</li>
* <li>...</li>
* </ul>
*
* @return the day of month indicating the start of an expense period
*/
public Integer getMonthPeriodStartDay() {
return monthPeriodStartDay;
}
public void setMonthPeriodStartDay(Integer monthPeriodStartDay) {
if (monthPeriodStartDay == null) {
throw new IllegalArgumentException("Parameter 'financer.monthPeriodStartDay' is not set!");
}
else if (monthPeriodStartDay < 1 || monthPeriodStartDay > 28) {
throw new IllegalArgumentException("Parameter 'financer.monthPeriodStartDay' is out of range! Valid range from 1-28");
}
this.monthPeriodStartDay = monthPeriodStartDay;
}
} }

View File

@@ -79,4 +79,19 @@ public class TransactionController {
return responseReason.toResponseEntity(); return responseReason.toResponseEntity();
} }
@RequestMapping("getExpensesCurrentPeriod")
public Long getExpensesCurrentPeriod() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("/transactions/getExpensesCurrentPeriod called");
}
final Long response = this.transactionService.getExpensesCurrentPeriod();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("/transactions/getExpensesCurrentPeriod returns with %s", response));
}
return response;
}
} }

View File

@@ -1,12 +1,19 @@
package de.financer.dba; package de.financer.dba;
import de.financer.model.Account; import de.financer.model.Account;
import de.financer.model.AccountType;
import de.financer.model.Transaction; import de.financer.model.Transaction;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public interface TransactionRepository extends CrudRepository<Transaction, Long> { public interface TransactionRepository extends CrudRepository<Transaction, Long> {
Iterable<Transaction> findTransactionsByFromAccountOrToAccount(Account fromAccount, Account toAccount); Iterable<Transaction> findTransactionsByFromAccountOrToAccount(Account fromAccount, Account toAccount);
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.toAccount a WHERE t.date BETWEEN :periodStart AND :periodEnd AND a.type IN :expenseTypes")
Long getExpensesCurrentPeriod(LocalDate periodStart, LocalDate periodEnd, AccountType... expenseTypes);
} }

View File

@@ -66,8 +66,8 @@ public class TransactionService {
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public ResponseReason createTransaction(String fromAccountKey, String toAccountKey, Long amount, String date, public ResponseReason createTransaction(String fromAccountKey, String toAccountKey, Long amount, String date,
String description) String description
{ ) {
return this.createTransaction(fromAccountKey, toAccountKey, amount, date, description, null); return this.createTransaction(fromAccountKey, toAccountKey, amount, date, description, null);
} }
@@ -95,8 +95,7 @@ public class TransactionService {
if (AccountType.START.equals(fromAccount.getType()) && AccountType.LIABILITY.equals(toAccount.getType())) { if (AccountType.START.equals(fromAccount.getType()) && AccountType.LIABILITY.equals(toAccount.getType())) {
toAccount.setCurrentBalance(toAccount.getCurrentBalance() + (this.ruleService toAccount.setCurrentBalance(toAccount.getCurrentBalance() + (this.ruleService
.getMultiplierToAccount(toAccount) * amount * -1)); .getMultiplierToAccount(toAccount) * amount * -1));
} } else {
else {
toAccount.setCurrentBalance(toAccount.getCurrentBalance() + (this.ruleService toAccount.setCurrentBalance(toAccount.getCurrentBalance() + (this.ruleService
.getMultiplierToAccount(toAccount) * amount)); .getMultiplierToAccount(toAccount) * amount));
} }
@@ -216,8 +215,7 @@ public class TransactionService {
this.accountService.saveAccount(fromAccount); this.accountService.saveAccount(fromAccount);
this.accountService.saveAccount(toAccount); this.accountService.saveAccount(toAccount);
} } catch (Exception e) {
catch (Exception e) {
LOGGER.error("Could not delete transaction!", e); LOGGER.error("Could not delete transaction!", e);
response = ResponseReason.UNKNOWN_ERROR; response = ResponseReason.UNKNOWN_ERROR;
@@ -225,4 +223,14 @@ public class TransactionService {
return response; return response;
} }
public Long getExpensesCurrentPeriod() {
final LocalDate periodStart = LocalDate.now().withDayOfMonth(this.financerConfig.getMonthPeriodStartDay());
final LocalDate periodEnd = periodStart.plusMonths(1);
final Long expensesCurrentPeriod = this.transactionRepository
.getExpensesCurrentPeriod(periodStart, periodEnd, AccountType.EXPENSE, AccountType.LIABILITY);
return Optional.ofNullable(expensesCurrentPeriod).orElse(Long.valueOf(0l));
}
} }

View File

@@ -46,4 +46,7 @@ spring.mail.host=localhost
# Disable JMX as we don't need it and it blocks parallel deployment on Tomcat # Disable JMX as we don't need it and it blocks parallel deployment on Tomcat
# because the connection pool cannot shutdown properly # because the connection pool cannot shutdown properly
spring.jmx.enabled=false spring.jmx.enabled=false
# The day of month indicating the start of an expense period. Valid values range from 1-28
financer.monthPeriodStartDay=15

View File

@@ -13,6 +13,7 @@ public class FinancerConfig {
private String serverUrl; private String serverUrl;
private String dateFormat; private String dateFormat;
private String version; private String version;
private Integer monthPeriodStartDay;
public String getServerUrl() { public String getServerUrl() {
return serverUrl; return serverUrl;
@@ -37,4 +38,20 @@ public class FinancerConfig {
public void setVersion(String version) { public void setVersion(String version) {
this.version = version; this.version = version;
} }
// The same property exists on the server, look there for documentation
public Integer getMonthPeriodStartDay() {
return monthPeriodStartDay;
}
public void setMonthPeriodStartDay(Integer monthPeriodStartDay) {
if (monthPeriodStartDay == null) {
throw new IllegalArgumentException("Parameter 'financer.monthPeriodStartDay' is not set!");
}
else if (monthPeriodStartDay < 1 || monthPeriodStartDay > 28) {
throw new IllegalArgumentException("Parameter 'financer.monthPeriodStartDay' is out of range! Valid range from 1-28");
}
this.monthPeriodStartDay = monthPeriodStartDay;
}
} }

View File

@@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.time.LocalDate;
import java.util.List; import java.util.List;
@Controller @Controller
@@ -33,12 +34,18 @@ public class AccountController {
final ResponseEntity<Iterable<RecurringTransaction>> rtAllActRes = new GetAllActiveRecurringTransactionsTemplate() final ResponseEntity<Iterable<RecurringTransaction>> rtAllActRes = new GetAllActiveRecurringTransactionsTemplate()
.exchange(this.financerConfig); .exchange(this.financerConfig);
final ResponseEntity<Long> currentAssets = new GetCurrentAssetsTemplate().exchange(this.financerConfig); final ResponseEntity<Long> currentAssets = new GetCurrentAssetsTemplate().exchange(this.financerConfig);
final ResponseEntity<Long> currentExpenses = new GetExpensesCurrentPeriodTemplate().exchange(this.financerConfig);
final boolean showClosedBoolean = BooleanUtils.toBoolean(showClosed); final boolean showClosedBoolean = BooleanUtils.toBoolean(showClosed);
final LocalDate periodStart = LocalDate.now().withDayOfMonth(this.financerConfig.getMonthPeriodStartDay());
final LocalDate periodEnd = periodStart.plusMonths(1);
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(response.getBody(), showClosedBoolean)); model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(response.getBody(), showClosedBoolean));
model.addAttribute("rtDueTodayCount", IterableUtils.size(rtDtRes.getBody())); model.addAttribute("rtDueTodayCount", IterableUtils.size(rtDtRes.getBody()));
model.addAttribute("rtAllActiveCount", IterableUtils.size(rtAllActRes.getBody())); model.addAttribute("rtAllActiveCount", IterableUtils.size(rtAllActRes.getBody()));
model.addAttribute("currentAssets", currentAssets.getBody()); model.addAttribute("currentAssets", currentAssets.getBody());
model.addAttribute("currentExpenses", currentExpenses.getBody());
model.addAttribute("periodStart", ControllerUtils.formatDate(this.financerConfig, periodStart));
model.addAttribute("periodEnd", ControllerUtils.formatDate(this.financerConfig, periodEnd));
model.addAttribute("showClosed", showClosedBoolean); model.addAttribute("showClosed", showClosedBoolean);
ControllerUtils.addVersionAttribute(model, this.financerConfig); ControllerUtils.addVersionAttribute(model, this.financerConfig);

View File

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

View File

@@ -0,0 +1,15 @@
package de.financer.controller.template;
import de.financer.config.FinancerConfig;
import de.financer.controller.Function;
import de.financer.util.ControllerUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
public class GetExpensesCurrentPeriodTemplate {
public ResponseEntity<Long> exchange(FinancerConfig financerConfig) {
return new FinancerRestTemplate<Long>().exchange(ControllerUtils
.buildUrl(financerConfig, Function.TR_EXPENSES_CURRENT_PERIOD), new ParameterizedTypeReference<Long>() {
});
}
}

View File

@@ -40,6 +40,10 @@ public class ControllerUtils {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public static String formatDate(FinancerConfig financerConfig, LocalDate date) {
return date.format(DateTimeFormatter.ofPattern(financerConfig.getDateFormat()));
}
public static String formatDate(FinancerConfig financerConfig, String dateFromForm) { public static String formatDate(FinancerConfig financerConfig, String dateFromForm) {
if (StringUtils.isEmpty(dateFromForm)) { if (StringUtils.isEmpty(dateFromForm)) {
return null; return null;

View File

@@ -27,4 +27,7 @@ financer.dateFormat=dd.MM.yyyy
financer.version=@project.version@ financer.version=@project.version@
financer.serverUrl=http://localhost:8080/financer-server/ financer.serverUrl=http://localhost:8080/financer-server/
spring.messages.basename=i18n/message spring.messages.basename=i18n/message
# The day of month indicating the start of an expense period. Valid values range from 1-28
financer.monthPeriodStartDay=15

View File

@@ -11,12 +11,14 @@ financer.account-overview.status=Status\:
financer.account-overview.status.recurring-transaction-due-today=Recurring transactions due today\: financer.account-overview.status.recurring-transaction-due-today=Recurring transactions due today\:
financer.account-overview.status.recurring-transaction-active=Active recurring transactions\: financer.account-overview.status.recurring-transaction-active=Active recurring transactions\:
financer.account-overview.status.current-assets=Current assets\: financer.account-overview.status.current-assets=Current assets\:
financer.account-overview.status.current-expenses=Expenses of the current period\:
financer.account-overview.table-header.id=ID financer.account-overview.table-header.id=ID
financer.account-overview.table-header.key=Account financer.account-overview.table-header.key=Account
financer.account-overview.table-header.group=Group financer.account-overview.table-header.group=Group
financer.account-overview.table-header.balance=Current Balance financer.account-overview.table-header.balance=Current Balance
financer.account-overview.table-header.type=Type financer.account-overview.table-header.type=Type
financer.account-overview.table-header.status=Status financer.account-overview.table-header.status=Status
financer.account-overview.tooltip.status.current-expenses=Period from {0} to {1}
financer.account-new.title=financer\: create new account financer.account-new.title=financer\: create new account
financer.account-new.label.key=Key\: financer.account-new.label.key=Key\:

View File

@@ -11,12 +11,14 @@ financer.account-overview.status=Status\:
financer.account-overview.status.recurring-transaction-due-today=Wiederkehrende Buchungen heute f\u00E4llig\: financer.account-overview.status.recurring-transaction-due-today=Wiederkehrende Buchungen heute f\u00E4llig\:
financer.account-overview.status.recurring-transaction-active=Aktive wiederkehrende Buchungen\: financer.account-overview.status.recurring-transaction-active=Aktive wiederkehrende Buchungen\:
financer.account-overview.status.current-assets=Umlaufverm\u00F6gen\: financer.account-overview.status.current-assets=Umlaufverm\u00F6gen\:
financer.account-overview.status.current-expenses=Ausgaben in der aktuellen Periode\:
financer.account-overview.table-header.id=ID financer.account-overview.table-header.id=ID
financer.account-overview.table-header.key=Konto financer.account-overview.table-header.key=Konto
financer.account-overview.table-header.group=Gruppe financer.account-overview.table-header.group=Gruppe
financer.account-overview.table-header.balance=Kontostand financer.account-overview.table-header.balance=Kontostand
financer.account-overview.table-header.type=Typ financer.account-overview.table-header.type=Typ
financer.account-overview.table-header.status=Status financer.account-overview.table-header.status=Status
financer.account-overview.tooltip.status.current-expenses=Periode ab {0} bis {1}
financer.account-new.title=financer\: Neues Konto erstellen financer.account-new.title=financer\: Neues Konto erstellen
financer.account-new.label.key=Schl\u00FCssel\: financer.account-new.label.key=Schl\u00FCssel\:

View File

@@ -14,6 +14,10 @@
<span th:text="#{financer.account-overview.status.current-assets}"/> <span th:text="#{financer.account-overview.status.current-assets}"/>
<span th:text="${#numbers.formatDecimal(currentAssets/100D, 1, 'DEFAULT', 2, 'DEFAULT')}"/> <span th:text="${#numbers.formatDecimal(currentAssets/100D, 1, 'DEFAULT', 2, 'DEFAULT')}"/>
</div> </div>
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${periodStart}, ${periodEnd})}">
<span th:text="#{financer.account-overview.status.current-expenses}"/>
<span th:text="${#numbers.formatDecimal(currentExpenses/100D, 1, 'DEFAULT', 2, 'DEFAULT')}"/>
</div>
<div> <div>
<span th:text="#{financer.account-overview.status.recurring-transaction-due-today}"/> <span th:text="#{financer.account-overview.status.recurring-transaction-due-today}"/>
<a th:href="@{/recurringTransactionDueToday}" th:text="${rtDueTodayCount}"/> <a th:href="@{/recurringTransactionDueToday}" th:text="${rtDueTodayCount}"/>