Offtopic projekt 1. Hogyan fűzzük össze az elektronikus vonatjegyeket és számlákat?

Minden hónap elején eljön az a szomorú nap, amikor megcsörren a telefonom és beleszól egy ismerős hang: Dani, ideje lenne leadni a múlt havi jegyeket! Ekkor elkezdődik a tortúra, amely a jegyek és számlák letöltéséből, azok egyenkénti kinyomtatásából, sorba rendezéséből, majd összetűzéséből áll. Egy spontán ötlettől vezérelve azonban kipróbáltam, hogy nem lehetne-e egy kicsit automatizálni ezt a folyamatot. És igen, lehet!

Mivel elég gyakran járok vonattal a munkahelyemre, ezért az oda-vissza utakat is beleszámítva akár kéttucatnyi alkalom is összejöhet egy-egy hónapban. És ehhez ugyanennyi számla társul. A jegyeket természetesen elektronikusan veszem meg és eszem ágában sem lenne kinyomtatni azokat. Csakhogy az utazások elszámolásához ezt kérik. Nincs apelláta.

A jegyek és számlák letöltésével járó munkát sajnos nem tudjuk megspórolni, ezt manuálisan kell továbbra is elvégezni a MÁV-START internetes jegyvásárlási felületén. Ezen túlmenően viszont a célom az, hogy ne darabonként kelljen kinyomtatni minden egyes jegyet és számlát, hanem egy algoritmus lefuttatása után kerüljön az összes letöltött fájl egyetlen PDF dokumentumba. Természetesen úgy, hogy az összetartozó jegyek és számlák az egymást követő oldalakon legyenek, dátum szerint sorba rendezve.

Ezt követően már annak sincs akadálya, hogy az egymáshoz tartozó jegyeket és számlákat egyetlen oldalra nyomtassuk ki. Így ezek összetűzésével sem kell bajlódnunk, és utazásonként egy lappal kevesebben kell nyomtatni. Hát nem szuper?

A művelet elvi alapja és végrehajtása

A letöltött PDF dokumentumok elnevezése véletlenszerűnek tűnő módon legenerált betűkből és számokból áll, például: f0daebbd-7fbe-4648-b061-e1b6c5325671.pdf Ezek alapján nem lehet megállapítani azt, hogy az adott fájl milyen dátumhoz kapcsolódik. Erre más módszert kell találni.

Az időrend szerinti sorba rendezés azon alapul majd, hogy a jegyeket és számlákat csak egyenként lehet letölteni, közben pedig telik az idő. Elsőként az utoljára megvásárolt jegyet, majd az ehhez tartozó számlát töltjük le, utána az eggyel korábbiakat és így tovább. A letöltött fájlok tehát különböző időpontokban kerülnek rá a számítógépünkre. Ha az utolsó módosítás dátuma alapján sorba rendezzük őket, akkor az egyúttal megfelel a vásárlás sorrendiségének is.

A letöltés mindig a legutóbbi vásárlással kezdődik, mert a rendszer ezt hozza ki elsőként. Én viszont az adott hónap legelső vásárlását szeretném a paksaméta tetején látni a nyomtatás után. Ennek érdekében az utolsó módosítás dátuma szerinti csökkenő sorrendbe kell rendezni a fájlokat, hiszen a legrégebben megvett jegyet töltjük le utoljára.

Egyéb tudnivaló nem nagyon van az alábbi rövid algoritmussal kapcsolatban. Illetve ami van, azt beleírtam a kódblokkba. (A futtatás során egy halom figyelmeztetés fogunk kapni arról, hogy a számlák jelszóval védettek. Az általunk végrehajtani kívánt művelethez azonban nem szükséges a jelszó, ezért az algoritmus minden további nélkül végrehajtódik.)

Hozzunk tehát létre egy munkakönyvtárat a számítógépünkön, mentsük bele a jegyeket és számlákat, aztán hajrá!

Az alábbiakban R nyelven (v4.1.2) írt kódot használok a feladat végrehajtásához. A magyarázó szövegek közé ékelt fekete kódblokkok tartalmát az RStudio-ban egymás alá illesztve elvileg bárki által reprodukálható az itt bemutatott műveletsor. A kódblokkok # kezdetű sorai pusztán magyarázó funkcióval bírnak, ezekre a program futtatásakor nincs szükség.

A staplr csomag a Java telepítését vagy frissítését igényli a számítógépen, ennek hiányában nem fog futni!

# A szükséges csomag betöltése.
# A legelső használat előtt az install.packages("staplr") utasítással telepíteni kell azt.
library(staplr)

# Ide a saját munkakönyvtárunk elérését kell beírni!
setwd("C:/Munkakönyvtár")

