Skip to content

Commit 34daa95

Browse files
authored
Merge pull request #225 from RobinGeuze/addMatchesSupport
Add support for matches field on transactions
2 parents e2b2e68 + 1c40a15 commit 34daa95

File tree

6 files changed

+249
-28
lines changed

6 files changed

+249
-28
lines changed

src/BaseTransactionLine.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Money\Money;
66
use PhpTwinfield\Enums\LineType;
7+
use PhpTwinfield\Transactions\MatchSet;
78
use PhpTwinfield\Transactions\TransactionLine;
89
use PhpTwinfield\Transactions\TransactionLineFields\CommentField;
910
use PhpTwinfield\Transactions\TransactionLineFields\FreeCharField;
@@ -18,7 +19,7 @@
1819
* @todo $vatRepValue Only if line type is detail. VAT amount in reporting currency.
1920
* @todo $destOffice Office code. Used for inter company transactions.
2021
* @todo $comment Comment set on the transaction line.
21-
* @todo $matches Contains matching information. Read-only attribute.
22+
* @todo $matches Implement for BankTransactionLine
2223
*
2324
* @link https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/BankTransactions
2425
*/
@@ -117,6 +118,11 @@ abstract class BaseTransactionLine implements TransactionLine
117118
*/
118119
protected $currencyDate;
119120

121+
/**
122+
* @var MatchSet[] Empty if not available, readonly.
123+
*/
124+
protected $matches = [];
125+
120126
public function getLineType(): LineType
121127
{
122128
return $this->lineType;
@@ -414,4 +420,17 @@ public function getReference(): MatchReferenceInterface
414420
$this->getId()
415421
);
416422
}
423+
424+
public function addMatch(MatchSet $match)
425+
{
426+
$this->matches[] = $match;
427+
}
428+
429+
/**
430+
* @return MatchSet[]
431+
*/
432+
public function getMatches(): array
433+
{
434+
return $this->matches;
435+
}
417436
}

src/Mappers/TransactionMapper.php

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpTwinfield\Mappers;
44

5+
use DOMElement;
56
use DOMXPath;
67
use Money\Currency;
78
use Money\Money;
@@ -18,16 +19,20 @@
1819
use PhpTwinfield\Office;
1920
use PhpTwinfield\Response\Response;
2021
use PhpTwinfield\SalesTransaction;
22+
use PhpTwinfield\Transactions\MatchLine;
23+
use PhpTwinfield\Transactions\MatchSet;
2124
use PhpTwinfield\Transactions\TransactionFields\DueDateField;
2225
use PhpTwinfield\Transactions\TransactionFields\FreeTextFields;
2326
use PhpTwinfield\Transactions\TransactionFields\InvoiceNumberField;
2427
use PhpTwinfield\Transactions\TransactionFields\PaymentReferenceField;
2528
use PhpTwinfield\Transactions\TransactionFields\StatementNumberField;
29+
use PhpTwinfield\Transactions\TransactionLine;
2630
use PhpTwinfield\Transactions\TransactionLineFields\MatchDateField;
2731
use PhpTwinfield\Transactions\TransactionLineFields\PerformanceFields;
2832
use PhpTwinfield\Transactions\TransactionLineFields\ValueOpenField;
2933
use PhpTwinfield\Transactions\TransactionLineFields\VatTotalFields;
3034
use PhpTwinfield\Util;
35+
use UnexpectedValueException;
3136

3237
class TransactionMapper
3338
{
@@ -83,55 +88,60 @@ public static function map(string $transactionClassName, Response $response): Ba
8388
$transaction->setRaiseWarning(Util::parseBoolean($raiseWarning));
8489
}
8590

91+
$header = self::getFirstChildElementByName($transactionElement, 'header');
92+
if ($header === null) {
93+
throw new UnexpectedValueException('Transaction section is missing a header section');
94+
}
95+
8696
$office = new Office();
87-
$office->setCode(self::getField($transaction, $transactionElement, 'office'));
97+
$office->setCode(self::getField($transaction, $header, 'office'));
8898

8999
$transaction
90100
->setOffice($office)
91-
->setCode(self::getField($transaction, $transactionElement, 'code'))
92-
->setPeriod(self::getField($transaction, $transactionElement, 'period'))
93-
->setDateFromString(self::getField($transaction, $transactionElement, 'date'))
94-
->setOrigin(self::getField($transaction, $transactionElement, 'origin'))
95-
->setFreetext1(self::getField($transaction, $transactionElement, 'freetext1'))
96-
->setFreetext2(self::getField($transaction, $transactionElement, 'freetext2'))
97-
->setFreetext3(self::getField($transaction, $transactionElement, 'freetext3'));
98-
99-
$currency = new Currency(self::getField($transaction, $transactionElement, 'currency') ?? self::DEFAULT_CURRENCY_CODE);
101+
->setCode(self::getField($transaction, $header, 'code'))
102+
->setPeriod(self::getField($transaction, $header, 'period'))
103+
->setDateFromString(self::getField($transaction, $header, 'date'))
104+
->setOrigin(self::getField($transaction, $header, 'origin'))
105+
->setFreetext1(self::getField($transaction, $header, 'freetext1'))
106+
->setFreetext2(self::getField($transaction, $header, 'freetext2'))
107+
->setFreetext3(self::getField($transaction, $header, 'freetext3'));
108+
109+
$currency = new Currency(self::getField($transaction, $header, 'currency') ?? self::DEFAULT_CURRENCY_CODE);
100110
$transaction->setCurrency($currency);
101111

