Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CetesDirecto PDF Extractor #4511

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package name.abuchen.portfolio.datatransfer.pdf.cetesdirecto;

import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countAccountTransactions;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countBuySell;
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countSecurities;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.junit.Assert.assertNull;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import name.abuchen.portfolio.datatransfer.Extractor.BuySellEntryItem;
import name.abuchen.portfolio.datatransfer.Extractor.Item;
import name.abuchen.portfolio.datatransfer.Extractor.SecurityItem;
import name.abuchen.portfolio.datatransfer.actions.AssertImportActions;
import name.abuchen.portfolio.datatransfer.pdf.CetesDirectoPDFExtractor;
import name.abuchen.portfolio.datatransfer.pdf.PDFInputFile;
import name.abuchen.portfolio.model.AccountTransaction;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;

@SuppressWarnings("nls")
public class CetesDirectoPDFExtractorTest
{

@Test
public void testEdoCta01()
{
CetesDirectoPDFExtractor extractor = new CetesDirectoPDFExtractor(new Client());

List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "EdoCta01.txt"), errors);

assertThat(errors, empty());
assertThat(countSecurities(results), is(2L));
assertThat(countBuySell(results), is(12L));
assertThat(countAccountTransactions(results), is(0L));
assertThat(results.size(), is(14));
new AssertImportActions().check(results, CurrencyUnit.MXN);

// check security
Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst()
.orElseThrow(IllegalArgumentException::new).getSecurity();
assertNull(security.getIsin());
assertNull(security.getWkn());
assertNull(security.getTickerSymbol());
assertThat(security.getName(), is("CETES"));
assertThat(security.getCurrencyCode(), is(CurrencyUnit.MXN));

// check buy sell transaction
BuySellEntry entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).findFirst()
.orElseThrow(IllegalArgumentException::new).getSubject();

assertThat(entry.getPortfolioTransaction().getType(), is(PortfolioTransaction.Type.BUY));
assertThat(entry.getAccountTransaction().getType(), is(AccountTransaction.Type.BUY));

assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2022-01-06T00:00:00")));
assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(6080)));
assertThat(entry.getSource(), is("EdoCta01.txt"));
assertThat(entry.getNote(), is("ID:SVD147529623 Series:220203 Term:2 Rate:5.51"));

