Create null statistic entries for accounts not used in the period to close

This commit is contained in:
2019-11-14 21:57:34 +01:00
parent 278bd41f0f
commit 8cec43ee91
6 changed files with 82 additions and 9 deletions

View File

@@ -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<AccountStatistic, Long> {
@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<Account> getAccountsWithoutStatisticInPeriod(Period period, AccountStatus accountStatus);
}

View File

@@ -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
* <code>null</code>
*/
public Iterable<AccountGroupExpense> 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 <code>null</code>
*/
public Iterable<AccountGroupExpense> getAccountGroupExpensesCurrentExpensePeriod() {
final Period period = this.periodService.getCurrentExpensePeriod();
// Actual calculation done via SQL
return this.accountGroupRepository.getAccountGroupExpensesCurrentExpensePeriod(period, AccountType.LIABILITY, AccountType.EXPENSE);
}
}

View File

@@ -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 <code>null</code>
*/
public Iterable<AccountExpense> 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 <code>null</code>
*/
public Iterable<AccountExpense> getAccountExpensesCurrentExpensePeriod() {
final Period period = this.periodService.getCurrentExpensePeriod();
// Actual calculation done in SQL
return this.accountRepository.getAccountExpenses(period, AccountType.LIABILITY, AccountType.EXPENSE);
}
}

View File

@@ -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 <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) {
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);
}

View File

@@ -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);