Coder Social home page Coder Social logo

exact-php-client's Introduction

Exact PHP Client

Run phpunit

PHP client library for the Exact Online API. This client lets you integrate with Exact Online, for example by:

  • creating and sending invoices,
  • add journal entries,
  • or upload received invoices.

This client uses the same naming and conventions as the Exact API itself, so best way to find out how this client works is by looking at the Exact Online documentation and API reference.

This library is created and maintained by Picqer. We are looking for PHP developers to join our team!

Composer install

Installing this Exact client for PHP can be done through Composer.

composer require picqer/exact-php-client

Usage

  1. Set up app at Exact App Center to retrieve credentials
  2. Authorize the integration from your app
  3. Parse callback and finish connection set up
  4. Use the library to do stuff

Steps 1 - 3 are only required once on set up.

Set up app at Exact App Center to retrieve credentials

Set up an App at the Exact App Center to retrieve your Client ID and Client Secret. You will also need to set the correct Callback URL for the oAuth dance to work.

Authorize the integration from your app

The code below is an example authorize() function.

$connection = new \Picqer\Financials\Exact\Connection();
$connection->setRedirectUrl('CALLBACK_URL'); // Same as entered online in the App Center
$connection->setExactClientId('CLIENT_ID');
$connection->setExactClientSecret('CLIENT_SECRET');
$connection->redirectForAuthorization();

This will redirect the user to Exact to login and authorize your integration with their account.

Parse callback and finish connection set up

Exact will redirect back to the callback url you provided. The callback will receive a code param. This is the authorization code for oAuth. Store this code.

Make a new connection to Exact so the library can exchange codes and fetch the accesstoken and refreshtoken. The accesstoken is a temporary token which allows for communication between your app and Exact. The refreshtoken is a token which is used to get a new accesstoken which also refreshes the refreshtoken. The library will settle all of this for you. The code below could be an general connect() function, which returns the api connection.

$connection = new \Picqer\Financials\Exact\Connection();
$connection->setRedirectUrl('CALLBACK_URL');
$connection->setExactClientId('CLIENT_ID');
$connection->setExactClientSecret('CLIENT_SECRET');

if (getValue('authorizationcode')) {
    // Retrieves authorizationcode from database
    $connection->setAuthorizationCode(getValue('authorizationcode'));
}

if (getValue('accesstoken')) {
    // Retrieves accesstoken from database
    $connection->setAccessToken(unserialize(getValue('accesstoken')));
}

if (getValue('refreshtoken')) {
    // Retrieves refreshtoken from database
    $connection->setRefreshToken(getValue('refreshtoken'));
}

if (getValue('expires_in')) {
    // Retrieves expires timestamp from database
    $connection->setTokenExpires(getValue('expires_in'));
}

// Make the client connect and exchange tokens
try {
    $connection->connect();
} catch (\Exception $e)
{
    throw new Exception('Could not connect to Exact: ' . $e->getMessage());
}

// Save the new tokens for next connections
setValue('accesstoken', serialize($connection->getAccessToken()));
setValue('refreshtoken', $connection->getRefreshToken());

// Optionally, save the expiry-timestamp. This prevents exchanging valid tokens (ie. saves you some requests)
setValue('expires_in', $connection->getTokenExpires());

// Optionally, set the lock and unlock callbacks to prevent multiple request for acquiring a new refresh token with the same refresh token.
$connection->setAcquireAccessTokenLockCallback('CALLBACK_FUNCTION');
$connection->setAcquireAccessTokenUnlockCallback('CALLBACK_FUNCTION');

About divisions (administrations)

By default the library will use the default administration of the user. This means that when the user switches administrations in Exact Online. The library will also start working with this administration.

Rate limits

Exact uses a minutely and daily rate limit. There are a maximum number of calls per day you can do per company, and to prevent bursting they have also implemented a limit per minute. This PR stores this information in the Connection and adds methods to read the rate limits so you can handle these as appropriate for your app. Exact documentation on rate limits is found here: https://support.exactonline.com/community/s/knowledge-base#All-All-DNO-Simulation-gen-apilimits

If you hit a rate limit, an ApiException will be thrown with code 429. At that point you can determine whether you've hit the minutely or the daily limit. If you've hit the minutely limit, try again after 60 seconds. If you've hit the daily limit, try again after the daily reset.

You can use the following methods on the Connection, which will return the limits after your first API call (based on the headers from Exact).

$connection->getDailyLimit(); // Retrieve your daily limit
$connection->getDailyLimitRemaining(); // Retrieve the remaining amount of API calls for this day
$connection->getDailyLimitReset(); // Retrieve the timestamp for when the limit will reset
$connection->getMinutelyLimit(); // Retrieve your limit per minute
$connection->getMinutelyLimitRemaining(); // Retrieve the amount of API calls remaining for this minute
$connection->getMinutelyLimitReset(); // Retrieve the timestamp for when the minutely limit will reset

Do note when you have no more minutely calls available, Exact only sends the Minutely Limit headers. So in that case, the Daily Limit headers will remain 0 until the minutely reset rolls over.

There is basic support to sleep upon hitting the minutely rate limits. If you enable "Wait on minutely rate limit hit", the client will sleep until the limit is reset. Daily limits are not considered.

