Introduce module financer-common that hosts common code for server and (web) client

Also various fixes all over the place
This commit is contained in:
2019-06-27 20:25:22 +02:00
parent 099562c408
commit eae6f374f5
40 changed files with 176 additions and 434 deletions

View File

@@ -39,7 +39,19 @@ public class FinancerConfig {
this.version = version;
}
// The same property exists on the server, look there for documentation
/**
* The day of month that indicates a start of an expense period. Valid values range from 1-28. There is no special
* handling for months with more days.
* If the value is 15 for example an expense period is always from the 15th of the current month to the 15th of the
* next month:
* <ul>
* <li>15.01.2019 - 15.02.2019</li>
* <li>15.02.2019 - 15.03.2019</li>
* <li>...</li>
* </ul>
*
* @return the day of month indicating the start of an expense period
*/
public Integer getMonthPeriodStartDay() {
return monthPeriodStartDay;
}

View File

@@ -6,6 +6,7 @@ import de.financer.controller.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;
@@ -36,16 +37,16 @@ public class AccountController {
final ResponseEntity<Long> currentAssets = new GetCurrentAssetsTemplate().exchange(this.financerConfig);
final ResponseEntity<Long> currentExpenses = new GetExpensesCurrentPeriodTemplate().exchange(this.financerConfig);
final boolean showClosedBoolean = BooleanUtils.toBoolean(showClosed);
final LocalDate periodStart = LocalDate.now().withDayOfMonth(this.financerConfig.getMonthPeriodStartDay());
final LocalDate periodEnd = periodStart.plusMonths(1);
final ExpensePeriod expensePeriod = ExpensePeriod
.getCurrentExpensePeriod(this.financerConfig.getMonthPeriodStartDay());
model.addAttribute("accounts", ControllerUtils.filterAndSortAccounts(response.getBody(), showClosedBoolean));
model.addAttribute("rtDueTodayCount", IterableUtils.size(rtDtRes.getBody()));
model.addAttribute("rtAllActiveCount", IterableUtils.size(rtAllActRes.getBody()));
model.addAttribute("currentAssets", currentAssets.getBody());
model.addAttribute("currentExpenses", currentExpenses.getBody());
model.addAttribute("periodStart", ControllerUtils.formatDate(this.financerConfig, periodStart));
model.addAttribute("periodEnd", ControllerUtils.formatDate(this.financerConfig, periodEnd));
model.addAttribute("periodStart", expensePeriod.getStart());
model.addAttribute("periodEnd", expensePeriod.getEnd());
model.addAttribute("showClosed", showClosedBoolean);
ControllerUtils.addVersionAttribute(model, this.financerConfig);

View File

@@ -2,14 +2,20 @@ package de.financer.controller.template;
import de.financer.config.FinancerConfig;
import de.financer.controller.Function;
import de.financer.model.Transaction;
import de.financer.util.ControllerUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.web.util.UriComponentsBuilder;
public class GetExpensesCurrentPeriodTemplate {
public ResponseEntity<Long> exchange(FinancerConfig financerConfig) {
return new FinancerRestTemplate<Long>().exchange(ControllerUtils
.buildUrl(financerConfig, Function.TR_EXPENSES_CURRENT_PERIOD), new ParameterizedTypeReference<Long>() {
});
final UriComponentsBuilder transactionBuilder = UriComponentsBuilder
.fromHttpUrl(ControllerUtils.buildUrl(financerConfig, Function.TR_EXPENSES_CURRENT_PERIOD))
.queryParam("monthPeriodStartDay", financerConfig.getMonthPeriodStartDay());
return new FinancerRestTemplate<Long>()
.exchange(transactionBuilder.toUriString(), new ParameterizedTypeReference<Long>() {
});
}
}

View File

@@ -1,54 +0,0 @@
package de.financer.model;
public class Account {
private Long id;
private String key;
private AccountType type;
private AccountStatus status;
private Long currentBalance;
private AccountGroup accountGroup;
public Long getId() {
return id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public AccountType getType() {
return type;
}
public void setType(AccountType type) {
this.type = type;
}
public AccountStatus getStatus() {
return status;
}
public void setStatus(AccountStatus status) {
this.status = status;
}
public Long getCurrentBalance() {
return currentBalance;
}
public void setCurrentBalance(Long currentBalance) {
this.currentBalance = currentBalance;
}
public AccountGroup getAccountGroup() {
return accountGroup;
}
public void setAccountGroup(AccountGroup accountGroup) {
this.accountGroup = accountGroup;
}
}

View File

@@ -1,18 +0,0 @@
package de.financer.model;
public class AccountGroup {
private Long id;
private String name;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -1,20 +0,0 @@
package de.financer.model;
import java.util.Arrays;
public enum AccountStatus {
/** Indicates that the account is open for bookings */
OPEN,
/** Indicates that the account is closed and bookings to it are forbidden */
CLOSED;
/**
* This method validates whether the given string represents a valid account status.
*
* @param status to check
* @return whether the given status represents a valid account status
*/
public static boolean isValidType(String status) {
return Arrays.stream(AccountStatus.values()).anyMatch((accountStatus) -> accountStatus.name().equals(status));
}
}

View File

@@ -1,38 +0,0 @@
package de.financer.model;
import java.util.*;
import java.util.stream.Collectors;
public enum AccountType {
/** Used to mark an account that acts as a source of money, e.g. monthly wage */
INCOME,
/** Indicates a real account at a bank, e.g. a check payment account */
BANK,
/** Marks an account as physical cash, e.g. the money currently in the purse */
CASH,
/** Used to mark an account that acts as a destination of money, e.g. through buying goods */
EXPENSE,
/** Marks an account as a liability from a third party, e.g. credit card or loan */
LIABILITY,
/** Marks the start account that is to be used to book all the opening balances for the different accounts */
START;
/**
* This method validates whether the given string represents a valid account type.
*
* @param type to check
* @return whether the given type represents a valid account type
*/
public static boolean isValidType(String type) {
return Arrays.stream(AccountType.values()).anyMatch((accountType) -> accountType.name().equals(type));
}
public static List<String> valueList() {
return Arrays.stream(AccountType.values()).map(AccountType::name).collect(Collectors.toList());
}
}

View File

@@ -1,71 +0,0 @@
package de.financer.model;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* This enum specifies constants that control how actions should be handled that would fall on a holiday
* or weekday (where usually are no bookings done by e.g. banks)
*/
public enum HolidayWeekendType {
/** Indicates that the action should be done on the specified day regardless whether it's a holiday or a weekend */
SAME_DAY,
/**
* <p>
* Indicates that the action should be deferred to the next workday.
* </p>
* <pre>
* Example 1:
* MO TU WE TH FR SA SO
* H WE WE -&gt; Holiday/WeekEnd
* X -&gt; Due date of action
* X' -&gt; Deferred, effective due date of action
* </pre>
* <pre>
* Example 2:
* TU WE TH FR SA SO MO
* H WE WE -&gt; Holiday/WeekEnd
* X -&gt; Due date of action
* X' -&gt; Deferred, effective due date of action
* </pre>
*
*/
NEXT_WORKDAY,
/**
* <p>
* Indicates that the action should preponed to the previous day
* </p>
* <pre>
* Example 1:
* MO TU WE TH FR SA SO
* H WE WE -&gt; Holiday/WeekEnd
* X -&gt; Due date of action
* X' -&gt; Earlier, effective due date of action
* </pre>
* <pre>
* Example 2:
* MO TU WE TH FR SA SO
* H WE WE -&gt; Holiday/WeekEnd
* X -&gt; Due date of action
* X' -&gt; Earlier, effective due date of action
* </pre>
*/
PREVIOUS_WORKDAY;
/**
* This method validates whether the given string represents a valid holiday weekend type.
*
* @param type to check
* @return whether the given type represents a valid holiday weekend type
*/
public static boolean isValidType(String type) {
return Arrays.stream(HolidayWeekendType.values()).anyMatch((holidayWeekendType) -> holidayWeekendType.name().equals(type));
}
public static List<String> valueList() {
return Arrays.stream(HolidayWeekendType.values()).map(HolidayWeekendType::name).collect(Collectors.toList());
}
}

View File

@@ -1,36 +0,0 @@
package de.financer.model;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public enum IntervalType {
/** Indicates that an action should be executed every day */
DAILY,
/** Indicates that an action should be executed once a week */
WEEKLY,
/** Indicates that an action should be executed once a month */
MONTHLY,
/** Indicates that an action should be executed once a quarter */
QUARTERLY,
/** Indicates that an action should be executed once a year */
YEARLY;
/**
* This method validates whether the given string represents a valid interval type.
*
* @param type to check
* @return whether the given type represents a valid interval type
*/
public static boolean isValidType(String type) {
return Arrays.stream(IntervalType.values()).anyMatch((intervalType) -> intervalType.name().equals(type));
}
public static List<String> valueList() {
return Arrays.stream(IntervalType.values()).map(IntervalType::name).collect(Collectors.toList());
}
}

View File

@@ -1,92 +0,0 @@
package de.financer.model;
import java.time.LocalDate;
public class RecurringTransaction {
private Long id;
private Account fromAccount;
private Account toAccount;
private String description;
private Long amount;
private IntervalType intervalType;
private LocalDate firstOccurrence;
private LocalDate lastOccurrence;
private HolidayWeekendType holidayWeekendType;
private boolean remind;
public Long getId() {
return id;
}
public Account getFromAccount() {
return fromAccount;
}
public void setFromAccount(Account fromAccount) {
this.fromAccount = fromAccount;
}
public Account getToAccount() {
return toAccount;
}
public void setToAccount(Account toAccount) {
this.toAccount = toAccount;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getAmount() {
return amount;
}
public void setAmount(Long amount) {
this.amount = amount;
}
public HolidayWeekendType getHolidayWeekendType() {
return holidayWeekendType;
}
public void setHolidayWeekendType(HolidayWeekendType holidayWeekendType) {
this.holidayWeekendType = holidayWeekendType;
}
public LocalDate getLastOccurrence() {
return lastOccurrence;
}
public void setLastOccurrence(LocalDate lastOccurrence) {
this.lastOccurrence = lastOccurrence;
}
public LocalDate getFirstOccurrence() {
return firstOccurrence;
}
public void setFirstOccurrence(LocalDate firstOccurrence) {
this.firstOccurrence = firstOccurrence;
}
public IntervalType getIntervalType() {
return intervalType;
}
public void setIntervalType(IntervalType intervalType) {
this.intervalType = intervalType;
}
public boolean isRemind() {
return remind;
}
public void setRemind(boolean remind) {
this.remind = remind;
}
}

View File

@@ -1,65 +0,0 @@
package de.financer.model;
import java.time.LocalDate;
public class Transaction {
private Long id;
private Account fromAccount;
private Account toAccount;
private LocalDate date;
private String description;
private Long amount;
private RecurringTransaction recurringTransaction;
public Long getId() {
return id;
}
public Account getFromAccount() {
return fromAccount;
}
public void setFromAccount(Account fromAccount) {
this.fromAccount = fromAccount;
}
public Account getToAccount() {
return toAccount;
}
public void setToAccount(Account toAccount) {
this.toAccount = toAccount;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getAmount() {
return amount;
}
public void setAmount(Long amount) {
this.amount = amount;
}
public RecurringTransaction getRecurringTransaction() {
return recurringTransaction;
}
public void setRecurringTransaction(RecurringTransaction recurringTransaction) {
this.recurringTransaction = recurringTransaction;
}
}

View File

@@ -19,6 +19,7 @@ financer.account-overview.table-header.balance=Current Balance
financer.account-overview.table-header.type=Type
financer.account-overview.table-header.status=Status
financer.account-overview.tooltip.status.current-expenses=Period from {0} to {1}
financer.account-overview.tooltip.status.current-assets=Assets available at short-notice
financer.account-new.title=financer\: create new account
financer.account-new.label.key=Key\:
@@ -133,6 +134,8 @@ financer.heading.recurring-transaction-list.active=financer\: active recurring t
financer.heading.recurring-transaction-list.all=financer\: all recurring transaction
financer.heading.recurring-to-transaction-with-amount=financer\: create transaction from recurring with amount
financer.cancel-back-to-overview=Cancel and back to overview
financer.error-message.UNKNOWN_ERROR=An unknown error occurred!
financer.error-message.INVALID_ACCOUNT_TYPE=The selected account type is not valid!
financer.error-message.FROM_ACCOUNT_NOT_FOUND=The specified from account has not been found!

View File

@@ -19,6 +19,7 @@ financer.account-overview.table-header.balance=Kontostand
financer.account-overview.table-header.type=Typ
financer.account-overview.table-header.status=Status
financer.account-overview.tooltip.status.current-expenses=Periode ab {0} bis {1}
financer.account-overview.tooltip.status.current-assets=Kurzfristig verf\u00FCgbares Verm\u00F6gen
financer.account-new.title=financer\: Neues Konto erstellen
financer.account-new.label.key=Schl\u00FCssel\:
@@ -131,4 +132,6 @@ financer.heading.account-details=financer\: Kontodetails f\u00FCr {0}
financer.heading.recurring-transaction-list.dueToday=financer\: wiederkehrende Buchungen heute f\u00E4llig
financer.heading.recurring-transaction-list.active=financer\: aktive wiederkehrende Buchungen
financer.heading.recurring-transaction-list.all=financer\: alle wiederkehrende Buchungen
financer.heading.recurring-to-transaction-with-amount=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen
financer.heading.recurring-to-transaction-with-amount=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen
financer.cancel-back-to-overview=Abbrechen und zur \u00DCbersicht

View File

@@ -74,6 +74,14 @@ tr:hover {
box-sizing: border-box;
}
input[type=submit] {
margin-bottom: 1em;
}
#footer-container {
margin-top: 1em;
}
#footer-container * {
display: block;
}

View File

@@ -47,7 +47,7 @@
<td class="hideable-column" th:text="${transaction.id}"/>
<td th:text="${transaction.fromAccount.key}" />
<td th:text="${transaction.toAccount.key}" />
<td th:text="${transaction.date}" />
<td th:text="${#temporals.format(transaction.date)}" />
<td th:text="${#numbers.formatDecimal(transaction.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT')}"/>
<td th:text="${transaction.description}" />
<td th:text="${transaction.recurringTransaction != null}" />

View File

@@ -10,11 +10,11 @@
<h1 th:text="#{financer.heading.account-overview}" />
<div id="status-container">
<span th:text="#{financer.account-overview.status}"/>
<div>
<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')}"/>
</div>
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${periodStart}, ${periodEnd})}">
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${#temporals.format(periodStart)}, ${#temporals.format(periodEnd)})}">
<span th:text="#{financer.account-overview.status.current-expenses}"/>
<span th:text="${#numbers.formatDecimal(currentExpenses/100D, 1, 'DEFAULT', 2, 'DEFAULT')}"/>
</div>

View File

@@ -9,6 +9,7 @@
<body>
<h1 th:text="#{financer.heading.account-new}" />
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<form id="new-account-form" action="#" th:action="@{/saveAccount}" th:object="${form}" method="post">
<label for="inputKey" th:text="#{financer.account-new.label.key}"/>
<input type="text" id="inputKey" th:field="*{key}" />

View File

@@ -9,6 +9,7 @@
<body>
<h1 th:text="#{financer.heading.account-group-new}" />
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<form id="new-account-group-form" action="#" th:action="@{/saveAccountGroup}" th:object="${form}" method="post">
<label for="inputName" th:text="#{financer.account-group-new.label.name}"/>
<input type="text" id="inputName" th:field="*{name}" />

View File

@@ -9,6 +9,7 @@
<body>
<h1 th:text="#{financer.heading.recurring-transaction-new}" />
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<form id="new-recurring-transaction-form" action="#" th:action="@{/saveRecurringTransaction}" th:object="${form}"
method="post">
<label for="selectFromAccount" th:text="#{financer.recurring-transaction-new.label.from-account}"/>

View File

@@ -9,6 +9,7 @@
<body>
<h1 th:text="#{financer.heading.recurring-to-transaction-with-amount}" />
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<form id="recurring-to-transaction-with-amount-form" action="#" th:action="@{/createTransactionWithAmount}" th:object="${form}"
method="post">
<label for="inputAmount" th:text="#{financer.recurring-to-transaction-with-amount.label.amount}"/>

View File

@@ -30,8 +30,8 @@
<td>
<a th:href="@{/accountDetails(key=${rt.toAccount.key})}" th:text="${rt.toAccount.key}"/>
</td>
<td th:text="${rt.firstOccurrence}"/>
<td th:text="${rt.lastOccurrence}"/>
<td th:text="${#temporals.format(rt.firstOccurrence)}"/>
<td th:text="${#temporals.format(rt.lastOccurrence)}"/>
<td th:text="${#numbers.formatDecimal(rt.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT')}"/>
<td th:text="${rt.description}"/>
<td th:text="#{'financer.interval-type.' + ${rt.intervalType}}"/>

View File

@@ -9,6 +9,7 @@
<body>
<h1 th:text="#{financer.heading.transaction-new}" />
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<form id="new-transaction-form" action="#" th:action="@{/saveTransaction}" th:object="${form}"
method="post">
<label for="selectFromAccount" th:text="#{financer.transaction-new.label.from-account}"/>
@@ -29,5 +30,6 @@
<input type="text" id="inputDescription" th:field="*{description}"/>
<input type="submit" th:value="#{financer.transaction-new.submit}"/>
</form>
<div th:replace="includes/footer :: footer"/>
</body>
</html>