assertThat(entry.getPortfolioTransaction().getMonetaryAmount(),
is(Money.of(CurrencyUnit.MXN, Values.Amount.factorize(60540.38))));
assertThat(entry.getPortfolioTransaction().getGrossValue(),
is(Money.of(CurrencyUnit.MXN, Values.Amount.factorize(60540.38))));
assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX),
is(Money.of(CurrencyUnit.MXN, Values.Amount.factorize(0.00))));
assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE),
is(Money.of(CurrencyUnit.MXN, Values.Amount.factorize(0.00))));
Comment on lines +63 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We created custom matcher that make these kinds of tests easier to write and easier to read. I have refactored the code accordingly.

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.73.0
System: win32 | x86_64 | 21.0.5+11-LTS | Azul Systems, Inc.
-----------------------------------------
Nombre: IPzyS JkuKzI CNnOg DMcBtsNo
Domicilio:
RFC: XGHT553425OM9
Contrato/Cuenta CLABE: 529556644906340154
Período del: 01/01/2022 al 31/01/2022
Numero de dias del período: 31
Hoja: 1/3
Resumen del portafolio
Composición del portafolio Moneda: PESOS Total de cartera: $ 138,886.81
ISR del período: $ 7.59 Ingresos del período: $ 0.00
ISR acumulado del ejercicio fiscal: $ 7.59 Egresos del período: $ 0.00
Intereses del período: $ 514.11 Total de efectivo: $ 0.80
Intereses acumulados del ejercicio fiscal: $ 514.11 Total final: $ 138,887.61
CETES 90% Resultado por Venta Anticipada GUBER: $ 0.00
EFECTIVO 0% Servicios derivados del Depósito Bancario: $ 0.00
UDIBONO 9% Servicios de Comisión Mercantil: $ 0.00 Ganancia de capital (BONDDIA): $ 0.00
BONDDIA 2% Avisos
Posición final del período
Emisora Serie Títulos Precio de mercado Valuación % de participación Precio ponderado Plazo Tasa ponderada
BONDDIA PF2 1,332 1.57958700 2,104.01 1.51 1.54443151 2.28
CETES 220203 6,080 9.99540200 60,772.04 43.76 9.95730000 3 5.51
CETES 220210 21 9.98474600 209.68 0.15 9.95722220 10 5.52
CETES 220217 2,872 9.97437700 28,646.41 20.63 9.95683330 17 5.57
CETES 220224 3,505 9.96333500 34,921.49 25.14 9.95737780 24 5.50
UDIBONO 231116 17 719.59861600 12,233.18 8.81 786.14790884 654 2.29
Posición Total 138,886.81
* Plusvalía/Minusvalía: Utilidad o Pérdida que resulta del efecto de valuación a mercado, de un activo o instrumento de inversión, con respecto a su precio de adquisición a una fecha determinada de corte.
Nombre: kFhQo kaBpiP sswZe nfufgUea
Domicilio:
RFC: XGHT553425OM9
Contrato/Cuenta CLABE: 150923731997561671
Período del: 01/01/2022 al 31/01/2022
Numero de dias del período: 31
Hoja: 2/3
Posición inicial del período
Total de cartera: $ 138,436.95
Total de efectivo: $ 0.28
Total inicial: $ 138,437.23
Emisora Serie Títulos Precio de mercado Valuación % de participación
BONDDIA PF2 1,322 1.57245300 2,078.78 1.50
CETES 220106 6,055 9.99090800 60,494.95 43.70
CETES 220113 21 9.98025000 209.59 0.15
CETES 220120 2,860 9.96975800 28,513.51 20.60
CETES 220127 3,491 9.95899400 34,766.85 25.11
UDIBONO 231116 17 727.83957800 12,373.27 8.94
Posición Total 138,436.95
Movimientos del período
Fecha de Fecha de Folio Descripción Emisora Serie Títulos Precio Plazo Tasa Cargo Abono Saldo efectivo
registro liquidación
Saldo inicial 0.28
04/01/22 06/01/22 SVD147529623COMPRA CETES 220203 6,080 9.95730000 2 5.51 60,540.38 0.00 -60,540.10
06/01/22 06/01/22 SVD147779466AMORTIZACION CETES 220106 6,055 0 0.00 60,550.00 9.90
06/01/22 06/01/22 SVD147779466ISR CETES 220106 0 3.70 0.00 6.20
06/01/22 06/01/22 SVD148097667COMPSI BONDDIA PF2 3 1.57377100 0 0.00 4.72 0.00 1.48
11/01/22 13/01/22 SVD148801631COMPRA CETES 220210 21 9.95722220 2 5.52 209.10 0.00 -207.62
13/01/22 13/01/22 SVD148972304AMORTIZACION CETES 220113 21 0 0.00 210.00 2.38
13/01/22 13/01/22 SVD148972304ISR CETES 220113 0 0.01 0.00 2.37
13/01/22 13/01/22 SVD149165188COMPSI BONDDIA PF2 1 1.57540700 0 0.00 1.58 0.00 0.79
18/01/22 20/01/22 SVD149724600COMPRA CETES 220217 2,872 9.95683330 2 5.57 28,596.03 0.00 -28,595.24
20/01/22 20/01/22 SVD149962624AMORTIZACION CETES 220120 2,860 0 0.00 28,600.00 4.76
20/01/22 20/01/22 SVD149962624ISR CETES 220120 0 1.75 0.00 3.01
20/01/22 20/01/22 SVD150259785COMPSI BONDDIA PF2 1 1.57703800 0 0.00 1.58 0.00 1.43
25/01/22 27/01/22 SVD150921807COMPRA CETES 220224 3,505 9.95737780 2 5.50 34,900.61 0.00 -34,899.18
27/01/22 27/01/22 SVD151085643AMORTIZACION CETES 220127 3,491 0 0.00 34,910.00 10.82
27/01/22 27/01/22 SVD151085643ISR CETES 220127 0 2.13 0.00 8.69
27/01/22 27/01/22 SVD151273788COMPSI BONDDIA PF2 5 1.57866800 0 0.00 7.89 0.00 0.80
Saldo final 0.80
Nombre: SKxSW EpoUKI EZrCQ tkWmdtff
Domicilio:
RFC: XGHT553425OM9
Contrato/Cuenta CLABE: 937485968529274455
Período del: 01/01/2022 al 31/01/2022
Numero de dias del período: 31
Hoja: 3/3

