From ede0cdd22884ccd4e1f26a3469e83be99d4182e0 Mon Sep 17 00:00:00 2001 From: Marc Date: Fri, 4 Apr 2025 14:20:48 +0200 Subject: [PATCH] Added Currency and Currency rate support Only adding new rates and creating currencies. No support for getting existing currencies and rates from Twinfield. --- readme.md | 27 ++--- src/ApiConnectors/CurrencyApiConnector.php | 65 +++++++++++ src/Currency.php | 89 +++++++++++++++ src/CurrencyRate.php | 47 ++++++++ src/DomDocuments/CurrenciesDocument.php | 121 +++++++++++++++++++++ src/Mappers/CurrencyMapper.php | 82 ++++++++++++++ 6 files changed, 418 insertions(+), 13 deletions(-) create mode 100644 src/ApiConnectors/CurrencyApiConnector.php create mode 100644 src/Currency.php create mode 100644 src/CurrencyRate.php create mode 100644 src/DomDocuments/CurrenciesDocument.php create mode 100644 src/Mappers/CurrencyMapper.php diff --git a/readme.md b/readme.md index 17b76ccd..aea871d0 100644 --- a/readme.md +++ b/readme.md @@ -309,20 +309,21 @@ $connector = new BrowseDataApiConnector( Not all resources from the Twinfield API are currently implemented. Feel free to create a pull request when you need support for another resource. -| Component | get() | listAll() | send() | delete() | Mapper | -| --------------------------------------------------------------------------------------------------------------- | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | -| [Articles](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Articles) | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | -| [BankTransaction](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/BankTransactions)| | | :white_check_mark: | :white_check_mark: | | -| [Customer](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Customers) | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| [Electronic Bank Statements](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/BankStatements)| | | :white_check_mark: | | | -| [Sales Invoices](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/SalesInvoices) | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | -| [Matching](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Miscellaneous/Matching) | | | :white_check_mark: | | :white_check_mark: | | -| [Offices](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Offices) | | :white_check_mark: | | | :white_check_mark: | -| [Suppliers](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Suppliers) | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| Component | get() | listAll() | send() | delete() | Mapper | +|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | +| [Articles](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Articles) | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | +| [BankTransaction](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/BankTransactions) | | | :white_check_mark: | :white_check_mark: | | +| [Currency](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Currencies) | | | :white_check_mark: | | :white_check_mark: | +| [Customer](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Customers) | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| [Electronic Bank Statements](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/BankStatements) | | | :white_check_mark: | | | +| [Sales Invoices](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/SalesInvoices) | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | +| [Matching](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Miscellaneous/Matching) | | | :white_check_mark: | | :white_check_mark: | | +| [Offices](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Offices) | | :white_check_mark: | | | :white_check_mark: | +| [Suppliers](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Suppliers) | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | Transactions:
[Purchase](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/PurchaseTransactions), [Sale](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/SalesTransactions), [Journal](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/JournalTransactions), [Cash](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/CashTransactions) | :white_check_mark: | | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| [Users](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Users) | | :white_check_mark: | | | | -| [Vat types](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/VAT) | | :white_check_mark: | | | | -| [Browse Data](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Request/BrowseData) | :white_check_mark: | | | | :white_check_mark: | +| [Users](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/Users) | | :white_check_mark: | | | | +| [Vat types](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Masters/VAT) | | :white_check_mark: | | | | +| [Browse Data](https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Request/BrowseData) | :white_check_mark: | | | | :white_check_mark: | ## Links diff --git a/src/ApiConnectors/CurrencyApiConnector.php b/src/ApiConnectors/CurrencyApiConnector.php new file mode 100644 index 00000000..f8ef310a --- /dev/null +++ b/src/ApiConnectors/CurrencyApiConnector.php @@ -0,0 +1,65 @@ + + */ +class CurrencyApiConnector extends BaseApiConnector +{ + /** + * Sends an Currency instance to Twinfield to update or add. + * + * @param Currency $currency + * @return Currency + * @throws Exception + */ + public function send(Currency $currency): Currency + { + return $this->unwrapSingleResponse($this->sendAll([$currency])); + } + + /** + * @param Currency[] $currencies + * @return MappedResponseCollection + * @throws Exception + */ + public function sendAll(array $currencies): MappedResponseCollection + { + Assert::allIsInstanceOf($currencies, Currency::class); + + /** @var Response[] $responses */ + $responses = []; + + foreach ($this->getProcessXmlService()->chunk($currencies) as $chunk) { + + $currenciesDocument = new CurrenciesDocument(); + + foreach ($chunk as $currency) { + $currenciesDocument->addCurrency($currency); + } + + $responses[] = $this->sendXmlDocument($currenciesDocument); + } + + return $this->getProcessXmlService()->mapAll($responses, "currency", function(Response $response): Currency { + return CurrencyMapper::map($response); + }); + } +} diff --git a/src/Currency.php b/src/Currency.php new file mode 100644 index 00000000..f0c32050 --- /dev/null +++ b/src/Currency.php @@ -0,0 +1,89 @@ +getOffice()->getCode(); + } + + public function setOfficeByCode(string $code): self + { + $office = new Office(); + $office->setCode($code); + + $this->setOffice( $office ); + return $this; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + return $this; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getShortName() + { + return $this->shortName; + } + + public function setShortName($shortName) + { + $this->shortName = $shortName; + return $this; + } + + public function getRates() + { + return $this->rates; + } + + public function addRate(CurrencyRate $rate) + { + $this->rates[] = $rate; + return $this; + } + + public function removeRate(CurrencyRate $rate) + { + $index = array_search($rate, $this->rates); + + if ($index !== false) { + unset($this->rates[$index]); + return true; + } else { + return false; + } + } +} diff --git a/src/CurrencyRate.php b/src/CurrencyRate.php new file mode 100644 index 00000000..041111b4 --- /dev/null +++ b/src/CurrencyRate.php @@ -0,0 +1,47 @@ +status; + } + + public function setStatus($status) + { + $this->status = $status; + return $this; + } + + public function getStartdate() + { + return $this->startdate; + } + + public function setStartdate($startdate) + { + $this->startdate = $startdate; + return $this; + } + + public function getRate() + { + return $this->rate; + } + + public function setRate($rate) + { + $this->rate = $rate; + return $this; + } +} diff --git a/src/DomDocuments/CurrenciesDocument.php b/src/DomDocuments/CurrenciesDocument.php new file mode 100644 index 00000000..507d8021 --- /dev/null +++ b/src/DomDocuments/CurrenciesDocument.php @@ -0,0 +1,121 @@ + + */ +class CurrenciesDocument extends \DOMDocument +{ + /** + * Holds the element + * that all additional elements should be a child of + * @var \DOMElement + */ + private $currencyElement; + + /** + * Creates the element and adds it to the property + * currencyElement + * + * @access public + */ + public function __construct() + { + parent::__construct(); + + $this->currencyElement = $this->createElement('currency'); + $this->appendChild($this->currencyElement); + } + + /** + * Turns a passed Currency class into the required markup for interacting + * with Twinfield. + * + * This method doesn't return anything, instead just adds the Currency to + * this DOMDOcument instance for submission usage. + * + * @access public + * @param Currency $article + * @return void | [Adds to this instance] + */ + public function addCurrency(Currency $currency) + { + // Currency->header elements and their methods + $currencyTags = array( + 'office' => 'getOfficeCode', + 'code' => 'getCode', + 'name' => 'getName', + 'shortname' => 'getShortName', + ); + + // Go through each Currency element and use the assigned method + foreach ($currencyTags as $tag => $method) { + // Make text node for method value + $nodeValue = $currency->$method(); + + if( ! empty( $nodeValue) ) { + $node = $this->createTextNode($nodeValue); + + // Make the actual element and assign the node + $element = $this->createElement($tag); + $element->appendChild($node); + + // Add the full element + $this->currencyElement->appendChild($element); + } + } + + $rates = $currency->getRates(); + + if (!empty($rates)) { + // Element tags and their methods for lines + $rateTags = [ + 'startdate' => 'getStartdate', + 'rate' => 'getRate' + ]; + + // Make addresses element + $ratesElement = $this->createElement('rates'); + $this->currencyElement->appendChild($ratesElement); + + // Go through each line assigned to the currency + foreach ($rates as $line) { + // Makes new currencyLine element + $rateElement = $this->createElement('rate'); + $ratesElement->appendChild($rateElement); + + if( ! empty($line->getStatus() ) ) { + $statusElement = $this->createTextElement($line->getStatus()); + $element = $this->createElement('status'); + $element->appendChild($statusElement); + $rateElement->appendChild($element); + } + + // Go through each line element and use the assigned method + foreach ($rateTags as $tag => $method) { + // Make the text node for the method value + $node = $this->createTextNode($line->$method()); + + // Make the actual element and assign the text node + $element = $this->createElement($tag); + $element->appendChild($node); + + // Add the completed element + $rateElement->appendChild($element); + } + } + } + } +} diff --git a/src/Mappers/CurrencyMapper.php b/src/Mappers/CurrencyMapper.php new file mode 100644 index 00000000..570fcae8 --- /dev/null +++ b/src/Mappers/CurrencyMapper.php @@ -0,0 +1,82 @@ + + */ +class CurrencyMapper extends BaseMapper +{ + + /** + * Maps a Response object to a clean Currency entity. + * + * @access public + * + * @param \PhpTwinfield\Response\Response $response + * + * @return Currency + * @throws \PhpTwinfield\Exception + */ + public static function map(Response $response) + { + // Generate new Article object + $currency = new Currency(); + + // Gets the raw DOMDocument response. + $responseDOM = $response->getResponseDocument(); + + // Article elements and their methods + $currencyTags = [ + 'code' => 'setCode', + 'office' => 'setOfficeByCode', + 'name' => 'setName', + 'shortname' => 'setShortName' + ]; + + foreach ($currencyTags as $tagName => $tagValue) { + $value = $responseDOM->getElementsByTagName($tagName)->item(0)->nodeValue; + $method = $currencyTags[$tagName]; + $currency->$method($value); + } + + $ratesDOMTag = $responseDOM->getElementsByTagName('rates'); + + if (isset($ratesDOMTag) && $ratesDOMTag->length > 0) { + // Element tags and their methods for lines + $lineTags = [ + 'status' => 'setStatus', + 'startdate' => 'startdate', + 'rate' => 'setRate' + ]; + + $ratesDOM = $ratesDOMTag->item(0); + + // Loop through each returned line for the article + foreach ($ratesDOM->getElementsByTagName('rate') as $rateDom) { + if( $rateDom->childElementCount > 0 ) { + // Make a new tempory CurrencyRate class + $rateLine = new CurrencyRate(); + + // Set the attributes ( id,status,inuse) + $rateLine->setStartdate($rateDom->getElementsByTagName('startdate')->item(0)->nodeValue) + ->setRate($rateDom->getElementsByTagName('rate')->item(0)->nodeValue); + + // Add the bank to the customer + $currency->addRate($rateLine); + + // Clean that memory! + unset ($rateLine); + } + } + } + return $currency; + } +}