diff --git a/src/ApiConnectors/InvoiceApiConnector.php b/src/ApiConnectors/InvoiceApiConnector.php index 3c9a6219..84678963 100644 --- a/src/ApiConnectors/InvoiceApiConnector.php +++ b/src/ApiConnectors/InvoiceApiConnector.php @@ -2,6 +2,7 @@ namespace PhpTwinfield\ApiConnectors; +use PhpTwinfield\Customer; use PhpTwinfield\DomDocuments\InvoicesDocument; use PhpTwinfield\Exception; use PhpTwinfield\Invoice; @@ -10,6 +11,7 @@ use PhpTwinfield\Request as Request; use PhpTwinfield\Response\MappedResponseCollection; use PhpTwinfield\Response\Response; +use PhpTwinfield\Services\FinderService; use Webmozart\Assert\Assert; /** @@ -25,7 +27,7 @@ class InvoiceApiConnector extends BaseApiConnector { /** - * Requires a specific invoice based off the passed in code, invoiceNumber and optionally the office. + * Requires a specific Invoice based off the passed in code, invoiceNumber and optionally the office. * * @param string $code * @param string $invoiceNumber @@ -35,7 +37,7 @@ class InvoiceApiConnector extends BaseApiConnector */ public function get(string $code, string $invoiceNumber, Office $office) { - // Make a request to read a single invoice. Set the required values + // Make a request to read a single Invoice. Set the required values $request_invoice = new Request\Read\Invoice(); $request_invoice ->setCode($code) @@ -57,12 +59,8 @@ public function get(string $code, string $invoiceNumber, Office $office) */ public function send(Invoice $invoice): Invoice { - $invoiceResponses = $this->sendAll([$invoice]); - - Assert::count($invoiceResponses, 1); - - foreach ($invoiceResponses as $invoiceResponse) { - return $invoiceResponse->unwrap(); + foreach($this->sendAll([$invoice]) as $each) { + return $each->unwrap(); } } @@ -93,4 +91,66 @@ public function sendAll(array $invoices): MappedResponseCollection return InvoiceMapper::map($response); }); } + + /** + * List all sales invoices. + * + * @param string $pattern The search pattern. May contain wildcards * and ? + * @param int $field The search field determines which field or fields will be searched. The available fields + * depends on the finder type. Passing a value outside the specified values will cause an + * error. + * @param int $firstRow First row to return, useful for paging + * @param int $maxRows Maximum number of rows to return, useful for paging + * @param array $options The Finder options. Passing an unsupported name or value causes an error. It's possible + * to add multiple options. An option name may be used once, specifying an option multiple + * times will cause an error. + * + * @return Invoice[] The sales invoices found. + */ + public function listAll( + string $pattern = '*', + int $field = 0, + int $firstRow = 1, + int $maxRows = 100, + array $options = [] + ): array { + $optionsArrayOfString = array('ArrayOfString' => array()); + + foreach ($options as $key => $value) { + $optionsArrayOfString['ArrayOfString'][] = array($key, $value); + } + + $response = $this->getFinderService()->searchFinder(FinderService::TYPE_LIST_OF_AVAILABLE_INVOICES, $pattern, $field, $firstRow, $maxRows, $optionsArrayOfString); + + if ($response->data->TotalRows == 0) { + return []; + } + + $invoices = []; + + foreach ($response->data->Items->ArrayOfString as $invoiceArray) { + $invoice = new Invoice(); + $customer = new Customer(); + + if (isset($invoiceArray->string[0])) { + $invoice->setInvoiceNumber($invoiceArray->string[0]); + $invoice->setInvoiceAmount($invoiceArray->string[1]); + $customer->setCode($invoiceArray->string[2]); + $invoice->setCustomerName($invoiceArray->string[3]); + $invoice->setDebitCredit($invoiceArray->string[4]); + } else { + $invoice->setInvoiceNumber($invoiceArray[0]); + $invoice->setInvoiceAmount($invoiceArray[1]); + $customer->setCode($invoiceArray[2]); + $invoice->setCustomerName($invoiceArray[3]); + $invoice->setDebitCredit($invoiceArray[4]); + } + + $invoice->setCustomer($customer); + + $invoices[] = $invoice; + } + + return $invoices; + } } diff --git a/src/DomDocuments/InvoicesDocument.php b/src/DomDocuments/InvoicesDocument.php index ad6a9274..20d8e438 100644 --- a/src/DomDocuments/InvoicesDocument.php +++ b/src/DomDocuments/InvoicesDocument.php @@ -24,10 +24,10 @@ final protected function getRootTagName(): string /** * Turns a passed Invoice class into the required markup for interacting * with Twinfield. - * + * * This method doesn't return anything, instead just adds the invoice * to this DOMDocument instance for submission usage. - * + * * @access public * @param Invoice $invoice * @return void | [Adds to this instance] @@ -40,7 +40,7 @@ public function addInvoice(Invoice $invoice) // Makes a child header element $headerElement = $this->createElement('header'); $invoiceElement->appendChild($headerElement); - + // Set customer element $customer = $invoice->getCustomer(); @@ -66,20 +66,20 @@ public function addInvoice(Invoice $invoice) 'headertext' => 'getHeaderText', 'footertext' => 'getFooterText' ); - + // Go through each element and use the assigned method foreach ($headerTags as $tag => $method) { $value = $this->getValueFromCallback([$invoice, $method]); - + if(null !== $value) { // Make text node for method value $node = $this->createTextNode($value); - + // Make the actual element and assign the node $element = $this->createElement($tag); $element->appendChild($node); - + // Add the full element $headerElement->appendChild($element); } @@ -88,7 +88,7 @@ public function addInvoice(Invoice $invoice) // Add orders $linesElement = $this->createElement('lines'); $invoiceElement->appendChild($linesElement); - + // Elements and their associated methods for lines $lineTags = array( 'quantity' => 'getQuantity', @@ -96,6 +96,7 @@ public function addInvoice(Invoice $invoice) 'subarticle' => 'getSubArticle', 'description' => 'getDescription', 'unitspriceexcl' => 'getUnitsPriceExcl', + 'unitspriceinc' => 'getUnitsPriceInc', 'units' => 'getUnits', 'vatcode' => 'getVatCode', 'freetext1' => 'getFreeText1', @@ -116,7 +117,7 @@ public function addInvoice(Invoice $invoice) // Go through each element and use the assigned method foreach ($lineTags as $tag => $method) { - + // Make text node for method value $node = $this->createTextNode($this->getValueFromCallback([$line, $method])); diff --git a/src/Invoice.php b/src/Invoice.php index 658ed7b4..d5bf3b89 100644 --- a/src/Invoice.php +++ b/src/Invoice.php @@ -24,20 +24,22 @@ * * @see https://c3.twinfield.com/webservices/documentation/#/ApiReference/SalesInvoices * @todo Add documentation and typehints to all properties. - * @todo Add support for VatLines. */ -class Invoice +class Invoice extends BaseObject { use PeriodField; - use DueDateField; use OfficeField; private $customer; + private $customerName; + private $debitCredit; + private $invoiceAmount; private $invoiceType; private $invoiceNumber; private $status; private $currency; private $invoiceDate; + private $dueDate; private $performanceDate; private $paymentMethod; private $bank; @@ -69,6 +71,25 @@ public function getLines(): array return $this->lines; } + /** + * @var InvoiceVatLine[] + */ + private $vatlines = []; + + public function addVatLine(InvoiceVatLine $vatline) + { + $this->vatlines[] = $vatline; + return $this; + } + + /** + * @return InvoiceVatLine[] + */ + public function getVatLines(): array + { + return $this->vatlines; + } + public function getCustomer(): Customer { return $this->customer; @@ -80,6 +101,28 @@ public function setCustomer(Customer $customer) return $this; } + public function getCustomerName() + { + return $this->customerName; + } + + public function setCustomerName($customerName) + { + $this->customerName = $customerName; + return $this; + } + + public function getDebitCredit() + { + return $this->debitCredit; + } + + public function setDebitCredit($debitCredit) + { + $this->debitCredit = $debitCredit; + return $this; + } + public function setTotals(InvoiceTotals $totals) { $this->totals = $totals; @@ -91,6 +134,17 @@ public function getTotals(): InvoiceTotals return $this->totals; } + public function getInvoiceAmount() + { + return $this->invoiceAmount; + } + + public function setInvoiceAmount($invoiceAmount) + { + $this->invoiceAmount = $invoiceAmount; + return $this; + } + public function getInvoiceType() { return $this->invoiceType; @@ -146,6 +200,17 @@ public function setInvoiceDate($invoiceDate) return $this; } + public function getDueDate() + { + return $this->dueDate; + } + + public function setDueDate($dueDate) + { + $this->dueDate = $dueDate; + return $this; + } + public function getPerformanceDate() { return $this->performanceDate; diff --git a/src/InvoiceVatLine.php b/src/InvoiceVatLine.php new file mode 100644 index 00000000..50ba73a8 --- /dev/null +++ b/src/InvoiceVatLine.php @@ -0,0 +1,55 @@ +vatValue; + } + + public function setVatValue($vatValue) + { + $this->vatValue = $vatValue; + return $this; + } + + public function getPerformanceDate() + { + return $this->performanceDate; + } + + public function setPerformanceDate($performanceDate) + { + $this->performanceDate = $performanceDate; + return $this; + } + + public function getPerformanceType(): ?PerformanceType + { + return $this->performanceType; + } + + public function setPerformanceType(?PerformanceType $performanceType): self + { + $this->performanceType = $performanceType; + return $this; + } +} diff --git a/src/Mappers/InvoiceMapper.php b/src/Mappers/InvoiceMapper.php index 5fccf1f9..4d70f536 100644 --- a/src/Mappers/InvoiceMapper.php +++ b/src/Mappers/InvoiceMapper.php @@ -4,6 +4,7 @@ use PhpTwinfield\Customer; use PhpTwinfield\Invoice; use PhpTwinfield\InvoiceLine; +use PhpTwinfield\InvoiceVatLine; use PhpTwinfield\InvoiceTotals; use PhpTwinfield\Response\Response; @@ -12,44 +13,40 @@ class InvoiceMapper extends BaseMapper public static function map(Response $response) { $responseDOM = $response->getResponseDocument(); + $invoiceElement = $responseDOM->documentElement; + + // Generate new Invoice + $invoice = new Invoice(); + $invoice->setResult($invoiceElement->getAttribute('result')); $invoiceTags = array( - 'office' => 'setOffice', - 'invoicetype' => 'setInvoiceType', - 'invoicenumber' => 'setInvoiceNumber', - 'status' => 'setStatus', - 'currency' => 'setCurrency', - 'period' => 'setPeriod', - 'invoicedate' => 'setInvoiceDate', - 'duedate' => 'setDueDateFromString', - 'performancedate' => 'setPerformanceDate', - 'paymentmethod' => 'setPaymentMethod', 'bank' => 'setBank', - 'invoiceaddressnumber' => 'setInvoiceAddressNumber', + 'currency' => 'setCurrency', 'deliveraddressnumber' => 'setDeliverAddressNumber', - 'headertext' => 'setHeaderText', + 'duedate' => 'setDueDate', 'footertext' => 'setFooterText', + 'headertext' => 'setHeaderText', + 'invoiceaddressnumber' => 'setInvoiceAddressNumber', + 'invoicedate' => 'setInvoiceDate', + 'invoicenumber' => 'setInvoiceNumber', + 'invoicetype' => 'setInvoiceType', + 'office' => 'setOffice', + 'paymentmethod' => 'setPaymentMethod', + 'performancedate' => 'setPerformanceDate', + 'period' => 'setPeriod', + 'status' => 'setStatus', ); - $customerTags = array( - 'customer' => 'setCode', - ); - - $totalsTags = array( - 'valueexcl' => 'setValueExcl', - 'valueinc' => 'setValueInc', - ); - - // Generate new Invoice - $invoice = new Invoice(); - // Loop through all invoice tags foreach ($invoiceTags as $tag => $method) { - self::setFromTagValue($responseDOM, $tag, [$invoice, $method]); } - // Make a custom, and loop through custom tags + // Make a customer, and loop through custom tags + $customerTags = array( + 'customer' => 'setCode', + ); + $customer = new Customer(); foreach ($customerTags as $tag => $method) { $_tag = $responseDOM->getElementsByTagName($tag)->item(0); @@ -59,56 +56,110 @@ public static function map(Response $response) } } + // Set the custom class to the invoice + $invoice->setCustomer($customer); + // Make an InvoiceTotals and loop through custom tags + $totalsTags = array( + 'valueexcl' => 'setValueExcl', + 'valueinc' => 'setValueInc', + ); + $invoiceTotals = new InvoiceTotals(); - foreach ($totalsTags as $tag => $method) { - $_tag = $responseDOM->getElementsByTagName($tag)->item(0); - if (isset($_tag) && isset($_tag->textContent)) { - $invoiceTotals->$method($_tag->textContent); + $totalElement = $responseDOM->getElementsByTagName('totals')->item(0); + + if ($totalElement !== null) { + // Go through each total element and add to the assigned method + foreach ($totalsTags as $tag => $method) { + $invoiceTotals->$method(self::getField($totalElement, $tag)); } } - // Set the custom classes to the invoice - $invoice->setCustomer($customer); + // Set the custom class to the invoice $invoice->setTotals($invoiceTotals); - $lineTags = array( - 'article' => 'setArticle', - 'subarticle' => 'setSubArticle', - 'quantity' => 'setQuantity', - 'units' => 'setUnits', - 'allowdiscountorpremium' => 'setAllowDiscountOrPremium', - 'description' => 'setDescription', - 'valueexcl' => 'setValueExcl', - 'vatvalue' => 'setVatValue', - 'valueinc' => 'setValueInc', - 'unitspriceexcl' => 'setUnitsPriceExcl', - 'unitspriceinc' => 'setUnitsPriceInc', - 'freetext1' => 'setFreeText1', - 'freetext2' => 'setFreeText2', - 'freetext3' => 'setFreeText3', - 'performancedate' => 'setPerformanceDate', - 'performancetype' => 'setPerformanceType', - 'dim1' => 'setDim1', - ); - - /** @var \DOMElement $lineDOM */ - foreach ($responseDOM->getElementsByTagName('line') as $lineDOM) { + $linesDOMTag = $responseDOM->getElementsByTagName('lines'); + + if (isset($linesDOMTag) && $linesDOMTag->length > 0) { + // Element tags and their methods for lines + $lineTags = array( + 'allowdiscountorpremium' => 'setAllowDiscountOrPremium', + 'article' => 'setArticle', + 'description' => 'setDescription', + 'dim1' => 'setDim1', + 'freetext1' => 'setFreeText1', + 'freetext2' => 'setFreeText2', + 'freetext3' => 'setFreeText3', + 'performancedate' => 'setPerformanceDate', + 'performancetype' => 'setPerformanceType', + 'quantity' => 'setQuantity', + 'subarticle' => 'setSubArticle', + 'units' => 'setUnits', + 'unitspriceexcl' => 'setUnitsPriceExcl', + 'unitspriceinc' => 'setUnitsPriceInc', + 'valueexcl' => 'setValueExcl', + 'valueinc' => 'setValueInc', + 'vatcode' => 'setVatCode', + 'vatvalue' => 'setVatValue', + ); + + $linesDOM = $linesDOMTag->item(0); + + // Loop through each returned lines for the invoice + foreach ($linesDOM->childNodes as $lineDOM) { + if ($lineDOM->nodeType !== 1) { + continue; + } - $invoiceLine = new InvoiceLine(); - $invoiceLine->setID($lineDOM->getAttribute('id')); + $invoiceLine = new InvoiceLine(); + $invoiceLine->setID($lineDOM->getAttribute('id')); - foreach ($lineTags as $tag => $method) { + foreach ($lineTags as $tag => $method) { - $content = self::getField($lineDOM, $tag); + $content = self::getField($lineDOM, $tag); - if (null !== $content) { - $invoiceLine->$method($content); + if (null !== $content) { + $invoiceLine->$method($content); + } } + + $invoice->addLine($invoiceLine); } + } + + $vatlinesDOMTag = $responseDOM->getElementsByTagName('vatlines'); + + if (isset($vatlinesDOMTag) && $vatlinesDOMTag->length > 0) { + // Element tags and their methods for vatlines + $vatlineTags = array( + 'performancedate' => 'setPerformanceDate', + 'performancetype' => 'setPerformanceType', + 'vatcode' => 'setVatCode', + 'vatvalue' => 'setVatValue', + ); + + $vatlinesDOM = $vatlinesDOMTag->item(0); - $invoice->addLine($invoiceLine); + // Loop through each returned lines for the invoice + foreach ($vatlinesDOM->childNodes as $vatlineDOM) { + if ($vatlineDOM->nodeType !== 1) { + continue; + } + + $invoiceVatLine = new InvoiceVatLine(); + + foreach ($vatlineTags as $tag => $method) { + + $content = self::getField($vatlineDOM, $tag); + + if (null !== $content) { + $invoiceVatLine->$method($content); + } + } + + $invoice->addVatLine($invoiceVatLine); + } } // Financial elements and their methods @@ -130,4 +181,4 @@ public static function map(Response $response) return $invoice; } -} +} \ No newline at end of file diff --git a/src/Request/Read/Invoice.php b/src/Request/Read/Invoice.php index 27aec93a..8dd88bf8 100644 --- a/src/Request/Read/Invoice.php +++ b/src/Request/Read/Invoice.php @@ -2,7 +2,7 @@ namespace PhpTwinfield\Request\Read; /** - * Used to request a specific invoice from a certain + * Used to request a specific Invoice from a certain * code, number and office. * * @package PhpTwinfield