Create null statistic entries for accounts not used in the period to close
This commit is contained in:
@@ -2,6 +2,7 @@ package de.financer.dba;
|
|||||||
|
|
||||||
import de.financer.model.Account;
|
import de.financer.model.Account;
|
||||||
import de.financer.model.AccountStatistic;
|
import de.financer.model.AccountStatistic;
|
||||||
|
import de.financer.model.AccountStatus;
|
||||||
import de.financer.model.Period;
|
import de.financer.model.Period;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.CrudRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
@@ -12,4 +13,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
public interface AccountStatisticRepository extends CrudRepository<AccountStatistic, Long> {
|
public interface AccountStatisticRepository extends CrudRepository<AccountStatistic, Long> {
|
||||||
@Query("SELECT accStat FROM AccountStatistic accStat WHERE accStat.account = :account AND accStat.period = :period")
|
@Query("SELECT accStat FROM AccountStatistic accStat WHERE accStat.account = :account AND accStat.period = :period")
|
||||||
AccountStatistic findForAccountAndPeriod(Account account, 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<Account> getAccountsWithoutStatisticInPeriod(Period period, AccountStatus accountStatus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,16 @@ public class AccountGroupService {
|
|||||||
return ResponseReason.OK;
|
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
|
||||||
|
* <code>null</code>
|
||||||
|
*/
|
||||||
public Iterable<AccountGroupExpense> getAccountGroupExpenses(String periodStart, String periodEnd) {
|
public Iterable<AccountGroupExpense> getAccountGroupExpenses(String periodStart, String periodEnd) {
|
||||||
LocalDate startDate;
|
LocalDate startDate;
|
||||||
LocalDate endDate;
|
LocalDate endDate;
|
||||||
@@ -96,12 +106,21 @@ public class AccountGroupService {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actual calculation done via SQL
|
||||||
return this.accountGroupRepository.getAccountGroupExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE);
|
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 <code>null</code>
|
||||||
|
*/
|
||||||
public Iterable<AccountGroupExpense> getAccountGroupExpensesCurrentExpensePeriod() {
|
public Iterable<AccountGroupExpense> getAccountGroupExpensesCurrentExpensePeriod() {
|
||||||
final Period period = this.periodService.getCurrentExpensePeriod();
|
final Period period = this.periodService.getCurrentExpensePeriod();
|
||||||
|
|
||||||
|
// Actual calculation done via SQL
|
||||||
return this.accountGroupRepository.getAccountGroupExpensesCurrentExpensePeriod(period, AccountType.LIABILITY, AccountType.EXPENSE);
|
return this.accountGroupRepository.getAccountGroupExpensesCurrentExpensePeriod(period, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,8 @@ public class AccountService {
|
|||||||
*
|
*
|
||||||
* @param periodStart the start of the arbitrary period
|
* @param periodStart the start of the arbitrary period
|
||||||
* @param periodEnd the end 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 <code>null</code>
|
||||||
*/
|
*/
|
||||||
public Iterable<AccountExpense> getAccountExpenses(String periodStart, String periodEnd) {
|
public Iterable<AccountExpense> getAccountExpenses(String periodStart, String periodEnd) {
|
||||||
LocalDate startDate;
|
LocalDate startDate;
|
||||||
@@ -183,17 +184,20 @@ public class AccountService {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actual calculation done in SQL
|
||||||
return this.accountRepository.getAccountExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE);
|
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 <code>null</code>
|
||||||
*/
|
*/
|
||||||
public Iterable<AccountExpense> getAccountExpensesCurrentExpensePeriod() {
|
public Iterable<AccountExpense> getAccountExpensesCurrentExpensePeriod() {
|
||||||
final Period period = this.periodService.getCurrentExpensePeriod();
|
final Period period = this.periodService.getCurrentExpensePeriod();
|
||||||
|
|
||||||
|
// Actual calculation done in SQL
|
||||||
return this.accountRepository.getAccountExpenses(period, AccountType.LIABILITY, AccountType.EXPENSE);
|
return this.accountRepository.getAccountExpenses(period, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package de.financer.service;
|
package de.financer.service;
|
||||||
|
|
||||||
import de.financer.dba.AccountStatisticRepository;
|
import de.financer.dba.AccountStatisticRepository;
|
||||||
import de.financer.model.Account;
|
import de.financer.model.*;
|
||||||
import de.financer.model.AccountStatistic;
|
import org.apache.commons.collections4.IterableUtils;
|
||||||
import de.financer.model.Period;
|
|
||||||
import de.financer.model.Transaction;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -14,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AccountStatisticService {
|
public class AccountStatisticService {
|
||||||
@@ -22,6 +21,17 @@ public class AccountStatisticService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AccountStatisticRepository accountStatisticRepository;
|
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)
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
public void calculateStatistics(Transaction transaction) {
|
public void calculateStatistics(Transaction transaction) {
|
||||||
final Account fromAccount = transaction.getFromAccount();
|
final Account fromAccount = transaction.getFromAccount();
|
||||||
@@ -52,6 +62,34 @@ public class AccountStatisticService {
|
|||||||
this.accountStatisticRepository.saveAll(resultList);
|
this.accountStatisticRepository.saveAll(resultList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates an {@link AccountStatistic}s entry with from and to spending <code>0</code>,
|
||||||
|
* 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<Account> 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) {
|
private AccountStatistic calculateInternal(Account account, Period period, long amount, boolean from, int multiplier) {
|
||||||
AccountStatistic accountStatistic = this.accountStatisticRepository
|
AccountStatistic accountStatistic = this.accountStatisticRepository
|
||||||
.findForAccountAndPeriod(account, period);
|
.findForAccountAndPeriod(account, period);
|
||||||
@@ -66,8 +104,7 @@ public class AccountStatisticService {
|
|||||||
if (from) {
|
if (from) {
|
||||||
accountStatistic.setSpendingTotalFrom(accountStatistic.getSpendingTotalFrom() + amount * multiplier);
|
accountStatistic.setSpendingTotalFrom(accountStatistic.getSpendingTotalFrom() + amount * multiplier);
|
||||||
accountStatistic.setTransactionCountFrom(accountStatistic.getTransactionCountFrom() + multiplier);
|
accountStatistic.setTransactionCountFrom(accountStatistic.getTransactionCountFrom() + multiplier);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
accountStatistic.setSpendingTotalTo(accountStatistic.getSpendingTotalTo() + amount * multiplier);
|
accountStatistic.setSpendingTotalTo(accountStatistic.getSpendingTotalTo() + amount * multiplier);
|
||||||
accountStatistic.setTransactionCountTo(accountStatistic.getTransactionCountTo() + multiplier);
|
accountStatistic.setTransactionCountTo(accountStatistic.getTransactionCountTo() + multiplier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ public class PeriodService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PeriodRepository periodRepository;
|
private PeriodRepository periodRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AccountStatisticService accountStatisticService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the currently open expense period
|
* @return the currently open expense period
|
||||||
*/
|
*/
|
||||||
@@ -48,6 +51,8 @@ public class PeriodService {
|
|||||||
this.periodRepository.save(currentPeriod);
|
this.periodRepository.save(currentPeriod);
|
||||||
this.periodRepository.save(nextPeriod);
|
this.periodRepository.save(nextPeriod);
|
||||||
|
|
||||||
|
this.accountStatisticService.generateNullStatisticsForUnusedAccounts(currentPeriod);
|
||||||
|
|
||||||
response = ResponseReason.OK;
|
response = ResponseReason.OK;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Could not close current expense period!", e);
|
LOGGER.error("Could not close current expense period!", e);
|
||||||
|
|||||||
@@ -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:
|
v24 -> v25:
|
||||||
- Add color column in account overview
|
- Add color column in account overview
|
||||||
- Fix a bug that caused the chart generation to crash
|
- Fix a bug that caused the chart generation to crash
|
||||||
|
|||||||
Reference in New Issue
Block a user