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.Urgency;
|
||||
import de.financer.template.*;
|
||||
import de.financer.template.StringTemplate;
|
||||
import de.financer.template.exception.FinancerRestException;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import de.financer.util.TransactionUtils;
|
||||
|
||||
@@ -6,6 +6,7 @@ import de.financer.model.*;
|
||||
import de.financer.template.*;
|
||||
import de.financer.form.NewRecurringTransactionForm;
|
||||
import de.financer.form.RecurringToTransactionWithOverrideForm;
|
||||
import de.financer.template.StringTemplate;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.thymeleaf.expression.Numbers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -349,9 +351,17 @@ public class TransactionController {
|
||||
final RecurringTransaction emptyRecurring = new RecurringTransaction();
|
||||
|
||||
emptyRecurring.setDescription("-");
|
||||
emptyRecurring.setAmount(0L);
|
||||
emptyRecurring.setFirstOccurrence(LocalDate.EPOCH);
|
||||
emptyRecurring.setLastOccurrence(LocalDate.of(9999, 12, 31));
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public class CreateUploadedTransactionForm {
|
||||
public static CreateUploadedTransactionForm of(Collection<UploadedTransaction> uploadedTransactions, Long newPeriodOnRecurringTransaction) {
|
||||
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);
|
||||
|
||||
return form;
|
||||
@@ -46,6 +46,7 @@ public class CreateUploadedTransactionForm {
|
||||
private Boolean taxRelevant;
|
||||
private MultipartFile file;
|
||||
private Long recurringTransactionId;
|
||||
private boolean matched;
|
||||
|
||||
protected static CreateUploadedTransactionFormEntry of(UploadedTransaction uploadedTransaction) {
|
||||
final CreateUploadedTransactionFormEntry entry = new CreateUploadedTransactionFormEntry();
|
||||
@@ -55,6 +56,8 @@ public class CreateUploadedTransactionForm {
|
||||
entry.setDescription(uploadedTransaction.getDescription());
|
||||
entry.setDate(uploadedTransaction.getDate());
|
||||
entry.setToAccountKey(uploadedTransaction.getToAccountKey());
|
||||
entry.setFromAccountKey(uploadedTransaction.getFromAccountKey());
|
||||
entry.setMatched(uploadedTransaction.isMatched());
|
||||
|
||||
return entry;
|
||||
}
|
||||
@@ -130,5 +133,13 @@ public class CreateUploadedTransactionForm {
|
||||
public void setCreate(Boolean 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) {
|
||||
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) {
|
||||
Matcher matcher = Pattern.compile(regex).matcher(rawDescription);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.financer.transactionUpload;
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.dto.TransactionUploadFormat;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountType;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
@@ -58,6 +59,8 @@ public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWo
|
||||
final Pair<Account, String> accountAndDescription =
|
||||
this.getAccountAndDescription(buildDescription(r));
|
||||
|
||||
boolean isIncome = Optional.ofNullable(accountAndDescription.getLeft()).map(a -> a.getType() == AccountType.INCOME).orElse(false);
|
||||
|
||||
return new UploadedTransaction(
|
||||
// Amount
|
||||
formatAmount(r.get(AMOUNT_INDEX)),
|
||||
@@ -66,8 +69,11 @@ public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWo
|
||||
// Date
|
||||
formatDate(r.get(DATE_INDEX)),
|
||||
// To account key
|
||||
Optional.ofNullable(accountAndDescription.getLeft())
|
||||
.map(Account::getKey).orElse(null));
|
||||
isIncome ? null : Optional.ofNullable(accountAndDescription.getLeft())
|
||||
.map(Account::getKey).orElse(null),
|
||||
// From account key
|
||||
isIncome ? Optional.ofNullable(accountAndDescription.getLeft())
|
||||
.map(Account::getKey).orElse(null) : null);
|
||||
}
|
||||
)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package de.financer.transactionUpload;
|
||||
|
||||
public class UploadedTransaction {
|
||||
private boolean matched;
|
||||
private Long amount;
|
||||
private String description;
|
||||
private String date;
|
||||
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.description = description;
|
||||
this.date = date;
|
||||
this.toAccountKey = toAccountKey;
|
||||
this.fromAccountKey = fromAccountKey;
|
||||
this.matched = toAccountKey != null || fromAccountKey != null;
|
||||
}
|
||||
|
||||
public Long getAmount() {
|
||||
@@ -44,4 +48,16 @@ public class UploadedTransaction {
|
||||
public void setToAccountKey(String 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.dto.TransactionUploadFormat;
|
||||
import de.financer.model.Account;
|
||||
import de.financer.model.AccountType;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVParser;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
@@ -51,6 +52,8 @@ public class VBCSVTransactionUploadWorker extends AbstractTransactionUploadWorke
|
||||
final Pair<Account, String> accountAndDescription =
|
||||
this.getAccountAndDescription(buildDescription(r));
|
||||
|
||||
boolean isIncome = Optional.ofNullable(accountAndDescription.getLeft()).map(a -> a.getType() == AccountType.INCOME).orElse(false);
|
||||
|
||||
return new UploadedTransaction(
|
||||
// Amount
|
||||
formatAmount(r.get(AMOUNT_INDEX)),
|
||||
@@ -59,8 +62,11 @@ public class VBCSVTransactionUploadWorker extends AbstractTransactionUploadWorke
|
||||
// Date
|
||||
formatDate(r.get(DATE_INDEX)),
|
||||
// To account key
|
||||
Optional.ofNullable(accountAndDescription.getLeft())
|
||||
.map(Account::getKey).orElse(null));
|
||||
isIncome ? null : Optional.ofNullable(accountAndDescription.getLeft())
|
||||
.map(Account::getKey).orElse(null),
|
||||
// From account key
|
||||
isIncome ? Optional.ofNullable(accountAndDescription.getLeft())
|
||||
.map(Account::getKey).orElse(null) : null);
|
||||
}
|
||||
)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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:
|
||||
- Add new deployment profile
|
||||
|
||||
@@ -97,6 +97,10 @@ a {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#create-upload-transactions-table textarea {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
#account-overview-table th,
|
||||
#transaction-table th,
|
||||
#recurring-transaction-list-table th,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<tr>
|
||||
<th />
|
||||
<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.toAccount}"/>
|
||||
<th th:text="#{financer.create-upload-transactions.table-header.date}"/>
|
||||
@@ -33,6 +34,10 @@
|
||||
<td>
|
||||
<input type="checkbox" th:field="*{entries[__${i.index}__].create}"/>
|
||||
</td>
|
||||
<td>
|
||||
<span th:if="${entry.matched}" class="icon color-good"></span>
|
||||
<span th:if="${!entry.matched}" class="icon color-bad"></span>
|
||||
</td>
|
||||
<td>
|
||||
<select th:field="*{entries[__${i.index}__].fromAccountKey}">
|
||||
<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}"/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" th:field="*{entries[__${i.index}__].description}"/>
|
||||
<textarea type="text" th:field="*{entries[__${i.index}__].description}"/>
|
||||
</td>
|
||||
<td>
|
||||
<select th:field="*{entries[__${i.index}__].recurringTransactionId}">
|
||||
<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>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Reference in New Issue
Block a user