Add calendar, SKIP and fix some bugs

This commit is contained in:
2020-10-09 16:44:05 +02:00
parent da3ce372b7
commit 2ee77f9d4f
29 changed files with 649 additions and 40 deletions

View File

@@ -0,0 +1,41 @@
package de.financer.dto;
import de.financer.model.RecurringTransaction;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import java.time.LocalDate;
public class RecurringTransactionDueInRangeResponseDto {
private LocalDate date;
private Iterable<RecurringTransaction> recurringTransactions;
public RecurringTransactionDueInRangeResponseDto() {
}
public RecurringTransactionDueInRangeResponseDto(LocalDate date, Iterable<RecurringTransaction> recurringTransactions) {
this.date = date;
this.recurringTransactions = recurringTransactions;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public Iterable<RecurringTransaction> getRecurringTransactions() {
return recurringTransactions;
}
public void setRecurringTransactions(Iterable<RecurringTransaction> recurringTransactions) {
this.recurringTransactions = recurringTransactions;
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}

View File

@@ -53,7 +53,14 @@ public enum HolidayWeekendType {
* X' -&gt; Earlier, effective due date of action * X' -&gt; Earlier, effective due date of action
* </pre> * </pre>
*/ */
PREVIOUS_WORKDAY; PREVIOUS_WORKDAY,
/**
* <p>
* Indicates that the action should be skipped on the particular occurrence
* </p>
*/
SKIP;
/** /**
* This method validates whether the given string represents a valid holiday weekend type. * This method validates whether the given string represents a valid holiday weekend type.

View File

@@ -17,7 +17,7 @@
<properties> <properties>
<packaging.type>jar</packaging.type> <packaging.type>jar</packaging.type>
<activeProfiles>hsqldb,dev</activeProfiles> <activeProfiles>postgres,dev</activeProfiles>
<deploymentProfile>mk</deploymentProfile> <deploymentProfile>mk</deploymentProfile>
</properties> </properties>

View File

@@ -96,7 +96,7 @@ L_PAREN : '(' ;
R_PAREN : ')' ; R_PAREN : ')' ;
IDENTIFIER : [a-zA-Z]+ ; IDENTIFIER : [a-zA-Z]+ ;
INT_VALUE : [0-9]+ ; INT_VALUE : [0-9]+ ;
STRING_VALUE : '\'' [a-zA-Z0-9- ]+ '\'' ; STRING_VALUE : '\'' [a-zA-Z0-9\-/ ]+ '\'' ;
DATE_VALUE : [0-9-]+ ; DATE_VALUE : [0-9-]+ ;
NEWLINE : ('\r'? '\n' | '\r')+ ; NEWLINE : ('\r'? '\n' | '\r')+ ;

View File

@@ -1,12 +1,14 @@
package de.financer.controller; package de.financer.controller;
import de.financer.ResponseReason; import de.financer.ResponseReason;
import de.financer.dto.RecurringTransactionDueInRangeResponseDto;
import de.financer.model.RecurringTransaction; import de.financer.model.RecurringTransaction;
import de.financer.service.RecurringTransactionService; import de.financer.service.RecurringTransactionService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -114,4 +116,22 @@ public class RecurringTransactionController {
return responseReason.toResponseEntity(); return responseReason.toResponseEntity();
} }
@GetMapping("getAllDueInRange")
public Iterable<RecurringTransactionDueInRangeResponseDto> getAllDueInRange(String start, String end) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String
.format("/recurringTransactions/getAllDueInRange got parameters: %s, %s",
start, end));
}
final Iterable<RecurringTransactionDueInRangeResponseDto> dueInRange = this.recurringTransactionService.getAllDueInRange(start, end);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String
.format("/recurringTransactions/getAllDueInRange returns with %s", dueInRange));
}
return dueInRange;
}
} }

View File

@@ -3,7 +3,11 @@ package de.financer.service;
import de.financer.ResponseReason; import de.financer.ResponseReason;
import de.financer.config.FinancerConfig; import de.financer.config.FinancerConfig;
import de.financer.dba.RecurringTransactionRepository; import de.financer.dba.RecurringTransactionRepository;
import de.financer.model.*; import de.financer.dto.RecurringTransactionDueInRangeResponseDto;
import de.financer.model.Account;
import de.financer.model.HolidayWeekendType;
import de.financer.model.IntervalType;
import de.financer.model.RecurringTransaction;
import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -17,9 +21,12 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -144,8 +151,19 @@ public class RecurringTransactionService {
// If so the recurring transaction is due today // If so the recurring transaction is due today
.anyMatch((d) -> d.equals(now)); .anyMatch((d) -> d.equals(now));
final boolean weekend = this.ruleService.isWeekend(now); final boolean weekend = this.ruleService.isWeekend(now);
boolean defer = false;
// If a recurring transaction has holiday weekend type SKIP and today is either a weekend day or a holiday that
// transaction cannot be due today
if (recurringTransaction.getHolidayWeekendType() == HolidayWeekendType.SKIP && (holiday || weekend)) {
LOGGER.debug(String
.format("Recurring transaction %s not due today because it has HWT %s and today is(holiday=%s or weekend=%s)",
ReflectionToStringBuilder.toString(recurringTransaction), recurringTransaction
.getHolidayWeekendType(), holiday, weekend));
return false;
}
boolean defer = false;
if (holiday || weekend) { if (holiday || weekend) {
defer = recurringTransaction.getHolidayWeekendType() == HolidayWeekendType.NEXT_WORKDAY defer = recurringTransaction.getHolidayWeekendType() == HolidayWeekendType.NEXT_WORKDAY
@@ -174,7 +192,7 @@ public class RecurringTransactionService {
* @return <code>true</code> if the recurring transaction is due today, <code>false</code> otherwise * @return <code>true</code> if the recurring transaction is due today, <code>false</code> otherwise
*/ */
private boolean checkRecurringTransactionDuePast(RecurringTransaction recurringTransaction, LocalDate now) { private boolean checkRecurringTransactionDuePast(RecurringTransaction recurringTransaction, LocalDate now) {
// Recurring transactions with holiday weekend type SAME_DAY or PREVIOUS_WORKDAY can't be due in the past // Recurring transactions with holiday weekend type SAME_DAY, SKIP or PREVIOUS_WORKDAY can't be due in the past
if (!HolidayWeekendType.NEXT_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) { if (!HolidayWeekendType.NEXT_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) {
LOGGER.debug(String.format("Recurring transaction %s has HWT %s and thus cannot be due in the past", LOGGER.debug(String.format("Recurring transaction %s has HWT %s and thus cannot be due in the past",
ReflectionToStringBuilder.toString(recurringTransaction), ReflectionToStringBuilder.toString(recurringTransaction),
@@ -255,7 +273,7 @@ public class RecurringTransactionService {
* @return <code>true</code> if the recurring transaction is due today, <code>false</code> otherwise * @return <code>true</code> if the recurring transaction is due today, <code>false</code> otherwise
*/ */
private boolean checkRecurringTransactionDueFuture(RecurringTransaction recurringTransaction, LocalDate now) { private boolean checkRecurringTransactionDueFuture(RecurringTransaction recurringTransaction, LocalDate now) {
// Recurring transactions with holiday weekend type SAME_DAY or PREVIOUS_WORKDAY can't be due in the future // Recurring transactions with holiday weekend type SAME_DAY, SKIP or PREVIOUS_WORKDAY can't be due in the future
if (!HolidayWeekendType.PREVIOUS_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) { if (!HolidayWeekendType.PREVIOUS_WORKDAY.equals(recurringTransaction.getHolidayWeekendType())) {
LOGGER.debug(String.format("Recurring transaction %s has HWT %s and thus cannot be due in the future", LOGGER.debug(String.format("Recurring transaction %s has HWT %s and thus cannot be due in the future",
ReflectionToStringBuilder.toString(recurringTransaction), ReflectionToStringBuilder.toString(recurringTransaction),
@@ -267,12 +285,12 @@ public class RecurringTransactionService {
// If today is a weekend day or holiday the recurring transaction cannot be due today, because the // If today is a weekend day or holiday the recurring transaction cannot be due today, because the
// holiday weekend type says PREVIOUS_WORKDAY. // holiday weekend type says PREVIOUS_WORKDAY.
if (this.ruleService.isHoliday(now) || this.ruleService.isWeekend(now)) { if (this.ruleService.isHoliday(now) || this.ruleService.isWeekend(now)) {
LOGGER.debug(String.format("Recurring transaction %s has HWT %s and today is either a holiday or weekend," + LOGGER.debug(String.format("Recurring transaction %s has HWT %s and today is either a holiday or weekend," +
" thus it cannot be due in the future", " thus it cannot be due in the future",
ReflectionToStringBuilder.toString(recurringTransaction), ReflectionToStringBuilder.toString(recurringTransaction),
recurringTransaction.getHolidayWeekendType())); recurringTransaction.getHolidayWeekendType()));
return false; // early return return false; // early return
} }
@@ -313,7 +331,6 @@ public class RecurringTransactionService {
} }
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public ResponseReason createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount, public ResponseReason createRecurringTransaction(String fromAccountKey, String toAccountKey, Long amount,
String description, String holidayWeekendType, String description, String holidayWeekendType,
@@ -512,4 +529,26 @@ public class RecurringTransactionService {
return response; return response;
} }
/**
* This method calculates all {@link RecurringTransaction}s due in the given date range.
*
* @param start the start of the range, inclusive
* @param end the end of the range, inclusive
*
* @return a list of date->recurring transactions due mappings
*/
public Iterable<RecurringTransactionDueInRangeResponseDto> getAllDueInRange(String start, String end) {
final List<RecurringTransactionDueInRangeResponseDto> dueInRange = new ArrayList<>();
final LocalDate startDate = LocalDate
.parse(start, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
final LocalDate tmpEndDate = LocalDate
.parse(end, DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat()));
final LocalDate endDate = tmpEndDate.plusDays(1); // Range end is always exclusive in Java API
startDate.datesUntil(endDate, Period.ofDays(1)).forEach(d -> dueInRange
.add(new RecurringTransactionDueInRangeResponseDto(d, this.getAllDueToday(d))));
return dueInRange;
}
} }

View File

@@ -68,6 +68,7 @@ public class RuleService implements InitializingBean {
* <p> * <p>
* The multiplier controls whether the current amount of the given from account is increased or decreased depending * The multiplier controls whether the current amount of the given from account is increased or decreased depending
* on the {@link AccountType} of the given account. * on the {@link AccountType} of the given account.
* </p>
* *
* @param fromAccount the from account to get the multiplier for * @param fromAccount the from account to get the multiplier for
* *
@@ -102,6 +103,7 @@ public class RuleService implements InitializingBean {
* <p> * <p>
* The multiplier controls whether the current amount of the given to account is increased or decreased depending on * The multiplier controls whether the current amount of the given to account is increased or decreased depending on
* the {@link AccountType} of the given account. * the {@link AccountType} of the given account.
* </p>
* *
* @param toAccount the to account to get the multiplier for * @param toAccount the to account to get the multiplier for
* *

View File

@@ -0,0 +1,49 @@
package de.financer.calendar;
import de.financer.model.RecurringTransaction;
import java.time.LocalDate;
import java.time.temporal.ChronoField;
public class Day {
private LocalDate date;
private Iterable<RecurringTransaction> transactions;
public Day(LocalDate date) {
this.date = date;
}
public int getDay() {
return this.date.get(ChronoField.DAY_OF_MONTH);
}
public LocalDate getDate() {
return this.date;
}
public Iterable<RecurringTransaction> getTransactions() {
return transactions;
}
public boolean isPreviousOrNextMonth(Integer offset) {
LocalDate now = LocalDate.now().plusMonths(offset);
return !now.getMonth().equals(this.date.getMonth());
}
public boolean isToday() {
return LocalDate.now().equals(this.date);
}
public void setTransactions(Iterable<RecurringTransaction> transactions) {
this.transactions = transactions;
}
@Override
public String toString() {
return "Day{" +
"date=" + date +
", transactions=" + transactions +
'}';
}
}

View File

@@ -0,0 +1,32 @@
package de.financer.calendar;
import java.util.List;
public class Week {
private int weekNumber;
private List<Day> days;
public int getWeekNumber() {
return weekNumber;
}
public void setWeekNumber(int weekNumber) {
this.weekNumber = weekNumber;
}
public List<Day> getDays() {
return days;
}
public void setDays(List<Day> days) {
this.days = days;
}
@Override
public String toString() {
return "Week{" +
"weekNumber=" + weekNumber +
", days=" + days +
'}';
}
}

View File

@@ -6,6 +6,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.time.DayOfWeek;
import java.util.Currency; import java.util.Currency;
@Configuration @Configuration
@@ -19,6 +20,7 @@ public class FinancerConfig {
private String version; private String version;
private String currencyCode; private String currencyCode;
private Currency currency; private Currency currency;
private DayOfWeek firstDayOfWeek;
public String getServerUrl() { public String getServerUrl() {
return serverUrl; return serverUrl;
@@ -53,4 +55,12 @@ public class FinancerConfig {
public Currency getCurrency() { public Currency getCurrency() {
return currency; return currency;
} }
public DayOfWeek getFirstDayOfWeek() {
return firstDayOfWeek;
}
public void setFirstDayOfWeek(String firstDayOfWeek) {
this.firstDayOfWeek = DayOfWeek.valueOf(firstDayOfWeek);
}
} }

View File

@@ -0,0 +1,146 @@
package de.financer.controller;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import de.financer.calendar.Day;
import de.financer.calendar.Week;
import de.financer.config.FinancerConfig;
import de.financer.dto.RecurringTransactionDueInRangeResponseDto;
import de.financer.dto.SearchTransactionsResponseDto;
import de.financer.template.FinancerRestTemplate;
import de.financer.template.exception.FinancerRestException;
import de.financer.util.ControllerUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@Controller
public class CalendarController {
@Autowired
private FinancerConfig financerConfig;
@GetMapping("/recurringTransactionCalendar")
public String recurringTransactionCalendar(Model model, String offset) {
Integer offsetNumber = null;
if (!NumberUtils.isCreatable(offset)) {
// TODO
}
offsetNumber = Integer.valueOf(offset);
// 1. Add days for calendar headings
model.addAttribute("firstWeekDay", this.financerConfig.getFirstDayOfWeek());
model.addAttribute("secondWeekDay", this.financerConfig.getFirstDayOfWeek().plus(1));
model.addAttribute("thirdWeekDay", this.financerConfig.getFirstDayOfWeek().plus(2));
model.addAttribute("fourthWeekDay", this.financerConfig.getFirstDayOfWeek().plus(3));
model.addAttribute("fifthWeekDay", this.financerConfig.getFirstDayOfWeek().plus(4));
model.addAttribute("sixthWeekDay", this.financerConfig.getFirstDayOfWeek().plus(5));
model.addAttribute("seventhWeekDay", this.financerConfig.getFirstDayOfWeek().plus(6));
// 2. Calculate the days in the calendar
DayOfWeek firstDayOfWeek = this.financerConfig.getFirstDayOfWeek();
int tmpLastDayOfWeek = firstDayOfWeek.getValue() - 1;
DayOfWeek lastOfWeek = DayOfWeek.of(tmpLastDayOfWeek == 0 ? 7 : tmpLastDayOfWeek);
LocalDate today = LocalDate.now().plusMonths(offsetNumber);
LocalDate firstOfMonth = today.withDayOfMonth(1);
LocalDate start = null;
model.addAttribute("today", today);
if (firstOfMonth.getDayOfWeek() == firstDayOfWeek) {
// nothing to do, first is firstDayOfWeek
start = firstOfMonth;
}
// walk back till we have firstDayOfWeek
else {
LocalDate tmp = firstOfMonth;
while ((tmp = tmp.minusDays(1)).getDayOfWeek() != firstDayOfWeek) {
// Just spinning
}
start = tmp; // last firstDayOfWeek of last month
}
List<Day> days = new ArrayList<>();
LocalDate end = today.plusMonths(1).withDayOfMonth(1);
// Go till first lastOfWeek in next month, but only if the last day of the current month is
// not lastOfWeek
if (end.minusDays(1).getDayOfWeek() != lastOfWeek) {
while (end.getDayOfWeek() != lastOfWeek) {
end = end.plusDays(1);
}
end = end.plusDays(1); // because datesUntil parameter is exclusive
}
start.datesUntil(end).forEach((ld) -> days.add(new Day(ld)));
// 3. Get the recurring transactions due at the calculated days from the server
final String rangeStart = ControllerUtils.formatDate(this.financerConfig, days.get(0).getDate());
final String rangeEnd = ControllerUtils.formatDate(this.financerConfig, days.get(days.size() - 1).getDate());
try {
final UriComponentsBuilder transactionBuilder = UriComponentsBuilder
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.RT_GET_ALL_DUE_IN_RANGE));
transactionBuilder.queryParam("start", rangeStart);
transactionBuilder.queryParam("end", rangeEnd);
final Iterable<RecurringTransactionDueInRangeResponseDto> trxs = FinancerRestTemplate.exchangeGet(transactionBuilder,
new ParameterizedTypeReference<Iterable<RecurringTransactionDueInRangeResponseDto>>() {
});
// 3a. Match days and recurring transactions from server
IterableUtils.toList(trxs).stream().forEach(trx -> days.stream()
.filter(d -> d.getDate().equals(trx.getDate()))
.findFirst()
.get()
.setTransactions(trx.getRecurringTransactions()));
}
catch(FinancerRestException e) {
// TODO
model.addAttribute("errorMessage", e.getResponseReason().name());
}
// 4. Build the actual weeks from the data calculated before
List<Week> weeks = Lists.partition(days, 7).stream().map(l -> {
Week w = new Week();
LocalDate firstOfWeek = l.get(0).getDate();
TemporalField woy = WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear(); // Not sure about using Default locale, but well ...
w.setWeekNumber(firstOfWeek.get(woy));
w.setDays(l);
return w;
}).collect(Collectors.toList());
model.addAttribute("weeks", weeks);
model.addAttribute("offset", offsetNumber);
ControllerUtils.addVersionAttribute(model, this.financerConfig);
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
return "recurringTransaction/calendar";
}
}

View File

@@ -30,6 +30,7 @@ public enum Function {
RT_CREATE_RECURRING_TRANSACTION("recurringTransactions/createRecurringTransaction"), RT_CREATE_RECURRING_TRANSACTION("recurringTransactions/createRecurringTransaction"),
RT_DELETE_RECURRING_TRANSACTION("recurringTransactions/deleteRecurringTransaction"), RT_DELETE_RECURRING_TRANSACTION("recurringTransactions/deleteRecurringTransaction"),
RT_CREATE_TRANSACTION("recurringTransactions/createTransaction"), RT_CREATE_TRANSACTION("recurringTransactions/createTransaction"),
RT_GET_ALL_DUE_IN_RANGE("recurringTransactions/getAllDueInRange"),
P_GET_CURRENT_EXPENSE_PERIOD("periods/getCurrentExpensePeriod"), P_GET_CURRENT_EXPENSE_PERIOD("periods/getCurrentExpensePeriod"),
P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod"), P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod"),

View File

@@ -1,6 +1,9 @@
package de.financer.controller; package de.financer.controller;
import com.google.common.collect.Lists;
import de.financer.ResponseReason; import de.financer.ResponseReason;
import de.financer.calendar.Day;
import de.financer.calendar.Week;
import de.financer.config.FinancerConfig; import de.financer.config.FinancerConfig;
import de.financer.model.*; import de.financer.model.*;
import de.financer.template.*; import de.financer.template.*;
@@ -15,8 +18,15 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Controller @Controller
@@ -257,5 +267,4 @@ public class RecurringTransactionController {
return "redirect:/accountOverview"; return "redirect:/accountOverview";
} }
} }

View File

@@ -11,6 +11,7 @@ import de.financer.form.NewTransactionForm;
import de.financer.model.Account; import de.financer.model.Account;
import de.financer.template.exception.FinancerRestException; import de.financer.template.exception.FinancerRestException;
import de.financer.util.ControllerUtils; import de.financer.util.ControllerUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -56,10 +58,12 @@ public class TransactionController {
model.addAttribute("transactions", trxs); model.addAttribute("transactions", trxs);
model.addAttribute("transactionCount", IterableUtils.size(trxs));
} }
catch(FinancerRestException e) { catch(FinancerRestException e) {
// TODO // TODO
model.addAttribute("errorMessage", e.getResponseReason().name()); model.addAttribute("errorMessage", e.getResponseReason().name());
model.addAttribute("transactionCount", 0);
} }
model.addAttribute("form", form); model.addAttribute("form", form);

View File

@@ -97,6 +97,6 @@ public class ControllerUtils {
public static void addCurrencySymbol(Model model, FinancerConfig financerConfig) { public static void addCurrencySymbol(Model model, FinancerConfig financerConfig) {
// Add a space right in front of the currency symbol, so it's not glued to the amount // Add a space right in front of the currency symbol, so it's not glued to the amount
model.addAttribute("currencySymbol", " " + financerConfig.getCurrency().getSymbol()); model.addAttribute("currencySymbol", "&nbsp;" + financerConfig.getCurrency().getSymbol());
} }
} }

View File

@@ -36,3 +36,7 @@ financer.currencyCode=EUR
push-service-client.serverUrl=http://localhost:8077/push-service-server/ push-service-client.serverUrl=http://localhost:8077/push-service-server/
logging.level.de.pushservice=DEBUG logging.level.de.pushservice=DEBUG
# The first day of a week in a calendar
# Possible values: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
financer.firstDayOfWeek=MONDAY

View File

@@ -10,6 +10,7 @@ financer.account-overview.available-actions.recurring-transaction-all=Show all r
financer.account-overview.available-actions.create-account-group=Create new account group 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.select-chart=Generate a chart
financer.account-overview.available-actions.close-current-period=Close the current expense period financer.account-overview.available-actions.close-current-period=Close the current expense period
financer.account-overview.available-actions.recurring-transaction-calendar=Recurring transaction calendar
financer.account-overview.status=Status\: financer.account-overview.status=Status\:
financer.account-overview.status.recurring-transaction-due-today=Recurring transactions due today\: financer.account-overview.status.recurring-transaction-due-today=Recurring transactions due today\:
financer.account-overview.status.recurring-transaction-active=Active recurring transactions\: financer.account-overview.status.recurring-transaction-active=Active recurring transactions\:
@@ -118,6 +119,7 @@ financer.transaction-list.table.recurring.yes=Yes
financer.transaction-list.table.recurring.no=No financer.transaction-list.table.recurring.no=No
financer.transaction-list.table.taxRelevant.true=Yes financer.transaction-list.table.taxRelevant.true=Yes
financer.transaction-list.table.taxRelevant.false=No financer.transaction-list.table.taxRelevant.false=No
financer.transaction-list.transaction-count=Found {0} transactions
financer.recurring-to-transaction-with-amount.title=financer\: create transaction from recurring with amount financer.recurring-to-transaction-with-amount.title=financer\: create transaction from recurring with amount
financer.recurring-to-transaction-with-amount.label.amount=Amount\: financer.recurring-to-transaction-with-amount.label.amount=Amount\:
@@ -174,6 +176,7 @@ financer.interval-type.YEARLY=Yearly
financer.holiday-weekend-type.SAME_DAY=Same day financer.holiday-weekend-type.SAME_DAY=Same day
financer.holiday-weekend-type.NEXT_WORKDAY=Next workday financer.holiday-weekend-type.NEXT_WORKDAY=Next workday
financer.holiday-weekend-type.PREVIOUS_WORKDAY=Previous workday financer.holiday-weekend-type.PREVIOUS_WORKDAY=Previous workday
financer.holiday-weekend-type.SKIP=Skip once
financer.account-type.BANK=Bank financer.account-type.BANK=Bank
financer.account-type.CASH=Cash financer.account-type.CASH=Cash
@@ -199,8 +202,10 @@ financer.heading.chart-select=financer\: select a chart to generate
financer.heading.chart-config-account-group-expenses-for-period=financer\: configure account group expenses for period chart financer.heading.chart-config-account-group-expenses-for-period=financer\: configure account group expenses for period chart
financer.heading.chart-config-account-expenses-for-period=financer\: configure account expenses for period chart financer.heading.chart-config-account-expenses-for-period=financer\: configure account expenses for period chart
financer.heading.search-transactions=financer\: search transactions financer.heading.search-transactions=financer\: search transactions
financer.heading.recurring-transaction-calendar=financer\: recurring transaction calendar
financer.cancel-back-to-overview=Cancel and back to overview financer.cancel-back-to-overview=Cancel and back to overview
financer.back-to-overview=Back to overview
financer.chart.account-group-expenses-current-period.title=Expenses of the current period grouped by account group financer.chart.account-group-expenses-current-period.title=Expenses of the current period grouped by account group
financer.chart.account-group-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account group financer.chart.account-group-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account group
@@ -218,6 +223,19 @@ financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Expenses for a configurable peri
financer.chart.name.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR=Overview about the income and expenses in the current year (bar chart) financer.chart.name.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR=Overview about the income and expenses in the current year (bar chart)
financer.chart.name.EXPENSE_PERIOD_TOTALS_FOR_YEAR=Overview about the income and expenses for a configurable year (bar chart) financer.chart.name.EXPENSE_PERIOD_TOTALS_FOR_YEAR=Overview about the income and expenses for a configurable year (bar chart)
financer.recurring-transaction-calendar.title=financer\: recurring transaction calendar
financer.calendar.calendarweek=CW
financer.calendar.weekdays.MONDAY=Monday
financer.calendar.weekdays.TUESDAY=Tuesday
financer.calendar.weekdays.WEDNESDAY=Wednesday
financer.calendar.weekdays.THURSDAY=Thursday
financer.calendar.weekdays.FRIDAY=Friday
financer.calendar.weekdays.SATURDAY=Saturday
financer.calendar.weekdays.SUNDAY=Sunday
financer.calendar.previous=Previous month
financer.calendar.next=Next month
financer.calendar.current=Current month
financer.error-message.UNKNOWN_ERROR=An unknown error occurred! 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.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! financer.error-message.FROM_ACCOUNT_NOT_FOUND=The specified from account has not been found!

View File

@@ -10,6 +10,7 @@ financer.account-overview.available-actions.recurring-transaction-all=Zeige alle
financer.account-overview.available-actions.create-account-group=Neue Konto-Gruppe erstellen 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.select-chart=Ein Diagramm erzeugen
financer.account-overview.available-actions.close-current-period=Aktuelle Periode schlie\u00DFen financer.account-overview.available-actions.close-current-period=Aktuelle Periode schlie\u00DFen
financer.account-overview.available-actions.recurring-transaction-calendar=Kalender wiederkehrende Buchung
financer.account-overview.status=Status\: financer.account-overview.status=Status\:
financer.account-overview.status.recurring-transaction-due-today=Wiederkehrende Buchungen heute f\u00E4llig\: financer.account-overview.status.recurring-transaction-due-today=Wiederkehrende Buchungen heute f\u00E4llig\:
financer.account-overview.status.recurring-transaction-active=Aktive wiederkehrende Buchungen\: financer.account-overview.status.recurring-transaction-active=Aktive wiederkehrende Buchungen\:
@@ -118,6 +119,7 @@ financer.transaction-list.table.recurring.yes=Ja
financer.transaction-list.table.recurring.no=Nein financer.transaction-list.table.recurring.no=Nein
financer.transaction-list.table.taxRelevant.true=Ja financer.transaction-list.table.taxRelevant.true=Ja
financer.transaction-list.table.taxRelevant.false=Nein financer.transaction-list.table.taxRelevant.false=Nein
financer.transaction-list.transaction-count={0} Buchungen gefunden
financer.recurring-to-transaction-with-amount.title=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen financer.recurring-to-transaction-with-amount.title=financer\: Buchung mit Betrag aus wiederkehrender Buchung erstellen
financer.recurring-to-transaction-with-amount.label.amount=Betrag\: financer.recurring-to-transaction-with-amount.label.amount=Betrag\:
@@ -174,6 +176,7 @@ financer.interval-type.YEARLY=J\u00E4hrlich
financer.holiday-weekend-type.SAME_DAY=Gleicher Tag financer.holiday-weekend-type.SAME_DAY=Gleicher Tag
financer.holiday-weekend-type.NEXT_WORKDAY=N\u00E4chster Werktag financer.holiday-weekend-type.NEXT_WORKDAY=N\u00E4chster Werktag
financer.holiday-weekend-type.PREVIOUS_WORKDAY=Vorheriger Werktag financer.holiday-weekend-type.PREVIOUS_WORKDAY=Vorheriger Werktag
financer.holiday-weekend-type.SKIP=Einmalig \u00FCberspringen
financer.account-type.BANK=Bank financer.account-type.BANK=Bank
financer.account-type.CASH=Bar financer.account-type.CASH=Bar
@@ -198,8 +201,10 @@ financer.heading.recurring-to-transaction-with-amount=financer\: Buchung mit Bet
financer.heading.chart-select=financer\: Ein Diagramm zum Erzeugen ausw\u00E4hlen financer.heading.chart-select=financer\: Ein Diagramm zum Erzeugen ausw\u00E4hlen
financer.heading.chart-config-account-group-expenses-for-period=financer\: Konfigurieren von Ausgaben f\u00FCr Periode gruppiert nach Konto-Gruppe Diagramm financer.heading.chart-config-account-group-expenses-for-period=financer\: Konfigurieren von Ausgaben f\u00FCr Periode gruppiert nach Konto-Gruppe Diagramm
financer.heading.search-transactions=financer\: Buchungen suchen financer.heading.search-transactions=financer\: Buchungen suchen
financer.heading.recurring-transaction-calendar=financer\: Kalender wiederkehrende Buchung
financer.cancel-back-to-overview=Abbrechen und zur \u00DCbersicht financer.cancel-back-to-overview=Abbrechen und zur\u00FCck zur \u00DCbersicht
financer.back-to-overview=Zur\u00FCck zur \u00DCbersicht
financer.chart.account-group-expenses-current-period.title=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe financer.chart.account-group-expenses-current-period.title=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe
financer.chart.account-group-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto-Gruppe financer.chart.account-group-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto-Gruppe
@@ -217,6 +222,19 @@ financer.chart.name.ACCOUNT_EXPENSES_FOR_PERIOD=Ausgaben f\u00FCr eine konfiguri
financer.chart.name.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR=\u00DCbersicht über das Einkommen und die Ausgaben im laufenden Jahr (Balkendiagramm) financer.chart.name.EXPENSE_PERIOD_TOTALS_CURRENT_YEAR=\u00DCbersicht über das Einkommen und die Ausgaben im laufenden Jahr (Balkendiagramm)
financer.chart.name.EXPENSE_PERIOD_TOTALS_FOR_YEAR=\u00DCbersicht über das Einkommen und die Ausgaben für ein konfigurierbares Jahr (Balkendiagramm) financer.chart.name.EXPENSE_PERIOD_TOTALS_FOR_YEAR=\u00DCbersicht über das Einkommen und die Ausgaben für ein konfigurierbares Jahr (Balkendiagramm)
financer.recurring-transaction-calendar.title=financer\: Kalender wiederkehrende Buchung
financer.calendar.calendarweek=KW
financer.calendar.weekdays.MONDAY=Montag
financer.calendar.weekdays.TUESDAY=Dienstag
financer.calendar.weekdays.WEDNESDAY=Mittwoch
financer.calendar.weekdays.THURSDAY=Donnerstag
financer.calendar.weekdays.FRIDAY=Freitag
financer.calendar.weekdays.SATURDAY=Samstag
financer.calendar.weekdays.SUNDAY=Sonntag
financer.calendar.previous=Letzter Monat
financer.calendar.next=N\u00E4chster Monat
financer.calendar.current=Aktueller Monat
financer.error-message.UNKNOWN_ERROR=Ein unbekannter Fehler ist aufgetreten! financer.error-message.UNKNOWN_ERROR=Ein unbekannter Fehler ist aufgetreten!
financer.error-message.INVALID_ACCOUNT_TYPE=Der ausgew\u00E4hlte Kontotyp ist ung\u00FCltig! financer.error-message.INVALID_ACCOUNT_TYPE=Der ausgew\u00E4hlte Kontotyp ist ung\u00FCltig!
financer.error-message.FROM_ACCOUNT_NOT_FOUND=Das ausgew\u00E4hlte Von-Konto wurde nicht gefunden! financer.error-message.FROM_ACCOUNT_NOT_FOUND=Das ausgew\u00E4hlte Von-Konto wurde nicht gefunden!

View File

@@ -1,3 +1,10 @@
v32 -> v33:
- Introduce Holiday/Weekend Type SKIP to skip an occurrence of a recurring transaction once, if the due date is either a
holiday or a weekend day
- Implement a calendar for recurring transactions
- Add result count to transaction search
- Fix a bug in the parsing of FQL
v31 -> v32: v31 -> v32:
- Integrate new push-service release with VAPID support for Chrome based browsers - Integrate new push-service release with VAPID support for Chrome based browsers
@@ -26,13 +33,13 @@ v26 -> v27:
usage) usage)
v25 -> v26: v25 -> v26:
- Close of the current expense period now creates null statistic entries for accounts that have not been used in - Closing the current expense period now creates null statistic entries for accounts that have not been used in
bookings in the period to close. This way the average spending better reflects the period average bookings in the period to close. This way the average spending better reflects the period average
- Introduce period type GRAND TOTAL, that denotes a continuous, cumulative period, starting with the inception of the - Introduce period type GRAND TOTAL, that denotes a continuous, cumulative period, starting with the inception of the
financer app, without an end financer app, without an end
- Introduce period type EXPENSE YEAR, that acts as a group for all expense periods in a year. This may not be a - Introduce period type EXPENSE YEAR, that acts as a group for all expense periods in a year. This may not be a
calendar year, if e.g. the EXPENSE periods do not start at the first of every month. This includes all periods that calendar year, if e.g. the EXPENSE periods do not start at the first of every month. This includes all periods that
start the in the year. This is a preparation for extended reports and year-based booking start in the year
- Transactions with a date in the past are now assigned to the proper EXPENSE and EXPENSE YEAR periods - Transactions with a date in the past are now assigned to the proper EXPENSE and EXPENSE YEAR periods
- Add a fav icon - Add a fav icon

View File

@@ -93,7 +93,8 @@ tr:hover {
text-align: center; text-align: center;
} }
#status-container > span, div { #status-container > span,
#status-container > div {
display: block; display: block;
} }
@@ -135,7 +136,8 @@ input[type=submit] {
margin-top: 1em; margin-top: 1em;
} }
#footer-container > hr, div { #footer-container > hr,
#footer-container > div {
display: block; display: block;
} }
@@ -204,3 +206,111 @@ input[type=submit] {
#search-transactions-fql-detail > * { #search-transactions-fql-detail > * {
font-size: 0.7em; font-size: 0.7em;
} }
#cal {
width: 100%;
margin-top: 1.5em;
margin-bottom: 1.5em;
border-collapse: collapse;
}
#cal th {
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
}
#cal th:nth-last-child(1) {
border-right: 0px solid #ddd;
}
#cal-control-con {
width: 100%;
}
#cal-control-con-table {
width: 100%;
}
#cal-control-con-table > tbody > tr > * {
width: 33%;
text-align: center;
}
#cal-control-con-table > tbody > tr:hover {
background-color: transparent;
}
.cal-day-previous {
background-color: #F0EEEE;
}
.cal-day-con {
display: inline-block;
width: 100%;
}
.cal-week-row:hover {
background-color: transparent;
}
#cal-header-row:hover {
background-color: transparent;
}
.cal-calendarweek {
text-align: center;
vertical-align: top;
}
.cal-calendarweek-con-content {
margin-top: 0.5em;
}
.cal-day-con-content-con-text {
margin-left: 0.5em;
margin-right: 0.5em;
margin-top: 0.3em;
margin-bottom: 0.3em;
background-color: #e9f9f9;
border: 1px;
border-style: solid;
border-color: #b9dFdF;
border-radius: 4px;
padding-left: 1em;
padding-right: 1em;
color: #000000;
display: inline-block;
}
#transaction-list-container-count-con-text {
margin-bottom: 0px;
margin-top: 2em;
}
#transaction-table {
margin-top: 0.2em;
}
.cal-day-con-number-con-text {
margin: 0.5em;
color: #444242;
}
.cal-day {
vertical-align: top;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
}
.cal-calendarweek {
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
.cal-week-row td:nth-last-child(1) {
border-right: 0px solid #ddd;
}
.cal-day-con-today {
background-color: #f5e5b8;
}

View File

@@ -200,5 +200,3 @@
This chapter lists planned features. The list is in no particular order: This chapter lists planned features. The list is in no particular order:
- Transaction import from online banking (file based) - Transaction import from online banking (file based)
- Extended reports, e.g. forecasting based on recurring transactions and average spending - Extended reports, e.g. forecasting based on recurring transactions and average spending
- Receivable account type
- Edit masks for accounts, transactions, recurring transactions

View File

@@ -17,7 +17,7 @@
</div> </div>
<div id="balance-container"> <div id="balance-container">
<span th:text="#{financer.account-details.details.balance}"/> <span th:text="#{financer.account-details.details.balance}"/>
<span th:text="${#numbers.formatDecimal(account.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/> <span th:utext="${#numbers.formatDecimal(account.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
</div> </div>
<div id="group-container" th:if="${account.accountGroup != null}"> <div id="group-container" th:if="${account.accountGroup != null}">
<span th:text="#{financer.account-details.details.group}"/> <span th:text="#{financer.account-details.details.group}"/>

View File

@@ -17,12 +17,12 @@
<span th:text="#{financer.account-overview.status}"/> <span th:text="#{financer.account-overview.status}"/>
<div th:title="#{financer.account-overview.tooltip.status.current-assets}"> <div th:title="#{financer.account-overview.tooltip.status.current-assets}">
<span th:text="#{financer.account-overview.status.current-assets}"/> <span th:text="#{financer.account-overview.status.current-assets}"/>
<span th:text="${#numbers.formatDecimal(currentAssets/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/> <span th:utext="${#numbers.formatDecimal(currentAssets/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
</div> </div>
<div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${#temporals.format(periodStart)})}"> <div th:title="#{'financer.account-overview.tooltip.status.current-expenses'(${#temporals.format(periodStart)})}">
<span th:text="#{financer.account-overview.status.current-expenses}"/> <span th:text="#{financer.account-overview.status.current-expenses}"/>
<a th:href="@{/getAccountGroupExpensesCurrentPeriod}"> <a th:href="@{/getAccountGroupExpensesCurrentPeriod}">
<span th:text="${#numbers.formatDecimal(currentExpenses/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}" /> <span th:utext="${#numbers.formatDecimal(currentExpenses/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}" />
</a> </a>
</div> </div>
<div> <div>
@@ -58,6 +58,8 @@
th:text="#{financer.account-overview.available-actions.create-recurring-transaction}"/> th:text="#{financer.account-overview.available-actions.create-recurring-transaction}"/>
<a th:href="@{/recurringTransactionAll}" <a th:href="@{/recurringTransactionAll}"
th:text="#{financer.account-overview.available-actions.recurring-transaction-all}"/> th:text="#{financer.account-overview.available-actions.recurring-transaction-all}"/>
<a th:href="@{'/recurringTransactionCalendar?offset=0'}"
th:text="#{financer.account-overview.available-actions.recurring-transaction-calendar}"/>
</div> </div>
<div id="action-container-sub-period"> <div id="action-container-sub-period">
<a th:href="@{/closePeriod}" <a th:href="@{/closePeriod}"
@@ -91,14 +93,14 @@
<td> <td>
<a th:href="@{/accountDetails(key=${acc.key})}" th:text="${acc.key}"/> <a th:href="@{/accountDetails(key=${acc.key})}" th:text="${acc.key}"/>
</td> </td>
<td th:text="${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/> <td th:utext="${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td class="hideable-column" <td class="hideable-column"
th:if="${acc.spendingCurrentExpensePeriod != null}" th:if="${acc.spendingCurrentExpensePeriod != null}"
th:text="${#numbers.formatDecimal(acc.spendingCurrentExpensePeriod/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}" th:utext="${#numbers.formatDecimal(acc.spendingCurrentExpensePeriod/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"
th:classappend="${acc.spendingCurrentExpensePeriod > acc.averageSpendingExpensePeriod} ? overspend"/> th:classappend="${acc.spendingCurrentExpensePeriod > acc.averageSpendingExpensePeriod} ? overspend"/>
<td class="hideable-column" <td class="hideable-column"
th:if="${acc.averageSpendingExpensePeriod != null}" th:if="${acc.averageSpendingExpensePeriod != null}"
th:text="${#numbers.formatDecimal(acc.averageSpendingExpensePeriod/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/> th:utext="${#numbers.formatDecimal(acc.averageSpendingExpensePeriod/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td class="hideable-column" th:if="${acc.spendingCurrentExpensePeriod == null}">-</td> <td class="hideable-column" th:if="${acc.spendingCurrentExpensePeriod == null}">-</td>
<td class="hideable-column" th:if="${acc.averageSpendingExpensePeriod == null}">-</td> <td class="hideable-column" th:if="${acc.averageSpendingExpensePeriod == null}">-</td>
<td class="hideable-column" th:text="${acc.accountGroup?.name}"/> <td class="hideable-column" th:text="${acc.accountGroup?.name}"/>

View File

@@ -0,0 +1,63 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{financer.recurring-transaction-calendar.title}"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" th:href="@{/css/main.css}">
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
</head>
<body>
<h1 th:text="#{financer.heading.recurring-transaction-calendar} + ' — ' + ${#temporals.monthName(today)} + ' ' + ${#temporals.year(today)}"/>
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.back-to-overview}"/>
<div id="calendar-container" th:fragment="recurring-transaction-calendar">
<table id="cal">
<tr id="cal-header-row">
<th class="header-col" th:text="#{'financer.calendar.calendarweek'}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${firstWeekDay}}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${secondWeekDay}}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${thirdWeekDay}}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${fourthWeekDay}}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${fifthWeekDay}}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${sixthWeekDay}}"/>
<th class="header-col" th:text="#{'financer.calendar.weekdays.' + ${seventhWeekDay}}"/>
</tr>
<tr class="cal-week-row" th:each="week : ${weeks}">
<td class="cal-calendarweek">
<div class="cal-calendarweek-con">
<p class="cal-calendarweek-con-content" th:text="${week.weekNumber}"/>
</div>
</td>
<td th:class="${day.isPreviousOrNextMonth(offset) ? 'cal-day-previous cal-day' : 'cal-day'}" th:each="day : ${week.days}">
<div th:class="${day.today ? 'cal-day-con cal-day-con-today' : 'cal-day-con'}">
<div class="cal-day-con-number-con">
<p class="cal-day-con-number-con-text" th:text="${day.day}"/>
</div>
<div th:if="${!day.isPreviousOrNextMonth(offset)}" class="cal-day-con-content">
<div class="cal-day-con-content-con" th:each="transaction : ${day.transactions}">
<p class="cal-day-con-content-con-text" th:utext="${transaction.description + ' — ' + #numbers.formatDecimal(transaction.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}" />
</div>
</div>
</div>
</td>
</table>
<div id="cal-control-con">
<table id="cal-control-con-table">
<tr>
<td>
<a id="cal-control-con-prev" th:href="@{'/recurringTransactionCalendar?offset=' + ${offset-1} + ''}" th:text="#{financer.calendar.previous}" />
</td>
<td>
<a id="cal-control-con-curr" th:href="@{'/recurringTransactionCalendar?offset=0'}" th:text="#{financer.calendar.current}" />
</td>
<td>
<a id="cal-control-con-next" th:href="@{'/recurringTransactionCalendar?offset=' + ${offset+1} + ''}" th:text="#{financer.calendar.next}" />
</td>
</tr>
</table>
</div>
</div>
<div th:replace="includes/footer :: footer"/>
</body>
</html>

View File

@@ -16,12 +16,12 @@
<label for="selectFromAccount" th:text="#{financer.recurring-transaction-new.label.from-account}"/> <label for="selectFromAccount" th:text="#{financer.recurring-transaction-new.label.from-account}"/>
<select id="selectFromAccount" th:field="*{fromAccountKey}"> <select id="selectFromAccount" th:field="*{fromAccountKey}">
<option th:each="acc : ${fromAccounts}" 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})}"/> th:utext="#{'financer.recurring-transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
</select> </select>
<label for="selectToAccount" th:text="#{financer.recurring-transaction-new.label.to-account}"/> <label for="selectToAccount" th:text="#{financer.recurring-transaction-new.label.to-account}"/>
<select id="selectToAccount" th:field="*{toAccountKey}"> <select id="selectToAccount" th:field="*{toAccountKey}">
<option th:each="acc : ${toAccounts}" 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})}"/> th:utext="#{'financer.recurring-transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
</select> </select>
<label for="inputAmount" th:text="#{financer.recurring-transaction-new.label.amount}"/> <label for="inputAmount" th:text="#{financer.recurring-transaction-new.label.amount}"/>
<input type="text" id="inputAmount" th:field="*{amount}"/> <input type="text" id="inputAmount" th:field="*{amount}"/>