Centro de Atención Telefónica (CAT) ABREVIATURAS/GLOSARIO
Cualquier consulta, aclaración o reclamación será atendida comunicándose al teléfono 55 5000 7999, AMORTIZACION AMORTIZACION DE EMISORA
de lunes a viernes de 9:00 a 18:00 horas, o si lo prefiere, puede enviar un correo electrónico a
[email protected]. Si no está de acuerdo con alguno de los movimientos que aparecen en BONDES Bonos de Desarrollo del Gobierno Federal
este estado de cuenta tiene un plazo de noventa días naturales para presentar una solicitud de BONOS Bonos de Desarrollo del Gobierno Federal con tasa de Interés Fija
aclaración. CETES Certificados de la Tesorería de la Federación
Unidad Especializada de Atención a Usuarios de NAFIN (UNE) COMPRA COMPRA DE TITULOS
En caso de no estar de acuerdo con la respuesta del CAT, puede acudir ante la UNE, en un horario COMPSI COMPRA ACCIONES DE SOCIEDAD DE INV. LIQ
de lunes a viernes de 9:00 a 18:00 horas, con domicilio en Avenida Insurgentes Sur 1971, Colonia
Guadalupe Inn, Álvaro Obregón, Código Postal 01020, en la Ciudad de México, cuya Titular es la Lic. CONDUSEF Comisión Nacional para la Protección y Defensa de los Usuarios de ServiciosFinancieros
Tania Elizabeth Vázquez Moreno con correo electrónico [email protected] y teléfono 55 5325
6112. EGREFVO EGRESO DE EFECTIVO
INGEFVO INGRESO DE EFECTIVO
Comisión Nacional para la Protección y Defensa de los Usuarios de
Servicios Financieros (CONDUSEF) ISR RETENCION DE ISR
PAGINTCU PAGO INTERESES CUPON
De no obtener una respuesta de la UNE satisfactoria a sus intereses, puede acudir a la CONDUSEF
con oficinas en Avenida Insurgentes Sur 762, Colonia del Valle, Benito Juárez, Código Postal 03100, UDIBONOS Bonos de Desarrollo del Gobierno Federal denominados en unidades de
en la Ciudad de México, para lo cual podrá consultar la página electrónica www.condusef.gob.mx o inversión
llamar al número telefónico 55 5340 0999 del Centro de Atención Telefónica de la CONDUSEF. VTASI VENTA DE ACCIONES DE SOCIEDAD DE INV LIQ
Domicilio: Av. Insurgentes Sur 1971, Nivel Jardín Anexo Piso Financiero, Demarcación Territorial Álvaro Obregón C.P. 01020, Ciudad de México.

Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ private static DateTimeFormatter createFormatter(String pattern, Locale locale)
createFormatter("d MMM yyyy", AdditionalLocales.MEXICO),
createFormatter("dd MMM yyyy", AdditionalLocales.MEXICO),
createFormatter("d MMMM yyyy", AdditionalLocales.MEXICO),
createFormatter("dd MMMM yyyy", AdditionalLocales.MEXICO) };
createFormatter("dd MMMM yyyy", AdditionalLocales.MEXICO),
createFormatter("dd/MM/yy", AdditionalLocales.MEXICO) };

