Transaction upload improvements
This commit is contained in:
@@ -12,6 +12,7 @@ import de.financer.notification.Notification;
|
|||||||
import de.financer.notification.PushServiceProxy;
|
import de.financer.notification.PushServiceProxy;
|
||||||
import de.financer.notification.Urgency;
|
import de.financer.notification.Urgency;
|
||||||
import de.financer.template.*;
|
import de.financer.template.*;
|
||||||
|
import de.financer.template.StringTemplate;
|
||||||
import de.financer.template.exception.FinancerRestException;
|
import de.financer.template.exception.FinancerRestException;
|
||||||
import de.financer.util.ControllerUtils;
|
import de.financer.util.ControllerUtils;
|
||||||
import de.financer.util.TransactionUtils;
|
import de.financer.util.TransactionUtils;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import de.financer.model.*;
|
|||||||
import de.financer.template.*;
|
import de.financer.template.*;
|
||||||
import de.financer.form.NewRecurringTransactionForm;
|
import de.financer.form.NewRecurringTransactionForm;
|
||||||
import de.financer.form.RecurringToTransactionWithOverrideForm;
|
import de.financer.form.RecurringToTransactionWithOverrideForm;
|
||||||
|
import de.financer.template.StringTemplate;
|
||||||
import de.financer.util.ControllerUtils;
|
import de.financer.util.ControllerUtils;
|
||||||
import org.apache.commons.collections4.IterableUtils;
|
import org.apache.commons.collections4.IterableUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ import org.springframework.ui.Model;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
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 org.thymeleaf.expression.Numbers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -349,9 +351,17 @@ public class TransactionController {
|
|||||||
final RecurringTransaction emptyRecurring = new RecurringTransaction();
|
final RecurringTransaction emptyRecurring = new RecurringTransaction();
|
||||||
|
|
||||||
emptyRecurring.setDescription("-");
|
emptyRecurring.setDescription("-");
|
||||||
|
emptyRecurring.setAmount(0L);
|
||||||
|
emptyRecurring.setFirstOccurrence(LocalDate.EPOCH);
|
||||||
|
emptyRecurring.setLastOccurrence(LocalDate.of(9999, 12, 31));
|
||||||
|
|
||||||
recurringTransactionList.add(emptyRecurring);
|
recurringTransactionList.add(emptyRecurring);
|
||||||
recurringTransactionList.addAll(IterableUtils.toList(response.getBody()));
|
recurringTransactionList.addAll(IterableUtils.toList(response.getBody()).stream().peek(rt -> {
|
||||||
|
if(rt.getLastOccurrence() == null) {
|
||||||
|
rt.setLastOccurrence(LocalDate.of(9999, 12, 31));
|
||||||
|
}
|
||||||
|
|
||||||
|
}).collect(Collectors.toList()));
|
||||||
|
|
||||||
model.addAttribute("recurringTransactions", recurringTransactionList);
|
model.addAttribute("recurringTransactions", recurringTransactionList);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class CreateUploadedTransactionForm {
|
|||||||
public static CreateUploadedTransactionForm of(Collection<UploadedTransaction> uploadedTransactions, Long newPeriodOnRecurringTransaction) {
|
public static CreateUploadedTransactionForm of(Collection<UploadedTransaction> uploadedTransactions, Long newPeriodOnRecurringTransaction) {
|
||||||
final CreateUploadedTransactionForm form = new CreateUploadedTransactionForm();
|
final CreateUploadedTransactionForm form = new CreateUploadedTransactionForm();
|
||||||
|
|
||||||
uploadedTransactions.stream().forEach(e -> form.getEntries().add(CreateUploadedTransactionFormEntry.of(e)));
|
uploadedTransactions.forEach(e -> form.getEntries().add(CreateUploadedTransactionFormEntry.of(e)));
|
||||||
form.setNewPeriodOnRecurringTransaction(newPeriodOnRecurringTransaction);
|
form.setNewPeriodOnRecurringTransaction(newPeriodOnRecurringTransaction);
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
@@ -46,6 +46,7 @@ public class CreateUploadedTransactionForm {
|
|||||||
private Boolean taxRelevant;
|
private Boolean taxRelevant;
|
||||||
private MultipartFile file;
|
private MultipartFile file;
|
||||||
private Long recurringTransactionId;
|
private Long recurringTransactionId;
|
||||||
|
private boolean matched;
|
||||||
|
|
||||||
protected static CreateUploadedTransactionFormEntry of(UploadedTransaction uploadedTransaction) {
|
protected static CreateUploadedTransactionFormEntry of(UploadedTransaction uploadedTransaction) {
|
||||||
final CreateUploadedTransactionFormEntry entry = new CreateUploadedTransactionFormEntry();
|
final CreateUploadedTransactionFormEntry entry = new CreateUploadedTransactionFormEntry();
|
||||||
@@ -55,6 +56,8 @@ public class CreateUploadedTransactionForm {
|
|||||||
entry.setDescription(uploadedTransaction.getDescription());
|
entry.setDescription(uploadedTransaction.getDescription());
|
||||||
entry.setDate(uploadedTransaction.getDate());
|
entry.setDate(uploadedTransaction.getDate());
|
||||||
entry.setToAccountKey(uploadedTransaction.getToAccountKey());
|
entry.setToAccountKey(uploadedTransaction.getToAccountKey());
|
||||||
|
entry.setFromAccountKey(uploadedTransaction.getFromAccountKey());
|
||||||
|
entry.setMatched(uploadedTransaction.isMatched());
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -130,5 +133,13 @@ public class CreateUploadedTransactionForm {
|
|||||||
public void setCreate(Boolean create) {
|
public void setCreate(Boolean create) {
|
||||||
this.create = create;
|
this.create = create;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMatched() {
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMatched(boolean matched) {
|
||||||
|
this.matched = matched;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public abstract class AbstractTransactionUploadWorker implements TransactionUplo
|
|||||||
for (Account a : this.accounts) {
|
for (Account a : this.accounts) {
|
||||||
final String[] regexps = Optional.ofNullable(a.getUploadMatchRegexps()).orElse("").split("\r\n");
|
final String[] regexps = Optional.ofNullable(a.getUploadMatchRegexps()).orElse("").split("\r\n");
|
||||||
|
|
||||||
if (regexps.length > 0) {
|
if (regexps.length > 0 && StringUtils.isNotEmpty(regexps[0])) {
|
||||||
for(String regex : regexps) {
|
for(String regex : regexps) {
|
||||||
Matcher matcher = Pattern.compile(regex).matcher(rawDescription);
|
Matcher matcher = Pattern.compile(regex).matcher(rawDescription);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.financer.transactionUpload;
|
|||||||
import de.financer.config.FinancerConfig;
|
import de.financer.config.FinancerConfig;
|
||||||
import de.financer.dto.TransactionUploadFormat;
|
import de.financer.dto.TransactionUploadFormat;
|
||||||
import de.financer.model.Account;
|
import de.financer.model.Account;
|
||||||
|
import de.financer.model.AccountType;
|
||||||
import org.apache.commons.csv.CSVFormat;
|
import org.apache.commons.csv.CSVFormat;
|
||||||
import org.apache.commons.csv.CSVParser;
|
import org.apache.commons.csv.CSVParser;
|
||||||
import org.apache.commons.csv.CSVRecord;
|
import org.apache.commons.csv.CSVRecord;
|
||||||
@@ -58,6 +59,8 @@ public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWo
|
|||||||
final Pair<Account, String> accountAndDescription =
|
final Pair<Account, String> accountAndDescription =
|
||||||
this.getAccountAndDescription(buildDescription(r));
|
this.getAccountAndDescription(buildDescription(r));
|
||||||
|
|
||||||
|
boolean isIncome = Optional.ofNullable(accountAndDescription.getLeft()).map(a -> a.getType() == AccountType.INCOME).orElse(false);
|
||||||
|
|
||||||
return new UploadedTransaction(
|
return new UploadedTransaction(
|
||||||
// Amount
|
// Amount
|
||||||
formatAmount(r.get(AMOUNT_INDEX)),
|
formatAmount(r.get(AMOUNT_INDEX)),
|
||||||
@@ -66,8 +69,11 @@ public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWo
|
|||||||
// Date
|
// Date
|
||||||
formatDate(r.get(DATE_INDEX)),
|
formatDate(r.get(DATE_INDEX)),
|
||||||
// To account key
|
// To account key
|
||||||
Optional.ofNullable(accountAndDescription.getLeft())
|
isIncome ? null : Optional.ofNullable(accountAndDescription.getLeft())
|
||||||
.map(Account::getKey).orElse(null));
|
.map(Account::getKey).orElse(null),
|
||||||
|
// From account key
|
||||||
|
isIncome ? Optional.ofNullable(accountAndDescription.getLeft())
|
||||||
|
.map(Account::getKey).orElse(null) : null);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package de.financer.transactionUpload;
|
package de.financer.transactionUpload;
|
||||||
|
|
||||||
public class UploadedTransaction {
|
public class UploadedTransaction {
|
||||||
|
private boolean matched;
|
||||||
private Long amount;
|
private Long amount;
|
||||||
private String description;
|
private String description;
|
||||||
private String date;
|
private String date;
|
||||||
private String toAccountKey;
|
private String toAccountKey;
|
||||||
|
private String fromAccountKey;
|
||||||
|
|
||||||
public UploadedTransaction(Long amount, String description, String date, String toAccountKey) {
|
public UploadedTransaction(Long amount, String description, String date, String toAccountKey, String fromAccountKey) {
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.toAccountKey = toAccountKey;
|
this.toAccountKey = toAccountKey;
|
||||||
|
this.fromAccountKey = fromAccountKey;
|
||||||
|
this.matched = toAccountKey != null || fromAccountKey != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getAmount() {
|
public Long getAmount() {
|
||||||
@@ -44,4 +48,16 @@ public class UploadedTransaction {
|
|||||||
public void setToAccountKey(String toAccountKey) {
|
public void setToAccountKey(String toAccountKey) {
|
||||||
this.toAccountKey = toAccountKey;
|
this.toAccountKey = toAccountKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFromAccountKey() {
|
||||||
|
return fromAccountKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromAccountKey(String fromAccountKey) {
|
||||||
|
this.fromAccountKey = fromAccountKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMatched() {
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.financer.transactionUpload;
|
|||||||
import de.financer.config.FinancerConfig;
|
import de.financer.config.FinancerConfig;
|
||||||
import de.financer.dto.TransactionUploadFormat;
|
import de.financer.dto.TransactionUploadFormat;
|
||||||
import de.financer.model.Account;
|
import de.financer.model.Account;
|
||||||
|
import de.financer.model.AccountType;
|
||||||
import org.apache.commons.csv.CSVFormat;
|
import org.apache.commons.csv.CSVFormat;
|
||||||
import org.apache.commons.csv.CSVParser;
|
import org.apache.commons.csv.CSVParser;
|
||||||
import org.apache.commons.csv.CSVRecord;
|
import org.apache.commons.csv.CSVRecord;
|
||||||
@@ -51,6 +52,8 @@ public class VBCSVTransactionUploadWorker extends AbstractTransactionUploadWorke
|
|||||||
final Pair<Account, String> accountAndDescription =
|
final Pair<Account, String> accountAndDescription =
|
||||||
this.getAccountAndDescription(buildDescription(r));
|
this.getAccountAndDescription(buildDescription(r));
|
||||||
|
|
||||||
|
boolean isIncome = Optional.ofNullable(accountAndDescription.getLeft()).map(a -> a.getType() == AccountType.INCOME).orElse(false);
|
||||||
|
|
||||||
return new UploadedTransaction(
|
return new UploadedTransaction(
|
||||||
// Amount
|
// Amount
|
||||||
formatAmount(r.get(AMOUNT_INDEX)),
|
formatAmount(r.get(AMOUNT_INDEX)),
|
||||||
@@ -59,8 +62,11 @@ public class VBCSVTransactionUploadWorker extends AbstractTransactionUploadWorke
|
|||||||
// Date
|
// Date
|
||||||
formatDate(r.get(DATE_INDEX)),
|
formatDate(r.get(DATE_INDEX)),
|
||||||
// To account key
|
// To account key
|
||||||
Optional.ofNullable(accountAndDescription.getLeft())
|
isIncome ? null : Optional.ofNullable(accountAndDescription.getLeft())
|
||||||
.map(Account::getKey).orElse(null));
|
.map(Account::getKey).orElse(null),
|
||||||
|
// From account key
|
||||||
|
isIncome ? Optional.ofNullable(accountAndDescription.getLeft())
|
||||||
|
.map(Account::getKey).orElse(null) : null);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
v55 -> v56:
|
v55 -> v56:
|
||||||
-
|
- Change description field in transaction upload from input to textarea and increase its width
|
||||||
|
- Recurring transaction selection in transaction upload now shows additional information like amount and dates
|
||||||
|
- From account regex support for transaction upload
|
||||||
|
- Add matching indicator to transaction upload
|
||||||
|
|
||||||
v54 -> v55:
|
v54 -> v55:
|
||||||
- Add new deployment profile
|
- Add new deployment profile
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ a {
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#create-upload-transactions-table textarea {
|
||||||
|
width: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
#account-overview-table th,
|
#account-overview-table th,
|
||||||
#transaction-table th,
|
#transaction-table th,
|
||||||
#recurring-transaction-list-table th,
|
#recurring-transaction-list-table th,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
<th th:text="#{financer.create-upload-transactions.table-header.create}"/>
|
<th th:text="#{financer.create-upload-transactions.table-header.create}"/>
|
||||||
|
<th />
|
||||||
<th th:text="#{financer.create-upload-transactions.table-header.fromAccount}"/>
|
<th th:text="#{financer.create-upload-transactions.table-header.fromAccount}"/>
|
||||||
<th th:text="#{financer.create-upload-transactions.table-header.toAccount}"/>
|
<th th:text="#{financer.create-upload-transactions.table-header.toAccount}"/>
|
||||||
<th th:text="#{financer.create-upload-transactions.table-header.date}"/>
|
<th th:text="#{financer.create-upload-transactions.table-header.date}"/>
|
||||||
@@ -33,6 +34,10 @@
|
|||||||
<td>
|
<td>
|
||||||
<input type="checkbox" th:field="*{entries[__${i.index}__].create}"/>
|
<input type="checkbox" th:field="*{entries[__${i.index}__].create}"/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span th:if="${entry.matched}" class="icon color-good"></span>
|
||||||
|
<span th:if="${!entry.matched}" class="icon color-bad"></span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select th:field="*{entries[__${i.index}__].fromAccountKey}">
|
<select th:field="*{entries[__${i.index}__].fromAccountKey}">
|
||||||
<option th:each="acc : ${fromAccounts}" th:value="${acc.key}"
|
<option th:each="acc : ${fromAccounts}" th:value="${acc.key}"
|
||||||
@@ -52,12 +57,12 @@
|
|||||||
<input id="create-upload-transactions-amount" type="text" th:field="*{entries[__${i.index}__].amount}"/>
|
<input id="create-upload-transactions-amount" type="text" th:field="*{entries[__${i.index}__].amount}"/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" th:field="*{entries[__${i.index}__].description}"/>
|
<textarea type="text" th:field="*{entries[__${i.index}__].description}"/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select th:field="*{entries[__${i.index}__].recurringTransactionId}">
|
<select th:field="*{entries[__${i.index}__].recurringTransactionId}">
|
||||||
<option th:each="rt : ${recurringTransactions}" th:value="${rt.id}"
|
<option th:each="rt : ${recurringTransactions}" th:value="${rt.id}"
|
||||||
th:text="${rt.description}"/>
|
th:utext="${rt.description} + '|' + ${#temporals.format(rt.firstOccurrence)} + '-' + ${#temporals.format(rt.lastOccurrence)} + '|' + ${#numbers.formatDecimal(rt.amount/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in New Issue
Block a user