$connection->setWaitOnMinutelyRateLimitHit(true);

Use the library to do stuff (examples)

// Optionally set administration, otherwise use the current administration of the user
$connection->setDivision(123456);

// Create a new account
$account = new \Picqer\Financials\Exact\Account($connection);
$account->AddressLine1 = 'Customers address line';
$account->AddressLine2 = 'Customer address line 2';
$account->City = 'Customer city';
$account->Code = 'Customer code';
$account->Country = 'Customer country';
$account->IsSales = 'true';
$account->Name = 'Customer name';
$account->Postcode = 'Customer postcode';
$account->Status = 'C';
$account->save();

// Add a product in Exact
$item = new \Picqer\Financials\Exact\Item($connection);
$item->Code = 'product code';
$item->CostPriceStandard = 2.50;
$item->Description = 'product description';
$item->IsSalesItem = true;
$item->SalesVatCode = 'VH';
$item->save();

// Retrieve an item by id
$item = new \Picqer\Financials\Exact\Item($connection);
$id = '097A82A9-6EF7-4EDC-8036-3F7559D9EF82';
$item->find($id);

// List items
$item = new \Picqer\Financials\Exact\Item($connection);
$item->get();

// List items with filter (using a filter always returns a collection) and loop through the result
$item = new \Picqer\Financials\Exact\Item($connection);
$items = $item->filter("Code eq '$item->Code'"); // Uses filters as described in Exact API docs (odata filters)
foreach($items as $itemObject){
    $attrs = (array) $itemObject->attributes(); // Turns the endpoint properties into an array
    $picture = $itemObject->download();         // Fetches an image string instead of the url
    // Do something with $attrs and or $picture, e.g. imagecreatefromstring($picture) 
}

// Create new invoice with invoice lines
$invoiceLines[] = [
    'Item'      => $item->ID,
    'Quantity'  => 1,
    'UnitPrice' => $item->CostPriceStandard
];

$salesInvoice = new \Picqer\Financials\Exact\SalesInvoice($connection);
$salesInvoice->InvoiceTo = $account->ID;
$salesInvoice->OrderedBy = $account->ID;
$salesInvoice->YourRef = 'Invoice reference';
$salesInvoice->SalesInvoiceLines = $invoiceLines;
$salesInvoice->save();

// Print and email the invoice
$printedInvoice = new \Picqer\Financials\Exact\PrintedSalesInvoice($connection);
$printedInvoice->InvoiceID = $salesInvoice->InvoiceID;
$printedInvoice->SendEmailToCustomer = true;
$printedInvoice->SenderEmailAddress = "[email protected]";
$printedInvoice->DocumentLayout = "401f3020-35cd-49a2-843a-d904df0c09ff";
$printedInvoice->ExtraText = "Some additional text";
$printedInvoice->save();

Use generators to prevent memory overflow

This package allows you to interact with the Exact API using PHP generators. This may be useful when you're retrieving large sets of data that are too big to load into memory all at once.

$item = new \Picqer\Financials\Exact\Item($connection);
$item->getAsGenerator();
$item->filterAsGenerator('IsWebshopItem eq 1');

Connect to other Exact country than NL

Choose the right base URL according to Exact developers guide

$connection = new \Picqer\Financials\Exact\Connection();
$connection->setRedirectUrl('CALLBACK_URL');
$connection->setExactClientId('CLIENT_ID');
$connection->setExactClientSecret('CLIENT_SECRET');
$connection->setBaseUrl('https://start.exactonline.de');

Check src/Picqer/Financials/Exact for all available entities.

Webhooks

Managaging webhook subscriptions is possible through the WebhookSubscription entitiy.

For authenticating incoming webhook calls you can use the Authenticatable trait. Supply the authenticate method with the full JSON request and your Webhook secret supplied by Exact, it will return true or false.

Troubleshooting

'Picqer\Financials\Exact\ApiException' with message 'Error 400: Please add a $select or a $top=1 statement to the query string.'

In specific instances, sadly not documented in the API documentation of Exact this is a requirement. Probably to prevent overflooding requests. What you have to do when encountering this error is adding a select or top. The select is used to provide a list of fields you want to extract, the $top=1 limits the results to one item.

Examples:

Return only the EntryID and FinancialYear.

$test = new \Picqer\Financials\Exact\GeneralJournalEntry($connection);
var_dump($test->filter('', '', 'EntryID, FinancialYear'));

The $top=1 is added like this:

$test = new \Picqer\Financials\Exact\GeneralJournalEntry($connection);
var_dump($test->filter('', '', '', ['$top'=> 1]));

Authentication error

'Fatal error: Uncaught Exception: Could not connect to Exact: Client error:POST https://start.exactonline.nl/api/oauth2/token resulted in a 400 Bad Request response: Bad Request in /var/www/html/oauth_call_connect.php:61 Stack trace: #0 {main} thrown in /var/www/html/oauth_call_connect.php on line 61`'

This error occurs because the code you get in your redirect URL is only valid for one call. When you call the authentication-process again with a "used" code. You get this error. Make sure you use the provided code by Exact Online only once to get your access token.

Code example

See for example: example/example.php

Guzzle versions

