diff --git a/src/main/java/link/biosmarcel/baka/bankimport/DKB2CSV.java b/src/main/java/link/biosmarcel/baka/bankimport/DKB2CSV.java new file mode 100644 index 0000000..a8b87e2 --- /dev/null +++ b/src/main/java/link/biosmarcel/baka/bankimport/DKB2CSV.java @@ -0,0 +1,91 @@ +package link.biosmarcel.baka.bankimport; + +import link.biosmarcel.baka.data.Account; +import link.biosmarcel.baka.data.Payment; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.QuoteMode; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Format for new DKB UI. + */ +public class DKB2CSV { + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yy"); + private static final DecimalFormat CURRENCY_FORMAT; + + static { + CURRENCY_FORMAT = (DecimalFormat) DecimalFormat.getNumberInstance(Locale.GERMAN); + CURRENCY_FORMAT.setParseBigDecimal(true); + } + + public static List parse(final Account account, final File file) { + try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.ISO_8859_1)) { + final var format = CSVFormat.Builder + .create() + .setDelimiter(';') + .setQuote('"') + // Haven't figured out the escape character yet. + .setQuoteMode(QuoteMode.MINIMAL) + .setIgnoreSurroundingSpaces(true) + .setIgnoreEmptyLines(true) + .setAllowMissingColumnNames(true) + .build(); + final var records = format.parse(reader).iterator(); + // Skip header row; Format.setSkipHeaderRecord doesn't seem to work. + // Also skip rows that just contain meta data. Empty rows don't count, so we skip 5. + for (int i = 0; i < 5; i++) { + records.next(); + } + + final List newPayments = new ArrayList<>(); + records.forEachRemaining(record -> { + final var reference = record.get(5); + final BigDecimal amount; + try { + amount = (BigDecimal) CURRENCY_FORMAT.parse(record.get(8)); + } catch (final ParseException exception) { + throw new RuntimeException(exception); + } + + final var bookingDate = LocalDate.parse(record.get(0), DATE_FORMAT).atStartOfDay(); + final var effectiveDate = switch (record.get(1)) { + // EffectiveDate is optional, so to avoid confusion, we just set it to the same as bookingDate. + case null -> bookingDate; + case "" -> bookingDate; + default -> LocalDate.parse(record.get(1), DATE_FORMAT).atStartOfDay(); + }; + final String name = record.get(3); + + final Payment payment = new Payment( + account, + amount, + reference, + name, + bookingDate, + effectiveDate + ); + + payment.participant = Import.prepareIBAN(record.get(7)); + + newPayments.add(payment); + }); + + return newPayments; + } catch (final IOException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/src/main/java/link/biosmarcel/baka/bankimport/DKBCSV.java b/src/main/java/link/biosmarcel/baka/bankimport/DKBCSV.java index 2c6d249..aebee8e 100644 --- a/src/main/java/link/biosmarcel/baka/bankimport/DKBCSV.java +++ b/src/main/java/link/biosmarcel/baka/bankimport/DKBCSV.java @@ -19,6 +19,9 @@ import java.util.List; import java.util.Locale; +/** + * Format for old DKB UI. The new UI uses a new format. + */ public class DKBCSV { private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy"); private static final DecimalFormat CURRENCY_FORMAT; @@ -75,7 +78,6 @@ public static List parse(final Account account, final File file) { effectiveDate ); - // Revolut CSV doesn't supply this, we just got the reference, which is called "description" payment.participant = Import.prepareIBAN(record.get(5)); newPayments.add(payment); diff --git a/src/main/java/link/biosmarcel/baka/bankimport/INGCSV.java b/src/main/java/link/biosmarcel/baka/bankimport/INGCSV.java new file mode 100644 index 0000000..950070a --- /dev/null +++ b/src/main/java/link/biosmarcel/baka/bankimport/INGCSV.java @@ -0,0 +1,88 @@ +package link.biosmarcel.baka.bankimport; + +import link.biosmarcel.baka.data.Account; +import link.biosmarcel.baka.data.Payment; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.QuoteMode; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class INGCSV { + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy"); + private static final DecimalFormat CURRENCY_FORMAT; + + static { + CURRENCY_FORMAT = (DecimalFormat) DecimalFormat.getNumberInstance(Locale.GERMAN); + CURRENCY_FORMAT.setParseBigDecimal(true); + } + + public static List parse(final Account account, final File file) { + try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.ISO_8859_1)) { + final var format = CSVFormat.Builder + .create() + .setDelimiter(';') + .setQuote('"') + // Haven't figured out the escape character yet. + .setQuoteMode(QuoteMode.MINIMAL) + .setIgnoreSurroundingSpaces(true) + .setIgnoreEmptyLines(true) + .setAllowMissingColumnNames(true) + .build(); + final var records = format.parse(reader).iterator(); + // Skip header row; Format.setSkipHeaderRecord doesn't seem to work. + // Also skip rows that just contain meta data. Empty rows don't count, so we skip 10. + for (int i = 0; i < 10; i++) { + records.next(); + } + + final List newPayments = new ArrayList<>(); + records.forEachRemaining(record -> { + final var reference = record.get(4); + final BigDecimal amount; + try { + amount = (BigDecimal) CURRENCY_FORMAT.parse(record.get(7)); + } catch (final ParseException exception) { + throw new RuntimeException(exception); + } + + final var bookingDate = LocalDate.parse(record.get(0), DATE_FORMAT).atStartOfDay(); + final var effectiveDate = switch (record.get(1)) { + // EffectiveDate is optional, so to avoid confusion, we just set it to the same as bookingDate. + case null -> bookingDate; + case "" -> bookingDate; + default -> LocalDate.parse(record.get(1), DATE_FORMAT).atStartOfDay(); + }; + final String name = record.get(2); + + final Payment payment = new Payment( + account, + amount, + reference, + name, + bookingDate, + effectiveDate + ); + + payment.participant = ""; // Not included in ING format + + newPayments.add(payment); + }); + + return newPayments; + } catch (final IOException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/src/main/java/link/biosmarcel/baka/data/ImportFormat.java b/src/main/java/link/biosmarcel/baka/data/ImportFormat.java index 3b2e75b..62f1827 100644 --- a/src/main/java/link/biosmarcel/baka/data/ImportFormat.java +++ b/src/main/java/link/biosmarcel/baka/data/ImportFormat.java @@ -1,8 +1,20 @@ package link.biosmarcel.baka.data; +import java.io.File; +import java.util.List; +import java.util.function.BiFunction; + public enum ImportFormat { - SparkasseCSV, - DKBCSV, - RevolutCSV, + SparkasseCSV(link.biosmarcel.baka.bankimport.SparkasseCSV::parse), + DKBCSV(link.biosmarcel.baka.bankimport.DKBCSV::parse), + RevolutCSV(link.biosmarcel.baka.bankimport.RevolutCSV::parse), + DKB2CSV(link.biosmarcel.baka.bankimport.DKB2CSV::parse), + INGCSV(link.biosmarcel.baka.bankimport.INGCSV::parse), ; + + public final transient BiFunction> func; + + ImportFormat(final BiFunction> func) { + this.func = func; + } } diff --git a/src/main/java/link/biosmarcel/baka/view/PaymentsView.java b/src/main/java/link/biosmarcel/baka/view/PaymentsView.java index 5264e00..ec48092 100644 --- a/src/main/java/link/biosmarcel/baka/view/PaymentsView.java +++ b/src/main/java/link/biosmarcel/baka/view/PaymentsView.java @@ -9,9 +9,7 @@ import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import link.biosmarcel.baka.ApplicationState; -import link.biosmarcel.baka.bankimport.DKBCSV; -import link.biosmarcel.baka.bankimport.RevolutCSV; -import link.biosmarcel.baka.bankimport.SparkasseCSV; +import link.biosmarcel.baka.bankimport.*; import link.biosmarcel.baka.data.Account; import link.biosmarcel.baka.data.Payment; @@ -137,18 +135,11 @@ protected void onTabActivated() { } final var menuItem = new MenuItem(account.name); - menuItem.setOnAction(__ -> { - switch (account.importFormat) { - case SparkasseCSV -> importHandler(account, SparkasseCSV::parse); - case DKBCSV -> importHandler(account, DKBCSV::parse); - case RevolutCSV -> importHandler(account, RevolutCSV::parse); - } - }); + menuItem.setOnAction(_ -> importHandler(account, account.importFormat.func)); importButton.getItems().add(menuItem); } } - @Override protected void onTabDeactivated() { details.activePayment.unbind();