Skip to content

Commit

Permalink
Add initial implementation to import Cetesdirecto PDF
Browse files Browse the repository at this point in the history
Issue: #4511
Signed-off-by: Jonathan <[email protected]>
[squashed commits; use latest matcher for test; removed global variable
CurrencyUnit.MXN; rebased to master]
Signed-off-by: Andreas Buchen <[email protected]>
  • Loading branch information
jonathlan authored and buchen committed Feb 23, 2025
1 parent 6b30007 commit 6ab3212
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package name.abuchen.portfolio.datatransfer.pdf.cetesdirecto;

import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasAmount;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasCurrencyCode;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasDate;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFees;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasGrossValue;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasIsin;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasName;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasNote;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasShares;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasSource;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasTaxes;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasTicker;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasWkn;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.purchase;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.sale;
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.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsEmptyCollection.empty;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import name.abuchen.portfolio.datatransfer.Extractor.Item;
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.Client;

@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(), "Purchase01.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, "MXN");

// check security
assertThat(results, hasItem(security( //
hasIsin(null), hasWkn(null), hasTicker(null), //
hasName("CETES"), //
hasCurrencyCode("MXN"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2022-01-06"), hasShares(6080), //
hasSource("Purchase01.txt"), //
hasNote("ID:SVD147529623 Series:220203 Term:2 Rate:5.51"), //
hasAmount("MXN", 60540.38), //
hasGrossValue("MXN", 60540.38), //
hasTaxes("MXN", 0.00), hasFees("MXN", 0.00))));

assertThat(results, hasItem(sale( //
hasDate("2022-01-06"), hasShares(6055), //
hasSource("Purchase01.txt"), //
hasNote("ID:SVD147779466 Series:220106 Term:0"), //
hasAmount("MXN", 60550.00 - 3.7), //
hasGrossValue("MXN", 60550.00), //
hasTaxes("MXN", 3.70), hasFees("MXN", 0.00))));
}

}
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,129 @@
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.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", "MXN");
t.setSecurity(getOrCreateSecurity(v));
t.setDate(asDate(v.get("date")));
t.setShares(asShares(v.get("shares")));
t.setCurrencyCode("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", "MXN");
t.setSecurity(getOrCreateSecurity(v));
t.setDate(asDate(v.get("date")));
t.setShares(asShares(v.get("shares")));
t.setCurrencyCode("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("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);
}

@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

0 comments on commit 6ab3212

Please sign in to comment.