// Date formatters with case-insensitive support for the United Kingdom
private static final DateTimeFormatter[] DATE_FORMATTER_UK = { //
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package name.abuchen.portfolio.datatransfer.pdf;

import java.time.LocalDateTime;

import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Block;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.DocumentType;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Transaction;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.util.AdditionalLocales;

@SuppressWarnings("nls")
public class CetesDirectoPDFExtractor extends AbstractPDFExtractor
{

public CetesDirectoPDFExtractor(Client client)
{
super(client);

addBankIdentifier("CETES");

addBuySellTransaction();
}

@Override
public String getLabel()
{
return "CETES";
}

private void addBuySellTransaction()
{
DocumentType type = new DocumentType("Movimientos del per");
this.addDocumentTyp(type);

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

Block firstRelevantLine = new Block(
"^[\\d]{2}\\/[\\d]{2}\\/[\\d]{2} [\\d]{2}\\/[\\d]{2}\\/[\\d]{2} [A-Z0-9]+(COMPRA|COMPSI|AMORTIZACION).*$");
//Block firstRelevantLine = new Block("^Saldo inicial+.*$", "^Saldo final+.*$"); // This does not work
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

pdfTransaction //
.subject(() -> {
BuySellEntry portfolioTransaction = new BuySellEntry();
portfolioTransaction.setType(PortfolioTransaction.Type.BUY);
return portfolioTransaction;
})

.oneOf(
// @formatter:off
// Registration | Settlement | ID+Type | Product | Series | Shares | Price | Term | Rate | Charge | Credit | Balance
// 04/01/22 06/01/22 SVD147529623COMPRA CETES 220203 6,080 9.95730000 2 5.51 60,540.38 0.00 -60,540.10
// 06/01/22 06/01/22 SVD148097667COMPSI BONDDIA PF2 3 1.57377100 0 0.00 4.72 0.00 1.48
// @formatter:on
section -> section
.attributes("date", "id", "name", "series", "shares", "term", "rate", "amount") //
.match("^[\\d]{2}\\/[\\d]{2}\\/[\\d]{2} (?<date>[\\d]{2}\\/[\\d]{2}\\/[\\d]{2}) (?<id>[A-Z0-9]+)(?<type>COMPRA|COMPSI) (?<name>[A-Z]+) (?<series>[\\dA-Z]+) (?<shares>[\\d,\\.]+) (?<price>[\\d,\\.]+) (?<term>[\\d]+) (?<rate>[\\d\\.]+) (?<amount>[\\d\\,\\.]+) .*$") //
.assign((t, v) -> {
v.put("currency", CurrencyUnit.MXN);
t.setSecurity(getOrCreateSecurity(v));
t.setDate(asDate(v.get("date")));
t.setShares(asShares(v.get("shares")));
t.setCurrencyCode(CurrencyUnit.MXN);
t.setAmount(asAmount(v.get("amount")));
t.setNote("ID:" + v.get("id") + " Series:" + v.get("series") + " Term:" + v.get("term") + " Rate:" + v.get("rate"));
}),

// @formatter:off
// Registration | Settlement | ID+Type | Product | Series | Shares | Term | Charge | Credit | Balance
// 06/01/22 06/01/22 SVD147779466AMORTIZACION CETES 220106 6,055 0 0.00 60,550.00 9.90
// Registration | Settlement | ID+Type | Product | Series | Term | Charge | Credit | Balance
// 06/01/22 06/01/22 SVD147779466ISR CETES 220106 0 3.70 0.00 6.20
// @formatter:on
section -> section.attributes("date", "id", "type", "name", "series", "shares", "term", "amount", "tax") //
.match("^[\\d]{2}\\/[\\d]{2}\\/[\\d]{2} (?<date>[\\d]{2}\\/[\\d]{2}\\/[\\d]{2}) (?<id>[A-Z0-9]+)(?<type>AMORTIZACION) (?<name>[A-Z]+) (?<series>[\\dA-Z]+) (?<shares>[\\d,\\.]+) (?<term>[\\d,\\.]+) (?<charge>[\\d,\\.]+) (?<amount>[\\d\\,\\.]+).*$") //
.match("^[\\d]{2}\\/[\\d]{2}\\/[\\d]{2} [\\d]{2}\\/[\\d]{2}\\/[\\d]{2} [A-Z0-9]+ISR [A-Z]+ [\\dA-Z]+ [\\d]+ (?<tax>[\\d\\,\\.]+).*$") //
.assign((t, v) -> {
v.put("currency", CurrencyUnit.MXN);
t.setSecurity(getOrCreateSecurity(v));
t.setDate(asDate(v.get("date")));
t.setShares(asShares(v.get("shares")));
t.setCurrencyCode(CurrencyUnit.MXN);
long amount = asAmount(v.get("amount"));
long tax = asAmount(v.get("tax"));
// Tax discounted after the amount.
t.setAmount(amount-tax);
Money moneyTax = Money.of(CurrencyUnit.MXN, tax);
t.getPortfolioTransaction().addUnit(new Unit(Unit.Type.TAX, moneyTax));
if ("AMORTIZACION".equals(v.get("type")))
t.setType(PortfolioTransaction.Type.SELL);
t.setNote("ID:" + v.get("id") + " Series:" + v.get("series") + " Term:" + v.get("term"));
})

.wrap(BuySellEntryItem::new));

//addTaxesSectionsTransaction(pdfTransaction, type); // Not needed apparently, after adding the TAX Unit.
// addFeesSectionsTransaction(pdfTransaction, type);
}

private <T extends Transaction<?>> void addTaxesSectionsTransaction(T transaction, DocumentType type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is never used locally.

{
transaction //

// @formatter:off
// 06/01/22 06/01/22 SVD147779466ISR CETES 220106 0 3.70 0.00 6.20
// @formatter:on
.section("tax").optional() //
.match("^[\\d]{2}\\/[\\d]{2}\\/[\\d]{2} (?<date>[\\d]{2}\\/[\\d]{2}\\/[\\d]{2}) (?<id>[A-Z0-9]+)(?<type>ISR) (?<name>[A-Z]+) (?<series>[\\dA-Z]+) (?<term>[\\d]+) (?<tax>[\\d\\,\\.]+)") //
.assign((t, v) -> processTaxEntries(t, v, type));

}

@Override
protected long asAmount(String value)
{
return asAmount(value, "es", "MX");
}

private LocalDateTime asDate(String date)
{
return asDate(date, AdditionalLocales.MEXICO);
}

@Override
protected long asShares(String value)
{
return asShares(value, "es", "MX");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public PDFImportAssistant(Client client, List<File> files)
extractors.add(new BondoraCapitalPDFExtractor(client));
extractors.add(new BoursoBankPDFExtractor(client));
extractors.add(new C24BankGmbHPDFExtractor(client));
extractors.add(new CetesDirectoPDFExtractor(client));
extractors.add(new ComdirectPDFExtractor(client));
extractors.add(new CommerzbankPDFExtractor(client));
extractors.add(new CommSecPDFExtractor(client));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public final class CurrencyUnit implements Comparable<CurrencyUnit>
Messages.LabelNoCurrencyDescription, null);
public static final String EUR = "EUR"; //$NON-NLS-1$
public static final String USD = "USD"; //$NON-NLS-1$
public static final String MXN = "MXN"; //$NON-NLS-1$

private static final String BUNDLE_NAME = "name.abuchen.portfolio.money.currencies"; //$NON-NLS-1$
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
Expand Down