Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
JorisDebonnet committed Jul 1, 2017
0 parents commit ce7ccc8
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 FunkyTime

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# funkytime/yuki
A php connector for Yuki's Sales API (subset), intended to create Sales Invoices.

```php
$invoice = [
'Reference' => '',
// ...
'Contact' => [
'ContactCode' => '',
// ...
]
'ContactPerson' => ['FullName' => ''],
'InvoiceLines' => [
'InvoiceLine' => [
'ProductQuantity' => '',
'Product' => [
'Description' => '',
// ...
]
]
]
];
];
new \FunkyTime\Yuki($api_key)->ProcessInvoice($invoice);
```
24 changes: 24 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "funkytime/yuki",
"description": "A php connector for Yuki's Sales API (subset), intended to create Sales Invoices.",
"license": "MIT",
"authors": [
{
"name": "FunkyTime",
"homepage": "https://funkytime.com"
}
],
"require": {
"php": "^5.3.3 || ^7.0"
},
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
}
},
"autoload": {
"psr-4": {
"FunkyTime\\": "src/"
}
}
}
185 changes: 185 additions & 0 deletions src/Yuki.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

namespace FunkyTime;

use Exception, SoapClient, SoapVar;

/**
* Connector for Yuki's Sales SOAP Webservice (subset), intended to create Sales Invoices.
*
* by FunkyTime.com
*
* Magic methods (passed to SOAP call):
* @method array Authenticate(array $params)
* @method array Administrations(array $params)
* @method array ProcessSalesInvoices(array $params)
*/
class Yuki
{
const SALES_WSDL = 'https://api.yukiworks.nl/ws/Sales.asmx?WSDL';

private $soap, // the SOAP client
// the currently active identifiers for the logged in Yuki user:
$sid, // SessionID
$aid; // AdministrationID

/**
* Wrapper for ProcessSalesInvoices. Will throw an Exception if it did not succeed (e.g. duplicate invoice number).
* Format the parameter like this:
* $invoice = [
* 'Reference' => '',
* ...
* 'Contact' => [
* 'ContactCode' => '',
* ...
* ]
* 'ContactPerson' => ['FullName' => ''],
* 'InvoiceLines' => [
* 'InvoiceLine' => [
* 'ProductQuantity' => '',
* 'Product' => [
* 'Description' => '',
* ...
* ]
* ]
* ]
* ];
* See http://www.yukiworks.nl/schemas/SalesInvoices.xsd, but note that only a subset is supported here (see code)
*
* @param array $invoice Invoice data in associative array, with matching Yuki keys
* @param bool $escaped Whether the data still needs to be trimmed and escaped for inclusion in XML tags
* @return mixed
* @throws Exception
*/
public function ProcessInvoice($invoice, $escaped = false) {
// This currently assumes that all array values are properly escaped
if(!$escaped) {
// todo
}

// General fields
$SalesInvoice = '';
foreach(['Reference', 'Subject', 'PaymentMethod', 'Date', 'DueDate', 'Currency', 'ProjectCode', 'Remarks'] as $k) {
if(!empty($invoice[$k])) $SalesInvoice .= "<$k>$invoice[$k]</$k>";
if($k === 'PaymentMethod') $SalesInvoice .= '<Process>true</Process>'; // invoice is marked as fully prepared
}

// Client (company)
$Contact = '';
foreach(['ContactCode', 'FullName', 'CountryCode', 'City', 'Zipcode', 'AddressLine_1', 'AddressLine_2', 'EmailAddress', 'VATNumber', 'ContactType'] as $k) {
if(!empty($invoice['Contact'][$k])) $Contact .= "<$k>{$invoice['Contact'][$k]}</$k>";
}
$SalesInvoice .= "<Contact>$Contact</Contact>";

if(!empty($invoice['ContactPerson']) && !empty($invoice['ContactPerson']['FullName'])) {
$SalesInvoice .= "<ContactPerson><FullName>{$invoice['ContactPerson']['FullName']}</FullName></ContactPerson>";
}

// Invoice lines
if(!empty($invoice['InvoiceLines'])) {
$InvoiceLines = [];
foreach($invoice['InvoiceLines'] as $InvoiceLine) {
$Product = '';
foreach(['Description', 'SalesPrice', 'VATPercentage', 'VATType', 'GLAccountCode', 'Remarks'] as $k) {
if(!empty($InvoiceLine['Product'][$k])) $Product .= "<$k>{$InvoiceLine['Product'][$k]}</$k>";
}
$InvoiceLines[] = "<InvoiceLine><ProductQuantity>$InvoiceLine[ProductQuantity]</ProductQuantity><Product>$Product</Product></InvoiceLine>";
}
$SalesInvoice .= '<InvoiceLines>'. implode($InvoiceLines) .'</InvoiceLines>';
}
// die($SalesInvoice);
// XML doc, and send it
$xmlvar = new SoapVar('<ns1:xmlDoc><SalesInvoices xmlns="urn:xmlns:http://www.theyukicompany.com:salesinvoices" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><SalesInvoice>'.
$SalesInvoice.
'</SalesInvoice></SalesInvoices></ns1:xmlDoc>', XSD_ANYXML);

$return = $this->ProcessSalesInvoices(['sessionId' => $this->sid, 'administrationId' => $this->aid, 'xmlDoc' => $xmlvar]);

// Check the result to see whether it was succesful
$result_xml = simplexml_load_string(current($return->ProcessSalesInvoicesResult));
if(!$result_xml->TotalSucceeded->__toString()) {
// None succeeded, so throw the error message
throw new Exception($result_xml->Invoice->Message);
}
return true; // success
}

/**
* Also called from constructor, if constructed with api key
* @param string $api_key Yuki API key
* @throws Exception If api key is not set
*/
public function login($api_key) {
if (!$api_key) {
throw new Exception('Yuki API key not set. Please check your company\'s settings. You can find or create a Yuki API key (of type Administration) under Settings > Webservices in Yuki.');
}

$this->sid = $this->sid($api_key);
$this->aid = $this->aid();
}

/**
* Get the AdministrationID
* @return string AdministrationID
* @throws Exception
*/
private function aid() {
// could maybe be saved to DB the first time, but an API key might later get attached to a different Administration...
$result = $this->Administrations(['sessionID' => $this->sid]);

// Save and return the result
try {
$xml = simplexml_load_string(current($result->AdministrationsResult));
return $this->aid = $xml->Administration->attributes()['ID'];
}
catch(Exception $e) {
throw new Exception('Yuki authentication failed. The API key works, but it does not seem to have access to any Administration.');
}
}

/**
* Get a sessionID
* @param string $api_key Yuki accessKey
* @return string sessionID
* @throws Exception
*/
private function sid($api_key) {
$result = $this->Authenticate(['accessKey' => $api_key]);

// Save and return the result
if ($result && !empty($result->AuthenticateResult)) {
return $this->sid = $result->AuthenticateResult;
}
else throw new Exception('Authentication failed. Please check your company\'s Yuki accessKey.');
}


/**
* Generic call method (it just passes it on to the SoapClient)
* @param string $method
* @param array $params
* @return object Response
* @throws Exception
*/
public function __call($method, $params) {
try{
$result = $this->soap->__soapCall($method, $params);
return $result;
}
catch(Exception $e) {
// rethrow with a little more information
throw new Exception("$method failed: [".$e->getCode().'] '.$e->getMessage(), $e->getCode());
}
}

/**
* Yuki constructor (creates the SOAP client).
*
* @param null|string $apikey If provided, will immediately connect
* @throws Exception If the SOAP client could not be instantiated, or the login failed
*/
public function __construct($apikey = null) {
$this->soap = new SoapClient(self::SALES_WSDL);
if($apikey) $this->login($apikey);
}
}

0 comments on commit ce7ccc8

Please sign in to comment.