From 9fc90b81752b272739eef28a74bf65fc08b609f0 Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sat, 14 Jan 2023 17:52:47 +0100 Subject: [PATCH] #724 - parse date from csv --- .../TransactionImportController.java | 52 ++++++++++++------- .../csvimport/CsvTransaction.java | 7 +-- .../CsvTransactionParseException.java | 9 ++++ .../transactions/csvimport/DateParser.java | 32 ++++++++++++ .../resources/languages/base_de.properties | 1 + .../resources/languages/base_en.properties | 1 + .../transaction/csvimport/DateParserTest.java | 41 +++++++++++++++ 7 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransactionParseException.java create mode 100644 BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/DateParser.java create mode 100644 BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/DateParserTest.java diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java index 95ef20d1d..5677e13ca 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java @@ -4,7 +4,9 @@ import de.deadlocker8.budgetmaster.accounts.AccountService; import de.deadlocker8.budgetmaster.categories.CategoryService; import de.deadlocker8.budgetmaster.categories.CategoryType; import de.deadlocker8.budgetmaster.controller.BaseController; +import de.deadlocker8.budgetmaster.services.DateFormatStyle; import de.deadlocker8.budgetmaster.services.HelpersService; +import de.deadlocker8.budgetmaster.settings.SettingsService; import de.deadlocker8.budgetmaster.transactions.csvimport.*; import de.deadlocker8.budgetmaster.utils.Mappings; import de.thecodelabs.utils.util.Localization; @@ -60,14 +62,16 @@ public class TransactionImportController extends BaseController private final HelpersService helpers; private final CategoryService categoryService; private final AccountService accountService; + private final SettingsService settingsService; @Autowired - public TransactionImportController(TransactionService transactionService, HelpersService helpers, CategoryService categoryService, AccountService accountService) + public TransactionImportController(TransactionService transactionService, HelpersService helpers, CategoryService categoryService, AccountService accountService, SettingsService settingsService) { this.transactionService = transactionService; this.helpers = helpers; this.categoryService = categoryService; this.accountService = accountService; + this.settingsService = settingsService; } @GetMapping @@ -159,25 +163,17 @@ public class TransactionImportController extends BaseController final CsvRow csvRow = csvRows.get(i); try { - final String date = csvRow.getColumns().get(csvColumnSettings.columnDate() - 1); - final String name = csvRow.getColumns().get(csvColumnSettings.columnName() - 1); - final String description = csvRow.getColumns().get(csvColumnSettings.columnDescription() - 1); - - final String amount = csvRow.getColumns().get(csvColumnSettings.columnAmount() - 1); - final Optional<Integer> parsedAmountOptional = AmountParser.parse(amount); - if(parsedAmountOptional.isEmpty()) - { - errors.add(Localization.getString("transactions.import.error.parse.amount", i, csvRow)); - continue; - } - - csvTransactions.add(new CsvTransaction(date, name, parsedAmountOptional.get(), description, CsvTransactionStatus.PENDING)); + csvTransactions.add(createCsvTransactionFromCsvRow(csvRow, csvColumnSettings, i)); } catch(IndexOutOfBoundsException e) { LOGGER.error("Invalid access to column", e); errors.add(Localization.getString("transactions.import.error.column", i, csvRow)); } + catch(CsvTransactionParseException e) + { + errors.add(e.getMessage()); + } } request.setAttribute(RequestAttributeNames.ERRORS_COLUMN_SETTINGS, errors, RequestAttributes.SCOPE_SESSION); @@ -186,6 +182,28 @@ public class TransactionImportController extends BaseController return ReturnValues.REDIRECT_IMPORT; } + private CsvTransaction createCsvTransactionFromCsvRow(CsvRow csvRow, CsvColumnSettings csvColumnSettings, Integer index) throws CsvTransactionParseException + { + final String date = csvRow.getColumns().get(csvColumnSettings.columnDate() - 1); + final Optional<LocalDate> parsedDateOptional = DateParser.parse(date, DateFormatStyle.LONG.getKey(), settingsService.getSettings().getLanguage().getLocale()); + if(parsedDateOptional.isEmpty()) + { + throw new CsvTransactionParseException(Localization.getString("transactions.import.error.parse.date", index, csvRow)); + } + + final String name = csvRow.getColumns().get(csvColumnSettings.columnName() - 1); + final String description = csvRow.getColumns().get(csvColumnSettings.columnDescription() - 1); + + final String amount = csvRow.getColumns().get(csvColumnSettings.columnAmount() - 1); + final Optional<Integer> parsedAmountOptional = AmountParser.parse(amount); + if(parsedAmountOptional.isEmpty()) + { + throw new CsvTransactionParseException(Localization.getString("transactions.import.error.parse.amount", index, csvRow)); + } + + return new CsvTransaction(parsedDateOptional.get(), name, parsedAmountOptional.get(), description, CsvTransactionStatus.PENDING); + } + @GetMapping("/cancel") public String cancel(WebRequest request) { @@ -223,8 +241,7 @@ public class TransactionImportController extends BaseController final Transaction newTransaction = createTransactionFromCsvTransaction(csvTransaction); - // TODO use csvTransaction.getDate() instead of debug date - transactionService.prepareModelNewOrEdit(model, false, LocalDate.now(), false, newTransaction, accountService.getAllActivatedAccountsAsc()); + transactionService.prepareModelNewOrEdit(model, false, csvTransaction.getDate(), false, newTransaction, accountService.getAllActivatedAccountsAsc()); if(type.equals("transfer")) { @@ -260,8 +277,7 @@ public class TransactionImportController extends BaseController private Transaction createTransactionFromCsvTransaction(CsvTransaction csvTransaction) { final Transaction newTransaction = new Transaction(); - // TODO parse first -// newTransaction.setDate(csvTransaction.getDate()); + newTransaction.setDate(csvTransaction.getDate()); newTransaction.setName(csvTransaction.getName()); newTransaction.setDescription(csvTransaction.getDescription()); newTransaction.setAmount(csvTransaction.getAmount()); diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransaction.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransaction.java index 82b4a7058..70c46064a 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransaction.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransaction.java @@ -1,16 +1,17 @@ package de.deadlocker8.budgetmaster.transactions.csvimport; +import java.time.LocalDate; import java.util.Objects; public final class CsvTransaction { - private final String date; + private final LocalDate date; private String name; private final Integer amount; private String description; private CsvTransactionStatus status; - public CsvTransaction(String date, String name, Integer amount, String description, CsvTransactionStatus status) + public CsvTransaction(LocalDate date, String name, Integer amount, String description, CsvTransactionStatus status) { this.date = date; this.name = name; @@ -19,7 +20,7 @@ public final class CsvTransaction this.status = status; } - public String getDate() + public LocalDate getDate() { return date; } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransactionParseException.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransactionParseException.java new file mode 100644 index 000000000..9e36a3bf9 --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/CsvTransactionParseException.java @@ -0,0 +1,9 @@ +package de.deadlocker8.budgetmaster.transactions.csvimport; + +public class CsvTransactionParseException extends Exception +{ + public CsvTransactionParseException(String message) + { + super(message); + } +} diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/DateParser.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/DateParser.java new file mode 100644 index 000000000..80afaa8f4 --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvimport/DateParser.java @@ -0,0 +1,32 @@ +package de.deadlocker8.budgetmaster.transactions.csvimport; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import java.util.Optional; + +public class DateParser +{ + + private DateParser() + { + } + + public static Optional<LocalDate> parse(String dateString, String pattern, Locale locale) + { + if(dateString == null || pattern == null || locale == null) + { + return Optional.empty(); + } + + try + { + return Optional.of(LocalDate.parse(dateString, DateTimeFormatter.ofPattern(pattern).withLocale(locale))); + } + catch(DateTimeParseException e) + { + return Optional.empty(); + } + } +} diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties index 19640400c..9de718137 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_de.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties @@ -382,6 +382,7 @@ transactions.import.status.skipped=übersprungen transactions.import.actions=Aktionen transactions.import.error.column=Zugeordnete Spalten in Zeile {0} (Zählung beginnt relativ zu Anzahl übersprungener Zeilen) nicht gefunden: {1} transactions.import.error.parse.amount=Fehler beim Parsen des Betrags in Zeile {0} (Zählung beginnt relativ zu Anzahl übersprungener Zeilen) +transactions.import.error.parse.date=Fehler beim Parsen des Datums in Zeile {0} (Zählung beginnt relativ zu Anzahl übersprungener Zeilen) repeating.button.add=Wiederholung hinzufügen repeating.button.remove=Wiederholung entfernen diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties index 58d90b49a..f54cefd95 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_en.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties @@ -381,6 +381,7 @@ transactions.import.status.skipped=skipped transactions.import.actions=Actions transactions.import.error.column=Associated columns not found in row {0} (counting starts relative to the number of skipped rows): {1} transactions.import.error.parse.amount=Error parsing the amount in line {0} (counting starts relative to number of skipped lines) +transactions.import.error.parse.date=Error parsing the date in line {0} (counting starts relative to number of skipped lines) repeating.button.add=Add repetition repeating.button.remove=Remove repetition diff --git a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/DateParserTest.java b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/DateParserTest.java new file mode 100644 index 000000000..8ba22b79f --- /dev/null +++ b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/transaction/csvimport/DateParserTest.java @@ -0,0 +1,41 @@ +package de.deadlocker8.budgetmaster.unit.transaction.csvimport; + +import de.deadlocker8.budgetmaster.transactions.csvimport.DateParser; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.Locale; + +import static org.assertj.core.api.Assertions.assertThat; + +class DateParserTest +{ + @Test + void test_nullText() + { + assertThat(DateParser.parse(null, "dd.MM", Locale.ENGLISH)) + .isEmpty(); + } + + @Test + void test_nullPattern() + { + assertThat(DateParser.parse("14.01.23", null, Locale.ENGLISH)) + .isEmpty(); + } + + @Test + void test_textNotMatchingPattern() + { + assertThat(DateParser.parse("14.01.23", "dd.MM", Locale.ENGLISH)) + .isEmpty(); + } + + @Test + void test_matchingPattern() + { + assertThat(DateParser.parse("14.01.23", "dd.MM.yy", Locale.ENGLISH)) + .isPresent() + .get().isEqualTo(LocalDate.of(2023, 1, 14)); + } +} -- GitLab