View File

@@ -35,7 +35,7 @@
</td> </td>
<td th:text="${#temporals.format(rt.firstOccurrence)}"/> <td th:text="${#temporals.format(rt.firstOccurrence)}"/>
<td th:text="${#temporals.format(rt.lastOccurrence)}"/> <td th:text="${#temporals.format(rt.lastOccurrence)}"/>
<td th:text="${#numbers.formatDecimal(rt.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/> <td th:utext="${#numbers.formatDecimal(rt.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td th:text="${rt.description}"/> <td th:text="${rt.description}"/>
<td th:text="#{'financer.interval-type.' + ${rt.intervalType}}"/> <td th:text="#{'financer.interval-type.' + ${rt.intervalType}}"/>
<td th:text="#{'financer.holiday-weekend-type.' + ${rt.holidayWeekendType}}"/> <td th:text="#{'financer.holiday-weekend-type.' + ${rt.holidayWeekendType}}"/>

View File

@@ -16,12 +16,12 @@
<label for="selectFromAccount" th:text="#{financer.transaction-new.label.from-account}"/> <label for="selectFromAccount" th:text="#{financer.transaction-new.label.from-account}"/>
<select id="selectFromAccount" th:field="*{fromAccountKey}"> <select id="selectFromAccount" th:field="*{fromAccountKey}">
<option th:each="acc : ${fromAccounts}" 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})}"/> th:utext="#{'financer.transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
</select> </select>
<label for="selectToAccount" th:text="#{financer.transaction-new.label.to-account}"/> <label for="selectToAccount" th:text="#{financer.transaction-new.label.to-account}"/>
<select id="selectToAccount" th:field="*{toAccountKey}"> <select id="selectToAccount" th:field="*{toAccountKey}">
<option th:each="acc : ${toAccounts}" 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})}"/> th:utext="#{'financer.transaction-new.account-type.' + ${acc.type}(${acc.key},${#numbers.formatDecimal(acc.currentBalance/100D, 1, 'DEFAULT', 2, 'DEFAULT')},${currencySymbol})}"/>
</select> </select>
<label for="inputAmount" th:text="#{financer.transaction-new.label.amount}"/> <label for="inputAmount" th:text="#{financer.transaction-new.label.amount}"/>
<input type="text" id="inputAmount" th:field="*{amount}"/> <input type="text" id="inputAmount" th:field="*{amount}"/>