Guzzle 6 and 7 is supported starting from v3. For Guzzle 3 use v1.

TODO

  • Current entities do not contain all available properties. Feel free to submit a PR with added or extended entities if you require them. Use the userscript.js in greasemonkey or tampermonkey to generate entities consistently and completely.

exact-php-client's People

Contributors

alexjeen avatar ameenross avatar casperbakker avatar dannyvdsluijs avatar davidvandertuijn avatar dvdheiden avatar emaurits avatar it-can avatar jeff-99 avatar jordyvanderhaegen avatar kvij avatar lennartvdd avatar maartenwaegeman avatar marcelfw avatar markjongkind avatar mbm-peter avatar monkie avatar niekoost avatar paneidos avatar pdroge84 avatar remkobrenters avatar ruudk avatar spaggel avatar stephangroen avatar svenhaveman avatar tessbakker avatar tomcoonen avatar ttaelman avatar wietsewind avatar yohancreemers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exact-php-client's Issues

Attributes bug

Im trying to send a request like:

$StockPosition = new apis\ExactOnline\Exact\StockPosition($connection, array('ItemId' => '3a6700ce-fb14-477d-8ea9-fd73a2f8d858'));
$result = $StockPosition->get();

But it is not working, first of all, the attributes are not included.
Changed this in Query\Findable.php:

public function get()
{
$result = $this->connection()->get($this->url);
return $this->collectionFromResult($result);
}

to this:

public function get()
{
$result = $this->connection()->get($this->url, $this->attributes);
return $this->collectionFromResult($result);
}

And after that, i changed in Picqer\Financials\Exact\StockPosition the url to: read/logistics/StockPosition

Connection not working

Suddenly today the connection with exact stopped working and I am getting these errors:

Error - Could not connect to Exact: Client error: `POST https://start.exactonline.nl/api/oauth2/token` resulted in a `400 Bad Request`

and sometimes 

Error - Could not connect to Exact: Could not acquire tokens, json decode failed.

I think this line is producing the error:

https://github.com/picqer/exact-php-client/blob/master/src/Picqer/Financials/Exact/Connection.php#L426

I get a login screen from exact, this will redirect me to my own webpage with a code parameter and then it barfs the error...

I tried creating a new api key etc but the errors are the same... I cannot debug why this is not working, I don't know if this library is the problem or the Exact API... Hopefully you can help me

Exception after updating account

Hallo Stephan,

Bedankt voor deze code, het heeft mij een heel stuk op weg geholpen. Ik loop alleen tegen een ding aan bij het updaten en verwijderen van een record.

De wijzigingen worden wel doorgevoerd binnen Exact, maar na het verwerken van mijn opdracht krijg ik deze foutmelding.

ApiException in Connection.php line 498: array_key_exists() expects parameter 2 to be array, null given

Heb jij enig idee wat hier het probleem zou kunnen zijn?

Vriendelijke groet,

Michael

Cant connect to Exact

Hello,

i have an application on symfony 2.8 and want to connect to your api. The authorization success and i get the authorizationCode but when i want to get the Journals i am getting this error:

Could not connect to Exact: Client error: POST https://start.exactonline.de/api/oauth2/token resulted in a '400 Bad Request' response: Bad Request

My connect function:

public function connect() {
        $em = $this->em;
        //getting exact tokens from DB
        $clientConfig = $this->getConfig();

        $connection = new Connection();
        $connection->setRedirectUrl($this->exactCallbackUrl);
        $connection->setExactClientId($this->exactClientId);
        $connection->setExactClientSecret($this->exactClientSecret);
        $connection->setBaseUrl('https://start.exactonline.de');

        if ($clientConfig->getAuthorizationcode()) {
            $connection->setAuthorizationCode($clientConfig->getAuthorizationcode());
        } // Retrieves authorizationcode from database

        if (null !== $clientConfig->getAccesstoken()) {
            $connection->setAccessToken(unserialize($clientConfig->getAccesstoken()));
        }// Retrieves accesstoken from database

        if (null !== $clientConfig->getRefreshtoken()) {
            $connection->setRefreshToken($clientConfig->getRefreshtoken());
        }// Retrieves refreshtoken from database
        if (null !== $clientConfig->getExpiresIn()) { // Retrieves expires timestamp from database
            $connection->setTokenExpires($clientConfig->getExpiresIn());
        }
        // Make the client connect and exchange tokens
        try {
            $connection->connect();
        } catch (\Exception $e) {
            throw new Exception('Could not connect to Exact: ' . $e->getMessage());
        }
        // Save the new tokens for next connections
        $clientConfig->setAccesstoken(serialize($connection->getAccessToken()));
        $clientConfig->setRefreshtoken($connection->getRefreshToken());
        $clientConfig->setExpiresIn($connection->getTokenExpires());
        $clientConfig->setUpdateDate(new \DateTime());

        $em->persist($clientConfig);
        $em->flush();

        return $connection;
    }

An auhtorizationCode from the DB:

eTWA!IAAAAHCcr0YXhas8XRzqtKBc0xBCZGYUyNUVhcSj1jiX0W0vAQEAAAEyItD_eVi1LRgnj0O0aLN9E2L18zBYgYxZo5nBs4oG6k5aBtSjsP5sP0C-E8SEWVzlr7k_XS_xHHPe7S2Z9v285nYxHI3vhoUWM1wUeHJpCxNYLmmvhhOmZQ8TPFHT3bFr9jaTz45j88tx_tPDBcHJ3mxZt3L3VWZvWL3sBY4RBp09-AqIIgzwpGMJrdcE8CCN-LcXZBxOyc2t6cRSuVSOJZ8q9Ku7RJXjR2tPMSLWbL9qJ_IZr1rHBvXFcS0CAxHtH0xB1vaFCXBPapVsza2p86Ew2bpFas5sdDtbtMw_iOjvbw8_iVLhol-rcsKFZuqJ2hRzBk9H8YQWBC7kAp48

What i am doing wrong?

Small fix in documentation

Todo mentions:

  • Switch administration (now uses default)

However switching division already works as expected:

    $connection = \app\models\Exact::connect(\app\models\Credential::findOne(1));
    $connection->setDivision(123456);

    $account = new \Picqer\Financials\Exact\Account($connection);
    $account->Name = 'Alex';
    $account->Status = 'C';
    var_dump($account->save());

Webhook register

It's not possible to register webhook, i recivee this error,
Error 500: A problem has occurred. The cause of this issue will be investigated as soon as possible.

I try to fix that, changing in trait Storable.php

return $this->connection()->post($this->url, $this->json(0 TRUE));
into
return $this->connection()->post($this->url, $this->json(JSON_UNESCAPED_SLASHES, TRUE));

but still without result. Doeas anyoun try to register webhook thrue this library ?

The UPDATE / DELETE function seems to be a bit strange

I don't know if it's dependant on my PHP version, but I noticed some issues with the update / delete function in the Storable trait.
When I'm done, I'll file a PR for this.
If you want to research this in the mean while. Please see if changing lines 25 and 30 of Storable.php to:

        return $this->connection()->put($this->url . "(guid'{$this->{$this->primaryKey}}')", $this->json());

And

        return $this->connection()->delete($this->url . "(guid'{$this->{$this->primaryKey}}')");

Thanks ;-).

Forbidden request on specific exact environment

We successfully integrated the package in our application for quite a while now. Each user can use their own exact environment to log invoices, products, customers etc.

This was all working fine until recently, a specific user with a clean/newly created Exact environment get's a Forbidden response when we're trying to insert an invoice into their Exact environment. The OAuth procedure was succesful, we can even insert products and retrieve journals to work with.

I think it's probably a specific setting in Exact. Did anyone ever encounter the same issue?

Small stack trace:

  1. Picqer\Financials\Exact\SalesInvoice->save()
  2. Picqer\Financials\Exact\SalesInvoice->insert()
  3. Picqer\Financials\Exact\Connection->post('salesinvoice/Sa...', '{"Currency":"EU...')
  4. Request fails and is handled: Picqer\Financials\Exact\Connection>parseExceptionForErrorMessages(Object(GuzzleHttp\Exception\ClientException))

Dubbele entry

Hey Stephan,

Misschien had je 'm al gezien. In SalesEntryLine staat 2x AmountDC

Groeten!

Michael

Transaction lines

Question, how do we get transaction lines again?

//(where getTransactions is a wrapper to new Transactions($connection)
 $transactions = $this->exactClient->getTransactions()->filter('','','EntryNumber, ClosingBalanceFC, Description, Date, TransactionLines');

 foreach ($transactions as $transaction) {
var_dump($transaction->TransactionLines);
}

array(1) {
["__deferred"]=>
array(1) {
["uri"]=>
string(136) "https://start.exactonline.de/api/v1/31816/financialtransaction/Transactions(guid'2081821f-337f-45a1-ab62-fca677467846')/TransactionLines"
}
}

that URI leads me to an XML doc... (if i used $connection->get($the_above_url) , I get a '

401 - Unauthorized: Access is denied due to invalid credentials.

', which doesn't make sense as the client is able to ) any idea ?

How to add a "Boeking | Verkoopboek"

Hi,

We're trying to add a "Boeking | Verkoopboek".
I tried adding a SalesEntry but I'm stuck with the error: "Ongeldige referentie: Journals"

Is SalesEntry the correct model? And so, how can I fix the error above?
This is our current situation with the given error. Notice that I retrieve the journal id specifically to prevent any mismatch.

$journals = (new Journal($exact->connection()))->get();
$journal = array_filter($journals, function($journal) {
    return $journal->Code == 70;
});
$journal = array_shift($journal);

\Log::info($journal->ID);

$accountingServiceEntry = new SalesEntry($exact->connection(), [
    'Currency' => $invoice->valuta_id,
    'Customer' => $invoice->klant->accounting_service_id,
    'YourRef' => $invoice->nr,
    'Journal' => $journal->ID,
    'Description' => $description,
]);

$items = $invoice->getItems();

foreach($items as $invoiceItem) {
    $accountingServiceEntry->addItem([
        'Description' => $invoiceItem->omschr,
        'Quantity' => (int) $invoiceItem->aantal,
        'AmountFC' => $invoiceItem->bedrag,
        'VATPercentage' => $invoiceItem->btw_perc,
    ]);
}

\Log::info($accountingServiceEntry->save());

