Skip to content

Commit

Permalink
Improve some PDF-Importer to standardized tax lost adjustment (#4366)
Browse files Browse the repository at this point in the history
The tax lost adjustment has been standardized for various PDF importers.
  • Loading branch information
Nirus2000 authored Nov 29, 2024
1 parent 9069fe2 commit 07f16c5
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 306 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public void testWertpapierKauf04()

// check taxes transaction
assertThat(results, hasItem(taxes( //
hasDate("2014-03-10T00:00"), hasShares(0.1320), //
hasDate("2014-03-10T00:00"), hasShares(0.1318), //
hasSource("Kauf04.txt"), //
hasNote("Zwischengewinn 0,17 EUR | Ref.-Nr.: O:002831939:1"), //
hasAmount("EUR", 0.05), hasGrossValue("EUR", 0.05), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ private void addBuySellTransaction()
final DocumentType type = new DocumentType("Wertpapier Abrechnung (Kauf|Verkauf)", jointAccount);
this.addDocumentTyp(type);

// Handshake for tax refund transaction
Map<String, String> context = type.getCurrentContext();

Transaction<BuySellEntry> pdfTransaction = new Transaction<>();

Block firstRelevantLine = new Block("^Wertpapier Abrechnung (Kauf|Verkauf).*$");
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

// Map for tax lost adjustment transaction
Map<String, String> context = type.getCurrentContext();

pdfTransaction //

.subject(() -> {
Expand Down Expand Up @@ -112,24 +112,14 @@ private void addBuySellTransaction()
v.put("name", trim(v.get("name")) + " " + trim(v.get("name1")));

t.setSecurity(getOrCreateSecurity(v));

// Handshake, if there is a tax refund
context.put("name", v.get("name"));
context.put("isin", v.get("isin"));
context.put("wkn", v.get("wkn"));
})

// @formatter:off
// Stück 2.700 INTERNAT. CONS. AIRL. GROUP SA ES0177542018 (A1H6AJ)
// @formatter:on
.section("shares") //
.match("^St.ck (?<shares>[\\.,\\d]+) .*$") //
.assign((t, v) -> {
t.setShares(asShares(v.get("shares")));

// Handshake, if there is a tax refund
context.put("shares", v.get("shares"));
})
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))

// @formatter:off
// Schlusstag/-Zeit 17.02.2021 09:04:10 Auftraggeber Max Mustermann
Expand Down Expand Up @@ -158,11 +148,24 @@ private void addBuySellTransaction()
.match("^(?<note>(Limit|Stoplimit) .*)$") //
.assign((t, v) -> t.setNote(trim(v.get("note"))))

.wrap(BuySellEntryItem::new);
.wrap(t -> {
BuySellEntryItem item = new BuySellEntryItem(t);

// @formatter:off
// Handshake for tax lost adjustment transaction
// Also use number for that is also used to (later) convert it back to a number
// @formatter:on
context.put("name", item.getSecurity().getName());
context.put("isin", item.getSecurity().getIsin());
context.put("wkn", item.getSecurity().getWkn());
context.put("shares", Long.toString(item.getShares()));

return item;
});

addTaxesSectionsTransaction(pdfTransaction, type);
addFeesSectionsTransaction(pdfTransaction, type);
addTaxReturnBlock(context, type);
addTaxLostAdjustmentTransaction(context, type);
}

private void addDividendeTransaction()
Expand Down Expand Up @@ -954,7 +957,7 @@ public void addDepotStatementTransaction()
});
}

private void addTaxReturnBlock(Map<String, String> context, DocumentType type)
private void addTaxLostAdjustmentTransaction(Map<String, String> context, DocumentType type)
{
Transaction<AccountTransaction> pdfTransaction = new Transaction<>();

Expand All @@ -979,11 +982,11 @@ private void addTaxReturnBlock(Map<String, String> context, DocumentType type)
.match("^Den Gegenwert buchen wir mit Valuta (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}) .*$") //
.assign((t, v) -> {
t.setDateTime(asDate(v.get("date")));
t.setCurrencyCode(asCurrencyCode(v.get("currency")));
t.setAmount(asAmount(v.get("amount")));
t.setShares(asShares(context.get("shares")));

t.setShares(Long.parseLong(context.get("shares")));
t.setSecurity(getOrCreateSecurity(context));

t.setCurrencyCode(asCurrencyCode(v.get("currency")));
t.setAmount(asAmount(v.get("amount")));
})

// @formatter:off
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static name.abuchen.portfolio.util.TextUtil.trim;

import java.math.BigDecimal;
import java.util.Map;

import name.abuchen.portfolio.Messages;
import name.abuchen.portfolio.datatransfer.ExtrExchangeRate;
Expand Down Expand Up @@ -40,7 +41,6 @@ public Direkt1822BankPDFExtractor(Client client)
addBuySellTransaction();
addDividendeTransaction();
addAdvanceTaxTransaction();
addTaxesLostAdjustmentTransaction();
addAccountStatementTransaction();
addNonImportableTransaction();
}
Expand All @@ -62,6 +62,9 @@ private void addBuySellTransaction()
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