View File

@@ -1,4 +1,7 @@
<div id="transaction-list-container" th:fragment="transaction-list"> <div id="transaction-list-container" th:fragment="transaction-list">
<div id="transaction-list-container-count-con" th:if="${transactionCount != null}">
<p id="transaction-list-container-count-con-text" th:text="#{'financer.transaction-list.transaction-count'(${transactionCount})}"/>
</div>
<table id="transaction-table"> <table id="transaction-table">
<tr> <tr>
<th class="hideable-column" th:text="#{financer.transaction-list.table-header.id}"/> <th class="hideable-column" th:text="#{financer.transaction-list.table-header.id}"/>
@@ -16,7 +19,7 @@
<td th:text="${transaction.fromAccount.key}" /> <td th:text="${transaction.fromAccount.key}" />
<td th:text="${transaction.toAccount.key}" /> <td th:text="${transaction.toAccount.key}" />
<td th:text="${#temporals.format(transaction.date)}" /> <td th:text="${#temporals.format(transaction.date)}" />
<td th:text="${#numbers.formatDecimal(transaction.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/> <td th:utext="${#numbers.formatDecimal(transaction.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td th:text="${transaction.description}" /> <td th:text="${transaction.description}" />
<td th:if="${transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.yes}" /> <td th:if="${transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.yes}" />
<td th:if="${!transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.no}" /> <td th:if="${!transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.no}" />

32
pom.xml
View File

@@ -30,8 +30,6 @@
</properties> </properties>
<modules> <modules>
<module>financer-server</module>
<module>financer-web-client</module>
<module>financer-common</module> <module>financer-common</module>
</modules> </modules>
@@ -90,7 +88,7 @@
<dependency> <dependency>
<groupId>de.77zzcx7.push-service</groupId> <groupId>de.77zzcx7.push-service</groupId>
<artifactId>push-service-client-lib</artifactId> <artifactId>push-service-client-lib</artifactId>
<version>3</version> <version>4-SNAPSHOT</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@@ -122,5 +120,33 @@
</plugins> </plugins>
</build> </build>
</profile> </profile>
<profile>
<id>all</id>
<modules>
<module>financer-common</module>
<module>financer-server</module>
<module>financer-web-client</module>
</modules>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>web-client</id>
<modules>
<module>financer-common</module>
<module>financer-web-client</module>
</modules>
</profile>
<profile>
<id>server</id>
<modules>
<module>financer-common</module>
<module>financer-server</module>
</modules>
</profile>
</profiles> </profiles>
</project> </project>