Introduce periods (and other smaller stuff)
This commit is contained in:
@@ -28,6 +28,34 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
<groupId>xml-apis</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Dependencies for meta model generation of Hibernate entities -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<!-- Misc dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package de.financer.dto;
|
||||
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.Period;
|
||||
|
||||
public class ExpensePeriodTotal {
|
||||
private Long total;
|
||||
private String periodShortCode;
|
||||
private Period period;
|
||||
private AccountType type;
|
||||
|
||||
public ExpensePeriodTotal() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public ExpensePeriodTotal(String periodShortCode, Long total, AccountType type) {
|
||||
public ExpensePeriodTotal(Period period, Long total, AccountType type) {
|
||||
this.total = total;
|
||||
this.periodShortCode = periodShortCode;
|
||||
this.period = period;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@@ -25,12 +26,12 @@ public class ExpensePeriodTotal {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public String getPeriodShortCode() {
|
||||
return periodShortCode;
|
||||
public Period getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
public void setPeriodShortCode(String periodShortCode) {
|
||||
this.periodShortCode = periodShortCode;
|
||||
public void setPeriod(Period period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.financer.model;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
public class Account {
|
||||
@@ -16,6 +17,9 @@ public class Account {
|
||||
private Long currentBalance;
|
||||
@ManyToOne
|
||||
private AccountGroup accountGroup;
|
||||
@OneToMany(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "account_id")
|
||||
private Set<AccountStatistic> accountStatistics;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
@@ -60,4 +64,12 @@ public class Account {
|
||||
public void setAccountGroup(AccountGroup accountGroup) {
|
||||
this.accountGroup = accountGroup;
|
||||
}
|
||||
|
||||
public Set<AccountStatistic> getAccountStatistics() {
|
||||
return accountStatistics;
|
||||
}
|
||||
|
||||
public void setAccountStatistics(Set<AccountStatistic> accountStatistics) {
|
||||
this.accountStatistics = accountStatistics;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package de.financer.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
public class AccountStatistic {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
// We need to ignore this field during serialization as otherwise
|
||||
// the stack will overflow because of the circular dependency
|
||||
// between Account and AccountStatistic
|
||||
@JsonIgnore
|
||||
private Account account;
|
||||
@OneToOne(fetch = FetchType.EAGER)
|
||||
private Period period;
|
||||
private long spendingTotalFrom;
|
||||
private long transactionCountFrom;
|
||||
private long spendingTotalTo;
|
||||
private long transactionCountTo;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public Period getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
public void setPeriod(Period period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
public long getSpendingTotalFrom() {
|
||||
return spendingTotalFrom;
|
||||
}
|
||||
|
||||
public void setSpendingTotalFrom(long spendingTotalFrom) {
|
||||
this.spendingTotalFrom = spendingTotalFrom;
|
||||
}
|
||||
|
||||
public long getTransactionCountFrom() {
|
||||
return transactionCountFrom;
|
||||
}
|
||||
|
||||
public void setTransactionCountFrom(long transactionCountFrom) {
|
||||
this.transactionCountFrom = transactionCountFrom;
|
||||
}
|
||||
|
||||
public long getSpendingTotalTo() {
|
||||
return spendingTotalTo;
|
||||
}
|
||||
|
||||
public void setSpendingTotalTo(long spendingTotalTo) {
|
||||
this.spendingTotalTo = spendingTotalTo;
|
||||
}
|
||||
|
||||
public long getTransactionCountTo() {
|
||||
return transactionCountTo;
|
||||
}
|
||||
|
||||
public void setTransactionCountTo(long transactionCountTo) {
|
||||
this.transactionCountTo = transactionCountTo;
|
||||
}
|
||||
}
|
||||
44
financer-common/src/main/java/de/financer/model/Period.java
Normal file
44
financer-common/src/main/java/de/financer/model/Period.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package de.financer.model;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
public class Period {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
@Enumerated(EnumType.STRING)
|
||||
private PeriodType type;
|
||||
private LocalDateTime start;
|
||||
@Column(name = "\"end\"")
|
||||
private LocalDateTime end;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public PeriodType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(PeriodType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public LocalDateTime getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(LocalDateTime start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public LocalDateTime getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(LocalDateTime end) {
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.financer.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum PeriodType {
|
||||
/** User defined start and end */
|
||||
EXPENSE;
|
||||
|
||||
/**
|
||||
* This method validates whether the given string represents a valid period type.
|
||||
*
|
||||
* @param type to check
|
||||
* @return whether the given type represents a valid period type
|
||||
*/
|
||||
public static boolean isValidType(String type) {
|
||||
return Arrays.stream(PeriodType.values()).anyMatch((periodType) -> periodType.name().equals(type));
|
||||
}
|
||||
|
||||
public static List<String> valueList() {
|
||||
return Arrays.stream(PeriodType.values()).map(PeriodType::name).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package de.financer.model;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Table(name = "\"transaction\"")
|
||||
@@ -16,9 +17,16 @@ public class Transaction {
|
||||
@Column(name = "\"date\"")
|
||||
private LocalDate date;
|
||||
private String description;
|
||||
private Long amount;
|
||||
private long amount;
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
private RecurringTransaction recurringTransaction;
|
||||
@OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
|
||||
//@formatter:off
|
||||
@JoinTable(name = "link_transaction_period",
|
||||
joinColumns = @JoinColumn(name = "transaction_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "period_id"))
|
||||
//@formatter:on
|
||||
private Set<Period> periods;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
@@ -56,11 +64,11 @@ public class Transaction {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Long getAmount() {
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(Long amount) {
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@@ -71,4 +79,12 @@ public class Transaction {
|
||||
public void setRecurringTransaction(RecurringTransaction recurringTransaction) {
|
||||
this.recurringTransaction = recurringTransaction;
|
||||
}
|
||||
|
||||
public Set<Period> getPeriods() {
|
||||
return periods;
|
||||
}
|
||||
|
||||
public void setPeriods(Set<Period> periods) {
|
||||
this.periods = periods;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package de.financer.util;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.Period;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ExpensePeriod {
|
||||
private LocalDate start;
|
||||
private LocalDate end;
|
||||
|
||||
private ExpensePeriod(LocalDate start, LocalDate end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public static final ExpensePeriod getCurrentExpensePeriod(int periodStartDay) {
|
||||
LocalDate periodStart;
|
||||
LocalDate periodEnd;
|
||||
|
||||
if (LocalDate.now().getDayOfMonth() < periodStartDay) {
|
||||
// If the current day of month is less than the configured period start day
|
||||
// we need to go back one month in time and then set the day to the configured
|
||||
// start day. For example:
|
||||
// configured start day of month for period = 15
|
||||
// now = 2019-06-10
|
||||
// => 10 < 15
|
||||
// now - one month = 2019-05-10
|
||||
// set the day = 2019-05-15 = start
|
||||
// end = start + one month - one day = 2019-06-14
|
||||
// Period from 2019-05-15 to 2019-06-14
|
||||
periodStart = LocalDate.now().minusMonths(1).withDayOfMonth(periodStartDay);
|
||||
periodEnd = periodStart.plusMonths(1).minusDays(1);
|
||||
} else {
|
||||
// Else, the current day of month is greater or equals the configured period
|
||||
// start day, just reset the day of month to the configured day, for example:
|
||||
// configured start day of month for period = 15
|
||||
// now = 2019-06-26
|
||||
// => 26 > 15
|
||||
// set the day = 2019-06-15 = start
|
||||
// end = start + one month - one day = 2019-07-14
|
||||
// Period from 2019-06-15 to 2019-07-14
|
||||
periodStart = LocalDate.now().withDayOfMonth(periodStartDay);
|
||||
periodEnd = periodStart.plusMonths(1).minusDays(1);
|
||||
}
|
||||
|
||||
return new ExpensePeriod(periodStart, periodEnd);
|
||||
}
|
||||
|
||||
public static final List<ExpensePeriod> generateExpensePeriodsForYear(int periodStartDay, int year) {
|
||||
Stream<LocalDate> localDateStreamStart;
|
||||
Stream<LocalDate> localDateStreamEnd;
|
||||
|
||||
if (periodStartDay == 1) {
|
||||
localDateStreamStart = LocalDate.of(year, Month.JANUARY, 1)
|
||||
.datesUntil(LocalDate.of(year, Month.DECEMBER, 2), Period
|
||||
.ofMonths(1));
|
||||
|
||||
localDateStreamEnd = LocalDate.of(year, Month.JANUARY, 31)
|
||||
.datesUntil(LocalDate.of(year + 1, Month.JANUARY, 1), Period
|
||||
.ofMonths(1));
|
||||
}
|
||||
// If the start of the period is not the 1st we need to look to the previous year as well
|
||||
// because the period in January actually started in December last year
|
||||
else {
|
||||
localDateStreamStart = LocalDate.of(year, Month.JANUARY, periodStartDay).minusMonths(1)
|
||||
.datesUntil(LocalDate.of(year, Month.DECEMBER, periodStartDay + 1).plusDays(1), Period
|
||||
.ofMonths(1));
|
||||
|
||||
localDateStreamEnd = LocalDate.of(year, Month.JANUARY, periodStartDay - 1)
|
||||
.datesUntil(LocalDate.of(year, Month.DECEMBER, periodStartDay + 1).plusDays(1), Period
|
||||
.ofMonths(1));
|
||||
}
|
||||
|
||||
return Streams
|
||||
.zip(localDateStreamStart, localDateStreamEnd, (start, end) -> new ExpensePeriod(start, end))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public String generatePeriodShortCode() {
|
||||
return String.format("%s/%s", this.start.getYear(), this.start.getMonthValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Start[%s], End[%s], ShortCode[%s]", this.start, this.end, this.generatePeriodShortCode());
|
||||
}
|
||||
|
||||
public LocalDate getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public LocalDate getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@
|
||||
|
||||
<artifactId>financer-server</artifactId>
|
||||
<packaging>${packaging.type}</packaging>
|
||||
<description>The server part of the financer application - a simple app to manage your personal finances</description>
|
||||
<description>The server part of the financer application - a simple app to manage your personal finances
|
||||
</description>
|
||||
<name>financer-server</name>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -6,32 +6,32 @@ import org.springframework.http.ResponseEntity;
|
||||
public enum ResponseReason {
|
||||
OK(HttpStatus.OK),
|
||||
UNKNOWN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_ACCOUNT_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
FROM_ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
TO_ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
FROM_AND_TO_ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_DATE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_DATE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
AMOUNT_ZERO(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_AMOUNT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_BOOKING_ACCOUNTS(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_HOLIDAY_WEEKEND_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_HOLIDAY_WEEKEND_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_INTERVAL_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_INTERVAL_TYPE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_FIRST_OCCURRENCE(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_FIRST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_LAST_OCCURRENCE_FORMAT(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_RECURRING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_RECURRING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
RECURRING_TRANSACTION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
MISSING_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
INVALID_TRANSACTION_ID(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
TRANSACTION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
ACCOUNT_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
DUPLICATE_ACCOUNT_KEY(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
DUPLICATE_ACCOUNT_GROUP_NAME(HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
ACCOUNT_GROUP_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
INVALID_ACCOUNT_TYPE(HttpStatus.BAD_REQUEST),
|
||||
FROM_ACCOUNT_NOT_FOUND(HttpStatus.BAD_REQUEST),
|
||||
TO_ACCOUNT_NOT_FOUND(HttpStatus.BAD_REQUEST),
|
||||
FROM_AND_TO_ACCOUNT_NOT_FOUND(HttpStatus.BAD_REQUEST),
|
||||
INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST),
|
||||
MISSING_DATE(HttpStatus.BAD_REQUEST),
|
||||
AMOUNT_ZERO(HttpStatus.BAD_REQUEST),
|
||||
MISSING_AMOUNT(HttpStatus.BAD_REQUEST),
|
||||
INVALID_BOOKING_ACCOUNTS(HttpStatus.BAD_REQUEST),
|
||||
MISSING_HOLIDAY_WEEKEND_TYPE(HttpStatus.BAD_REQUEST),
|
||||
INVALID_HOLIDAY_WEEKEND_TYPE(HttpStatus.BAD_REQUEST),
|
||||
MISSING_INTERVAL_TYPE(HttpStatus.BAD_REQUEST),
|
||||
INVALID_INTERVAL_TYPE(HttpStatus.BAD_REQUEST),
|
||||
MISSING_FIRST_OCCURRENCE(HttpStatus.BAD_REQUEST),
|
||||
INVALID_FIRST_OCCURRENCE_FORMAT(HttpStatus.BAD_REQUEST),
|
||||
INVALID_LAST_OCCURRENCE_FORMAT(HttpStatus.BAD_REQUEST),
|
||||
MISSING_RECURRING_TRANSACTION_ID(HttpStatus.BAD_REQUEST),
|
||||
INVALID_RECURRING_TRANSACTION_ID(HttpStatus.BAD_REQUEST),
|
||||
RECURRING_TRANSACTION_NOT_FOUND(HttpStatus.BAD_REQUEST),
|
||||
MISSING_TRANSACTION_ID(HttpStatus.BAD_REQUEST),
|
||||
INVALID_TRANSACTION_ID(HttpStatus.BAD_REQUEST),
|
||||
TRANSACTION_NOT_FOUND(HttpStatus.BAD_REQUEST),
|
||||
ACCOUNT_NOT_FOUND(HttpStatus.BAD_REQUEST),
|
||||
DUPLICATE_ACCOUNT_KEY(HttpStatus.BAD_REQUEST),
|
||||
DUPLICATE_ACCOUNT_GROUP_NAME(HttpStatus.BAD_REQUEST),
|
||||
ACCOUNT_GROUP_NOT_FOUND(HttpStatus.BAD_REQUEST);
|
||||
|
||||
private HttpStatus httpStatus;
|
||||
|
||||
|
||||
@@ -111,4 +111,13 @@ public class AccountController {
|
||||
|
||||
return this.accountService.getAccountExpenses(periodStart, periodEnd);
|
||||
}
|
||||
|
||||
@RequestMapping("getAccountExpensesCurrentExpensePeriod")
|
||||
public Iterable<AccountExpense> getAccountExpensesCurrentExpensePeriod() {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("/accounts/getAccountExpensesCurrentExpensePeriod called");
|
||||
}
|
||||
|
||||
return this.accountService.getAccountExpensesCurrentExpensePeriod();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,13 @@ public class AccountGroupController {
|
||||
|
||||
return this.accountGroupService.getAccountGroupExpenses(periodStart, periodEnd);
|
||||
}
|
||||
|
||||
@RequestMapping("getAccountGroupExpensesCurrentExpensePeriod")
|
||||
public Iterable<AccountGroupExpense> getAccountGroupExpensesCurrentExpensePeriod() {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("/accountGroups/getAccountGroupExpenses called");
|
||||
}
|
||||
|
||||
return this.accountGroupService.getAccountGroupExpensesCurrentExpensePeriod();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.financer.controller;
|
||||
|
||||
import de.financer.model.Period;
|
||||
import de.financer.service.PeriodService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("periods")
|
||||
public class PeriodController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PeriodController.class);
|
||||
|
||||
@Autowired
|
||||
private PeriodService periodService;
|
||||
|
||||
@RequestMapping("closeCurrentExpensePeriod")
|
||||
public ResponseEntity closeCurrentExpensePeriod() {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("/periods/closeCurrentExpensePeriod called");
|
||||
}
|
||||
|
||||
final ResponseEntity responseEntity = this.periodService.closeCurrentExpensePeriod().toResponseEntity();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/periods/closeCurrentExpensePeriod returns with %s", responseEntity));
|
||||
}
|
||||
|
||||
return responseEntity;
|
||||
}
|
||||
|
||||
@RequestMapping("getCurrentExpensePeriod")
|
||||
public Period getCurrentExpensePeriod() {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("/periods/getCurrentExpensePeriod called");
|
||||
}
|
||||
|
||||
final Period currentExpensePeriod = this.periodService.getCurrentExpensePeriod();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/periods/getCurrentExpensePeriod returns with %s", currentExpensePeriod));
|
||||
}
|
||||
|
||||
return currentExpensePeriod;
|
||||
}
|
||||
}
|
||||
@@ -79,12 +79,12 @@ public class TransactionController {
|
||||
}
|
||||
|
||||
@RequestMapping("getExpensesCurrentPeriod")
|
||||
public Long getExpensesCurrentPeriod(Integer monthPeriodStartDay) {
|
||||
public Long getExpensesCurrentPeriod() {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/transactions/getExpensesCurrentPeriod got parameters: %s", monthPeriodStartDay));
|
||||
LOGGER.debug(String.format("/transactions/getExpensesCurrentPeriod called"));
|
||||
}
|
||||
|
||||
final Long response = this.transactionService.getExpensesCurrentPeriod(monthPeriodStartDay);
|
||||
final Long response = this.transactionService.getExpensesCurrentPeriod();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/transactions/getExpensesCurrentPeriod returns with %s", response));
|
||||
@@ -94,13 +94,13 @@ public class TransactionController {
|
||||
}
|
||||
|
||||
@RequestMapping("getExpensePeriodTotals")
|
||||
public Iterable<ExpensePeriodTotal> getExpensePeriodTotals(Integer monthPeriodStartDay, Integer year) {
|
||||
public Iterable<ExpensePeriodTotal> getExpensePeriodTotals(Integer year) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/transactions/getExpensePeriodTotals got parameters: %s, %s", monthPeriodStartDay, year));
|
||||
LOGGER.debug(String.format("/transactions/getExpensePeriodTotals got parameters: %s", year));
|
||||
}
|
||||
|
||||
final Iterable<ExpensePeriodTotal> expensePeriodTotals = this.transactionService
|
||||
.getExpensePeriodTotals(monthPeriodStartDay, year);
|
||||
.getExpensePeriodTotals(year);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("/transactions/getExpensePeriodTotals returns with %s", expensePeriodTotals));
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.financer.dba;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.model.AccountGroup;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.Period;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
@@ -16,4 +17,7 @@ public interface AccountGroupRepository extends CrudRepository<AccountGroup, Lon
|
||||
|
||||
@Query("SELECT new de.financer.dto.AccountGroupExpense(ag, SUM(t.amount)) FROM Transaction t JOIN t.toAccount a JOIN a.accountGroup ag WHERE a.type in :expenseTypes AND t.date BETWEEN :startDate AND :startEnd GROUP BY ag")
|
||||
Iterable<AccountGroupExpense> getAccountGroupExpenses(LocalDate startDate, LocalDate startEnd, AccountType... expenseTypes);
|
||||
|
||||
@Query("SELECT new de.financer.dto.AccountGroupExpense(ag, SUM(t.amount)) FROM Transaction t JOIN t.toAccount a JOIN a.accountGroup ag JOIN t.periods p WHERE a.type in :expenseTypes AND p = :period GROUP BY ag")
|
||||
Iterable<AccountGroupExpense> getAccountGroupExpensesCurrentExpensePeriod(Period period, AccountType... expenseTypes);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.financer.dba;
|
||||
import de.financer.dto.AccountExpense;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.Period;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
@@ -19,4 +20,7 @@ public interface AccountRepository extends CrudRepository<Account, Long> {
|
||||
|
||||
@Query("SELECT new de.financer.dto.AccountExpense(a, SUM(t.amount)) FROM Transaction t JOIN t.toAccount a WHERE a.type in :expenseTypes AND t.date BETWEEN :startDate AND :startEnd GROUP BY a")
|
||||
Iterable<AccountExpense> getAccountExpenses(LocalDate startDate, LocalDate startEnd, AccountType... expenseTypes);
|
||||
|
||||
@Query("SELECT new de.financer.dto.AccountExpense(a, SUM(t.amount)) FROM Transaction t JOIN t.toAccount a JOIN t.periods p WHERE a.type in :expenseTypes AND p = :period GROUP BY a")
|
||||
Iterable<AccountExpense> getAccountExpenses(Period period, AccountType... expenseTypes);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.financer.dba;
|
||||
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountStatistic;
|
||||
import de.financer.model.Period;
|
||||
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;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.financer.dba;
|
||||
|
||||
import de.financer.model.Period;
|
||||
import de.financer.model.PeriodType;
|
||||
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;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public interface PeriodRepository extends CrudRepository<Period, Long> {
|
||||
@Query("SELECT p FROM Period p WHERE p.type = :type AND p.end IS NULL")
|
||||
Period findCurrentExpensePeriod(PeriodType type);
|
||||
|
||||
@Query("SELECT p FROM Period p WHERE p.type = :type AND YEAR(p.start) = :year")
|
||||
Iterable<Period> getAllPeriodsForYear(PeriodType type, Integer year);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package de.financer.dba;
|
||||
import de.financer.dto.ExpensePeriodTotal;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.Period;
|
||||
import de.financer.model.Transaction;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
@@ -16,13 +17,12 @@ import java.util.List;
|
||||
public interface TransactionRepository extends CrudRepository<Transaction, Long> {
|
||||
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);
|
||||
@Query("SELECT SUM(t.amount) FROM Transaction t JOIN t.periods p JOIN t.toAccount a WHERE a.type IN :expenseTypes AND p = :period")
|
||||
Long getExpensesForPeriod(Period period, AccountType... expenseTypes);
|
||||
|
||||
// The HQL contains a hack because Hibernate can't resolve the alias of the CASE column in the GROUP BY clause
|
||||
// That's why the generated alias is used directly in the HQL. It will break if the columns in the SELECT clause get reordered
|
||||
// col_0_0_ instead of periodShortCode
|
||||
// col_2_0_ instead of AccType
|
||||
@Query("SELECT new de.financer.dto.ExpensePeriodTotal(CASE WHEN EXTRACT(DAY FROM t.date) >= :startDay THEN CONCAT(CAST(EXTRACT(YEAR FROM t.date) AS string), '/', CAST(EXTRACT(MONTH FROM t.date) AS string)) ELSE CASE WHEN EXTRACT(MONTH FROM t.date) = 1 THEN CONCAT(CAST(EXTRACT(YEAR FROM t.date) - 1 AS string), '/12') ELSE CONCAT(CAST(EXTRACT(YEAR FROM t.date) AS string), '/', CAST(EXTRACT(MONTH FROM t.date) - 1 AS string)) END END AS periodShortCode, SUM(t.amount), CASE WHEN fa.type = :incomeType THEN fa.type ELSE ta.type END AS AccType) FROM Transaction t JOIN t.toAccount ta JOIN t.fromAccount fa WHERE ((ta.type IN :expenseTypes AND fa.type <> :startType) OR (fa.type = :incomeType)) AND t.date BETWEEN :lowerBound AND :upperBound GROUP BY col_0_0_, col_2_0_ ORDER BY periodShortCode, AccType ASC")
|
||||
List<ExpensePeriodTotal> getAccountExpenseTotals(int startDay, LocalDate lowerBound, LocalDate upperBound, AccountType incomeType, AccountType startType, AccountType... expenseTypes);
|
||||
@Query("SELECT new de.financer.dto.ExpensePeriodTotal(p, SUM(t.amount), CASE WHEN fa.type = :incomeType THEN fa.type ELSE ta.type END AS AccType) FROM Transaction t JOIN t.toAccount ta JOIN t.fromAccount fa JOIN t.periods p WHERE ((ta.type IN :expenseTypes AND fa.type <> :startType) OR (fa.type = :incomeType)) AND p IN :periods GROUP BY p, col_2_0_")
|
||||
List<ExpensePeriodTotal> getAccountExpenseTotals(Iterable<Period> periods, AccountType incomeType, AccountType startType, AccountType... expenseTypes);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import de.financer.dba.AccountGroupRepository;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.model.AccountGroup;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.Period;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -26,6 +27,9 @@ public class AccountGroupService {
|
||||
@Autowired
|
||||
private AccountGroupRepository accountGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private PeriodService periodService;
|
||||
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@@ -94,4 +98,10 @@ public class AccountGroupService {
|
||||
|
||||
return this.accountGroupRepository.getAccountGroupExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||
}
|
||||
|
||||
public Iterable<AccountGroupExpense> getAccountGroupExpensesCurrentExpensePeriod() {
|
||||
final Period period = this.periodService.getCurrentExpensePeriod();
|
||||
|
||||
return this.accountGroupRepository.getAccountGroupExpensesCurrentExpensePeriod(period, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dba.AccountRepository;
|
||||
import de.financer.dto.AccountExpense;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountGroup;
|
||||
import de.financer.model.AccountStatus;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -32,6 +29,9 @@ public class AccountService {
|
||||
@Autowired
|
||||
private AccountGroupService accountGroupService;
|
||||
|
||||
@Autowired
|
||||
private PeriodService periodService;
|
||||
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@@ -161,6 +161,13 @@ public class AccountService {
|
||||
return this.accountRepository.getCurrentAssets(AccountType.BANK, AccountType.CASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the expenses per account in the given arbitrary period.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public Iterable<AccountExpense> getAccountExpenses(String periodStart, String periodEnd) {
|
||||
LocalDate startDate;
|
||||
LocalDate endDate;
|
||||
@@ -178,4 +185,15 @@ public class AccountService {
|
||||
|
||||
return this.accountRepository.getAccountExpenses(startDate, endDate, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the expenses per account in the current expense period.
|
||||
*
|
||||
* @return a mapping of {@link Account}<->its expenses in the current expense period
|
||||
*/
|
||||
public Iterable<AccountExpense> getAccountExpensesCurrentExpensePeriod() {
|
||||
final Period period = this.periodService.getCurrentExpensePeriod();
|
||||
|
||||
return this.accountRepository.getAccountExpenses(period, AccountType.LIABILITY, AccountType.EXPENSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AccountStatisticService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PeriodService.class);
|
||||
|
||||
@Autowired
|
||||
private AccountStatisticRepository accountStatisticRepository;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public void calculateStatistics(Transaction transaction) {
|
||||
final Account fromAccount = transaction.getFromAccount();
|
||||
final Account toAccount = transaction.getToAccount();
|
||||
final long amount = transaction.getAmount();
|
||||
final List<AccountStatistic> resultList = new ArrayList<>();
|
||||
|
||||
for (final Period period : transaction.getPeriods()) {
|
||||
resultList.add(calculateInternal(fromAccount, period, amount, true, 1));
|
||||
resultList.add(calculateInternal(toAccount, period, amount, false, 1));
|
||||
}
|
||||
|
||||
this.accountStatisticRepository.saveAll(resultList);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public void revertStatistics(Transaction transaction) {
|
||||
final Account fromAccount = transaction.getFromAccount();
|
||||
final Account toAccount = transaction.getToAccount();
|
||||
final long amount = transaction.getAmount();
|
||||
final List<AccountStatistic> resultList = new ArrayList<>();
|
||||
|
||||
for (final Period period : transaction.getPeriods()) {
|
||||
resultList.add(calculateInternal(fromAccount, period, amount, true, -1));
|
||||
resultList.add(calculateInternal(toAccount, period, amount, false, -1));
|
||||
}
|
||||
|
||||
this.accountStatisticRepository.saveAll(resultList);
|
||||
}
|
||||
|
||||
private AccountStatistic calculateInternal(Account account, Period period, long amount, boolean from, int multiplier) {
|
||||
AccountStatistic accountStatistic = this.accountStatisticRepository
|
||||
.findForAccountAndPeriod(account, period);
|
||||
|
||||
if (accountStatistic == null) {
|
||||
accountStatistic = new AccountStatistic();
|
||||
|
||||
accountStatistic.setAccount(account);
|
||||
accountStatistic.setPeriod(period);
|
||||
}
|
||||
|
||||
if (from) {
|
||||
accountStatistic.setSpendingTotalFrom(accountStatistic.getSpendingTotalFrom() + amount * multiplier);
|
||||
accountStatistic.setTransactionCountFrom(accountStatistic.getTransactionCountFrom() + multiplier);
|
||||
}
|
||||
else {
|
||||
accountStatistic.setSpendingTotalTo(accountStatistic.getSpendingTotalTo() + amount * multiplier);
|
||||
accountStatistic.setTransactionCountTo(accountStatistic.getTransactionCountTo() + multiplier);
|
||||
}
|
||||
|
||||
return accountStatistic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package de.financer.service;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.dba.PeriodRepository;
|
||||
import de.financer.model.Period;
|
||||
import de.financer.model.PeriodType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
public class PeriodService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PeriodService.class);
|
||||
|
||||
@Autowired
|
||||
private PeriodRepository periodRepository;
|
||||
|
||||
/**
|
||||
* @return the currently open expense period
|
||||
*/
|
||||
public Period getCurrentExpensePeriod() {
|
||||
return this.periodRepository.findCurrentExpensePeriod(PeriodType.EXPENSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method closes the current expense period and opens a new one.
|
||||
*
|
||||
* @return {@link ResponseReason#OK} if the operation succeeded, {@link ResponseReason#OK}
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
public ResponseReason closeCurrentExpensePeriod() {
|
||||
final Period currentPeriod = this.getCurrentExpensePeriod();
|
||||
final Period nextPeriod = new Period();
|
||||
final LocalDateTime now = LocalDateTime.now();
|
||||
ResponseReason response;
|
||||
|
||||
currentPeriod.setEnd(now);
|
||||
|
||||
nextPeriod.setStart(now.plusSeconds(1));
|
||||
nextPeriod.setType(PeriodType.EXPENSE);
|
||||
|
||||
try {
|
||||
this.periodRepository.save(currentPeriod);
|
||||
this.periodRepository.save(nextPeriod);
|
||||
|
||||
response = ResponseReason.OK;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Could not close current expense period!", e);
|
||||
|
||||
response = ResponseReason.UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public Iterable<Period> getAllExpensePeriodsForYear(Integer year) {
|
||||
return this.periodRepository.getAllPeriodsForYear(PeriodType.EXPENSE, year);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,10 @@
|
||||
package de.financer.service;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dba.TransactionRepository;
|
||||
import de.financer.dto.ExpensePeriodTotal;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.model.RecurringTransaction;
|
||||
import de.financer.model.Transaction;
|
||||
import de.financer.util.ExpensePeriod;
|
||||
import de.financer.model.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -23,7 +18,6 @@ import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@@ -36,6 +30,12 @@ public class TransactionService {
|
||||
@Autowired
|
||||
private RuleService ruleService;
|
||||
|
||||
@Autowired
|
||||
private PeriodService periodService;
|
||||
|
||||
@Autowired
|
||||
private AccountStatisticService accountStatisticService;
|
||||
|
||||
@Autowired
|
||||
private TransactionRepository transactionRepository;
|
||||
|
||||
@@ -91,6 +91,8 @@ public class TransactionService {
|
||||
try {
|
||||
final Transaction transaction = buildTransaction(fromAccount, toAccount, amount, description, date, recurringTransaction);
|
||||
|
||||
transaction.setPeriods(Collections.singleton(this.periodService.getCurrentExpensePeriod()));
|
||||
|
||||
fromAccount.setCurrentBalance(fromAccount.getCurrentBalance() + (this.ruleService
|
||||
.getMultiplierFromAccount(fromAccount) * amount));
|
||||
|
||||
@@ -106,6 +108,8 @@ public class TransactionService {
|
||||
|
||||
this.transactionRepository.save(transaction);
|
||||
|
||||
this.accountStatisticService.calculateStatistics(transaction);
|
||||
|
||||
this.accountService.saveAccount(fromAccount);
|
||||
this.accountService.saveAccount(toAccount);
|
||||
|
||||
@@ -215,7 +219,9 @@ public class TransactionService {
|
||||
.getMultiplierToAccount(toAccount) * amount * -1));
|
||||
|
||||
try {
|
||||
this.transactionRepository.deleteById(Long.valueOf(transactionId));
|
||||
this.transactionRepository.deleteById(transaction.getId());
|
||||
|
||||
this.accountStatisticService.revertStatistics(transaction);
|
||||
|
||||
this.accountService.saveAccount(fromAccount);
|
||||
this.accountService.saveAccount(toAccount);
|
||||
@@ -228,31 +234,19 @@ public class TransactionService {
|
||||
return response;
|
||||
}
|
||||
|
||||
public Long getExpensesCurrentPeriod(Integer monthPeriodStartDay) {
|
||||
if (monthPeriodStartDay == null) {
|
||||
return Long.valueOf(-1l);
|
||||
}
|
||||
|
||||
final ExpensePeriod expensePeriod = ExpensePeriod
|
||||
.getCurrentExpensePeriod(monthPeriodStartDay);
|
||||
public Long getExpensesCurrentPeriod() {
|
||||
final Period expensePeriod = this.periodService.getCurrentExpensePeriod();
|
||||
|
||||
final Long expensesCurrentPeriod = this.transactionRepository
|
||||
.getExpensesCurrentPeriod(expensePeriod.getStart(), expensePeriod
|
||||
.getEnd(), AccountType.EXPENSE, AccountType.LIABILITY);
|
||||
.getExpensesForPeriod(expensePeriod, AccountType.EXPENSE, AccountType.LIABILITY);
|
||||
|
||||
return Optional.ofNullable(expensesCurrentPeriod).orElse(Long.valueOf(0l));
|
||||
}
|
||||
|
||||
public Iterable<ExpensePeriodTotal> getExpensePeriodTotals(Integer monthPeriodStartDay, Integer year) {
|
||||
final List<ExpensePeriod> expensePeriods = ExpensePeriod.generateExpensePeriodsForYear(monthPeriodStartDay, year);
|
||||
public Iterable<ExpensePeriodTotal> getExpensePeriodTotals(Integer year) {
|
||||
final Iterable<Period> periods = this.periodService.getAllExpensePeriodsForYear(year);
|
||||
|
||||
final ExpensePeriod lowerBound = Iterables.get(expensePeriods, 0);
|
||||
final ExpensePeriod upperBound = Iterables.getLast(expensePeriods);
|
||||
|
||||
final List<ExpensePeriodTotal> expensePeriodTotals = this.transactionRepository
|
||||
.getAccountExpenseTotals(monthPeriodStartDay, lowerBound.getStart(), upperBound
|
||||
.getEnd(), AccountType.INCOME, AccountType.START, AccountType.EXPENSE, AccountType.LIABILITY);
|
||||
|
||||
return expensePeriodTotals;
|
||||
return this.transactionRepository
|
||||
.getAccountExpenseTotals(periods, AccountType.INCOME, AccountType.START, AccountType.EXPENSE, AccountType.LIABILITY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Hibernate
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.show-sql=true
|
||||
#logging.level.org.hibernate.type=trace
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE account ALTER COLUMN "key" NVARCHAR(1000);
|
||||
ALTER TABLE recurring_transaction ALTER COLUMN description NVARCHAR(1000);
|
||||
ALTER TABLE "transaction" ALTER COLUMN description NVARCHAR(1000);
|
||||
ALTER TABLE account_group ALTER COLUMN name NVARCHAR(1000);
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Account group table
|
||||
CREATE TABLE period (
|
||||
id BIGINT NOT NULL PRIMARY KEY IDENTITY,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
start DATETIME NOT NULL,
|
||||
"end" DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE link_transaction_period (
|
||||
id BIGINT NOT NULL PRIMARY KEY IDENTITY,
|
||||
transaction_id BIGINT NOT NULL,
|
||||
period_id BIGINT NOT NULL,
|
||||
|
||||
CONSTRAINT fk_link_transaction_period_transaction FOREIGN KEY (transaction_id) REFERENCES "transaction" (id),
|
||||
CONSTRAINT fk_link_transaction_period_period FOREIGN KEY (period_id) REFERENCES period (id)
|
||||
);
|
||||
|
||||
INSERT INTO period (type, start)
|
||||
VALUES ('EXPENSE', NOW());
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Account statistic table
|
||||
CREATE TABLE account_statistic (
|
||||
id BIGINT NOT NULL PRIMARY KEY IDENTITY,
|
||||
account_id BIGINT NOT NULL,
|
||||
period_id BIGINT NOT NULL,
|
||||
spending_total_from BIGINT NOT NULL,
|
||||
transaction_count_from BIGINT NOT NULL,
|
||||
spending_total_to BIGINT NOT NULL,
|
||||
transaction_count_to BIGINT NOT NULL,
|
||||
|
||||
CONSTRAINT fk_account_statistic_account FOREIGN KEY (account_id) REFERENCES account (id),
|
||||
CONSTRAINT fk_account_statistic_period FOREIGN KEY (period_id) REFERENCES period (id)
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Account group table
|
||||
CREATE TABLE period (
|
||||
id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
start DATETIME NOT NULL,
|
||||
"end" DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE link_transaction_period (
|
||||
id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
transaction_id BIGINT NOT NULL,
|
||||
period_id BIGINT NOT NULL,
|
||||
|
||||
CONSTRAINT fk_link_transaction_period_transaction FOREIGN KEY (transaction_id) REFERENCES "transaction" (id),
|
||||
CONSTRAINT fk_link_transaction_period_period FOREIGN KEY (period_id) REFERENCES period (id)
|
||||
);
|
||||
|
||||
INSERT INTO period (type, start)
|
||||
VALUES ('EXPENSE', NOW());
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Account statistic table
|
||||
CREATE TABLE account_statistic (
|
||||
id BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
account_id BIGINT NOT NULL,
|
||||
period_id BIGINT NOT NULL,
|
||||
spending_total_from BIGINT NOT NULL,
|
||||
transaction_count_from BIGINT NOT NULL,
|
||||
spending_total_to BIGINT NOT NULL,
|
||||
transaction_count_to BIGINT NOT NULL,
|
||||
|
||||
CONSTRAINT fk_account_statistic_account FOREIGN KEY (account_id) REFERENCES account (id),
|
||||
CONSTRAINT fk_account_statistic_period FOREIGN KEY (period_id) REFERENCES period (id)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
The VARCHAR type on PostgreSQL is already multibyte capable so nothing to do here
|
||||
@@ -18,7 +18,7 @@ The recurring transaction table is defined like this (at least for postgres):
|
||||
|
||||
Note the
|
||||
deleted BOOLEAN DEFAULT 'TRUE' NOT NULL,
|
||||
column definition. Not sure why the default is TRUE here is it doesn't make sense.
|
||||
column definition. Not sure why the default is TRUE here as it doesn't make sense.
|
||||
It was probably a mistake, however fixing it here _WILL_ break existing installations
|
||||
as Flyway uses a checksum for scripts. So there is no easy fix, except for effectively
|
||||
overwriting this default in Java code when creating a new recurring transaction.
|
||||
|
||||
@@ -24,6 +24,12 @@ public class TransactionService_createTransactionTest {
|
||||
@Mock
|
||||
private RuleService ruleService;
|
||||
|
||||
@Mock
|
||||
private PeriodService periodService;
|
||||
|
||||
@Mock
|
||||
private AccountStatisticService accountStatisticService;
|
||||
|
||||
@Mock
|
||||
private TransactionRepository transactionRepository;
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ public class TransactionService_deleteTransactionTest {
|
||||
@Mock
|
||||
private RuleService ruleService;
|
||||
|
||||
@Mock
|
||||
private AccountStatisticService accountStatisticService;
|
||||
|
||||
@Mock
|
||||
private TransactionRepository transactionRepository;
|
||||
|
||||
@@ -82,7 +85,7 @@ public class TransactionService_deleteTransactionTest {
|
||||
// Arrange
|
||||
Mockito.when(this.transactionRepository.findById(Mockito.anyLong()))
|
||||
.thenReturn(Optional.of(createTransaction(AccountType.BANK, AccountType.EXPENSE)));
|
||||
Mockito.doThrow(new NullPointerException()).when(this.transactionRepository).deleteById(Mockito.anyLong());
|
||||
Mockito.doThrow(new NullPointerException()).when(this.transactionRepository).deleteById(Mockito.any());
|
||||
|
||||
// Act
|
||||
final ResponseReason response = this.classUnderTest.deleteTransaction("123");
|
||||
@@ -94,8 +97,10 @@ public class TransactionService_deleteTransactionTest {
|
||||
@Test
|
||||
public void test_deleteRecurringTransaction_OK() {
|
||||
// Arrange
|
||||
final Transaction transaction = createTransaction(AccountType.BANK, AccountType.EXPENSE);
|
||||
|
||||
Mockito.when(this.transactionRepository.findById(Mockito.anyLong()))
|
||||
.thenReturn(Optional.of(createTransaction(AccountType.BANK, AccountType.EXPENSE)));
|
||||
.thenReturn(Optional.of(transaction));
|
||||
|
||||
// Act
|
||||
final ResponseReason response = this.classUnderTest.deleteTransaction("123");
|
||||
@@ -103,10 +108,11 @@ public class TransactionService_deleteTransactionTest {
|
||||
// Assert
|
||||
Assert.assertEquals(ResponseReason.OK, response);
|
||||
|
||||
final InOrder inOrder = Mockito.inOrder(this.accountService);
|
||||
final InOrder inOrder = Mockito.inOrder(this.accountStatisticService, this.accountService);
|
||||
|
||||
inOrder.verify(this.accountService).saveAccount(ArgumentMatchers.argThat((Account arg) -> Long.valueOf(50000L).equals(arg.getCurrentBalance())));
|
||||
inOrder.verify(this.accountService).saveAccount(ArgumentMatchers.argThat((Account arg) -> Long.valueOf(5000L).equals(arg.getCurrentBalance())));
|
||||
inOrder.verify(this.accountStatisticService).revertStatistics(ArgumentMatchers.eq(transaction));
|
||||
inOrder.verify(this.accountService).saveAccount(ArgumentMatchers.argThat((arg) -> Long.valueOf(50000L).equals(arg.getCurrentBalance())));
|
||||
inOrder.verify(this.accountService).saveAccount(ArgumentMatchers.argThat((arg) -> Long.valueOf(5000L).equals(arg.getCurrentBalance())));
|
||||
}
|
||||
|
||||
private Transaction createTransaction(AccountType fromType, AccountType toType) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.financer.chart.impl.expense;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dto.AccountExpense;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.template.GetAccountExpensesCurrentExpensePeriodTemplate;
|
||||
import de.financer.template.GetAccountExpensesTemplate;
|
||||
import de.financer.template.GetAccountGroupExpensesTemplate;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
@@ -12,8 +13,14 @@ import org.jfree.data.general.PieDataset;
|
||||
public class AccountExpensesGenerator extends AbstractExpensesGenerator {
|
||||
@Override
|
||||
protected PieDataset getDataset(String start, String end, FinancerConfig financerConfig) {
|
||||
final Iterable<AccountExpense> expenses = new GetAccountExpensesTemplate()
|
||||
.exchange(financerConfig, start, end).getBody();
|
||||
Iterable<AccountExpense> expenses;
|
||||
|
||||
if (start == null && end == null) {
|
||||
expenses = new GetAccountExpensesCurrentExpensePeriodTemplate().exchange(financerConfig).getBody();
|
||||
}
|
||||
else {
|
||||
expenses = new GetAccountExpensesTemplate().exchange(financerConfig, start, end).getBody();
|
||||
}
|
||||
|
||||
final DefaultPieDataset dataSet = new DefaultPieDataset();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.financer.chart.impl.expense;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.template.GetAccountGroupExpensesCurrentExpensePeriodTemplate;
|
||||
import de.financer.template.GetAccountGroupExpensesTemplate;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.jfree.data.general.DefaultPieDataset;
|
||||
@@ -10,8 +11,14 @@ import org.jfree.data.general.PieDataset;
|
||||
public class AccountGroupExpensesGenerator extends AbstractExpensesGenerator {
|
||||
@Override
|
||||
protected PieDataset getDataset(String start, String end, FinancerConfig financerConfig) {
|
||||
final Iterable<AccountGroupExpense> expenses = new GetAccountGroupExpensesTemplate()
|
||||
.exchange(financerConfig, start, end).getBody();
|
||||
Iterable<AccountGroupExpense> expenses;
|
||||
|
||||
if (start == null && end == null) {
|
||||
expenses = new GetAccountGroupExpensesCurrentExpensePeriodTemplate().exchange(financerConfig).getBody();
|
||||
}
|
||||
else {
|
||||
expenses = new GetAccountGroupExpensesTemplate().exchange(financerConfig, start, end).getBody();
|
||||
}
|
||||
|
||||
final DefaultPieDataset dataSet = new DefaultPieDataset();
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ package de.financer.chart.impl.total;
|
||||
|
||||
import de.financer.chart.AbstractChartGenerator;
|
||||
import de.financer.dto.ExpensePeriodTotal;
|
||||
import de.financer.model.Period;
|
||||
import de.financer.template.GetExpensePeriodTotalsTemplate;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import de.financer.util.ExpensePeriod;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.jfree.chart.ChartFactory;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
@@ -17,7 +16,7 @@ import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.List;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class PeriodTotalGenerator extends AbstractChartGenerator<PeriodTotalParameter> {
|
||||
@Override
|
||||
@@ -45,30 +44,36 @@ public class PeriodTotalGenerator extends AbstractChartGenerator<PeriodTotalPara
|
||||
|
||||
private CategoryDataset getDataset(PeriodTotalParameter parameter) {
|
||||
final DefaultCategoryDataset result = new DefaultCategoryDataset();
|
||||
final List<ExpensePeriod> expensePeriods = ExpensePeriod
|
||||
.generateExpensePeriodsForYear(this.getFinancerConfig().getMonthPeriodStartDay(), parameter.getYear());
|
||||
|
||||
final Iterable<ExpensePeriodTotal> totalData = new GetExpensePeriodTotalsTemplate()
|
||||
.exchange(this.getFinancerConfig(), parameter.getYear()).getBody();
|
||||
|
||||
IterableUtils.toList(totalData).stream()
|
||||
.sorted(Comparator.comparing((ExpensePeriodTotal ept) -> ept.getPeriod().getStart())
|
||||
.thenComparing((ExpensePeriodTotal ept) -> ept.getType()))
|
||||
.forEach((ept) -> result.addValue((ept.getTotal() / 100D),
|
||||
this.getMessage("financer.account-type." + ept.getType()),
|
||||
expensePeriods.stream()
|
||||
.filter((ep) -> ep.generatePeriodShortCode()
|
||||
.equals(ept.getPeriodShortCode()))
|
||||
.map((ep) -> formatDateY(ep))
|
||||
.findFirst().get()));
|
||||
formatDateY(ept.getPeriod())));
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String formatDateY(ExpensePeriod ep) {
|
||||
return String.format("%s - %s",
|
||||
ep.getStart().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||
.withLocale(LocaleContextHolder.getLocale())),
|
||||
ep.getEnd().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||
.withLocale(LocaleContextHolder.getLocale())));
|
||||
private String formatDateY(Period ep) {
|
||||
final String start = ep.getStart().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||
.withLocale(LocaleContextHolder.getLocale()));
|
||||
|
||||
String end;
|
||||
|
||||
if (ep.getEnd() == null) {
|
||||
end = this.getMessage("financer.chart.expense-period-totals-current-year.open-period");
|
||||
}
|
||||
else {
|
||||
end = ep.getEnd().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||
.withLocale(LocaleContextHolder.getLocale()));
|
||||
}
|
||||
|
||||
return String.format("%s - %s", start, end);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.decorator.AccountDecorator;
|
||||
import de.financer.template.*;
|
||||
import de.financer.form.NewAccountForm;
|
||||
import de.financer.model.*;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import de.financer.util.ExpensePeriod;
|
||||
import de.financer.util.TransactionUtils;
|
||||
import de.financer.util.comparator.TransactionByDateByIdDescComparator;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class AccountController {
|
||||
@@ -34,18 +35,20 @@ public class AccountController {
|
||||
final ResponseEntity<Iterable<RecurringTransaction>> rtAllActRes = new GetAllActiveRecurringTransactionsTemplate()
|
||||
.exchange(this.financerConfig);
|
||||
final ResponseEntity<Long> currentAssets = new GetCurrentAssetsTemplate().exchange(this.financerConfig);
|
||||
final ResponseEntity<Long> currentExpenses = new GetExpensesCurrentPeriodTemplate().exchange(this.financerConfig);
|
||||
final ResponseEntity<Long> currentExpenses = new GetExpensesCurrentPeriodTemplate()
|
||||
.exchange(this.financerConfig);
|
||||
final boolean showClosedBoolean = BooleanUtils.toBoolean(showClosed);
|
||||
final ExpensePeriod expensePeriod = ExpensePeriod
|
||||
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
|
||||
final ResponseEntity<Period> expensePeriodRes = new GetCurrentExpensePeriodTemplate()
|
||||
.exchange(this.financerConfig);
|
||||
final Period expensePeriod = expensePeriodRes.getBody();
|
||||
|
||||
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(response.getBody(), showClosedBoolean));
|
||||
model.addAttribute("accounts", decorateAccounts(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", expensePeriod.getStart());
|
||||
model.addAttribute("periodEnd", expensePeriod.getEnd());
|
||||
model.addAttribute("showClosed", showClosedBoolean);
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
@@ -53,6 +56,10 @@ public class AccountController {
|
||||
return "account/accountOverview";
|
||||
}
|
||||
|
||||
private Iterable<AccountDecorator> decorateAccounts(List<Account> accounts) {
|
||||
return accounts.stream().map((a) -> new AccountDecorator(a)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/newAccount")
|
||||
public String newAccount(Model model) {
|
||||
final ResponseEntity<Iterable<AccountGroup>> accountGroupResponse = new GetAllAccountGroupsTemplate()
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package de.financer.controller;
|
||||
|
||||
import de.financer.chart.impl.expense.ExpensesParameter;
|
||||
import de.financer.chart.ChartGenerator;
|
||||
import de.financer.chart.ChartType;
|
||||
import de.financer.chart.FinancerChartFactory;
|
||||
import de.financer.chart.impl.expense.ExpensesParameter;
|
||||
import de.financer.chart.impl.total.PeriodTotalParameter;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.form.ConfigAccountExpenseForPeriodForm;
|
||||
import de.financer.form.ConfigAccountGroupExpenseForPeriodForm;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import de.financer.util.ExpensePeriod;
|
||||
import org.jfree.chart.ChartUtils;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -32,12 +31,8 @@ public class ChartController {
|
||||
|
||||
@GetMapping("/getAccountGroupExpensesCurrentPeriod")
|
||||
public void getAccountGroupExpensesCurrentPeriod(HttpServletResponse response) {
|
||||
final ExpensePeriod period = ExpensePeriod
|
||||
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
|
||||
final ExpensesParameter parameter = new ExpensesParameter();
|
||||
|
||||
parameter.setPeriodStart(period.getStart());
|
||||
parameter.setPeriodEnd(period.getEnd());
|
||||
parameter.setTitle("financer.chart.account-group-expenses-current-period.title");
|
||||
|
||||
final ChartGenerator<ExpensesParameter> generator =
|
||||
@@ -70,12 +65,8 @@ public class ChartController {
|
||||
|
||||
@GetMapping("/getAccountExpensesCurrentPeriod")
|
||||
public void getAccountExpensesCurrentPeriod(HttpServletResponse response) {
|
||||
final ExpensePeriod period = ExpensePeriod
|
||||
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
|
||||
final ExpensesParameter parameter = new ExpensesParameter();
|
||||
|
||||
parameter.setPeriodStart(period.getStart());
|
||||
parameter.setPeriodEnd(period.getEnd());
|
||||
parameter.setTitle("financer.chart.account-expenses-current-period.title");
|
||||
|
||||
final ChartGenerator<ExpensesParameter> generator =
|
||||
|
||||
@@ -8,10 +8,12 @@ public enum Function {
|
||||
ACC_OPEN_ACCOUNT("accounts/openAccount"),
|
||||
ACC_CURRENT_ASSETS("accounts/getCurrentAssets"),
|
||||
ACC_GET_ACC_EXPENSES("accounts/getAccountExpenses"),
|
||||
ACC_GET_ACC_EXPENSES_CURRENT_EXPENSE_PERIOD("accounts/getAccountExpensesCurrentExpensePeriod"),
|
||||
|
||||
ACC_GP_CREATE_ACCOUNT_GROUP("accountGroups/createAccountGroup"),
|
||||
ACC_GP_GET_ALL("accountGroups/getAll"),
|
||||
ACC_GP_GET_ACC_GP_EXPENSES("accountGroups/getAccountGroupExpenses"),
|
||||
ACC_GP_GET_ACC_GP_EXPENSES_CURRENT_EXPENSE_PERIOD("accountGroups/getAccountGroupExpensesCurrentExpensePeriod"),
|
||||
|
||||
TR_GET_ALL("transactions/getAll"),
|
||||
TR_GET_ALL_FOR_ACCOUNT("transactions/getAllForAccount"),
|
||||
@@ -26,7 +28,10 @@ public enum Function {
|
||||
RT_GET_ALL_DUE_TODAY("recurringTransactions/getAllDueToday"),
|
||||
RT_CREATE_RECURRING_TRANSACTION("recurringTransactions/createRecurringTransaction"),
|
||||
RT_DELETE_RECURRING_TRANSACTION("recurringTransactions/deleteRecurringTransaction"),
|
||||
RT_CREATE_TRANSACTION("recurringTransactions/createTransaction");
|
||||
RT_CREATE_TRANSACTION("recurringTransactions/createTransaction"),
|
||||
|
||||
P_GET_CURRENT_EXPENSE_PERIOD("periods/getCurrentExpensePeriod"),
|
||||
P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod");
|
||||
|
||||
private String path;
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.financer.controller;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.template.StringTemplate;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@Controller
|
||||
public class PeriodController {
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@GetMapping("/closePeriod")
|
||||
public String closePeriod() {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.P_CLOSE_CURRENT_EXPENSE_PERIOD));
|
||||
|
||||
new StringTemplate().exchange(builder);
|
||||
|
||||
return "redirect:/accountOverview";
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,10 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.model.*;
|
||||
import de.financer.template.*;
|
||||
import de.financer.form.NewRecurringTransactionForm;
|
||||
import de.financer.form.RecurringToTransactionWithAmountForm;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.HolidayWeekendType;
|
||||
import de.financer.model.IntervalType;
|
||||
import de.financer.model.RecurringTransaction;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -19,6 +16,8 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class RecurringTransactionController {
|
||||
@@ -30,10 +29,18 @@ public class RecurringTransactionController {
|
||||
public String newRecurringTransaction(Model model) {
|
||||
final ResponseEntity<Iterable<Account>> response = new GetAllAccountsTemplate().exchange(this.financerConfig);
|
||||
final NewRecurringTransactionForm form = new NewRecurringTransactionForm();
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(response.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(response.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
form.setRemind(Boolean.TRUE);
|
||||
|
||||
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(response.getBody()));
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
model.addAttribute("intervalTypes", IntervalType.valueList());
|
||||
model.addAttribute("holidayWeekendTypes", HolidayWeekendType.valueList());
|
||||
model.addAttribute("form", form);
|
||||
@@ -64,8 +71,16 @@ public class RecurringTransactionController {
|
||||
if (!ResponseReason.OK.equals(responseReason)) {
|
||||
final ResponseEntity<Iterable<Account>> getAllResponse = new GetAllAccountsTemplate()
|
||||
.exchange(this.financerConfig);
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(getAllResponse.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(getAllResponse.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(getAllResponse.getBody()));
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
model.addAttribute("intervalTypes", IntervalType.valueList());
|
||||
model.addAttribute("holidayWeekendTypes", HolidayWeekendType.valueList());
|
||||
model.addAttribute("form", form);
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.financer.controller;
|
||||
|
||||
import de.financer.ResponseReason;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.model.AccountType;
|
||||
import de.financer.template.GetAccountByKeyTemplate;
|
||||
import de.financer.template.GetAllAccountsTemplate;
|
||||
import de.financer.template.GetAllTransactionsForAccountTemplate;
|
||||
@@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class TransactionController {
|
||||
@@ -33,8 +35,16 @@ public class TransactionController {
|
||||
@GetMapping("/newTransaction")
|
||||
public String newTransaction(Model model) {
|
||||
final ResponseEntity<Iterable<Account>> response = new GetAllAccountsTemplate().exchange(this.financerConfig);
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(response.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(response.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(response.getBody()));
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
model.addAttribute("form", new NewTransactionForm());
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
@@ -57,8 +67,16 @@ public class TransactionController {
|
||||
|
||||
if (!ResponseReason.OK.equals(responseReason)) {
|
||||
final ResponseEntity<Iterable<Account>> accRes = new GetAllAccountsTemplate().exchange(this.financerConfig);
|
||||
final List<Account> fromAccounts = ControllerUtils.filterAndSortAccounts(accRes.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.EXPENSE)
|
||||
.collect(Collectors.toList());
|
||||
final List<Account> toAccounts = ControllerUtils.filterAndSortAccounts(accRes.getBody()).stream()
|
||||
.filter((a) -> a.getType() != AccountType.INCOME && a
|
||||
.getType() != AccountType.START)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(accRes.getBody()));
|
||||
model.addAttribute("fromAccounts", fromAccounts);
|
||||
model.addAttribute("toAccounts", toAccounts);
|
||||
model.addAttribute("form", form);
|
||||
model.addAttribute("errorMessage", responseReason.name());
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
@@ -79,7 +97,8 @@ public class TransactionController {
|
||||
final ResponseEntity<String> response = new StringTemplate().exchange(builder);
|
||||
final ResponseReason responseReason = ResponseReason.fromResponseEntity(response);
|
||||
|
||||
final ResponseEntity<Account> accountResponse = new GetAccountByKeyTemplate().exchange(this.financerConfig, accountKey);
|
||||
final ResponseEntity<Account> accountResponse = new GetAccountByKeyTemplate()
|
||||
.exchange(this.financerConfig, accountKey);
|
||||
final Account account = accountResponse.getBody();
|
||||
final ResponseEntity<Iterable<Transaction>> transactionResponse = new GetAllTransactionsForAccountTemplate()
|
||||
.exchange(this.financerConfig, account.getKey());
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.financer.decorator;
|
||||
|
||||
import de.financer.model.*;
|
||||
|
||||
public class AccountDecorator {
|
||||
private final Account account;
|
||||
|
||||
public Long getId() {
|
||||
return account.getId();
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return account.getKey();
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return account.getType();
|
||||
}
|
||||
|
||||
public AccountStatus getStatus() {
|
||||
return account.getStatus();
|
||||
}
|
||||
|
||||
public Long getCurrentBalance() {
|
||||
return account.getCurrentBalance();
|
||||
}
|
||||
|
||||
public AccountGroup getAccountGroup() {
|
||||
return account.getAccountGroup();
|
||||
}
|
||||
|
||||
public AccountDecorator(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public Long getSpendingCurrentExpensePeriod() {
|
||||
if (this.account.getType() == AccountType.EXPENSE || this.account.getType() == AccountType.LIABILITY) {
|
||||
return this.account.getAccountStatistics().stream()
|
||||
.filter((as) -> as.getPeriod().getType().equals(PeriodType.EXPENSE) && as.getPeriod()
|
||||
.getEnd() == null)
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
AccountStatistic as = new AccountStatistic();
|
||||
as.setTransactionCountTo(0l);
|
||||
return as;
|
||||
})
|
||||
.getSpendingTotalTo();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Long getAverageSpendingExpensePeriod() {
|
||||
if (this.account.getType() == AccountType.EXPENSE || this.account.getType() == AccountType.LIABILITY) {
|
||||
return Math.round(this.account.getAccountStatistics().stream()
|
||||
.filter((as) -> as.getPeriod().getType().equals(PeriodType.EXPENSE) && as.getPeriod()
|
||||
.getEnd() != null)
|
||||
.mapToLong((as) -> as.getSpendingTotalTo())
|
||||
.average()
|
||||
.orElseGet(() -> Double.valueOf(0d)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.dto.AccountExpense;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class GetAccountExpensesCurrentExpensePeriodTemplate {
|
||||
public ResponseEntity<Iterable<AccountExpense>> exchange(FinancerConfig financerConfig) {
|
||||
final UriComponentsBuilder expensesBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.ACC_GET_ACC_EXPENSES_CURRENT_EXPENSE_PERIOD));
|
||||
|
||||
return new FinancerRestTemplate<Iterable<AccountExpense>>()
|
||||
.exchange(expensesBuilder.toUriString(), new ParameterizedTypeReference<Iterable<AccountExpense>>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.dto.AccountGroupExpense;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class GetAccountGroupExpensesCurrentExpensePeriodTemplate {
|
||||
public ResponseEntity<Iterable<AccountGroupExpense>> exchange(FinancerConfig financerConfig) {
|
||||
final UriComponentsBuilder expensesBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.ACC_GP_GET_ACC_GP_EXPENSES_CURRENT_EXPENSE_PERIOD));
|
||||
|
||||
return new FinancerRestTemplate<Iterable<AccountGroupExpense>>()
|
||||
.exchange(expensesBuilder.toUriString(), new ParameterizedTypeReference<Iterable<AccountGroupExpense>>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.financer.template;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.controller.Function;
|
||||
import de.financer.model.Period;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class GetCurrentExpensePeriodTemplate {
|
||||
public ResponseEntity<Period> exchange(FinancerConfig financerConfig) {
|
||||
final UriComponentsBuilder expensesBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.P_GET_CURRENT_EXPENSE_PERIOD));
|
||||
|
||||
return new FinancerRestTemplate<Period>()
|
||||
.exchange(expensesBuilder.toUriString(), new ParameterizedTypeReference<Period>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ public class GetExpensePeriodTotalsTemplate {
|
||||
public ResponseEntity<Iterable<ExpensePeriodTotal>> exchange(FinancerConfig financerConfig, int year) {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_EXPENSE_PERIOD_TOTALS))
|
||||
.queryParam("monthPeriodStartDay", financerConfig.getMonthPeriodStartDay())
|
||||
.queryParam("year", year);
|
||||
|
||||
return new FinancerRestTemplate<Iterable<ExpensePeriodTotal>>()
|
||||
|
||||
@@ -10,8 +10,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
public class GetExpensesCurrentPeriodTemplate {
|
||||
public ResponseEntity<Long> exchange(FinancerConfig financerConfig) {
|
||||
final UriComponentsBuilder transactionBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_EXPENSES_CURRENT_PERIOD))
|
||||
.queryParam("monthPeriodStartDay", financerConfig.getMonthPeriodStartDay());
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_EXPENSES_CURRENT_PERIOD));
|
||||
|
||||
return new FinancerRestTemplate<Long>()
|
||||
.exchange(transactionBuilder.toUriString(), new ParameterizedTypeReference<Long>() {
|
||||
|
||||
@@ -44,6 +44,10 @@ public class ControllerUtils {
|
||||
}
|
||||
|
||||
public static String formatDate(FinancerConfig financerConfig, LocalDate date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return date.format(DateTimeFormatter.ofPattern(financerConfig.getDateFormat()));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ financer.account-overview.available-actions.create-recurring-transaction=Create
|
||||
financer.account-overview.available-actions.recurring-transaction-all=Show all recurring transactions
|
||||
financer.account-overview.available-actions.create-account-group=Create new account group
|
||||
financer.account-overview.available-actions.select-chart=Generate a chart
|
||||
financer.account-overview.available-actions.close-current-period=Close the current expense period
|
||||
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\:
|
||||
@@ -16,10 +17,12 @@ financer.account-overview.status.current-expenses=Expenses in 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.balance=Balance
|
||||
financer.account-overview.table-header.spending-current-period=Spending current period
|
||||
financer.account-overview.table-header.average-spending-period=Average spending
|
||||
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}. Clicking the amount will open a graphical overview about the expenses grouped by account group
|
||||
financer.account-overview.tooltip.status.current-expenses=Period starting at {0}. Clicking the amount will open a graphical overview about the expenses grouped by account group
|
||||
financer.account-overview.tooltip.status.current-assets=Assets available at short-notice
|
||||
|
||||
financer.account-new.title=financer\: create new account
|
||||
@@ -88,6 +91,7 @@ financer.account-details.available-actions=Available actions\:
|
||||
financer.account-details.available-actions.close-account=Close account
|
||||
financer.account-details.available-actions.open-account=Open account
|
||||
financer.account-details.available-actions.back-to-overview=Back to overview
|
||||
financer.account-details.available-actions.create-transaction=Create new transaction
|
||||
financer.account-details.table-header.id=ID
|
||||
financer.account-details.table-header.fromAccount=From account
|
||||
financer.account-details.table-header.toAccount=To account
|
||||
@@ -163,6 +167,7 @@ financer.chart.account-expenses-for-period.title=Expenses for period from {0} to
|
||||
financer.chart.expense-period-totals-current-year.title=Expense period totals for the current year
|
||||
financer.chart.expense-period-totals-current-year.x=Amount
|
||||
financer.chart.expense-period-totals-current-year.y=Period
|
||||
financer.chart.expense-period-totals-current-year.open-period=OPEN
|
||||
|
||||
financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Expenses of the current period grouped by account group (pie chart)
|
||||
financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Expenses for a configurable period grouped by account group (pie chart)
|
||||
|
||||
@@ -8,6 +8,7 @@ financer.account-overview.available-actions.create-recurring-transaction=Neue wi
|
||||
financer.account-overview.available-actions.recurring-transaction-all=Zeige alle wiederkehrende Buchungen
|
||||
financer.account-overview.available-actions.create-account-group=Neue Konto-Gruppe erstellen
|
||||
financer.account-overview.available-actions.select-chart=Ein Diagramm erzeugen
|
||||
financer.account-overview.available-actions.close-current-period=Aktuelle Periode schlie\u00DFen
|
||||
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\:
|
||||
@@ -19,7 +20,7 @@ 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}. Durch Klicken des Betrags kann eine grafische \u00DCbersicht über die Ausgaben gruppiert nach Konto-Gruppe angezeigt werden
|
||||
financer.account-overview.tooltip.status.current-expenses=Periode ab {0}. Durch Klicken des Betrags kann eine grafische \u00DCbersicht über die Ausgaben gruppiert nach Konto-Gruppe angezeigt werden
|
||||
financer.account-overview.tooltip.status.current-assets=Kurzfristig verf\u00FCgbares Verm\u00F6gen
|
||||
|
||||
financer.account-new.title=financer\: Neues Konto erstellen
|
||||
@@ -88,6 +89,7 @@ financer.account-details.available-actions=Verf\u00FCgbare Aktionen\:
|
||||
financer.account-details.available-actions.close-account=Konto schlie\u00DFen
|
||||
financer.account-details.available-actions.open-account=Konto \u00F6ffnen
|
||||
financer.account-details.available-actions.back-to-overview=Zur\u00FCck zur \u00DCbersicht
|
||||
financer.account-details.available-actions.create-transaction=Neue Buchung erstellen
|
||||
financer.account-details.table-header.id=ID
|
||||
financer.account-details.table-header.fromAccount=Von Konto
|
||||
financer.account-details.table-header.toAccount=An Konto
|
||||
@@ -162,6 +164,7 @@ financer.chart.account-expenses-for-period.title=Ausgaben in der Periode vom {0}
|
||||
financer.chart.expense-period-totals-current-year.title=Gesamtbetr\u00E4ge gruppiert nach Periode für das aktuelle Jahr
|
||||
financer.chart.expense-period-totals-current-year.x=Betrag
|
||||
financer.chart.expense-period-totals-current-year.y=Periode
|
||||
financer.chart.expense-period-totals-current-year.open-period=OFFEN
|
||||
|
||||
financer.chart.name.ACCOUNT_GROUP_EXPENSES_CURRENT_PERIOD=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe (Kuchendiagramm)
|
||||
financer.chart.name.ACCOUNT_GROUP_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfigurierbare Periode gruppiert nach Konto-Gruppe (Kuchendiagramm)
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
v18 -> v19:
|
||||
- Fix a bug in recurring transaction handling that caused a recurring transaction with Holiday/Weekend type
|
||||
'previous workday' and a planned occurrence on a Sunday to be due on Friday and Saturday
|
||||
- Filter from and to account lists when creating a transaction or a recurring transaction by account types that
|
||||
are allowed in from and to
|
||||
- Add the create new transaction action to the account detail view
|
||||
- Rework periods (e.g. expense) so that the underlying technical structures are better defined. This is preparation for
|
||||
more reports and budgeting functions
|
||||
- Add action to close the current expense period manually. This also opens a new one
|
||||
- Add spending in current expense period per account to the account overview
|
||||
- Add average spending in expense period per account to the account overview
|
||||
|
||||
v17 -> v18:
|
||||
- Add readme to the footer
|
||||
- Translate error messages to German
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<span th:text="#{financer.account-details.details.balance}"/>
|
||||
<span th:text="${#numbers.formatDecimal(account.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
</div>
|
||||
<div id="group-container">
|
||||
<div id="group-container" th:if="${account.accountGroup != null}">
|
||||
<span th:text="#{financer.account-details.details.group}"/>
|
||||
<span th:text="${account.accountGroup?.name}"/>
|
||||
<span th:text="${account.accountGroup.name}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="account-details-action-container">
|
||||
@@ -29,6 +29,8 @@
|
||||
th:text="#{financer.account-details.available-actions.close-account}"/>
|
||||
<a th:if="${isClosed}" th:href="@{/openAccount(key=${account.key})}"
|
||||
th:text="#{financer.account-details.available-actions.open-account}"/>
|
||||
<a th:href="@{/newTransaction}"
|
||||
th:text="#{financer.account-details.available-actions.create-transaction}"/>
|
||||
<a th:href="@{/accountOverview}"
|
||||
th:text="#{financer.account-details.available-actions.back-to-overview}"/>
|
||||
</div>
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1 th:text="#{financer.heading.account-overview}" />
|
||||
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
|
||||
<div id="status-container">
|
||||
<span th:text="#{financer.account-overview.status}"/>
|
||||
<div th:title="#{financer.account-overview.tooltip.status.current-assets}">
|
||||
<span th:text="#{financer.account-overview.status.current-assets}"/>
|
||||
<span th:text="${#numbers.formatDecimal(currentAssets/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
</div>
|
||||
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${#temporals.format(periodStart)}, ${#temporals.format(periodEnd)})}">
|
||||
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${#temporals.format(periodStart)})}">
|
||||
<span th:text="#{financer.account-overview.status.current-expenses}"/>
|
||||
<a th:href="@{/getAccountGroupExpensesCurrentPeriod}">
|
||||
<span th:text="${#numbers.formatDecimal(currentExpenses/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}" />
|
||||
@@ -28,7 +29,6 @@
|
||||
<span th:text="#{financer.account-overview.status.recurring-transaction-active}"/>
|
||||
<a th:href="@{/recurringTransactionActive}" th:text="${rtAllActiveCount}"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<span th:text="#{financer.account-overview.available-actions}"/>
|
||||
<div id="action-container">
|
||||
@@ -49,6 +49,10 @@
|
||||
<a th:href="@{/recurringTransactionAll}"
|
||||
th:text="#{financer.account-overview.available-actions.recurring-transaction-all}"/>
|
||||
</div>
|
||||
<div id="action-container-sub-period">
|
||||
<a th:href="@{/closePeriod}"
|
||||
th:text="#{financer.account-overview.available-actions.close-current-period}"/>
|
||||
</div>
|
||||
<div id="action-container-sub-reports">
|
||||
<a th:href="@{/selectChart}"
|
||||
th:text="#{financer.account-overview.available-actions.select-chart}"/>
|
||||
@@ -59,6 +63,8 @@
|
||||
<th class="hideable-column" th:text="#{financer.account-overview.table-header.id}"/>
|
||||
<th th:text="#{financer.account-overview.table-header.key}"/>
|
||||
<th th:text="#{financer.account-overview.table-header.balance}"/>
|
||||
<th class="hideable-column" th:text="#{financer.account-overview.table-header.spending-current-period}"/>
|
||||
<th class="hideable-column" th:text="#{financer.account-overview.table-header.average-spending-period}"/>
|
||||
<th class="hideable-column" th:text="#{financer.account-overview.table-header.group}"/>
|
||||
<th class="hideable-column" th:text="#{financer.account-overview.table-header.type}"/>
|
||||
<th class="hideable-column" th:text="#{financer.account-overview.table-header.status}"/>
|
||||
@@ -69,6 +75,12 @@
|
||||
<a th:href="@{/accountDetails(key=${acc.key})}" th:text="${acc.key}"/>
|
||||
</td>
|
||||
<td th:text="${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
<td th:if="${acc.spendingCurrentExpensePeriod != null}"
|
||||
th:text="${#numbers.formatDecimal(acc.spendingCurrentExpensePeriod/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
<td th:if="${acc.averageSpendingExpensePeriod != null}"
|
||||
th:text="${#numbers.formatDecimal(acc.averageSpendingExpensePeriod/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||
<td th:if="${acc.spendingCurrentExpensePeriod == null}">-</td>
|
||||
<td th:if="${acc.averageSpendingExpensePeriod == null}">-</td>
|
||||
<td class="hideable-column" th:text="${acc.accountGroup?.name}"/>
|
||||
<td class="hideable-column" th:text="#{'financer.account-type.' + ${acc.type}}"/>
|
||||
<td class="hideable-column" th:text="#{'financer.account-status.' + ${acc.status}}"/>
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
method="post">
|
||||
<label for="selectFromAccount" th:text="#{financer.recurring-transaction-new.label.from-account}"/>
|
||||
<select id="selectFromAccount" th:field="*{fromAccountKey}">
|
||||
<option th:each="acc : ${accounts}" th:value="${acc.key}"
|
||||
<option th:each="acc : ${fromAccounts}" th:value="${acc.key}"
|
||||
th:text="#{'financer.recurring-transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
|
||||
</select>
|
||||
<label for="selectToAccount" th:text="#{financer.recurring-transaction-new.label.to-account}"/>
|
||||
<select id="selectToAccount" th:field="*{toAccountKey}">
|
||||
<option th:each="acc : ${accounts}" th:value="${acc.key}"
|
||||
<option th:each="acc : ${toAccounts}" th:value="${acc.key}"
|
||||
th:text="#{'financer.recurring-transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
|
||||
</select>
|
||||
<label for="inputAmount" th:text="#{financer.recurring-transaction-new.label.amount}"/>
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
method="post">
|
||||
<label for="selectFromAccount" th:text="#{financer.transaction-new.label.from-account}"/>
|
||||
<select id="selectFromAccount" th:field="*{fromAccountKey}">
|
||||
<option th:each="acc : ${accounts}" th:value="${acc.key}"
|
||||
<option th:each="acc : ${fromAccounts}" th:value="${acc.key}"
|
||||
th:text="#{'financer.transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
|
||||
</select>
|
||||
<label for="selectToAccount" th:text="#{financer.transaction-new.label.to-account}"/>
|
||||
<select id="selectToAccount" th:field="*{toAccountKey}">
|
||||
<option th:each="acc : ${accounts}" th:value="${acc.key}"
|
||||
<option th:each="acc : ${toAccounts}" th:value="${acc.key}"
|
||||
th:text="#{'financer.transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
|
||||
</select>
|
||||
<label for="inputAmount" th:text="#{financer.transaction-new.label.amount}"/>
|
||||
|
||||
11
pom.xml
11
pom.xml
@@ -35,6 +35,17 @@
|
||||
<module>financer-common</module>
|
||||
</modules>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>77zzcx7-snapshots</id>
|
||||
<url>http://192.168.10.1:8100/repository/77zzcx7-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>77zzcx7-releases</id>
|
||||
<url>http://192.168.10.1:8100/repository/77zzcx7-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
|
||||
Reference in New Issue
Block a user