Cheers

Fetch more than 60 results

Hi,

I'm trying to fetch more than 60 results from the Exact API. I saw that you can limit the results using $top but how can I fetch a different "part" of the results.

Can't find anything in their documentation about pagination...

(Tried $bottom but that won't work obviously ^^)

Thanks.

Getting contact by account

I'm having a list of account and want to get the contact info that belongs to it.
Currently I'm trying something like:
$contact = new Contact($connection);
$info = $contact->filter('Account eq \''.$account->ID.'\'');

But this is not working, I'm getting the error:
Error 400: Operator 'eq' incompatible with operand types 'System.Nullable`1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' and 'System.String' at position 8.

When I output the results of $contact->get() it has a property Account that contains the account ID. So I should be able to get the contact info by Account ID right?

Guzzle Object could not be converted to string

I have used this library for some months now. Since 2 days, I am getting errors when connecting to Exact.
The first is:
Fatal error: Uncaught exception 'Exception' with message 'Could not connect to Exact: Client error: 'POST https://start.exactonline.nl/api/oauth2/token' resulted in a '400 Bad Request' response: 'Bad Request' in /vagrant/valvomo/include/application/class.Exact.php:901 Stack trace: #0 /vagrant/valvomo/include/application/class.Exact.php(40): Exact->connect(Array) #1 /vagrant/mijn/index.php(1040): Exact->index('10', Array) #2 {main} thrown in /vagrant/valvomo/include/application/class.Exact.php on line 901

After some debugging, I found:
Catchable fatal error: Object of class GuzzleHttp\Client could not be converted to string in /vagrant/vendor/picqer/exact-php-client/src/Picqer/Financials/Exact/Connection.php on line 114

But I have no clue how to fix it, going deeper is going into the src code of Guzzlehttp. Do you have a fix?

Representation after update,save

After save or add object exactonline api do not return info about codeid, and other data.

I fix this by changing in Connection.php headers from

$headers = array_merge($headers, [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json'
]);

into

$headers = array_merge($headers, [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json',
            'Prefer' => 'return=representation'
]);

Issue

Hi, I am getting this error message when using your example file... Can you help?

Exception [ Error ]:
Could not connect to Exact: Client error response [status code] 400 [reason phrase] Bad Request [url] https://start.exactonline.nl/api/oauth2/token

v3.0.0 release: incompatible with v2.15.0?

Does the new version number mean there are incompatibilities with the 2.* version? I could not find any information in the release notes in why a new major version number was chosen, does that mean that the changes are actually not that major?

Getting all or searching for clients

Hi there,

I'm completely new to Exact online. And I need to get all or search for clients in a application I'm making commissioned by my boss. Before downloading this and trying to make that work I want to ask if that is possible in the first place with this composer package? I can't seem to find anything like that in the examples.

I'm working with Laravel so this package would be ideal.

Thanks!

[RFC] Interest in Symfony bundle?

In short; great library, definitely the way to go for us to integrate with Exact.

Over the upcoming weeks we wil start implementing this, and the way we'd like to do this is by creating a bridge bundle to integrate this library with Symfony. The thing we're planning on:

  • Make Connection configurable via DIC
  • Double check codebase for PSR-3 compatibility, as Guzzle also require that
  • Inject logger into Connection and Guzzle client (I'd like to propose to go for Monolog)
  • Implement OptionResolver on the most important required fields of Connection
  • Decorate the Connection to make it a DataCollectingConnection that adds information to the profiler via a custom data collector.
  • Make profiler panel comparable to screenshots over here: https://github.com/ludofleury/GuzzleBundle#guzzle-bundle---
  • Inject stopwatch component to benchmark requests in the profiler performance metrics panel.
  • Replay requests directly from the profiler (?)
  • ...

Would love to hear your feedback on this.

Chicken-egg issue when trying to create SalesEntry

When adding a SalesEntry with addItem array item added I get an error Undefined index: ID
When trying to save a SalesEntryLine I need an EntryID
When trying to save a SalesEntry without items I get "No sales entry lines. Sales entry lines must be supplied to create a new sales entry."

How do I solve this chicken-egg conundrum?

Connection - apiURL, authUrl, tokenUrl are fixed to .nl Endpoints.

There's also no methods available to overwrite these values.

    /**
     * @var string
     */
    private $apiUrl = 'https://start.exactonline.nl/api/v1';

    /**
     * @var string
     */
    private $authUrl = 'https://start.exactonline.nl/api/oauth2/auth';

    /**
     * @var string
     */
    private $tokenUrl = 'https://start.exactonline.nl/api/oauth2/token';

Error 400, Creating new class

I get the following error when i try to create the class SalesItemPrice and use the get() function:

exception 'Picqer\Financials\Exact\ApiException' with message 'Error 400: Please add a $select or a $top=1 statement to the query string.'

This is the code for the class SalesItemPrice:

<?php namespace Picqer\Financials\Exact;

/**
 * Class SalesItemPrice
 *
 * @package Picqer\Financials\Exact
 * @see https://start.exactonline.nl/docs/HlpRestAPIResourcesDetails.aspx?name=LogisticsSalesItemPrices
 *
 * @property Guid $ID Primary key
 * @property Guid $Account ID of the customer
 * @property String $AccountName Name of the customer account
 * @property DateTime $Created Creation date
 * @property Guid $Creator User ID of creator
 * @property String $CreatorFullName Name of creator
 * @property String $Currency The currency of the price
 * @property String $DefaultItemUnit The default unit of the item
 * @property String $DefaultItemUnitDescription The description of the default item unit
 * @property Int32 $Division Division code
 * @property DateTime $EndDate Together with StartDate this determines if the item is active
 * @property Guid $Item Item ID
 * @property String $ItemCode Code of Item
 * @property String $ItemDescription Description of Item
 * @property DateTime $Modified Last modified date
 * @property Guid $Modifier User ID of modifier
 * @property String $ModifierFullName Name of modifier
 * @property Double $NumberOfItemsPerUnit This is the multiplication factor when going from default item unit to the unit of this price.For example if the default item unit is 'gram' and the price unit is 'kilogram' then the value of this property is 1000.
 * @property Double $Price The actual price of this sales item
 * @property Double $Quantity Minimum quantity to which the price is applicable
 * @property DateTime $StartDate Together with StartDate this determines if the item is active
 * @property String $Unit The unit code of the price
 * @property String $UnitDescription Description of the price unit
 */
class SalesItemPrice extends Model
{

    use Query\Findable;
    use Persistance\Storable;

    protected $fillable = [
        'ID',
        'Account',
        'AccountName',
        'Created',
        'Creator',
        'CreatorFullName',
        'Currency',
        'DefaultItemUnit',
        'DefaultItemUnitDescription',
        'Division',
        'EndDate',
        'Item',
        'ItemCode',
        'ItemDescription',
        'Modified',
        'Modifier',
        'ModifierFullName',
        'NumberOfItemsPerUnit',
        'Price',
        'Quantity',
        'StartDate',
        'Unit',
        'UnitDescription'
    ];

    protected $url = 'logistics/SalesItemPrices';

}

Adding XML posting and getting possibilities

As you might know Exact also has an XML option. This option is very handy for importing transactions in one go (you don't have to create the General ledgers and journals automatically as the XML will do this for you). So for some cases this is very easy because you can make less requests.

I couldn't find this in this library since this is focussed on the REST API but I really liked the way for authentication. So I wrote a small gist that can POST and GET XML:

https://gist.github.com/alexjeen/6211c363c4efd4c3034cb3f81f7520bf

It may or may not be the best way to keep this in connection but as you see it isn't really hard to add. So it's up to the maintainer of this library if you want to copy this in or not.

OrderBy

How can use the OrderBy ODATA function in a filter() request?

Fatal Error : 400 Bad Request

Hi,

I run into an error while making an connection. I use the example to make a connection and I got the following issue :

Fatal error: Uncaught Exception: Could not connect to Exact: Client error: POST https://start.exactonline.nl/api/oauth2/token` resulted in a 400 Bad Request response: Bad Request in /var/www/html/oauth_call_connect.php:61 Stack trace: #0 {main} thrown in /var/www/html/oauth_call_connect.php on line 61`

Any help?

find() needs a select variable?

When I want to update a purchaseEntry with a newly uploaded document, I get this following error:

Error 400: Please add a $select or a $top=1 statement to the query string.

I'm using this lines to fetch the right purchaseEntry.

$purchaseInvoice    = new \Picqer\Financials\Exact\PurchaseEntry($connection);
$purchaseInvoice    = $purchaseInvoice->find($purchaseInvoiceID);
$purchaseInvoice->Document = $documentID;
$purchaseInvoice->update();

When I change the find() function in Findable.php like this:

public function find($id, $select = '')
    {

        $result = $this->connection()->get($this->url, [
            '$filter' => $this->primaryKey . " eq guid'$id'",
            '$select' => $select
        ]);

        return new self($this->connection(), $result);
    }

Using this, it seems to work.

$purchaseInvoice    = new \Picqer\Financials\Exact\PurchaseEntry($connection);
$purchaseInvoice    = $purchaseInvoice->find($purchaseInvoiceID, 'EntryID, Document');
$purchaseInvoice->Document = $documentID;
$purchaseInvoice->update();

Division in formatUrl() defaults to CurrentDivision of Me

The following code (found here):

/**
     * @return mixed
     */
    private function getCurrentDivisionNumber()
    {
        if (empty($this->division)) {
            $me             = new Me($this);
            $this->division = $me->find()->CurrentDivision;
        }
        return $this->division;
    }

causes the URL that is created through formatUrl() (found here) to default to the CurrentDivision of the user that provided access to the API. When a user has access to multiple Divisions, and switches Divisions within the Exact Online webapp, the URL changes accordingly.

Wouldn't it be better to throw an Exception when $this->division is not explicitly set, to prevent accidental communication with the wrong Division?

Date handling

Unfortunately to late or 3.0 I wanted to add another possibly breaking change. The biggest question is how to go about it.
We get this weird '/Data(microtime)/' string what should be a DateTime object when working with it and than again transformed back in the json body.
The problem is that we have no good indication of what elements to transform. We could check for '/Date(' on each property in Model->fill() and perhaps fill another array so we don't have to look for DateTime objects in the Model->json() method. Or we could add some Type info in the Entity declarations like ['StartDate' => 'DateTime'] or even a separate property type hints.
Also some suggestions on where to place timezone information is welcome. Connection is the only long lived object in the current model. Perhaps we can trigger the DateTime object replacement only when timezone is set for backward compatibility.
Please let me know what you think!

For anyone reading this and in need of a solution now:

function exactToDateTime($exact) {
        $timestamp = substr($exact, 6, 10);
        $date = new \DateTime();
        $date->setTimestamp($timestamp);
        return $date;
}

Oauth via server

Is it possible to authenticate an application on server side, without using the web interface to login?

Too many redirects

Hi,

I've had my application running on my live environment. This worked perfectly fine.
Now I want to do some development locally (due to several reasons i can not use my live environment for this). However, when I transfered my application to my local environment and tested it I get the following error:
start.exactonline.nl redirected you too many times. Try clearing your cookies. ERR_TOO_MANY_REDIRECTS

Can anyone help me with figuring out what causes this? I can not find the problem.

I have of course changed the redirect urls to match with my local environment.

Kind regards

Selfmade class returns old price

Hi,

I've made a class named SalesItemPrice which I use to retrieve the sales price for an item, but the price returned is an old price. The price returned is 65 and the price in Exact is 95.

The code for the class i've made:

<?php namespace Picqer\Financials\Exact;

/**
 * Class SalesItemPrice
 *
 * @package Picqer\Financials\Exact
 * @see https://start.exactonline.nl/docs/HlpRestAPIResourcesDetails.aspx?name=LogisticsSalesItemPrices
 *
 * @property Guid $ID Primary key
 * @property Guid $Account ID of the customer
 * @property String $AccountName Name of the customer account
 * @property DateTime $Created Creation date
 * @property Guid $Creator User ID of creator
 * @property String $CreatorFullName Name of creator
 * @property String $Currency The currency of the price
 * @property String $DefaultItemUnit The default unit of the item
 * @property String $DefaultItemUnitDescription The description of the default item unit
 * @property Int32 $Division Division code
 * @property DateTime $EndDate Together with StartDate this determines if the item is active
 * @property Guid $Item Item ID
 * @property String $ItemCode Code of Item
 * @property String $ItemDescription Description of Item
 * @property DateTime $Modified Last modified date
 * @property Guid $Modifier User ID of modifier
 * @property String $ModifierFullName Name of modifier
 * @property Double $NumberOfItemsPerUnit This is the multiplication factor when going from default item unit to the unit of this price.For example if the default item unit is 'gram' and the price unit is 'kilogram' then the value of this property is 1000.
 * @property Double $Price The actual price of this sales item
 * @property Double $Quantity Minimum quantity to which the price is applicable
 * @property DateTime $StartDate Together with StartDate this determines if the item is active
 * @property String $Unit The unit code of the price
 * @property String $UnitDescription Description of the price unit
 */
class SalesItemPrice extends Model
{

    use Query\Findable;
    use Persistance\Storable;

    protected $fillable = [
        'ID',
        'Account',
        'AccountName',
        'Created',
        'Creator',
        'CreatorFullName',
        'Currency',
        'DefaultItemUnit',
        'DefaultItemUnitDescription',
        'Division',
        'EndDate',
        'Item',
        'ItemCode',
        'ItemDescription',
        'Modified',
        'Modifier',
        'ModifierFullName',
        'NumberOfItemsPerUnit',
        'Price',
        'Quantity',
        'StartDate',
        'Unit',
        'UnitDescription'
    ];

    protected $url = 'logistics/SalesItemPrices';

}

and here is the code that I use to retrieve the items:

$salesItemPrice = new \Picqer\Financials\Exact\SalesItemPrice($connection); $salesItemPrices = $salesItemPrice->filter('', '', 'Price, ItemCode');

Does anybody have any idea why it would return the old price and not the new one?

adding salesinvoice

Hi,
I've been tryin to add a salesinvoice in the same way you are doing it in the example, only by hardcoded ID's and stuff. Did you manage to get an invoice added?

The exception i get is :
{ "error": { "code": "", "message": { "lang": "", "value": "Verplicht: Factuur voor\r\nVerplicht: Besteld door" } } }

This is the code i used. $items is an array just like the way you showed in your example.
$salesinvoice = new \Picqer\Financials\Exact\SalesInvoice($connection);

    $salesinvoide->InvoiceTo = '8578a5d2-d4cf-48f2-a299-03ab75edcc74';    
    $salesinvoice->OrderedBy = '8578a5d2-d4cf-48f2-a299-03ab75edcc74';
    $salesinvoice->YourRef = '50098';
    $salesinvoice->Description = 'Beschrijving';
    $salesinvoice->SalesInvoiceLines = $items;
    $salesinvoice->save();

I've also reached out to exact online with this error, will update if i get it working with the solution they maybe provide.
Thanks

Creating a new class

I have created a new class 'ReportingBalance.php' using the code below. However, creating an instance of this class does not work.

Im trying to create the new instance using:
$RepBal = new \Picqer\Financials\Exact\ReportingBalance($connection);

And I have created the new class using:

<?php namespace Picqer\Financials\Exact;

class ReportingBalance extends Model
{    

    use Query\Findable;
    use Persistance\Storable;

    protected $fillable = [
        'ID',
        'Amount',
        'AmountCredit',
        'AmountDebit',
        'BalanceType',
        'CostCentercode',
        'CostCenterDescription',
        'CostUnitCode',
        'CostUnitDescription',
        'Count',
        'Division',
        'GLAccount',
        'GLAccountCode',
        'GLAccountDescription',
        'ReportingPeriod',
        'ReportingYear',
        'Status',
        'Type'        
    ];

    protected $url = 'financial/ReportingBalance';
}

Am I missing something in the creation of the class?

SalesOrder / SalesOrderLine API

I want to add SalesOrders to Exact Online. Will this be supported in the future?

I already tried adding SalesOrder.php and SalesOrderLine.php, based on the code from the SalesEntry files, but I can't get it to work yet. Adding a SalesOrder without SalesOrderLines returns an error telling me I need to add SalesOrderLines (so that's a good sign, ExactOnline seems to understand what I'm trying to do), but when I do add a SalesOrderLine before saving the SalesOrder it returns a 400 error.

Anybody have any suggestions on how to fix this?

Json output

Hi,
I'm using your library for a while now and i really like it. But i have a question about the output of a save action.
If i make a save action i get output that is protected so i can't really use it.
Is there a way to just get the json output from exact so i can use that for more actions?

regards,
Egbert

Ps, i wrote this in english but i am dutch, so if its more convenient to answer in dutch thats possible ;)

SalesEntryLine missing AmountFC

AmountFC is required by the Exact API for creating SalesEntryLines. However, in the $fillable property of the SalesEntryLine class it lists AmountDC instead of AmountFC.

The __call method of class Model prevents calling private method isFillable

The __call method of class Model (added in 8f4e812) prevents calling private method isFillable.

Code example:

$account = new Account($connection);
if ($account->isFillable('Description') )
{
	$account->Description = $sDescription;
}

Is there a reason why isFillable shouldn't be accessible on an instance?
Possible solutions to make the code example work again:

  1. Change isFillable in a public function, so it's call isn't handled by __call.
  2. Let __call test if $name doesn't refer to a method, before handling it as a property.

What's your opinion?

Find doesn't work for stockposition

The API docs for stockPosition (https://start.exactonline.nl/docs/HlpRestAPIResourcesDetails.aspx?name=ReadLogisticsStockPosition) states that the url to retrieve stock positions is like:
/api/v1/{division}/read/logistics/StockPosition?itemId=guid'00000000-0000-0000-0000-000000000000'

Currently this url is used:
/api/v1/{division}/read/logistics/StockPosition?%24filter=ItemId+eq+guid%2700000000-0000-0000-0000-000000000000%27
This results in a 400 error.

Probably exact-php-client/src/Picqer/Financials/Exact/Query/Findable.php should be edited on line 8-10 to account for this deviation, or StockPosition should not use the Findable mixin and define it's own method. (As StockPosition can also not be listed using get(), maybe this last method is best).

Currently I use:
$positions = $stock->filter([], '', '', ['itemId' => "guid'{$id}'"]);
$position = $positions[0];
which works.

Parsing Results

Hey, great work so far, works as expected and it is easy to handle.
However, I have a question which I can't answer myself.

I'm using your code like this:

$item = new \Picqer\Financials\Exact\SalesInvoice($connection);
$items = $item->filter("InvoiceToName eq 'Some name'");

And I get a response, however, do you offer something to parse it? Because it is returned as one long string and I can't access it via

$items["attributes"] 

or something like this.

cURL error 56: SSL read: error:1408F119:SSL routines:SSL3_GET_RECORD:decryption failed or bad record mac, errno 0

So after cache fail, a new one popped up for my clients. This time, I got no clue what is causing this, I tried reinstalling everything through composer, I restarted the services, checked my ssl cerificate. Nothing seems to have changed, anyone has this problem and a solution to it?

Fatal error: Uncaught exception 'Picqer\Financials\Exact\ApiException' with message 'cURL error 56: SSL read: error:1408F119:SSL routines:SSL3_GET_RECORD:decryption failed or bad record mac, errno 0 (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)' in /vagrant/vendor/picqer/exact-php-client/src/Picqer/Financials/Exact/Connection.php:526 Stack trace: #0 /vagrant/vendor/picqer/exact-php-client/src/Picqer/Financials/Exact/Connection.php(192): Picqer\Financials\Exact\Connection->parseExceptionForErrorMessages(Object(GuzzleHttp\Exception\RequestException)) #1 /vagrant/vendor/picqer/exact-php-client/src/Picqer/Financials/Exact/Query/Findable.php(52): Picqer\Financials\Exact\Connection->get('crm/Accounts') #2 /vagrant/valvomo/include/application/class.Exact.php(891): Picqer\Financials\Exact\Account->get() #3 /vagrant/valvomo/include/application/class.Exact.php(718): Exact->checkCustomer(Object(Picqer\Financials\Exact\Connection), Array) #4 /vagrant/mijn/index.php(1067): Exact->postToExact() #5 {main} thrown in /vagrant/vendor/picqer/exact-php-client/src/Picqer/Financials/Exact/Connection.php on line 526

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.