diff --git a/financer-server/src/main/java/de/financer/dba/AccountStatisticRepository.java b/financer-server/src/main/java/de/financer/dba/AccountStatisticRepository.java index 5dc3272..078b20e 100644 --- a/financer-server/src/main/java/de/financer/dba/AccountStatisticRepository.java +++ b/financer-server/src/main/java/de/financer/dba/AccountStatisticRepository.java @@ -2,6 +2,7 @@ package de.financer.dba; import de.financer.model.Account; import de.financer.model.AccountStatistic; +import de.financer.model.AccountStatus; import de.financer.model.Period; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; @@ -12,4 +13,7 @@ import org.springframework.transaction.annotation.Transactional; public interface AccountStatisticRepository extends CrudRepository { @Query("SELECT accStat FROM AccountStatistic accStat WHERE accStat.account = :account AND accStat.period = :period") AccountStatistic findForAccountAndPeriod(Account account, Period period); + + @Query("SELECT a FROM Account a WHERE a NOT IN (SELECT accStat.account FROM AccountStatistic accStat WHERE accStat.period = :period) AND a.status = :accountStatus") + Iterable getAccountsWithoutStatisticInPeriod(Period period, AccountStatus accountStatus); } diff --git a/financer-server/src/main/java/de/financer/service/AccountGroupService.java b/financer-server/src/main/java/de/financer/service/AccountGroupService.java index 14a58ea..4c95fa2 100644 --- a/financer-server/src/main/java/de/financer/service/AccountGroupService.java +++ b/financer-server/src/main/java/de/financer/service/AccountGroupService.java @@ -81,6 +81,16 @@ public class AccountGroupService { return ResponseReason.OK; } + /** + * This method gets the expenses for all account groups that were used between the given from and to + * dates. The result list contains one entry per account group that holds the sum of bookings. + * + * @param periodStart the start of the period as date without time + * @param periodEnd the end of the period as date without time + * @return a list containing the sum of bookings per account group in the given period, an empty list if + * either no bookings have been done yet or if the given period start and end dates are not valid, but never + * null + */ public Iterable getAccountGroupExpenses(String periodStart, String periodEnd) { LocalDate startDate; LocalDate endDate; @@ -96,12 +106,21 @@ public class AccountGroupService { return Collections.emptyList(); } + // Actual calculation done via SQL return this.accountGroupRepository.getAccountGroupExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE); } + /** + * This method gets the expenses for all account groups that were used in the current expense period. + * The result list contains one entry per account group that holds the sum of bookings. + * + * @return a list containing the sum of bookings per account group in the current expense period, an empty list if + * no bookings have been done yet, but never null + */ public Iterable getAccountGroupExpensesCurrentExpensePeriod() { final Period period = this.periodService.getCurrentExpensePeriod(); + // Actual calculation done via SQL return this.accountGroupRepository.getAccountGroupExpensesCurrentExpensePeriod(period, AccountType.LIABILITY, AccountType.EXPENSE); } } diff --git a/financer-server/src/main/java/de/financer/service/AccountService.java b/financer-server/src/main/java/de/financer/service/AccountService.java index ede5fa9..1b6422a 100644 --- a/financer-server/src/main/java/de/financer/service/AccountService.java +++ b/financer-server/src/main/java/de/financer/service/AccountService.java @@ -166,7 +166,8 @@ public class AccountService { * * @param periodStart the start of the arbitrary period * @param periodEnd the end of the arbitrary period - * @return a mapping of {@link Account}<->its expenses in the given period + * @return a mapping of {@link Account}<->its expenses in the given period, an empty list of no + * ookings have been done yet or the given start and end dates are invalid, but never null */ public Iterable getAccountExpenses(String periodStart, String periodEnd) { LocalDate startDate; @@ -183,17 +184,20 @@ public class AccountService { return Collections.emptyList(); } + // Actual calculation done in SQL return this.accountRepository.getAccountExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE); } /** - * This method calculates the expenses per account in the current expense period. + * This method gets the expenses per account in the current expense period. * - * @return a mapping of {@link Account}<->its expenses in the current expense period + * @return a mapping of {@link Account}<->its expenses in the current expense period, an empty list of no + * bookings have been done yet, but never null */ public Iterable getAccountExpensesCurrentExpensePeriod() { final Period period = this.periodService.getCurrentExpensePeriod(); + // Actual calculation done in SQL return this.accountRepository.getAccountExpenses(period, AccountType.LIABILITY, AccountType.EXPENSE); } } diff --git a/financer-server/src/main/java/de/financer/service/AccountStatisticService.java b/financer-server/src/main/java/de/financer/service/AccountStatisticService.java index b1b8858..22bf82d 100644 --- a/financer-server/src/main/java/de/financer/service/AccountStatisticService.java +++ b/financer-server/src/main/java/de/financer/service/AccountStatisticService.java @@ -1,10 +1,8 @@ package de.financer.service; import de.financer.dba.AccountStatisticRepository; -import de.financer.model.Account; -import de.financer.model.AccountStatistic; -import de.financer.model.Period; -import de.financer.model.Transaction; +import de.financer.model.*; +import org.apache.commons.collections4.IterableUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -14,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service public class AccountStatisticService { @@ -22,6 +21,17 @@ public class AccountStatisticService { @Autowired private AccountStatisticRepository accountStatisticRepository; + @Autowired + private AccountService accountService; + + /** + * This method calculates the {@link AccountStatistic}s for the given transaction. The statistics are always + * symmetric, meaning that one will be calculated for the from account and one for the to account, with their + * respective from or to spending filled. As a transaction can be assigned to more than one period this method will + * calculate two statistics per assigned period. + * + * @param transaction to calculate the statistics for + */ @Transactional(propagation = Propagation.REQUIRED) public void calculateStatistics(Transaction transaction) { final Account fromAccount = transaction.getFromAccount(); @@ -52,6 +62,34 @@ public class AccountStatisticService { this.accountStatisticRepository.saveAll(resultList); } + /** + * This method creates an {@link AccountStatistic}s entry with from and to spending 0, + * for every account not used in a booking in the given period. + * + * @param period to generate the null statistic entries for + */ + @Transactional(propagation = Propagation.REQUIRED) + public void generateNullStatisticsForUnusedAccounts(Period period) { + final Iterable accountsWithoutStat = this.accountStatisticRepository + .getAccountsWithoutStatisticInPeriod(period, AccountStatus.OPEN); + + this.accountStatisticRepository.saveAll(IterableUtils.toList(accountsWithoutStat) + .stream() + .map(a -> { + final AccountStatistic as = new AccountStatistic(); + + as.setTransactionCountTo(0); + as.setSpendingTotalTo(0); + as.setTransactionCountFrom(0); + as.setSpendingTotalFrom(0); + as.setAccount(a); + as.setPeriod(period); + + return as; + }) + .collect(Collectors.toList())); + } + private AccountStatistic calculateInternal(Account account, Period period, long amount, boolean from, int multiplier) { AccountStatistic accountStatistic = this.accountStatisticRepository .findForAccountAndPeriod(account, period); @@ -66,8 +104,7 @@ public class AccountStatisticService { if (from) { accountStatistic.setSpendingTotalFrom(accountStatistic.getSpendingTotalFrom() + amount * multiplier); accountStatistic.setTransactionCountFrom(accountStatistic.getTransactionCountFrom() + multiplier); - } - else { + } else { accountStatistic.setSpendingTotalTo(accountStatistic.getSpendingTotalTo() + amount * multiplier); accountStatistic.setTransactionCountTo(accountStatistic.getTransactionCountTo() + multiplier); } diff --git a/financer-server/src/main/java/de/financer/service/PeriodService.java b/financer-server/src/main/java/de/financer/service/PeriodService.java index 8bbd59f..e145264 100644 --- a/financer-server/src/main/java/de/financer/service/PeriodService.java +++ b/financer-server/src/main/java/de/financer/service/PeriodService.java @@ -20,6 +20,9 @@ public class PeriodService { @Autowired private PeriodRepository periodRepository; + @Autowired + private AccountStatisticService accountStatisticService; + /** * @return the currently open expense period */ @@ -48,6 +51,8 @@ public class PeriodService { this.periodRepository.save(currentPeriod); this.periodRepository.save(nextPeriod); + this.accountStatisticService.generateNullStatisticsForUnusedAccounts(currentPeriod); + response = ResponseReason.OK; } catch (Exception e) { LOGGER.error("Could not close current expense period!", e); diff --git a/financer-web-client/src/main/resources/static/changelog.txt b/financer-web-client/src/main/resources/static/changelog.txt index 9b0b676..1afeab3 100644 --- a/financer-web-client/src/main/resources/static/changelog.txt +++ b/financer-web-client/src/main/resources/static/changelog.txt @@ -1,3 +1,7 @@ +v25 -> v26: +- Close of the current expense period now creates null statistic entries for accounts that have not been used in + bookings in the period to close. This way the average spending better reflects the period average + v24 -> v25: - Add color column in account overview - Fix a bug that caused the chart generation to crash