102-
$number = self::getField($transaction, $transactionElement, 'number');
112+
$number = self::getField($transaction, $header, 'number');
103113
if (!empty($number)) {
104114
$transaction->setNumber($number);
105115
}
106116

107117
if (Util::objectUses(DueDateField::class, $transaction)) {
108-
$value = self::getField($transaction, $transactionElement, 'duedate');
118+
$value = self::getField($transaction, $header, 'duedate');
109119

110120
if ($value !== null) {
111121
$transaction->setDueDateFromString($value);
112122
}
113123
}
114124
if (Util::objectUses(InvoiceNumberField::class, $transaction)) {
115-
$transaction->setInvoiceNumber(self::getField($transaction, $transactionElement, 'invoicenumber'));
125+
$transaction->setInvoiceNumber(self::getField($transaction, $header, 'invoicenumber'));
116126
}
117127
if (Util::objectUses(PaymentReferenceField::class, $transaction)) {
118128
$transaction
119-
->setPaymentReference(self::getField($transaction, $transactionElement, 'paymentreference'));
129+
->setPaymentReference(self::getField($transaction, $header, 'paymentreference'));
120130
}
121131
if (Util::objectUses(StatementNumberField::class, $transaction)) {
122-
$transaction->setStatementnumber(self::getField($transaction, $transactionElement, 'statementnumber'));
132+
$transaction->setStatementnumber(self::getField($transaction, $header, 'statementnumber'));
123133
}
124134

