View Javadoc
1   package de.aikiit.bilanzanalyser.reader;
2   
3   import de.aikiit.bilanzanalyser.entity.BilanzRow;
4   import lombok.AccessLevel;
5   import lombok.NoArgsConstructor;
6   import lombok.extern.log4j.Log4j2;
7   import org.odftoolkit.odfdom.doc.table.OdfTableRow;
8   
9   import java.math.BigDecimal;
10  import java.text.DecimalFormat;
11  import java.text.DecimalFormatSymbols;
12  import java.time.LocalDate;
13  import java.util.Locale;
14  import java.util.Optional;
15  
16  @NoArgsConstructor(access = AccessLevel.PRIVATE)
17  @Log4j2
18  public final class BilanzRowParser {
19  
20      public static Optional<BilanzRow> fromOdfTableRow(final OdfTableRow row) {
21          try {
22              // in order to handle LibreOffice-specific currency values we need to parse properly and handle mixture of '.' and ',' in numbers.
23              DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.GERMANY);
24              DecimalFormat format = new DecimalFormat();
25              format.setDecimalFormatSymbols(symbols);
26              format.setParseBigDecimal(true);
27  
28              final String source = cleanUpAmount(row.getCellByIndex(1).getStringValue());
29              final var amount = new BigDecimal(format.parse(source).toString());
30              if (BigDecimal.ZERO.equals(amount) || source.isEmpty() || "0,00".equals(source)) {
31                  log.debug("Amount is zero - skipping entry.");
32                  return Optional.empty();
33              }
34  
35              var bilanzRow = BilanzRow.builder() //
36                      // remove trailing spaces and currency symbol
37                      .amount(amount) //
38                      .description(row.getCellByIndex(2).getStringValue()) //
39                      .shop(row.getCellByIndex(3).getStringValue()) //
40                      .payment(row.getCellByIndex(4).getStringValue()) //
41                      .category(row.getCellByIndex(5).getStringValue());
42  
43              // map special values in date column to fallback date in entity
44              var sourceDate = row.getCellByIndex(0).getStringValue().trim();
45              if (!sourceDate.isBlank() && !"?".equals(sourceDate)) {
46                  bilanzRow.date(LocalDate.parse(sourceDate));
47              }
48  
49              return Optional.of(bilanzRow.build());
50          } catch (Exception e) {
51              log.error("Skipping row '{}' due to: {}", debugRowValues(row), e.getMessage());
52              return Optional.empty();
53          }
54      }
55  
56      static String debugRowValues(final OdfTableRow row) {
57          if (row == null) {
58              return "null";
59          }
60          return "[" + row.getCellByIndex(0).getStringValue() + ", " + row.getCellByIndex(1).getStringValue() + ", " + row.getCellByIndex(2).getStringValue() + ", " + row.getCellByIndex(3).getStringValue() + ", " + row.getCellByIndex(4).getStringValue() + ", " + row.getCellByIndex(5).getStringValue() + "]";
61      }
62  
63      /**
64       * Removes currency symbol and trims the given amount value from an ODS file.
65       *
66       * @param amount given amount, e.g. 1,23 €
67       * @return trimmed amount in order to be parseable as a numeric.
68       */
69      static String cleanUpAmount(final String amount) {
70          if (amount != null && !amount.isEmpty()) {
71              return amount.replace("€", "").trim();
72          }
73          return amount;
74      }
75  }