diff --git a/financer-server/src/main/java/de/financer/config/FinancerConfig.java b/financer-server/src/main/java/de/financer/config/FinancerConfig.java index 3e8a5f6..55fd74b 100644 --- a/financer-server/src/main/java/de/financer/config/FinancerConfig.java +++ b/financer-server/src/main/java/de/financer/config/FinancerConfig.java @@ -21,6 +21,7 @@ public class FinancerConfig { private String dateFormat; private Collection mailRecipients; private String fromAddress; + private Integer monthPeriodStartDay; /** * @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) { 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: + * + * + * @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; + } } diff --git a/financer-server/src/main/java/de/financer/controller/TransactionController.java b/financer-server/src/main/java/de/financer/controller/TransactionController.java index 71de55a..7e4503e 100644 --- a/financer-server/src/main/java/de/financer/controller/TransactionController.java +++ b/financer-server/src/main/java/de/financer/controller/TransactionController.java @@ -79,4 +79,19 @@ public class TransactionController { 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; + } } diff --git a/financer-server/src/main/java/de/financer/dba/TransactionRepository.java b/financer-server/src/main/java/de/financer/dba/TransactionRepository.java index f62e74c..04ac1e6 100644 --- a/financer-server/src/main/java/de/financer/dba/TransactionRepository.java +++ b/financer-server/src/main/java/de/financer/dba/TransactionRepository.java @@ -1,12 +1,19 @@ package de.financer.dba; import de.financer.model.Account; +import de.financer.model.AccountType; import de.financer.model.Transaction; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; + @Transactional(propagation = Propagation.REQUIRED) public interface TransactionRepository extends CrudRepository { Iterable 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); } diff --git a/financer-server/src/main/java/de/financer/service/TransactionService.java b/financer-server/src/main/java/de/financer/service/TransactionService.java index 1cfa3cc..3f875bb 100644 --- a/financer-server/src/main/java/de/financer/service/TransactionService.java +++ b/financer-server/src/main/java/de/financer/service/TransactionService.java @@ -66,8 +66,8 @@ public class TransactionService { @Transactional(propagation = Propagation.REQUIRED) public ResponseReason createTransaction(String fromAccountKey, String toAccountKey, Long amount, String date, - String description) - { + String description + ) { 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())) { toAccount.setCurrentBalance(toAccount.getCurrentBalance() + (this.ruleService .getMultiplierToAccount(toAccount) * amount * -1)); - } - else { + } else { toAccount.setCurrentBalance(toAccount.getCurrentBalance() + (this.ruleService .getMultiplierToAccount(toAccount) * amount)); } @@ -216,8 +215,7 @@ public class TransactionService { this.accountService.saveAccount(fromAccount); this.accountService.saveAccount(toAccount); - } - catch (Exception e) { + } catch (Exception e) { LOGGER.error("Could not delete transaction!", e); response = ResponseReason.UNKNOWN_ERROR; @@ -225,4 +223,14 @@ public class TransactionService { 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)); + } } diff --git a/financer-server/src/main/resources/config/application.properties b/financer-server/src/main/resources/config/application.properties index bc847d2..740c91d 100644 --- a/financer-server/src/main/resources/config/application.properties +++ b/financer-server/src/main/resources/config/application.properties @@ -46,4 +46,7 @@ spring.mail.host=localhost # Disable JMX as we don't need it and it blocks parallel deployment on Tomcat # because the connection pool cannot shutdown properly -spring.jmx.enabled=false \ No newline at end of file +spring.jmx.enabled=false + +# The day of month indicating the start of an expense period. Valid values range from 1-28 +financer.monthPeriodStartDay=15 \ No newline at end of file diff --git a/financer-web-client/src/main/java/de/financer/config/FinancerConfig.java b/financer-web-client/src/main/java/de/financer/config/FinancerConfig.java index 26438a0..0648ef1 100644 --- a/financer-web-client/src/main/java/de/financer/config/FinancerConfig.java +++ b/financer-web-client/src/main/java/de/financer/config/FinancerConfig.java @@ -13,6 +13,7 @@ public class FinancerConfig { private String serverUrl; private String dateFormat; private String version; + private Integer monthPeriodStartDay; public String getServerUrl() { return serverUrl; @@ -37,4 +38,20 @@ public class FinancerConfig { public void setVersion(String 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; + } } diff --git a/financer-web-client/src/main/java/de/financer/controller/AccountController.java b/financer-web-client/src/main/java/de/financer/controller/AccountController.java index fb4971e..f039b85 100644 --- a/financer-web-client/src/main/java/de/financer/controller/AccountController.java +++ b/financer-web-client/src/main/java/de/financer/controller/AccountController.java @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.util.UriComponentsBuilder; +import java.time.LocalDate; import java.util.List; @Controller @@ -33,12 +34,18 @@ public class AccountController { final ResponseEntity> rtAllActRes = new GetAllActiveRecurringTransactionsTemplate() .exchange(this.financerConfig); final ResponseEntity currentAssets = new GetCurrentAssetsTemplate().exchange(this.financerConfig); + final ResponseEntity currentExpenses = new GetExpensesCurrentPeriodTemplate().exchange(this.financerConfig); 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("rtDueTodayCount", IterableUtils.size(rtDtRes.getBody())); model.addAttribute("rtAllActiveCount", IterableUtils.size(rtAllActRes.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); ControllerUtils.addVersionAttribute(model, this.financerConfig); diff --git a/financer-web-client/src/main/java/de/financer/controller/Function.java b/financer-web-client/src/main/java/de/financer/controller/Function.java index 009fe1f..d7d2a9f 100644 --- a/financer-web-client/src/main/java/de/financer/controller/Function.java +++ b/financer-web-client/src/main/java/de/financer/controller/Function.java @@ -15,6 +15,7 @@ public enum Function { TR_GET_ALL_FOR_ACCOUNT("transactions/getAllForAccount"), TR_CREATE_TRANSACTION("transactions/createTransaction"), TR_DELETE_TRANSACTION("transactions/deleteTransaction"), + TR_EXPENSES_CURRENT_PERIOD("transactions/getExpensesCurrentPeriod"), RT_GET_ALL("recurringTransactions/getAll"), RT_GET_ALL_ACTIVE("recurringTransactions/getAllActive"), diff --git a/financer-web-client/src/main/java/de/financer/controller/template/GetExpensesCurrentPeriodTemplate.java b/financer-web-client/src/main/java/de/financer/controller/template/GetExpensesCurrentPeriodTemplate.java new file mode 100644 index 0000000..3eed6ab --- /dev/null +++ b/financer-web-client/src/main/java/de/financer/controller/template/GetExpensesCurrentPeriodTemplate.java @@ -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 exchange(FinancerConfig financerConfig) { + return new FinancerRestTemplate().exchange(ControllerUtils + .buildUrl(financerConfig, Function.TR_EXPENSES_CURRENT_PERIOD), new ParameterizedTypeReference() { + }); + } +} diff --git a/financer-web-client/src/main/java/de/financer/util/ControllerUtils.java b/financer-web-client/src/main/java/de/financer/util/ControllerUtils.java index d167e6c..099dc57 100644 --- a/financer-web-client/src/main/java/de/financer/util/ControllerUtils.java +++ b/financer-web-client/src/main/java/de/financer/util/ControllerUtils.java @@ -40,6 +40,10 @@ public class ControllerUtils { .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) { if (StringUtils.isEmpty(dateFromForm)) { return null; diff --git a/financer-web-client/src/main/resources/config/application.properties b/financer-web-client/src/main/resources/config/application.properties index 6db2d9f..b9c7014 100644 --- a/financer-web-client/src/main/resources/config/application.properties +++ b/financer-web-client/src/main/resources/config/application.properties @@ -27,4 +27,7 @@ financer.dateFormat=dd.MM.yyyy financer.version=@project.version@ financer.serverUrl=http://localhost:8080/financer-server/ -spring.messages.basename=i18n/message \ No newline at end of file +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 \ No newline at end of file diff --git a/financer-web-client/src/main/resources/i18n/message.properties b/financer-web-client/src/main/resources/i18n/message.properties index 7eb5b38..9e3b18c 100644 --- a/financer-web-client/src/main/resources/i18n/message.properties +++ b/financer-web-client/src/main/resources/i18n/message.properties @@ -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-active=Active recurring transactions\: 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.key=Account financer.account-overview.table-header.group=Group financer.account-overview.table-header.balance=Current Balance financer.account-overview.table-header.type=Type 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.label.key=Key\: diff --git a/financer-web-client/src/main/resources/i18n/message_de_DE.properties b/financer-web-client/src/main/resources/i18n/message_de_DE.properties index 64b6eef..425644e 100644 --- a/financer-web-client/src/main/resources/i18n/message_de_DE.properties +++ b/financer-web-client/src/main/resources/i18n/message_de_DE.properties @@ -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-active=Aktive wiederkehrende Buchungen\: 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.key=Konto financer.account-overview.table-header.group=Gruppe financer.account-overview.table-header.balance=Kontostand financer.account-overview.table-header.type=Typ 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.label.key=Schl\u00FCssel\: diff --git a/financer-web-client/src/main/resources/templates/account/accountOverview.html b/financer-web-client/src/main/resources/templates/account/accountOverview.html index a633f54..2878d50 100644 --- a/financer-web-client/src/main/resources/templates/account/accountOverview.html +++ b/financer-web-client/src/main/resources/templates/account/accountOverview.html @@ -14,6 +14,10 @@ +
+ + +