125135
if ($transaction instanceof SalesTransaction) {
126-
$transaction->setOriginReference(self::getField($transaction, $transactionElement, 'originreference'));
136+
$transaction->setOriginReference(self::getField($transaction, $header, 'originreference'));
127137
}
128138
if ($transaction instanceof JournalTransaction) {
129-
$transaction->setRegime(self::getField($transaction, $transactionElement, 'regime'));
139+
$transaction->setRegime(self::getField($transaction, $header, 'regime'));
130140
}
131141
if ($transaction instanceof CashTransaction) {
132142
$transaction->setStartvalue(
133143
Util::parseMoney(
134-
self::getField($transaction, $transactionElement, 'startvalue'),
144+
self::getField($transaction, $header, 'startvalue'),
135145
$transaction->getCurrency()
136146
)
137147
);
@@ -140,6 +150,7 @@ public static function map(string $transactionClassName, Response $response): Ba
140150
// Parse the transaction lines
141151
$transactionLineClassName = $transaction->getLineClassName();
142152

153+
/** @var DOMElement $lineElement */
143154
foreach ((new DOMXPath($document))->query('/transaction/lines/line') as $lineElement) {
144155
self::checkForMessage($transaction, $lineElement);
145156

@@ -163,6 +174,11 @@ public static function map(string $transactionClassName, Response $response): Ba
163174
->setMatchLevel(self::getField($transaction, $lineElement, 'matchlevel'))
164175
->setVatCode(self::getField($transaction, $lineElement, 'vatcode'));
165176

177+
$matches = $lineElement->getElementsByTagName('matches')->item(0);
178+
if ($matches !== null) {
179+
self::parseMatches($transaction, $transactionLine, $matches);
180+
}
181+
166182
// TODO - according to the docs, the field is called <basevalueopen>, but the examples use <openbasevalue>.
167183
$baseValueOpen = self::getFieldAsMoney($transaction, $lineElement, 'basevalueopen', $currency) ?: self::getFieldAsMoney($transaction, $lineElement, 'openbasevalue', $currency);
168184
if ($baseValueOpen) {
@@ -255,9 +271,22 @@ public static function map(string $transactionClassName, Response $response): Ba
255271
return $transaction;
256272
}
257273

258-
private static function getField(BaseTransaction $transaction, \DOMElement $element, string $fieldTagName): ?string
274+
private static function getFirstChildElementByName(DOMElement $element, string $fieldTagName): ?DOMElement
259275
{
260-
$fieldElement = $element->getElementsByTagName($fieldTagName)->item(0);
276+
$fieldElement = null;
277+
foreach ($element->childNodes as $node) {
278+
if ($node->nodeName === $fieldTagName) {
279+
$fieldElement = $node;
280+
break;
281+
}
282+
}
283+
284+
return $fieldElement;
285+
}
286+
287+
private static function getField(BaseTransaction $transaction, DOMElement $element, string $fieldTagName): ?string
288+
{
289+
$fieldElement = self::getFirstChildElementByName($element, $fieldTagName);
261290

262291
if (!isset($fieldElement)) {
263292
return null;
@@ -270,7 +299,7 @@ private static function getField(BaseTransaction $transaction, \DOMElement $elem
270299

271300
private static function getFieldAsMoney(
272301
BaseTransaction $transaction,
273-
\DOMElement $element,
302+
DOMElement $element,
274303
string $fieldTagName,
275304
Currency $currency
276305
): ?Money {
@@ -283,7 +312,7 @@ private static function getFieldAsMoney(
283312
return new Money((string)(100 * $fieldValue), $currency);
284313
}
285314

286-
private static function checkForMessage(BaseTransaction $transaction, \DOMElement $element): void
315+
private static function checkForMessage(BaseTransaction $transaction, DOMElement $element): void
287316
{
288317
if ($element->hasAttribute('msg')) {
289318
$message = new Message();
@@ -294,4 +323,31 @@ private static function checkForMessage(BaseTransaction $transaction, \DOMElemen
294323
$transaction->addMessage($message);
295324
}
296325
}
326+
327+
private static function parseMatches(BaseTransaction $baseTransaction, BaseTransactionLine $transactionLine, DOMElement $element): void
328+
{
329+
/** @var DOMElement $set */
330+
foreach ($element->getElementsByTagName('set') as $set) {
331+
$status = Destiny::from($set->getAttribute('status'));
332+
$matchDate = Util::parseDate(self::getField($baseTransaction, $set, 'matchdate'));
333+
$matchValue = self::getFieldAsMoney($baseTransaction, $set, 'matchvalue', $baseTransaction->getCurrency());
334+
335+
$matchLines = [];
336+
/** @var DOMElement $lines */
337+
$lines = $set->getElementsByTagName('lines')->item(0);
338+
/** @var DOMElement $line */
339+
foreach ($lines->childNodes as $line) {
340+
if ($line->nodeName !== 'line') {
341+
continue;
342+
}
343+
$code = (string)self::getField($baseTransaction, $line, 'code');
344+
$number = (int)self::getField($baseTransaction, $line, 'number');
345+
$lineNum = (int)self::getField($baseTransaction, $line, 'line');
346+
$lineMatchValue = self::getFieldAsMoney($baseTransaction, $line, 'matchvalue', $baseTransaction->getCurrency());
347+
348+
$matchLines[] = new MatchLine($code, $number, $lineNum, $lineMatchValue);
349+
}
350+
$transactionLine->addMatch(new MatchSet($status, $matchDate, $matchValue, $matchLines));
351+
}
352+
}
297353
}

src/Transactions/MatchLine.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace PhpTwinfield\Transactions;
4+
5+
use Money\Money;
6+
7+
class MatchLine
8+
{
9+
/** @var string */
10+
private $code;
11+
12+
/** @var int */
13+
private $number;
14+
15+
/** @var int */
16+
private $line;
17+
18+
/** @var Money */
19+
private $matchValue;
20+
21+
public function __construct(
22+
string $code,
23+
int $number,
24+
int $line,
25+
Money $matchValue
26+
)
27+
{
28+
$this->code = $code;
29+
$this->number = $number;
30+
$this->line = $line;
31+
$this->matchValue = $matchValue;
32+
}
33+
34+
public function getCode(): string
35+
{
36+
return $this->code;
37+
}
38+
39+
public function getNumber(): int
40+
{
41+
return $this->number;
42+
}
43+
44+
public function getLine(): int
45+
{
46+
return $this->line;
47+
}
48+
49+
public function getMatchValue(): Money
50+
{
51+
return $this->matchValue;
52+
}
53+
}

src/Transactions/MatchSet.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace PhpTwinfield\Transactions;
4+
5+
use DateTimeInterface;
6+
use Money\Money;
7+
use PhpTwinfield\Enums\Destiny;
8+
9+
class MatchSet
10+
{
11+
/** @var Destiny */
12+
private $status;
13+
14+
/** @var DateTimeInterface */
15+
private $matchDate;
16+
17+
/** @var Money */
18+
private $matchValue;
19+
20+
/** @var MatchLine[] */
21+
private $matchLines;
22+
23+
/**
24+
* @param Destiny $status
25+
* @param DateTimeInterface $matchDate
26+
* @param Money $matchValue
27+
* @param MatchLine[] $matchLines
28+
*/
29+
public function __construct(
30+
Destiny $status,
31+
DateTimeInterface $matchDate,
32+
Money $matchValue,
33+
array $matchLines
34+
)
35+
{
36+
$this->status = $status;
37+
$this->matchDate = $matchDate;
38+
$this->matchValue = $matchValue;
39+
$this->matchLines = $matchLines;
40+
}
41+
42+
public function getStatus(): Destiny
43+
{
44+
return $this->status;
45+
}
46+
47+
public function getMatchDate(): DateTimeInterface
48+
{
49+
return $this->matchDate;
50+
}
51+
52+
public function getMatchValue(): Money
53+
{
54+
return $this->matchValue;
55+
}
56+
57+
/**
58+
* @return MatchLine[]
59+
*/
60+
public function getMatchLines(): array
61+
{
62+
return $this->matchLines;
63+
}
64+
}

0 commit comments

Comments
 (0)