// Map for tax lost adjustment transaction
Map<String, String> context = type.getCurrentContext();

pdfTransaction //

.subject(() -> {
Expand Down Expand Up @@ -161,10 +164,24 @@ private void addBuySellTransaction()

.conclude(ExtractorUtils.fixGrossValueBuySell())

.wrap(BuySellEntryItem::new);
.wrap(t -> {
BuySellEntryItem item = new BuySellEntryItem(t);

// @formatter:off
// Handshake for tax lost adjustment transaction
// Also use number for that is also used to (later) convert it back to a number
// @formatter:on
context.put("name", item.getSecurity().getName());
context.put("isin", item.getSecurity().getIsin());
context.put("wkn", item.getSecurity().getWkn());
context.put("shares", Long.toString(item.getShares()));

return item;
});

addTaxesSectionsTransaction(pdfTransaction, type);
addFeesSectionsTransaction(pdfTransaction, type);
addTaxLostAdjustmentTransaction(context, type);
}

private void addDividendeTransaction()
Expand Down Expand Up @@ -319,15 +336,8 @@ private void addAdvanceTaxTransaction()
.wrap(TransactionItem::new);
}

private void addTaxesLostAdjustmentTransaction()
private void addTaxLostAdjustmentTransaction(Map<String, String> context, DocumentType type)
{
DocumentType type = new DocumentType("(Wertpapier Abrechnung (Verkauf|(Ausgabe|R.cknahme) Investmentfonds)" //
+ "|Gutschrift von .*" //
+ "|Aussch.ttung Investmentfonds" //
+ "|Aussch.ttung aus Genussschein" //
+ "|Dividendengutschrift)");
this.addDocumentTyp(type);

Transaction<AccountTransaction> pdfTransaction = new Transaction<>();

Block firstRelevantLine = new Block("^Postfach.*$");
Expand All @@ -342,99 +352,24 @@ private void addTaxesLostAdjustmentTransaction()
return accountTransaction;
})

.oneOf( //
// @formatter:off
// Stück 13 COMSTA.-MSCI EM.MKTS.TRN U.ETF LU0635178014 (ETF127)
// INHABER-ANTEILE I O.N.
// Börse Außerbörslich (gemäß Weisung)
// Ausführungskurs 40,968 EUR Auftragserteilung Online-Banking
// @formatter:on
section -> section //
.attributes("name", "isin", "wkn", "name1", "currency") //
.match("^St.ck [\\.,\\d]+ (?<name>.*) (?<isin>[A-Z]{2}[A-Z0-9]{9}[0-9]) \\((?<wkn>[A-Z0-9]{6})\\)$") //
.match("(?<name1>.*)$") //
.match("^Ausf.hrungskurs [\\.,\\d]+ (?<currency>[\\w]{3}) .*$") //
.assign((t, v) -> {
if (!v.get("name1").startsWith("Börse"))
v.put("name", trim(v.get("name")) + " " + trim(v.get("name1")));

t.setSecurity(getOrCreateSecurity(v));
}),
// @formatter:off
// Stück 920 ISHSIV-FA.AN.HI.YI.CO.BD U.ETF IE00BYM31M36 (A2AFCX)
// REGISTERED SHARES USD O.N.
// Zahlbarkeitstag 29.12.2017 Ertrag pro St. 0,123000000 USD
// @formatter:on
section -> section //
.attributes("name", "isin", "wkn", "nameContinued", "currency") //
.match("^St.ck [\\.,\\d]+ (?<name>.*) (?<isin>[A-Z]{2}[A-Z0-9]{9}[0-9]) \\((?<wkn>[A-Z0-9]{6})\\)$") //
.match("(?<nameContinued>.*)") //
.match("^Zahlbarkeitstag [\\d]{2}\\.[\\d]{2}\\.[\\d]{4} (Aussch.ttung|Dividende|Ertrag) pro (St\\.|St.ck) [\\.,\\d]+ (?<currency>[\\w]{3})$") //
.assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))))

