More updates to the API and code improvements

This commit is contained in:
Deon George 2023-05-12 22:41:53 +10:00
parent 4be82315c5
commit 2f1b34a806
11 changed files with 393 additions and 80 deletions

View File

@ -2,12 +2,14 @@
namespace Intuit;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Intuit\Models\ProviderToken;
use Intuit\Response\{Customer,ListList};
use Intuit\Exceptions\{ConnectionIssueException,InvalidQueryResultException};
use Intuit\Models\{ProviderToken};
use Intuit\Response\{Customer,Invoice,ListList};
final class API
{
@ -121,13 +123,23 @@ final class API
try {
$response = curl_exec($request);
switch($x=curl_getinfo($request,CURLINFO_HTTP_CODE)) {
switch ($x=curl_getinfo($request,CURLINFO_HTTP_CODE)) {
case 0:
switch (curl_errno($request)) {
// DNS Resolving issue
case 6:
throw new ConnectionIssueException(sprintf('%s:%s',self::LOGKEY,curl_error($request)));
default:
dump(['getinfo' => curl_getinfo($request),'curlerror' => curl_error($request),'errno' => curl_errno($request)]);
abort(500,'Error 0');
}
case 400:
case 401:
case 403:
case 404:
dump([$xx=curl_getinfo($request),'response'=>$response]);
dump([$xx=curl_getinfo($request),'response' => $response]);
throw new \Exception(sprintf('CURL exec returned %d: %s (%s)',$x,curl_error($request),serialize($xx)));
}
@ -139,6 +151,9 @@ final class API
return json_decode(self::CURLOPT_HEADER ? substr($response,curl_getinfo($request,CURLINFO_HEADER_SIZE)) : $response);
} catch (ConnectException|ConnectionIssueException $e) {
throw new ConnectionIssueException($e->getMessage());
} catch (\Exception $e) {
dump(['error'=>$e->getMessage()]);
Log::error(sprintf('%s:Got an error while posting to [%s] (%s)',static::LOGKEY,$url,$e->getMessage()),['m'=>__METHOD__]);
@ -151,6 +166,44 @@ final class API
return $result;
}
/**
* Get a list of our classes
*
* @param array $parameters
* @return ListList
* @throws \Exception
*/
public function getClasses(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of classes',static::LOGKEY));
$key = 'Class';
$parameters['query'] = 'select * from Class';
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Find a customer by their email address
*
* @param string $id
* @param array $parameters
* @return Customer
* @throws InvalidQueryResultException
*/
public function getAccountQuery(string $id,array $parameters=[]): Customer
{
Log::debug(sprintf('%s:Get a specific account [%s]',static::LOGKEY,$id));
$parameters['query'] = sprintf("select * from Customer where PrimaryEmailAddr='%s'",$id);
$x = $this->execute('query',$parameters);
if ((! $x->QueryResponse) || (! $x->QueryResponse->Customer) || (count($x->QueryResponse->Customer) !== 1))
throw new InvalidQueryResultException(sprintf('%s:Query response malformed',self::LOGKEY));
return new Customer($x->QueryResponse);
}
/**
* Get a specific customer record
*
@ -166,22 +219,6 @@ final class API
return new Customer($this->execute('customer/'.$id,$parameters));
}
/**
* Get a list of our classes
*
* @param array $parameters
* @return ListList
* @throws \Exception
*/
public function getClasses(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of classes',static::LOGKEY));
$key = 'classes';
$parameters['query'] = 'select * from Class';
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Get a list of our clients
*
@ -192,12 +229,34 @@ final class API
public function getCustomers(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of customers',static::LOGKEY));
$key = 'customers';
$key = 'Customer';
$parameters['query'] = 'select * from Customer';
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Find an invoice by its Document Number
*
* @param string $id
* @param array $parameters
* @return Invoice
* @throws InvalidQueryResultException
*/
public function getInvoiceQuery(string $id,array $parameters=[]): Invoice
{
Log::debug(sprintf('%s:Get a specific invoice [%s]',static::LOGKEY,$id));
$parameters['query'] = sprintf("select * from Invoice where DocNumber='%s'",$id);
$x = $this->execute('query',$parameters);
if ((! $x->QueryResponse) || (! $x->QueryResponse->Invoice) || (count($x->QueryResponse->Invoice) !== 1))
throw new InvalidQueryResultException(sprintf('%s:Query response malformed',self::LOGKEY));
return new Invoice($x->QueryResponse);
}
/**
* Get a list of our invoices
*
@ -208,7 +267,7 @@ final class API
public function getInvoices(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of invoices',static::LOGKEY));
$key = 'invoices';
$key = 'Invoice';
$parameters['query'] = 'select * from Invoice';
return new ListList($this->execute('query',$parameters),$key);
@ -224,12 +283,28 @@ final class API
public function getItems(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of items',static::LOGKEY));
$key = 'items';
$key = 'Item';
$parameters['query'] = 'select * from Item';
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Get a list of our invoices
*
* @param array $parameters
* @return ListList
* @throws \Exception
*/
public function getTaxCodes(array $parameters=[]): ListList
{
Log::debug(sprintf('%s:Get a list of taxes',static::LOGKEY));
$key = 'TaxCode';
$parameters['query'] = 'select * from Taxcode';
return new ListList($this->execute('query',$parameters),$key);
}
/**
* Setup the API call
*
@ -269,7 +344,21 @@ final class API
}
/**
* Update a customer
* Create/Update an invoice
*
* @param array $parameters
* @return Invoice
* @throws \Exception
*/
public function updateInvoice(array $parameters=[]): Invoice
{
Log::debug(sprintf('%s:Update invoice',static::LOGKEY),['params'=>$parameters]);
return new Invoice($this->execute('invoice',array_merge($parameters,['method'=>'POST'])));
}
/**
* Create/Update a customer
*
* @param array $parameters
* @return Customer

View File

@ -0,0 +1,9 @@
<?php
namespace Intuit\Exceptions;
use Exception;
class ConnectionIssueException extends Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Intuit\Exceptions;
use Exception;
class InvalidQueryResultException extends Exception
{
}

View File

@ -9,69 +9,73 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Intuit\Models\{Customer,ProviderToken};
use App\Models\Account as AppAccount;
class AccountingCustomerUpdate implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels;
private const LOGKEY = 'JAS';
private Customer $customer;
private Customer $o;
private ProviderToken $to;
private bool $forceprod;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ProviderToken $to,Customer $customer,bool $forcprod=FALSE)
{
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ProviderToken $to,Customer $item)
{
$this->onQueue('intuit');
$this->customer = $customer;
$this->o = $item;
$this->to = $to->withoutRelations();
$this->forceprod = $forcprod;
}
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Config::set('site',$this->to->site);
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$api = $this->to->API();
// Get the original account, so we can get the provider sync details
$o = $this->to->provider->accounts->where('id',$x=(int)substr($this->o->ResaleNum,3));
$o = ($o->count() === 1) ? $o->pop() : AppAccount::findOrFail($x);
$api = $this->to->provider->API($this->to,$this->forceprod);
$updated = $api->updateCustomer(array_merge(
$this->customer->getDirty(),
[
'Id'=>$this->customer->id,
'SyncToken'=>$this->customer->synctoken,
]));
$this->o->getDirty(),
$o->providers->count()
?
[
'Id'=>$o->pivot->ref,
'SyncToken'=>$o->pivot->synctoken,
]
:
[]
));
if (($x=$this->to->provider->accounts->where('pivot.ref',$updated->id))->count() === 1) {
$ao = $x->pop();
$ao->providers()->syncWithoutDetaching([
if ($updated instanceof \Intuit\Response\Customer) {
$o->providers()->syncWithoutDetaching([
$this->to->provider->id => [
'ref' => $updated->id,
'synctoken' => $updated->synctoken,
'created_at'=>Carbon::create($updated->created_at),
'updated_at'=>Carbon::create($updated->updated_at),
'site_id'=>$ao->site_id, // @todo See if we can have this handled automatically
'site_id'=>$o->site_id, // @todo See if we can have this handled automatically
],
]);
Log::info(sprintf('%s:Updated account [%s] (%s:%s), synctoken now [%s]',self::LOGKEY,$ao->sid,$updated->id,$updated->DisplayName,$updated->synctoken));
Log::info(sprintf('%s:Updated account [%s:%s] (%s:%s), synctoken now [%s]',self::LOGKEY,$this->to->provider->name,$o->sid,$updated->id,$updated->DisplayName,$updated->synctoken));
} else {
Log::error(sprintf('%s:Unable to update account refer for [%s:%s]',self::LOGKEY,$updated->id,$updated->DisplayName));
Log::error(sprintf('%s:Unable to update account with provider [%s] for [%s:%s]',self::LOGKEY,$this->to->provider->name,$updated->id,$updated->DisplayName),['updated'=>get_class($updated)]);
}
}
}
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace Intuit\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Intuit\Models\{Invoice,ProviderToken};
use App\Models\Invoice as AppInvoice;
class AccountingInvoiceUpdate implements ShouldQueue
{
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels;
private const LOGKEY = 'JIU';
private Invoice $o;
private ProviderToken $to;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ProviderToken $to,Invoice $item)
{
$this->onQueue('intuit');
$this->o = $item;
$this->to = $to->withoutRelations();
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$api = $this->to->API();
// Get the original invoice, so we can get the provider sync details
$o = $this->to->provider->invoices->where('id',$this->o->DocNumber);
$o = ($o->count() === 1) ? $o->pop() : AppInvoice::findOrFail($this->o->DocNumber);
$updated = $api->updateInvoice(array_merge(
$this->o->getDirty(),
$o->providers->count()
?
[
'Id'=>$o->pivot->ref,
'SyncToken'=>$o->pivot->synctoken,
]
:
[]
));
if ($updated instanceof \Intuit\Response\Invoice) {
$o->providers()->syncWithoutDetaching([
$this->to->provider->id => [
'ref' => $updated->id,
'synctoken' => $updated->synctoken,
'created_at'=>Carbon::create($updated->created_at),
'updated_at'=>Carbon::create($updated->updated_at),
'site_id'=>$o->site_id, // @todo See if we can have this handled automatically
],
]);
Log::info(sprintf('%s:Updated invoice [%s:%s] (%s:%s), synctoken now [%s]',self::LOGKEY,$this->to->provider->name,$o->sid,$updated->id,$updated->DocNumber,$updated->synctoken));
} else {
Log::error(sprintf('%s:Unable to update invoice with provider [%s] for [%s:%s]',self::LOGKEY,$this->to->provider->name,$updated->id,$updated->DisplayName),['updated'=>get_class($updated)]);
}
}
}

66
src/Models/Taxcode.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace Intuit\Models;
use Illuminate\Support\Arr;
use Jenssegers\Model\Model;
use Intuit\Traits\CompareAttributes;
/*
{#2052
+"Name": "GST"
+"Description": "GST"
+"Active": true
+"Hidden": false
+"Taxable": true
+"TaxGroup": true
+"SalesTaxRateList": {#2053
+"TaxRateDetail": array:1 [
0 => {#2054
+"TaxRateRef": {#2055
+"value": "23"
+"name": "GST (sales)"
}
+"TaxTypeApplicable": "TaxOnAmount"
+"TaxOrder": 0
}
]
}
+"PurchaseTaxRateList": {#2056
+"TaxRateDetail": []
}
+"TaxCodeConfigType": "SYSTEM_GENERATED"
+"domain": "QBO"
+"sparse": false
+"Id": "14"
+"SyncToken": "0"
+"MetaData": {#2057
+"CreateTime": "2023-05-10T01:05:41-07:00"
+"LastUpdatedTime": "2023-05-10T01:05:41-07:00"
}
}
*/
final class Taxcode extends Model
{
use CompareAttributes;
public function __get($key) {
$keymap = [
'id' => 'Id',
'synctoken' => 'SyncToken',
'name' => 'Name',
];
switch ($key) {
case 'created_at':
return object_get($this->getAttribute('MetaData'),'CreateTime');
case 'updated_at':
return object_get($this->getAttribute('MetaData'),'LastUpdatedTime');
default:
return parent::__get(Arr::get($keymap,$key,$key));
}
}
}

View File

@ -34,6 +34,10 @@ abstract class Base implements \JsonSerializable
}
}
public function __get($key) {
return $this->_model->__get($key);
}
/* ABSTRACT */
/**

View File

@ -2,6 +2,8 @@
namespace Intuit\Response;
use Intuit\Models\Customer as CustomerModel;
/**
* This is a Customer Intuit Response to API calls
*/
@ -16,10 +18,6 @@ class Customer extends Base
if (object_get($response,'time'))
unset($response->time);
$this->_model = new \Intuit\Models\Customer((array)$response->Customer);
}
public function __get($key) {
return $this->_model->__get($key);
$this->_model = new CustomerModel((array)$response->Customer);
}
}

23
src/Response/Invoice.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace Intuit\Response;
use Intuit\Models\Invoice as InvoiceModel;
/**
* This is an Invoice Intuit Response to API calls
*/
class Invoice extends Base
{
protected const LOGKEY = 'RII';
public function __construct(object $response)
{
parent::__construct($response);
if (object_get($response,'time'))
unset($response->time);
$this->_model = new InvoiceModel((array)$response->Invoice);
}
}

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Intuit\Models\{Category,Customer,Invoice,Item};
use Intuit\Models\{Category,Customer,Invoice,Item,Taxcode};
/**
* This is a Generic Intuit Response to API calls that produces a list of objects
@ -23,10 +23,11 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
private ?int $counter = NULL;
protected const TYPES = [
'classes' => 'Class',
'customers' => 'Customer',
'invoices' => 'Invoice',
'items' => 'Item',
'Class' => Category::class,
'Customer' => Customer::class,
'Invoice' => Invoice::class,
'Item' => Item::class,
'TaxCode' => Taxcode::class,
];
/**
@ -49,7 +50,7 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
$this->_data = $this->data($response,$type);
// This is only for child classes
if (get_class($this) == Base::class) {
if (get_class($this) === Base::class) {
Log::debug(sprintf('%s:Intuit RESPONSE Initialised [%s]',static::LOGKEY,get_class($this)),['m'=>__METHOD__]);
if (App::environment() == 'dev')
@ -66,7 +67,7 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
if (count($args) !== 2)
throw new \BadMethodCallException(sprintf('Pluck requires to arguments %s::%s()',get_class($this),$name));
return $this->_data->map(function($item) use ($args) { return [$item->{$args[1]} => $item->{$args[0]}]; })->flatMap(function($item) { return $item; });
return collect(array_replace(...$this->_data->map(function($item) use ($args) { return [$item->{$args[1]} => $item->{$args[0]}]; })));
default:
throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()',get_class($this),$name));
@ -142,27 +143,33 @@ class ListList extends Base implements \Countable, \ArrayAccess, \Iterator
*/
private function data(object $response,string $type): Collection
{
if (! ($x=object_get($response->QueryResponse,Arr::get(self::TYPES,$type))))
if (! ($x=object_get($response->QueryResponse,$type)))
return collect();
switch ($type) {
case 'classes':
switch (Arr::get(self::TYPES,$type)) {
case Category::class:
$data = collect(Category::hydrate($x));
break;
case 'customers':
case Customer::class:
$data = collect(Customer::hydrate($x));
break;
case 'invoices':
case Invoice::class:
$data = collect(Invoice::hydrate($x));
break;
case 'items':
case Item::class:
$data = collect(Item::hydrate($x));
break;
default: throw new \Exception('Unknown object type: '.$this->_type);
case Taxcode::class:
$data = collect(Taxcode::hydrate($x));
break;
default:
Log::error(sprintf('%s:Unknown object type: %s',self::LOGKEY,$type));
throw new \Exception(sprintf('%s:Unknown object type: %s',self::LOGKEY,$type));
}
$this->startPosition = $response->QueryResponse->startPosition;

23
src/Response/Taxcode.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace Intuit\Response;
use Intuit\Models\Tax as TaxModel;
/**
* This is an Invoice Intuit Response to API calls
*/
class Taxcode extends Base
{
protected const LOGKEY = 'RTI';
public function __construct(object $response)
{
parent::__construct($response);
if (object_get($response,'time'))
unset($response->time);
$this->_model = new TaxModel((array)$response->Tax);
}
}