Lines
100.00%
65 / 65
Functions and Methods
100.00%
5 / 5
Classes and Traits
100.00%
1 / 1
| Name | Lines | Functions and Methods | CRAP | Classes and Traits | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| SkCsobMailParser | 100.00% | 65 / 65 | 100.00% | 5 / 5 | 22 | 100.00% | 1 / 1 | |||
| bankDomains | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| supports | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | |||||
| parseMulti | 100.00% | 20 / 20 | 100.00% | 1 / 1 | 6 | |||||
| parseAll | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 2 | |||||
| parse | 100.00% | 37 / 37 | 100.00% | 1 / 1 | 12 | |||||
| 1 | <?php | |
| 2 | ||
| 3 | declare(strict_types=1); | |
| 4 | ||
| 5 | namespace Tomaj\BankMailsParser\Parser\Csob; | |
| 6 | ||
| 7 | use Tomaj\BankMailsParser\Exception\InvalidEmailFormatException; | |
| 8 | use Tomaj\BankMailsParser\MailContent; | |
| 9 | use Tomaj\BankMailsParser\Parser\BankDate; | |
| 10 | use Tomaj\BankMailsParser\Parser\ParserInterface; | |
| 11 | use Tomaj\BankMailsParser\Parser\ParsesBankMail; | |
| 12 | ||
| 13 | final readonly class SkCsobMailParser implements ParserInterface | |
| 14 | { | |
| 15 | use ParsesBankMail; | |
| 16 | ||
| 17 | private const string TRANSACTION = '/(?:^[ \t\x{00A0}\x{2009}\x{202F}]*|že[ \t\x{00A0}\x{2009}\x{202F}]+)dňa[ \t\x{00A0}\x{2009}\x{202F}]+(\d{1,2}\.\d{1,2}\.\d{4})[ \t\x{00A0}\x{2009}\x{202F}]+bola na účte[ \t\x{00A0}\x{2009}\x{202F}]+(SK[0-9][0-9 \x{00A0}\x{2009}\x{202F}\t]*).*?zaúčtovaná suma SEPA platobného príkazu/mu'; | |
| 18 | ||
| 19 | private const string HEADER = '/(?:^[ \t\x{00A0}\x{2009}\x{202F}]*|že[ \t\x{00A0}\x{2009}\x{202F}]+)dňa[ \t\x{00A0}\x{2009}\x{202F}]+\d{1,2}\.\d{1,2}\.\d{4}[ \t\x{00A0}\x{2009}\x{202F}]+bola na účte/mu'; | |
| 20 | ||
| 21 | private const string BALANCE = '/Zostatok na účte po zaúčtovaní sumy platobnej operácie:[ \t\x{00A0}\x{2009}\x{202F}]*([+-])[ \t\x{00A0}\x{2009}\x{202F}]*((?:\d{1,3}(?:[ \t\x{00A0}\x{2009}\x{202F}]\d{3})*|\d+),\d{2})[ \t\x{00A0}\x{2009}\x{202F}]+([A-Z]{3})/u'; | |
| 22 | ||
| 23 | /** | |
| 24 | * @return list<string> | |
| 25 | */ | |
| 26 | #[\Override] | |
| 27 | public function bankDomains(): array | |
| 28 | { | |
| 29 | return ['csob.sk']; | |
| 30 | } | |
| 31 | ||
| 32 | #[\Override] | |
| 33 | public function supports(string $content, ?string $sender = null, ?string $subject = null): bool | |
| 34 | { | |
| 35 | return $this->supportsQuietly( | |
| 36 | fn(): bool => $this->matched(self::HEADER, $this->withoutFreeText($this->normalizeHtmlLineBreaks($this->withoutByteOrderMark($content)))), | |
| 37 | ); | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * @return list<MailContent> | |
| 42 | */ | |
| 43 | public function parseMulti(string $content): array | |
| 44 | { | |
| 45 | $content = $this->normalizeHtmlLineBreaks($this->requireUniformLineBreaks($content)); | |
| 46 | $chunks = $this->splitOutsideFreeText($content, self::HEADER); | |
| 47 | $parsed = []; | |
| 48 | $balances = []; | |
| 49 | $errors = []; | |
| 50 | $first = null; | |
| 51 | $index = 0; | |
| 52 | foreach ($chunks as $chunk) { | |
| 53 | if (!$this->matched(self::TRANSACTION, $this->withoutFreeText($chunk))) { | |
| 54 | continue; | |
| 55 | } | |
| 56 | ++$index; | |
| 57 | try { | |
| 58 | $parsed[] = $this->parse($chunk); | |
| 59 | $balances[] = $this->balanceAmount($chunk, self::BALANCE); | |
| 60 | } catch (InvalidEmailFormatException $exception) { | |
| 61 | $first ??= $exception; | |
| 62 | $errors[] = sprintf('transaction %d: %s', $index, $exception->getMessage()); | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | $parsed = $this->transactionsOrFail($parsed, $errors, $first); | |
| 67 | if (count($parsed) > 1 && $this->matched(self::BALANCE, $content)) { | |
| 68 | $this->assertBalanceChain($balances, $parsed); | |
| 69 | } | |
| 70 | ||
| 71 | return $parsed; | |
| 72 | } | |
| 73 | ||
| 74 | #[\Override] | |
| 75 | public function parseAll(string $content): array | |
| 76 | { | |
| 77 | $parsed = $this->parseMulti($content); | |
| 78 | if ($parsed === []) { | |
| 79 | throw InvalidEmailFormatException::malformedContent(self::class, 'Could not find transaction pattern'); | |
| 80 | } | |
| 81 | ||
| 82 | return $parsed; | |
| 83 | } | |
| 84 | ||
| 85 | public function parse(string $content): MailContent | |
| 86 | { | |
| 87 | $original = $this->normalizeHtmlLineBreaks($this->requireUniformLineBreaks($content)); | |
| 88 | $receiverMessage = $this->lineValue($original, 'informácia pre príjemcu'); | |
| 89 | $content = $this->withoutFreeText($original); | |
| 90 | if (!$this->matched(self::TRANSACTION, $content, $result)) { | |
| 91 | throw InvalidEmailFormatException::malformedContent(self::class, 'Could not find transaction pattern'); | |
| 92 | } | |
| 93 | ||
| 94 | $transactionDate = BankDate::parse($this->group($result, 1), '!j.n.Y', BankDate::bratislava()); | |
| 95 | $ownAccountNumber = $this->normalizeAccount($this->group($result, 2)); | |
| 96 | ||
| 97 | $money = null; | |
| 98 | if ($this->matched($this->field('suma:', '([+-])[ \t\x{00A0}\x{2009}\x{202F}]*(.*?)[ \t\x{00A0}\x{2009}\x{202F}]+([A-Z]{3})\b'), $content, $amount)) { | |
| 99 | $signed = ($this->group($amount, 1) === '-' ? '-' : '') . $this->group($amount, 2); | |
| 100 | $money = $this->moneyOrNull($signed, $this->group($amount, 3), ','); | |
| 101 | } | |
| 102 | ||
| 103 | $vs = null; | |
| 104 | $ss = null; | |
| 105 | $ks = null; | |
| 106 | if ($this->matched($this->field('referencia platiteľa:', '\/?VS(\d*)(?:\/SS(\d*))?(?:\/KS(\d*))?'), $original, $symbol)) { | |
| 107 | $vs = $this->paymentSymbol($this->group($symbol, 1), null); | |
| 108 | $ssRaw = $this->optionalGroup($symbol, 2); | |
| 109 | $ksRaw = $this->optionalGroup($symbol, 3); | |
| 110 | $ss = $ssRaw === null ? null : $this->paymentSymbol($ssRaw, null); | |
| 111 | $ks = $ksRaw === null ? null : $this->paymentSymbol($ksRaw, null); | |
| 112 | } | |
| 113 | // 4.1.0 reads the first VS token in the mail. That also matched the payer name (ADVS2). | |
| 114 | // The owner kept the 4.x value when it comes from this line only. Length is not capped. | |
| 115 | if ($vs === null && $this->matched($this->field('informácia pre príjemcu:', '.*?(?<![A-Za-z])VS([0-9]+)(?!\d)'), $original, $fromMessage)) { | |
| 116 | $vs = $this->group($fromMessage, 1); | |
| 117 | } | |
| 118 | if ($ks === null && $this->matched($this->field('informácia pre príjemcu:', '.*?(?<![A-Za-z])KS([0-9]+)(?!\d)'), $original, $fromMessage)) { | |
| 119 | $ks = $this->group($fromMessage, 1); | |
| 120 | } | |
| 121 | ||
| 122 | $accountNumber = null; | |
| 123 | if ($this->matched($this->field('z účtu:', '([A-Z0-9 \x{00A0}\x{2009}\x{202F}\t]+)'), $content, $source)) { | |
| 124 | $accountNumber = $this->normalizeAccount($this->group($source, 1)); | |
| 125 | } | |
| 126 | ||
| 127 | return new MailContent( | |
| 128 | money: $money, | |
| 129 | transactionDate: $transactionDate, | |
| 130 | accountNumber: $accountNumber, | |
| 131 | ownAccountNumber: $ownAccountNumber, | |
| 132 | vs: $vs, | |
| 133 | ss: $ss, | |
| 134 | ks: $ks, | |
| 135 | receiverMessage: $receiverMessage, | |
| 136 | ); | |
| 137 | } | |
| 138 | } |