# Információk begyűjtése a munkakönyvtárunkban lévő PDF fájlokról.
# Egy adatkeretet kapunk, amelynek minden sora egy-egy fájlnak felel meg.
vonatjegyek = file.info(list.files(pattern="pdf"))
# Az adatkeret sorainak átrendezése az utolsó módosítás dátuma szerinti csökkenő sorrendbe.
vonatjegyek <- vonatjegyek[order(vonatjegyek$mtime, decreasing = TRUE),]
# Az adatkeret sorainak neve megegyezik a PDF fájlok nevével.
# Ezeket átmásoljuk egy vektorba.
vonatjegyek <- rownames(vonatjegyek)
# Az iménti vektor, mint bemenet alapján megtörténik a fájlok összefűzése.
# Ez a munkakönyvtárunkban kerül elmentésre.
staplr::staple_pdf(input_files = vonatjegyek, output_filepath = "vonatjegyek.pdf")

Egy mindössze 6 soros kóddal meg is van oldva a probléma. És ezt is csak egyszer kellett megírni, ettől kezdve minden hónap elején használni tudom majd. Nem is értem, hogy miért nem így csináltam eddig.

Update

A blog Facebook oldalán a kollégáim részéről felmerült az az ötlet, hogy ha már ilyen szépen sikerült megoldani a jegyek összefűzését, akkor nem tudnám-e automatizálni az elszámoláshoz szükséges nyomtatvány kitöltését is? Természetesen megoldható a dolog. 😃

Mivel itt kétrétegű PDF dokumentumokkal van dolgunk, ezért azt fogom csinálni, hogy jegyekből kivonom azok szöveges rétegét, majd reguláris kifejezések segítségével kibányászom ebből az elszámoláshoz szükséges adatokat. Konkrétan: az utazás dátumát, a jegy árát, valamint az indulás helyét és a végállomás nevét. Mindezekből egy Excel táblázat készül, aminek tartalmát immáron egyszerűen be lehet másolni az elszámoló nyomtatványban lévő táblázatba.

Az adatokat a jegyekből fogjuk kibányászni. Ez technikailag azt jelenti, hogy a fentebb már időrendi sorba állított PDF dokumentumokból mindig a páros számúakat használjuk majd. Tudniillik a páratlanok a számlák. (Tehát az 1. dokumentum egy számla, a 2. jegy, a 3. egy számla, a 4. jegy…) Ehhez természetesen ügyelni kell arra, hogy először az adott utazáshoz tartozó jegyet töltsük le, és csak aztán a számlát. Ha nem így teszünk, akkor az algoritmus a számlákból próbál bányászni. Hibaüzenetet ugyan nem kapunk, de nem töltődik fel a táblázatunk.

Bár valószínűleg keveseket érint, de fontos tudnivaló továbbá, hogy az alábbi kód csak a magyar nyelven kiállított jegyekkel működik. Az angol nyelvű jegyekhez a reguláris kifejezéseken változtatni kell.

# A szükséges csomagok betöltése.
# A legelső használat előtt az install.packages("...") utasítással telepíteni
# kell ezeket. A ... helyére az adott csomag neve írandó.
library(pdftools)
library(stringr)
library(xlsx)

# Egy adatkeret létrehozása, melynek minden sora egy-egy utazásnak felel meg.
# Az első oszlopba a releváns PDF dokumentumok neve kerül, a többi egyelőre üres.
utazasok <- data.frame("dokumentum" = vonatjegyek[seq(2, length(vonatjegyek), 2)],
                       "datum" = as.character(NA),
                       "forint" = as.numeric(NA),
                       "honnan" = as.character(NA),
                       "hova" = as.character(NA))

# Egyenként végig megyünk az iménti adatkeret sorain és végrehajtjuk az alábbi utasításokat.
for (i in 1:nrow(utazasok)) {
  # A vonatjegy szövegének megszerzése a releváns PDF dokumentumból.
  vonatjegySzovege <- pdftools::pdf_text(utazasok[i,"dokumentum"])
  
  # Az utazás dátumának kibányászása és beírása a táblázat megfelelő helyére.
  datum <- stringr::str_extract(vonatjegySzovege, "Érvényes:.+") %>%
    stringr::str_extract("\\d{4}\\.\\d{2}\\.\\d{2}")
  utazasok[i,"datum"] <- paste0(datum,".")
  
  # Az utazás árának kibányászása és beírása a táblázat megfelelő helyére.
  forint <- stringr::str_extract(vonatjegySzovege, "HUF.+") %>%
    stringr::str_extract("[\\d]+")
  utazasok[i,"forint"] <- as.numeric(forint)
  
  # Az utazási viszonylat kibányászása és beírása a táblázat megfelelő helyére.
  allomasok <- stringr::str_extract(vonatjegySzovege, "HOVÁ\n.+") %>%
    stringr::str_extract_all("[:upper:][[:lower:]]+\\b") %>%
    unlist()
  utazasok[i,"honnan"] <- allomasok[1]
  utazasok[i,"hova"] <- allomasok[2]
}

# A PDF dokumentumok nevét tartalmazó oszlop törlése az adatkeretből.
utazasok$dokumentum <- NULL

# Az adatkeret tartalmának kiírása egy Excel táblázatba.
xlsx::write.xlsx2(utazasok, file = "utazasok.xlsx", row.names = F)

comments powered by Disqus