From d88e2583b5801af35b480bf6013b4668c1f8a021 Mon Sep 17 00:00:00 2001 From: MK13 Date: Thu, 2 Sep 2021 02:30:24 +0200 Subject: [PATCH] #24 Transaction import: rules for automatic account matching --- .../main/java/de/financer/model/Account.java | 9 +++++ .../controller/AccountController.java | 34 ++++++++++++++----- .../de/financer/service/AccountService.java | 16 +++++---- .../common/V49_0_0__accountRegexps.sql | 2 ++ .../AccountService_createAccountTest.java | 10 +++--- .../controller/AccountController.java | 20 +++++++---- .../controller/TransactionController.java | 11 ++++-- .../form/CreateUploadedTransactionForm.java | 1 + .../de/financer/form/EditAccountForm.java | 9 +++++ .../java/de/financer/form/NewAccountForm.java | 9 +++++ .../AbstractTransactionUploadWorker.java | 34 +++++++++++++++++-- .../MT940CSVTransactionUploadWorker.java | 22 ++++++++---- .../TransactionUploadWorker.java | 1 + .../TransactionUploadWorkerFactory.java | 5 +-- .../UploadedTransaction.java | 12 ++++++- .../main/resources/i18n/message.properties | 2 ++ .../resources/i18n/message_de_DE.properties | 2 ++ .../src/main/resources/static/changelog.txt | 3 ++ .../src/main/resources/static/readme.txt | 28 ++++++++------- .../templates/account/editAccount.html | 2 ++ .../templates/account/newAccount.html | 2 ++ .../createUploadedTransactions.html | 2 ++ 22 files changed, 184 insertions(+), 52 deletions(-) create mode 100644 financer-server/src/main/resources/database/common/V49_0_0__accountRegexps.sql diff --git a/financer-common/src/main/java/de/financer/model/Account.java b/financer-common/src/main/java/de/financer/model/Account.java index 88d0dcb..6603fc9 100644 --- a/financer-common/src/main/java/de/financer/model/Account.java +++ b/financer-common/src/main/java/de/financer/model/Account.java @@ -21,6 +21,7 @@ public class Account { @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "account_id") private Set accountStatistics; + private String uploadMatchRegexps; public Long getId() { return id; @@ -73,4 +74,12 @@ public class Account { public void setAccountStatistics(Set accountStatistics) { this.accountStatistics = accountStatistics; } + + public String getUploadMatchRegexps() { + return uploadMatchRegexps; + } + + public void setUploadMatchRegexps(String uploadMatchRegexps) { + this.uploadMatchRegexps = uploadMatchRegexps; + } } diff --git a/financer-server/src/main/java/de/financer/controller/AccountController.java b/financer-server/src/main/java/de/financer/controller/AccountController.java index 1696927..9b8a81a 100644 --- a/financer-server/src/main/java/de/financer/controller/AccountController.java +++ b/financer-server/src/main/java/de/financer/controller/AccountController.java @@ -11,6 +11,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + @RestController @RequestMapping("accounts") public class AccountController { @@ -37,15 +41,22 @@ public class AccountController { } @RequestMapping("createAccount") - public ResponseEntity createAccount(String key, String type, String accountGroupName) { + public ResponseEntity createAccount(String key, String type, String accountGroupName, String regexps) { final String decoded = ControllerUtil.urlDecode(key); final String decodedGroup = ControllerUtil.urlDecode(accountGroupName); + String decodedRegexps = null; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("/accounts/createAccount got parameters: %s, %s, %s", decoded, type, decodedGroup)); + try { + decodedRegexps = URLDecoder.decode(ControllerUtil.urlDecode(regexps), StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // Cannot happen } - final ResponseReason responseReason = this.accountService.createAccount(decoded, type, decodedGroup); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("/accounts/createAccount got parameters: %s, %s, %s, %s", decoded, type, decodedGroup, decodedRegexps)); + } + + final ResponseReason responseReason = this.accountService.createAccount(decoded, type, decodedGroup, decodedRegexps); if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("/accounts/createAccount returns with %s", responseReason.name())); @@ -55,15 +66,22 @@ public class AccountController { } @RequestMapping("editAccount") - public ResponseEntity editAccount(Long id, String key, String accountGroupName) { + public ResponseEntity editAccount(Long id, String key, String accountGroupName, String regexps) { final String decoded = ControllerUtil.urlDecode(key); final String decodedGroup = ControllerUtil.urlDecode(accountGroupName); + String decodedRegexps = null; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("/accounts/editAccount got parameters: %s, %s, %s", id, decoded, decodedGroup)); + try { + decodedRegexps = URLDecoder.decode(ControllerUtil.urlDecode(regexps), StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // Cannot happen } - final ResponseReason responseReason = this.accountService.editAccount(id, decoded, decodedGroup); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("/accounts/editAccount got parameters: %s, %s, %s, %s", id, decoded, decodedGroup, decodedRegexps)); + } + + final ResponseReason responseReason = this.accountService.editAccount(id, decoded, decodedGroup, decodedRegexps); if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("/accounts/editAccount returns with %s", responseReason.name())); diff --git a/financer-server/src/main/java/de/financer/service/AccountService.java b/financer-server/src/main/java/de/financer/service/AccountService.java index 8a426af..b6d5aeb 100644 --- a/financer-server/src/main/java/de/financer/service/AccountService.java +++ b/financer-server/src/main/java/de/financer/service/AccountService.java @@ -73,6 +73,7 @@ public class AccountService { * @param key the key of the new account * @param type the type of the new account. Must be one of {@link AccountType}. * @param accountGroupName the name of the account group to use + * @param regexps the regular expressions to assign to the new account * * @return {@link ResponseReason#INVALID_ACCOUNT_TYPE} if the given type is not a valid {@link AccountType}, {@link * ResponseReason#UNKNOWN_ERROR} if an unexpected error occurs, {@link ResponseReason#OK} if the operation completed @@ -81,7 +82,7 @@ public class AccountService { * accountGroupName does not identify a valid account group. Never returns null. */ @Transactional(propagation = Propagation.SUPPORTS) - public ResponseReason createAccount(String key, String type, String accountGroupName) { + public ResponseReason createAccount(String key, String type, String accountGroupName, String regexps) { if (!AccountType.isValidType(type)) { return ResponseReason.INVALID_ACCOUNT_TYPE; } @@ -101,15 +102,16 @@ public class AccountService { account.setStatus(AccountStatus.OPEN); // and has a current balance of 0 account.setCurrentBalance(0L); + account.setUploadMatchRegexps(regexps); try { this.accountRepository.save(account); } catch (DataIntegrityViolationException dive) { - LOGGER.error(String.format("Duplicate key! %s|%s|%s", key, type, accountGroupName), dive); + LOGGER.error(String.format("Duplicate key! %s|%s|%s|%s", key, type, accountGroupName, regexps), dive); return ResponseReason.DUPLICATE_ACCOUNT_KEY; } catch (Exception e) { - LOGGER.error(String.format("Could not save account %s|%s|%s", key, type, accountGroupName), e); + LOGGER.error(String.format("Could not save account %s|%s|%s|%s", key, type, accountGroupName, regexps), e); return ResponseReason.UNKNOWN_ERROR; } @@ -123,6 +125,7 @@ public class AccountService { * @param id the id of the account to edit * @param key the new key of the account * @param accountGroupName the new name of the account group to use + * @param regexps the regular expressions of the accounts * * @return {@link ResponseReason#OK} if the operation completed successfully, {@link ResponseReason#UNKNOWN_ERROR} * if an unexpected error occurs, {@link ResponseReason#DUPLICATE_ACCOUNT_KEY} if an account with the given key @@ -131,7 +134,7 @@ public class AccountService { * if the given id does not identify a valid account. Never returns null. */ @Transactional(propagation = Propagation.REQUIRED) - public ResponseReason editAccount(Long id, String key, String accountGroupName) { + public ResponseReason editAccount(Long id, String key, String accountGroupName, String regexps) { final Account account = this.accountRepository.findById(id).orElse(null); if(account == null) { @@ -146,15 +149,16 @@ public class AccountService { account.setKey(key); account.setAccountGroup(accountGroup); + account.setUploadMatchRegexps(regexps); try { this.accountRepository.save(account); } catch (DataIntegrityViolationException dive) { - LOGGER.error(String.format("Duplicate key! %s|%s", key, accountGroupName), dive); + LOGGER.error(String.format("Duplicate key! %s|%s|%s", key, accountGroupName, regexps), dive); return ResponseReason.DUPLICATE_ACCOUNT_KEY; } catch (Exception e) { - LOGGER.error(String.format("Could not save account %s|%s", key, accountGroupName), e); + LOGGER.error(String.format("Could not save account %s|%s|%s", key, accountGroupName, regexps), e); return ResponseReason.UNKNOWN_ERROR; } diff --git a/financer-server/src/main/resources/database/common/V49_0_0__accountRegexps.sql b/financer-server/src/main/resources/database/common/V49_0_0__accountRegexps.sql new file mode 100644 index 0000000..ec479e8 --- /dev/null +++ b/financer-server/src/main/resources/database/common/V49_0_0__accountRegexps.sql @@ -0,0 +1,2 @@ +ALTER TABLE account + ADD COLUMN upload_match_regexps VARCHAR(2000); \ No newline at end of file diff --git a/financer-server/src/test/java/de/financer/service/AccountService_createAccountTest.java b/financer-server/src/test/java/de/financer/service/AccountService_createAccountTest.java index a485a65..9995a28 100644 --- a/financer-server/src/test/java/de/financer/service/AccountService_createAccountTest.java +++ b/financer-server/src/test/java/de/financer/service/AccountService_createAccountTest.java @@ -31,7 +31,7 @@ public class AccountService_createAccountTest { // Nothing to do // Act - ResponseReason response = this.classUnderTest.createAccount(null, null, null); + ResponseReason response = this.classUnderTest.createAccount(null, null, null, null); // Assert Assert.assertEquals(ResponseReason.INVALID_ACCOUNT_TYPE, response); @@ -45,7 +45,7 @@ public class AccountService_createAccountTest { .thenReturn(Mockito.mock(AccountGroup.class)); // Act - ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1"); + ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1", null); // Assert Assert.assertEquals(ResponseReason.UNKNOWN_ERROR, response); @@ -58,7 +58,7 @@ public class AccountService_createAccountTest { .thenReturn(Mockito.mock(AccountGroup.class)); // Act - ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1"); + ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1", null); // Assert Assert.assertEquals(ResponseReason.OK, response); @@ -73,7 +73,7 @@ public class AccountService_createAccountTest { .thenReturn(null); // Act - ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1"); + ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1", null); // Assert Assert.assertEquals(ResponseReason.ACCOUNT_GROUP_NOT_FOUND, response); @@ -87,7 +87,7 @@ public class AccountService_createAccountTest { .thenReturn(Mockito.mock(AccountGroup.class)); // Act - ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1"); + ResponseReason response = this.classUnderTest.createAccount("Test", "BANK", "Group1", null); // Assert Assert.assertEquals(ResponseReason.DUPLICATE_ACCOUNT_KEY, response); diff --git a/financer-web-client/src/main/java/de/financer/controller/AccountController.java b/financer-web-client/src/main/java/de/financer/controller/AccountController.java index b9ae470..5d0dd29 100644 --- a/financer-web-client/src/main/java/de/financer/controller/AccountController.java +++ b/financer-web-client/src/main/java/de/financer/controller/AccountController.java @@ -28,9 +28,12 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -90,12 +93,13 @@ public class AccountController { } @PostMapping("/saveAccount") - public String saveAccount(NewAccountForm form, Model model) { + public String saveAccount(NewAccountForm form, Model model) throws UnsupportedEncodingException { final UriComponentsBuilder builder = UriComponentsBuilder .fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.ACC_CREATE_ACCOUNT)) .queryParam("key", form.getKey()) .queryParam("accountGroupName", form.getGroup()) - .queryParam("type", form.getType()); + .queryParam("type", form.getType()) + .queryParam("regexps", URLEncoder.encode(form.getRegexps(), StandardCharsets.UTF_8.name())); final ResponseEntity response = new StringTemplate().exchange(builder); final ResponseReason responseReason = ResponseReason.fromResponseEntity(response); @@ -230,12 +234,12 @@ public class AccountController { @GetMapping("/editAccount") public String editAccount(Model model, String key) { - _editAccount(model, key, Optional.empty(), Optional.empty()); + _editAccount(model, key, Optional.empty(), Optional.empty(), Optional.empty()); return "account/editAccount"; } - private void _editAccount(Model model, String originalKey, Optional newKey, Optional newGroup) { + private void _editAccount(Model model, String originalKey, Optional newKey, Optional newGroup, Optional regexps) { final ResponseEntity exchange = new GetAccountByKeyTemplate().exchange(this.financerConfig, originalKey); final ResponseEntity> accountGroupResponse = new GetAllAccountGroupsTemplate() .exchange(this.financerConfig); @@ -249,6 +253,7 @@ public class AccountController { form.setGroup(newGroup.orElse(Optional.ofNullable(account.getAccountGroup()).map(AccountGroup::getName).orElse(null))); form.setId(account.getId().toString()); form.setOriginalKey(originalKey); + form.setRegexps(regexps.orElse(account.getUploadMatchRegexps())); model.addAttribute("form", form); @@ -258,18 +263,19 @@ public class AccountController { } @PostMapping("/editAccount") - public String editAccount(Model model, EditAccountForm form) { + public String editAccount(Model model, EditAccountForm form) throws UnsupportedEncodingException { final UriComponentsBuilder editBuilder = UriComponentsBuilder .fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.ACC_EDIT_ACCOUNT)) .queryParam("id", form.getId()) .queryParam("key", form.getKey()) - .queryParam("accountGroupName", form.getGroup()); + .queryParam("accountGroupName", form.getGroup()) + .queryParam("regexps", URLEncoder.encode(form.getRegexps(), StandardCharsets.UTF_8.name())); final ResponseEntity closeResponse = new StringTemplate().exchange(editBuilder); final ResponseReason responseReason = ResponseReason.fromResponseEntity(closeResponse); if (!ResponseReason.OK.equals(responseReason)) { - _editAccount(model, form.getOriginalKey(), Optional.of(form.getKey()), Optional.of(form.getGroup())); + _editAccount(model, form.getOriginalKey(), Optional.of(form.getKey()), Optional.of(form.getGroup()), Optional.of(form.getRegexps())); return "account/editAccount"; } diff --git a/financer-web-client/src/main/java/de/financer/controller/TransactionController.java b/financer-web-client/src/main/java/de/financer/controller/TransactionController.java index b72b487..9ca40fa 100644 --- a/financer-web-client/src/main/java/de/financer/controller/TransactionController.java +++ b/financer-web-client/src/main/java/de/financer/controller/TransactionController.java @@ -211,8 +211,12 @@ public class TransactionController { // TODO ENCODING?? try { + final Iterable allAccounts = + FinancerRestTemplate.exchangeGet(this.financerConfig, + Function.ACC_GET_ALL, + new ParameterizedTypeReference>() {}); final TransactionUploadWorker worker = this.workerFactory - .getWorker(TransactionUploadFormat.valueOf(form.getFormat())); + .getWorker(TransactionUploadFormat.valueOf(form.getFormat()), allAccounts); final Collection uploadedTransactions = worker.process(form.getFile()); @@ -229,6 +233,7 @@ public class TransactionController { } catch(Exception e) { // TODO + e.printStackTrace(); final ResponseEntity> response = new GetAllRecurringTransactionsTemplate() .exchange(this.financerConfig); final List recurringTransactionList = new ArrayList<>(); @@ -248,7 +253,7 @@ public class TransactionController { ControllerUtils.addCurrencySymbol(model, this.financerConfig); ControllerUtils.addDarkMode(model, this.financerConfig); - return "transaction/createUploadedTransactions"; + return "transaction/uploadTransactions"; } } @@ -303,6 +308,8 @@ public class TransactionController { } catch(Exception e) { // TODO + e.printStackTrace(); + return _uploadTransactions(model, Optional.empty(), form); } diff --git a/financer-web-client/src/main/java/de/financer/form/CreateUploadedTransactionForm.java b/financer-web-client/src/main/java/de/financer/form/CreateUploadedTransactionForm.java index 8a33355..2b5aa9d 100644 --- a/financer-web-client/src/main/java/de/financer/form/CreateUploadedTransactionForm.java +++ b/financer-web-client/src/main/java/de/financer/form/CreateUploadedTransactionForm.java @@ -54,6 +54,7 @@ public class CreateUploadedTransactionForm { entry.setAmount(uploadedTransaction.getAmount()); entry.setDescription(uploadedTransaction.getDescription()); entry.setDate(uploadedTransaction.getDate()); + entry.setToAccountKey(uploadedTransaction.getToAccountKey()); return entry; } diff --git a/financer-web-client/src/main/java/de/financer/form/EditAccountForm.java b/financer-web-client/src/main/java/de/financer/form/EditAccountForm.java index 035408a..0882322 100644 --- a/financer-web-client/src/main/java/de/financer/form/EditAccountForm.java +++ b/financer-web-client/src/main/java/de/financer/form/EditAccountForm.java @@ -5,6 +5,7 @@ public class EditAccountForm { private String group; private String id; private String originalKey; + private String regexps; public String getKey() { return key; @@ -37,4 +38,12 @@ public class EditAccountForm { public void setOriginalKey(String originalKey) { this.originalKey = originalKey; } + + public String getRegexps() { + return regexps; + } + + public void setRegexps(String regexps) { + this.regexps = regexps; + } } diff --git a/financer-web-client/src/main/java/de/financer/form/NewAccountForm.java b/financer-web-client/src/main/java/de/financer/form/NewAccountForm.java index dea8a7d..4d12995 100644 --- a/financer-web-client/src/main/java/de/financer/form/NewAccountForm.java +++ b/financer-web-client/src/main/java/de/financer/form/NewAccountForm.java @@ -4,6 +4,7 @@ public class NewAccountForm { private String key; private String type; private String group; + private String regexps; public String getKey() { return key; @@ -28,4 +29,12 @@ public class NewAccountForm { public void setGroup(String group) { this.group = group; } + + public String getRegexps() { + return regexps; + } + + public void setRegexps(String regexps) { + this.regexps = regexps; + } } diff --git a/financer-web-client/src/main/java/de/financer/transactionUpload/AbstractTransactionUploadWorker.java b/financer-web-client/src/main/java/de/financer/transactionUpload/AbstractTransactionUploadWorker.java index ebefa45..a0c5a7e 100644 --- a/financer-web-client/src/main/java/de/financer/transactionUpload/AbstractTransactionUploadWorker.java +++ b/financer-web-client/src/main/java/de/financer/transactionUpload/AbstractTransactionUploadWorker.java @@ -3,14 +3,24 @@ package de.financer.transactionUpload; import com.google.common.base.CharMatcher; import de.financer.config.FinancerConfig; import de.financer.dto.TransactionUploadFormat; +import de.financer.model.Account; +import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public abstract class AbstractTransactionUploadWorker implements TransactionUploadWorker { - private FinancerConfig financerConfig; - private TransactionUploadFormat format; + private final FinancerConfig financerConfig; + private final TransactionUploadFormat format; + private final List accounts; - protected AbstractTransactionUploadWorker(TransactionUploadFormat format, FinancerConfig financerConfig) { + protected AbstractTransactionUploadWorker(TransactionUploadFormat format, FinancerConfig financerConfig, Iterable accounts) { this.format = format; this.financerConfig = financerConfig; + this.accounts = IterableUtils.toList(accounts); } protected FinancerConfig.TransactionUpload.Format getFormatConfig() { @@ -24,4 +34,22 @@ public abstract class AbstractTransactionUploadWorker implements TransactionUplo public FinancerConfig getFinancerConfig() { return financerConfig; } + + protected Pair getAccountAndDescription(String rawDescription) { + for (Account a : this.accounts) { + final String[] regexps = Optional.ofNullable(a.getUploadMatchRegexps()).orElse("").split("\r\n"); + + if (regexps.length > 0) { + for(String regex : regexps) { + Matcher matcher = Pattern.compile(regex).matcher(rawDescription); + + if(matcher.matches()) { + return Pair.of(a, matcher.group(1)); + } + } + } + } + + return Pair.of(null, rawDescription); + } } diff --git a/financer-web-client/src/main/java/de/financer/transactionUpload/MT940CSVTransactionUploadWorker.java b/financer-web-client/src/main/java/de/financer/transactionUpload/MT940CSVTransactionUploadWorker.java index 19b549a..1c805cf 100644 --- a/financer-web-client/src/main/java/de/financer/transactionUpload/MT940CSVTransactionUploadWorker.java +++ b/financer-web-client/src/main/java/de/financer/transactionUpload/MT940CSVTransactionUploadWorker.java @@ -2,10 +2,12 @@ package de.financer.transactionUpload; import de.financer.config.FinancerConfig; import de.financer.dto.TransactionUploadFormat; +import de.financer.model.Account; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -17,6 +19,7 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; +import java.util.Optional; import java.util.stream.Collectors; public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWorker { @@ -28,8 +31,8 @@ public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWo private static final int DESCRIPTION2_INDEX = 4; private static final int AMOUNT_INDEX = 8; - protected MT940CSVTransactionUploadWorker(FinancerConfig financerConfig) { - super(TransactionUploadFormat.MT940_CSV, financerConfig); + protected MT940CSVTransactionUploadWorker(FinancerConfig financerConfig, Iterable accounts) { + super(TransactionUploadFormat.MT940_CSV, financerConfig, accounts); } @Override @@ -44,16 +47,23 @@ public class MT940CSVTransactionUploadWorker extends AbstractTransactionUploadWo .parse(new InputStreamReader(is)); retVal.addAll(parser.stream().skip(1) - .map(r -> new UploadedTransaction( + .map(r -> { + final Pair accountAndDescription = + this.getAccountAndDescription(buildDescription(r)); + + return new UploadedTransaction( // Amount Math.abs(formatAmount(removeQuotes(r.get(AMOUNT_INDEX)))), // Description - buildDescription(r), + accountAndDescription.getRight(), // Date formatDate(LocalDate.parse(removeQuotes(r.get(DATE_INDEX)), DateTimeFormatter.ofPattern(this.getFormatConfig() - .getDateFormat()))) - ) + .getDateFormat()))), + // To account key + Optional.ofNullable(accountAndDescription.getLeft()) + .map(Account::getKey).orElse(null)); + } ) .collect(Collectors.toList())); } catch (IOException | RuntimeException e) { diff --git a/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorker.java b/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorker.java index 31794ef..ae26341 100644 --- a/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorker.java +++ b/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorker.java @@ -1,5 +1,6 @@ package de.financer.transactionUpload; +import de.financer.model.Account; import org.springframework.web.multipart.MultipartFile; import java.util.Collection; diff --git a/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorkerFactory.java b/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorkerFactory.java index c0b9542..ad5ea11 100644 --- a/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorkerFactory.java +++ b/financer-web-client/src/main/java/de/financer/transactionUpload/TransactionUploadWorkerFactory.java @@ -2,6 +2,7 @@ package de.financer.transactionUpload; import de.financer.config.FinancerConfig; import de.financer.dto.TransactionUploadFormat; +import de.financer.model.Account; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -10,12 +11,12 @@ public class TransactionUploadWorkerFactory { @Autowired private FinancerConfig financerConfig; - public TransactionUploadWorker getWorker(TransactionUploadFormat format) { + public TransactionUploadWorker getWorker(TransactionUploadFormat format, Iterable accounts) { AbstractTransactionUploadWorker worker; switch (format) { case MT940_CSV: - worker = new MT940CSVTransactionUploadWorker(this.financerConfig); + worker = new MT940CSVTransactionUploadWorker(this.financerConfig, accounts); break; default: diff --git a/financer-web-client/src/main/java/de/financer/transactionUpload/UploadedTransaction.java b/financer-web-client/src/main/java/de/financer/transactionUpload/UploadedTransaction.java index 8bfa49e..8462d9d 100644 --- a/financer-web-client/src/main/java/de/financer/transactionUpload/UploadedTransaction.java +++ b/financer-web-client/src/main/java/de/financer/transactionUpload/UploadedTransaction.java @@ -4,11 +4,13 @@ public class UploadedTransaction { private Long amount; private String description; private String date; + private String toAccountKey; - public UploadedTransaction(Long amount, String description, String date) { + public UploadedTransaction(Long amount, String description, String date, String toAccountKey) { this.amount = amount; this.description = description; this.date = date; + this.toAccountKey = toAccountKey; } public Long getAmount() { @@ -34,4 +36,12 @@ public class UploadedTransaction { public void setDate(String date) { this.date = date; } + + public String getToAccountKey() { + return toAccountKey; + } + + public void setToAccountKey(String toAccountKey) { + this.toAccountKey = toAccountKey; + } } diff --git a/financer-web-client/src/main/resources/i18n/message.properties b/financer-web-client/src/main/resources/i18n/message.properties index ea4041c..4802346 100644 --- a/financer-web-client/src/main/resources/i18n/message.properties +++ b/financer-web-client/src/main/resources/i18n/message.properties @@ -34,12 +34,14 @@ financer.account-new.title=financer\: create new account financer.account-new.label.key=Key\: financer.account-new.label.type=Type\: financer.account-new.label.group=Group\: +financer.account-new.label.upload-match-regexps=Import match regexps\: financer.account-new.submit=Create account financer.account-edit.title=financer\: edit account financer.account-edit.label.key=Key\: financer.account-edit.label.group=Group\: financer.account-edit.submit=Edit account +financer.account-edit.label.upload-match-regexps=Import match regexps\: financer.account-group-new.title=financer\: create new account group financer.account-group-new.label.name=Name\: diff --git a/financer-web-client/src/main/resources/i18n/message_de_DE.properties b/financer-web-client/src/main/resources/i18n/message_de_DE.properties index 1e121c5..5a2c230 100644 --- a/financer-web-client/src/main/resources/i18n/message_de_DE.properties +++ b/financer-web-client/src/main/resources/i18n/message_de_DE.properties @@ -34,6 +34,7 @@ financer.account-new.title=financer\: Neues Konto erstellen financer.account-new.label.key=Schl\u00FCssel\: financer.account-new.label.type=Typ\: financer.account-new.label.group=Gruppe\: +financer.account-new.label.upload-match-regexps=Zuordnungsregexps\: financer.account-new.submit=Konto erstellen financer.account-group-new.title=financer\: Neue Konto-Gruppe erstellen @@ -44,6 +45,7 @@ financer.account-edit.title=financer\: Bearbeite Konto financer.account-edit.label.key=Schl\u00FCssel\: financer.account-edit.label.group=Gruppe\: financer.account-edit.submit=Konto bearbeiten +financer.account-edit.label.upload-match-regexps=Zuordnungsregexps\: financer.transaction-new.title=financer\: Neue Buchung erstellen financer.transaction-new.label.from-account=Von Konto\: diff --git a/financer-web-client/src/main/resources/static/changelog.txt b/financer-web-client/src/main/resources/static/changelog.txt index 58a95ee..57b7a54 100644 --- a/financer-web-client/src/main/resources/static/changelog.txt +++ b/financer-web-client/src/main/resources/static/changelog.txt @@ -5,6 +5,9 @@ v48 -> v49: - #25 Added the possibility to edit accounts - #23 Added an option to specify a recurring transaction during transaction upload that will close and open expense periods +- Added a new column to the transaction upload creation form that is a consecutive number +- #24 Added the possibility to add regular expressions to accounts so that transactions uploaded can be automatically + matched to accounts v47 -> v48: - #20 Added new property 'transaction type' to a transaction, denoting the type of the transaction, e.g. asset swap, diff --git a/financer-web-client/src/main/resources/static/readme.txt b/financer-web-client/src/main/resources/static/readme.txt index 7197c6f..ecdb200 100644 --- a/financer-web-client/src/main/resources/static/readme.txt +++ b/financer-web-client/src/main/resources/static/readme.txt @@ -13,17 +13,17 @@ 6. Account groups 7. Transactions 8. Recurring transactions - 9. Reporting - 10. FQL - 11. Setup - 12. Links + 9. Transaction upload + 10. Reporting + 11. FQL + 12. Setup + 13. Links 1. About ======== This is the manual for the financer application - a simple app to support you in managing your personal finances. - The main goal of the financer application is to keep things simple by not attempting to provide sophisticated - automation. Instead it is merely a tool that provides basic key values to support you. + The main goal of the financer application is to keep things simple. 2. Overview =========== @@ -183,18 +183,22 @@ 8. Recurring transactions ========================= - 9. Reporting - ============ + 9. Transaction upload + ===================== + File types + regex - 10. FQL + 10. Reporting + ============= + + 11. FQL ======= - 11. Setup + 12. Setup ========= This chapter explains how to setup a financer instance. It requires PostgreSQL as a database backend and a Java Servlet Container (e.g. Apache Tomcat) as a runtime environment. - 11.1 Database setup + 12.1 Database setup ------------------- First install PostgreSQL. Then create a user for financer: sudo -iu postgres @@ -210,7 +214,7 @@ \q exit - 12. Links + 13. Links ========= This chapter contains useful links: - financer web page: https://financer.dev/ diff --git a/financer-web-client/src/main/resources/templates/account/editAccount.html b/financer-web-client/src/main/resources/templates/account/editAccount.html index f6b72c4..2975597 100644 --- a/financer-web-client/src/main/resources/templates/account/editAccount.html +++ b/financer-web-client/src/main/resources/templates/account/editAccount.html @@ -20,6 +20,8 @@ +