From a81a54b78227291f263697ac1b1000ee3cac317a Mon Sep 17 00:00:00 2001 From: natepage Date: Mon, 26 Jul 2021 14:30:21 +1000 Subject: [PATCH] [EasyBankFiles] Create DE Return parser --- .../src/Parsers/DirectEntryReturn/Parser.php | 232 ++++++++++++++++++ .../DirectEntryReturn/Results/Header.php | 67 +++++ .../DirectEntryReturn/Results/Trailer.php | 27 ++ .../DirectEntryReturn/Results/Transaction.php | 49 ++++ .../Parsers/DirectEntryReturn/ParserTest.php | 102 ++++++++ .../DirectEntryReturn/Results/HeaderTest.php | 62 +++++ .../DirectEntryReturn/data/DE_return.txt | 12 + 7 files changed, 551 insertions(+) create mode 100644 packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Parser.php create mode 100644 packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Header.php create mode 100644 packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Trailer.php create mode 100644 packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Transaction.php create mode 100644 packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/ParserTest.php create mode 100644 packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/Results/HeaderTest.php create mode 100644 packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/data/DE_return.txt diff --git a/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Parser.php b/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Parser.php new file mode 100644 index 0000000000..12feee390e --- /dev/null +++ b/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Parser.php @@ -0,0 +1,232 @@ +errors; + } + + /** + * Get header record. + */ + public function getHeader(): Header + { + return $this->header; + } + + /** + * Get trailer record. + */ + public function getTrailer(): Trailer + { + return $this->trailer; + } + + /** + * @return \EonX\EasyBankFiles\Parsers\DirectEntryReturn\Results\Transaction[] + */ + public function getTransactions(): array + { + return $this->transactions; + } + + /** + * {@inheritDoc} + */ + protected function processLine(int $lineNumber, string $line): void + { + // code is the first character in line + $code = $line[0] ?? self::EMPTY_LINE_CODE; + $lineLength = \strlen($line); + + if ($code === self::HEADER && $lineLength >= self::MIN_HEADER_LINE_LENGTH) { + $this->header = $this->processHeader($line); + + return; + } + + if ($code === self::TRAILER && $lineLength >= self::MIN_TRAILER_LINE_LENGTH) { + $this->trailer = $this->processTrailer($line); + + return; + } + + if ( + ($code === self::TRANSACTION_1 || $code === self::TRANSACTION_2) && + $lineLength >= self::MIN_TRANSACTION_LINE_LENGTH + ) { + $this->transactions[] = $this->processTransaction($line); + + return; + } + + $this->errors[] = new Error(\compact('line', 'lineNumber')); + } + + /** + * Process header block of line. + */ + private function processHeader(string $line): Header + { + /** @var string|false $dateProcessed */ + $dateProcessed = \substr($line, 74, 6); + /** @var string|false $description */ + $description = \substr($line, 62, 12); + /** @var string|false $userFinancialInstitution */ + $userFinancialInstitution = \substr($line, 20, 3); + /** @var string|false $userIdSupplyingFile */ + $userIdSupplyingFile = \substr($line, 56, 6); + /** @var string|false $userSupplyingFile */ + $userSupplyingFile = \substr($line, 30, 26); + /** @var string|false $reelSequenceNumber */ + $reelSequenceNumber = \substr($line, 18, 2); + + return new Header([ + 'dateProcessed' => $dateProcessed === false ? null : $dateProcessed, + 'description' => $description === false ? null : \trim($description), + 'userFinancialInstitution' => $userFinancialInstitution === false ? null : $userFinancialInstitution, + 'userIdSupplyingFile' => $userIdSupplyingFile === false ? null : $userIdSupplyingFile, + 'userSupplyingFile' => $userSupplyingFile === false ? null : \trim($userSupplyingFile), + 'reelSequenceNumber' => $reelSequenceNumber === false ? null : $reelSequenceNumber, + ]); + } + + /** + * Process trailer block of line. + */ + private function processTrailer(string $line): Trailer + { + /** @var string|false $bsb */ + $bsb = \substr($line, 1, 7); + /** @var string|false $numberPayments */ + $numberPayments = \substr($line, 74, 6); + /** @var string|false $totalNetAmount */ + $totalNetAmount = \substr($line, 20, 10); + /** @var string|false $totalCreditAmount */ + $totalCreditAmount = \substr($line, 30, 10); + /** @var string|false $totalDebitAmount */ + $totalDebitAmount = \substr($line, 40, 10); + + return new Trailer([ + 'bsb' => $bsb === false ? null : \str_replace('-', '', $bsb), + 'numberPayments' => $numberPayments === false ? null : $this->trimLeftZeros($numberPayments), + 'totalNetAmount' => $totalNetAmount === false ? null : $this->trimLeftZeros($totalNetAmount), + 'totalCreditAmount' => $totalCreditAmount === false ? null : $this->trimLeftZeros($totalCreditAmount), + 'totalDebitAmount' => $totalDebitAmount === false ? null : $this->trimLeftZeros($totalDebitAmount), + ]); + } + + /** + * Process transaction block of line. + */ + private function processTransaction(string $line): Transaction + { + /** @var string|false $accountName */ + $accountName = \substr($line, 30, 32); + /** @var string|false $accountNumber */ + $accountNumber = \substr($line, 8, 9); + /** @var string|false $amount */ + $amount = \substr($line, 20, 10); + /** @var string|false $bsb */ + $bsb = \substr($line, 1, 7); + /** @var string|false $lodgmentReference */ + $lodgmentReference = \substr($line, 62, 18); + /** @var string|false $originalDayOfProcessing */ + $originalDayOfProcessing = \substr($line, 112, 2); + /** @var string|false $originalUserIdNumber */ + $originalUserIdNumber = \substr($line, 114, 6); + /** @var string|false $remitterName */ + $remitterName = \substr($line, 96, 16); + /** @var string|false $traceAccountNumber */ + $traceAccountNumber = \substr($line, 87, 9); + /** @var string|false $traceBsb */ + $traceBsb = \substr($line, 80, 7); + /** @var string|false $txnCode */ + $txnCode = \substr($line, 18, 2); + + return new Transaction([ + 'accountName' => $accountName === false ? null : \trim($accountName), + 'accountNumber' => $accountNumber === false ? null : $accountNumber, + 'amount' => $amount === false ? null : $this->trimLeftZeros($amount), + 'bsb' => $bsb === false ? null : \str_replace('-', '', $bsb), + 'indicator' => $line[17] ?? '', + 'lodgmentReference' => $lodgmentReference === false ? null : \trim($lodgmentReference), + 'originalDayOfProcessing' => $originalDayOfProcessing === false ? null : \trim($originalDayOfProcessing), + 'originalUserIdNumber' => $originalUserIdNumber === false ? null : \trim($originalUserIdNumber), + 'recordType' => $line[0] ?? '', + 'remitterName' => $remitterName === false ? null : \trim($remitterName), + 'traceAccountNumber' => $traceAccountNumber === false ? null : $traceAccountNumber, + 'traceBsb' => $traceBsb === false ? null : \str_replace('-', '', $traceBsb), + 'txnCode' => $txnCode === false ? null : $txnCode, + ]); + } +} diff --git a/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Header.php b/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Header.php new file mode 100644 index 0000000000..3faab94366 --- /dev/null +++ b/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Header.php @@ -0,0 +1,67 @@ +data['dateProcessed']; + + if ( + \is_string($value) === true && + \strlen($value) === 6 && + \ctype_digit($value) === true + ) { + $stringDate = \sprintf( + self::DATE_STRING_PATTERN, + \substr($value, 4, 2), + \substr($value, 2, 2), + \substr($value, 0, 2) + ); + + return new DateTime($stringDate); + } + + return null; + } + + /** + * Return object attributes. + * + * @return string[] + */ + protected function initAttributes(): array + { + return [ + 'dateProcessed', + 'description', + 'userFinancialInstitution', + 'userIdSupplyingFile', + 'userSupplyingFile', + 'reelSequenceNumber', + ]; + } +} diff --git a/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Trailer.php b/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Trailer.php new file mode 100644 index 0000000000..730e9a0604 --- /dev/null +++ b/packages/EasyBankFiles/src/Parsers/DirectEntryReturn/Results/Trailer.php @@ -0,0 +1,27 @@ +getSampleFileContents('DE_return.txt')); + + $transactions = $parser->getTransactions(); + self::assertCount(10, $transactions); + $firstTransactionItem = $transactions[0]; + self::assertSame('18622', $firstTransactionItem->getAmount()); + self::assertSame('THOMPSON SARAH', $firstTransactionItem->getAccountName()); + self::assertSame('458799993', $firstTransactionItem->getAccountNumber()); + self::assertSame('082001', $firstTransactionItem->getBsb()); + self::assertSame('5', $firstTransactionItem->getIndicator()); + self::assertSame('694609', $firstTransactionItem->getLodgmentReference()); + self::assertSame('06', $firstTransactionItem->getOriginalDayOfProcessing()); + self::assertSame('337999', $firstTransactionItem->getOriginalUserIdNumber()); + self::assertSame('2', $firstTransactionItem->getRecordType()); + self::assertSame('SUNNY-PEOPLE', $firstTransactionItem->getRemitterName()); + self::assertSame('010479999', $firstTransactionItem->getTraceAccountNumber()); + self::assertSame('062184', $firstTransactionItem->getTraceBsb()); + self::assertSame('13', $firstTransactionItem->getTxnCode()); + } + + /** + * Should return error from the content. + */ + public function testProcessShouldReturnErrors(): void + { + $invalidLine = 'invalid'; + + $batchParser = new Parser($invalidLine); + + $firstError = $batchParser->getErrors()[0]; + self::assertSame(1, $firstError->getLineNumber()); + self::assertSame($invalidLine, $firstError->getLine()); + } + + /** + * Test process on parser returns header. + */ + public function testProcessShouldReturnHeader(): void + { + $expected = new Header([ + 'dateProcessed' => '070905', + 'description' => 'DE Returns', + 'userFinancialInstitution' => 'NAB', + 'userIdSupplyingFile' => '012345', + 'userSupplyingFile' => 'NAB', + 'reelSequenceNumber' => '01', + ]); + + $parser = new Parser($this->getSampleFileContents('DE_return.txt')); + + self::assertEquals($expected, $parser->getHeader()); + self::assertEquals(new DateTime('2005-09-07'), $parser->getHeader()->getDateProcessedObject()); + } + + /** + * Test if process on parser returns a trailer record. + */ + public function testProcessShouldReturnTrailer(): void + { + $expected = new Trailer([ + 'bsb' => '999999', + 'numberPayments' => '10', + 'totalNetAmount' => '296782', + 'totalCreditAmount' => '0', + 'totalDebitAmount' => '296782', + ]); + + $parser = new Parser($this->getSampleFileContents('DE_return.txt')); + + self::assertEquals($expected, $parser->getTrailer()); + } + + /** + * Get sample file contents. + */ + private function getSampleFileContents(string $file): string + { + return \file_get_contents(\realpath(__DIR__) . '/data/' . $file) ?: ''; + } +} diff --git a/packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/Results/HeaderTest.php b/packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/Results/HeaderTest.php new file mode 100644 index 0000000000..88d8d1a0cc --- /dev/null +++ b/packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/Results/HeaderTest.php @@ -0,0 +1,62 @@ + [ + 'dateProcessed' => [ + 'dateProcessed' => null, + ], + ]; + yield 'dateProcessed has non-digital symbols' => [ + 'dateProcessed' => [ + 'dateProcessed' => '201909ab', + ], + ]; + } + + /** + * Test if date conversion works as expected. + */ + public function testDateConversion(): void + { + $header = new Header([ + 'dateProcessed' => '070904', + ]); + + $expectedDateTime = new DateTime('2004-09-07'); + + self::assertEquals($expectedDateTime, $header->getDateProcessedObject()); + } + + /** + * Should return processing date as a null when date string is invalid. + * + * @param mixed[] $dateProcessed + * + * @dataProvider provideInvalidDateProcessedValues + */ + public function testGetDateProcessedShouldReturnNull(array $dateProcessed): void + { + $header = new Header($dateProcessed); + + self::assertNull($header->getDateProcessedObject()); + } +} diff --git a/packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/data/DE_return.txt b/packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/data/DE_return.txt new file mode 100644 index 0000000000..5e972a27f9 --- /dev/null +++ b/packages/EasyBankFiles/tests/Parsers/DirectEntryReturn/data/DE_return.txt @@ -0,0 +1,12 @@ +0 01NAB NAB 012345DE Returns 070905 +2082-0014587999935130000018622THOMPSON SARAH 694609 062-184010479999SUNNY-PEOPLE 06337999 +2082-0014587999936130000042350OWEN MELISSA 693549 633-000118309999SUNNY-PEOPLE 06337999 +2082-0014587999936130000002500TUCKER DANIELLE 669795 732-828000519999SUNNY-PEOPLE 06337999 +2082-0014587999936130000089937ATKINS SHARON 695276 637-000712789999SUNNY-PEOPLE 06337999 +2082-0014587999936130000019022CAPPETTA RENEE 586590 016-350512779999SUNNY-PEOPLE 06337999 +2082-0014587999936130000030995THOMPSON SARAH 707879 638-010009039999SUNNY-PEOPLE 06337999 +2082-0014587999936130000006293PEARSON MELANIE 610692 805-050061769999SUNNY-PEOPLE 06337999 +2082-0014587999936130000007008HANCOCK ALLIRA 650849 484-799046809999SUNNY-PEOPLE 06337999 +2082-0014587999936130000052436ZETTER KIM 549988 805-023023789999SUNNY-PEOPLE 06337999 +2082-0014587999936130000027619ZALEK ANN 569061 016-560538759999SUNNY-PEOPLE 06337999 +7999-999 000029678200000000000000296782 000010 \ No newline at end of file