// @formatter:off
// Stück 13 COMSTA.-MSCI EM.MKTS.TRN U.ETF LU0635178014 (ETF127)
// @formatter:on
.section("shares") //
.match("^St.ck (?<shares>[\\.,\\d]+) .* [A-Z]{2}[A-Z0-9]{9}[0-9] \\([A-Z0-9]{6}\\)$") //
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))

.oneOf( //
// @formatter:off
// Schlusstag/-Zeit 01.12.2017 10:30:52 Auftraggeber Mustermann, Max
// @formatter:on
section -> section //
.attributes("date", "time") //
.match("^Schlusstag\\/\\-Zeit (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}) (?<time>[\\d]{2}:[\\d]{2}:[\\d]{2}) .*$") //
.assign((t, v) -> t.setDateTime(asDate(v.get("date"), v.get("time")))),
// @formatter:off
// Schlusstag 09.07.2021 Auftraggeber Mustermann, Max
// @formatter:on
section -> section //
.attributes("date") //
.match("^Schlusstag (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}) .*$") //
.assign((t, v) -> t.setDateTime(asDate(v.get("date")))),
// @formatter:off
// Den Betrag buchen wir mit Wertstellung 03.01.2018 zu Gunsten des Kontos xxxxxxxxxx (IBAN DExx xxxx xxxx xxxx
// @formatter:on
section -> section //
.attributes("date") //
.match("^Den Betrag buchen wir mit Wertstellung (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}).*$") //
.assign((t, v) -> t.setDateTime(asDate(v.get("date")))))

// @formatter:off
// Steuerliche Ausgleichrechnung
// Ausmachender Betrag 3,49 EUR
// Den Gegenwert buchen wir mit Valuta 18.11.2024 zu Gunsten des Kontos 0123456789
// @formatter:on
.section("currency", "amount").optional() //
.find("Steuerliche Ausgleichrechnung")
.section("currency", "amount", "date").optional() //
.find("Steuerliche Ausgleichrechnung") //
.match("^Ausmachender Betrag (?<amount>[\\.,\\d]+)([\\-|\\+])? (?<currency>[\\w]{3})$") //
.match("^Den Gegenwert buchen wir mit Valuta (?<date>[\\d]{2}\\.[\\d]{2}\\.[\\d]{4}).*$") //
.assign((t, v) -> {
t.setAmount(asAmount(v.get("amount")));
t.setDateTime(asDate(v.get("date")));
t.setShares(Long.parseLong(context.get("shares")));
t.setSecurity(getOrCreateSecurity(context));

t.setCurrencyCode(asCurrencyCode(v.get("currency")));
t.setAmount(asAmount(v.get("amount")));
})

.optionalOneOf( //
// @formatter:off
// Devisenkurs (EUR/USD) 1,1987 vom 02.03.2021
// Steuerliche Ausgleichrechnung
// Ausmachender Betrag 3,49 EUR
// @formatter:on
section -> section //
.attributes("baseCurrency", "termCurrency", "exchangeRate", "gross") //
.match("^Devisenkurs \\((?<baseCurrency>[\\w]{3})\\/(?<termCurrency>[\\w]{3})\\) (?<exchangeRate>[\\.,\\d]+) .*$") //
.find("Steuerliche Ausgleichrechnung") //
.match("^Ausmachender Betrag (?<gross>[\\.,\\d]+)([\\-|\\+])? [\\w]{3}$") //
.assign((t, v) -> {
ExtrExchangeRate rate = asExchangeRate(v);
type.getCurrentContext().putType(rate);

Money gross = Money.of(rate.getBaseCurrency(), asAmount(v.get("gross")));
Money fxGross = rate.convert(rate.getTermCurrency(), gross);

checkAndSetGrossUnit(gross, fxGross, t, type.getCurrentContext());
}))

// @formatter:off
// Auftragsnummer 123456/10.00
// @formatter:on
Expand Down
Loading

0 comments on commit 07f16c5

Please sign in to comment.