2024-08-21 05:22:46 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Invoice Ninja (https://invoiceninja.com).
|
|
|
|
*
|
|
|
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
|
|
|
*
|
|
|
|
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
|
|
|
*
|
|
|
|
* @license https://www.elastic.co/licensing/elastic-license
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace App\Services\Import\Quickbooks;
|
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
use App\Factory\ClientContactFactory;
|
2024-08-26 00:24:51 +02:00
|
|
|
use App\Factory\ClientFactory;
|
|
|
|
use App\Models\Client;
|
2024-08-21 05:22:46 +02:00
|
|
|
use App\Models\Company;
|
2024-08-23 06:17:37 +02:00
|
|
|
use QuickBooksOnline\API\Core\CoreConstants;
|
2024-08-21 05:22:46 +02:00
|
|
|
use QuickBooksOnline\API\DataService\DataService;
|
2024-08-26 00:24:51 +02:00
|
|
|
use App\Services\Import\Quickbooks\Transformers\ClientTransformer;
|
2024-08-21 05:22:46 +02:00
|
|
|
|
|
|
|
// quickbooks_realm_id
|
|
|
|
// quickbooks_refresh_token
|
|
|
|
// quickbooks_refresh_expires
|
|
|
|
class QuickbooksService
|
2024-08-22 08:45:06 +02:00
|
|
|
{
|
2024-08-23 06:17:37 +02:00
|
|
|
public DataService $sdk;
|
2024-08-26 00:24:51 +02:00
|
|
|
|
|
|
|
private $entities = [
|
|
|
|
'client' => 'Customer',
|
|
|
|
'invoice' => 'Invoice',
|
|
|
|
'quote' => 'Estimate',
|
|
|
|
'purchase_order' => 'PurchaseOrder',
|
|
|
|
'payment' => 'Payment',
|
|
|
|
'product' => 'Item',
|
|
|
|
];
|
2024-08-21 05:22:46 +02:00
|
|
|
|
2024-08-23 06:17:37 +02:00
|
|
|
private bool $testMode = true;
|
2024-08-21 05:22:46 +02:00
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
private mixed $settings;
|
2024-08-26 00:24:51 +02:00
|
|
|
|
2024-08-21 05:22:46 +02:00
|
|
|
public function __construct(private Company $company)
|
|
|
|
{
|
2024-08-23 06:17:37 +02:00
|
|
|
$this->init();
|
2024-08-26 00:24:51 +02:00
|
|
|
$this->settings = $this->company->quickbooks->settings;
|
2024-08-21 05:22:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private function init(): self
|
|
|
|
{
|
2024-08-22 08:45:06 +02:00
|
|
|
|
2024-08-23 06:17:37 +02:00
|
|
|
$config = [
|
2024-08-21 05:22:46 +02:00
|
|
|
'ClientID' => config('services.quickbooks.client_id'),
|
|
|
|
'ClientSecret' => config('services.quickbooks.client_secret'),
|
|
|
|
'auth_mode' => 'oauth2',
|
|
|
|
'scope' => "com.intuit.quickbooks.accounting",
|
2024-08-23 06:17:37 +02:00
|
|
|
// 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl',
|
2024-08-23 14:55:31 +02:00
|
|
|
'RedirectURI' => $this->testMode ? 'https://above-distinctly-teal.ngrok-free.app/quickbooks/authorized' : 'https://invoicing.co/quickbooks/authorized',
|
2024-08-23 06:17:37 +02:00
|
|
|
'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL,
|
|
|
|
];
|
2024-08-21 05:22:46 +02:00
|
|
|
|
2024-08-23 06:17:37 +02:00
|
|
|
$merged = array_merge($config, $this->ninjaAccessToken());
|
2024-08-23 14:55:31 +02:00
|
|
|
|
2024-08-23 06:17:37 +02:00
|
|
|
$this->sdk = DataService::Configure($merged);
|
|
|
|
|
|
|
|
$this->sdk->setLogLocation(storage_path("logs/quickbooks.log"));
|
|
|
|
$this->sdk->enableLog();
|
2024-08-21 05:22:46 +02:00
|
|
|
|
|
|
|
$this->sdk->setMinorVersion("73");
|
|
|
|
$this->sdk->throwExceptionOnError(true);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2024-08-23 06:17:37 +02:00
|
|
|
private function ninjaAccessToken()
|
2024-08-21 05:22:46 +02:00
|
|
|
{
|
2024-08-23 06:17:37 +02:00
|
|
|
return isset($this->company->quickbooks->accessTokenKey) ? [
|
|
|
|
'accessTokenKey' => $this->company->quickbooks->accessTokenKey,
|
|
|
|
'refreshTokenKey' => $this->company->quickbooks->refresh_token,
|
|
|
|
'QBORealmID' => $this->company->quickbooks->realmID,
|
|
|
|
] : [];
|
2024-08-21 05:22:46 +02:00
|
|
|
}
|
|
|
|
|
2024-08-26 00:24:51 +02:00
|
|
|
public function sdk(): SdkWrapper
|
2024-08-21 05:22:46 +02:00
|
|
|
{
|
2024-08-26 00:24:51 +02:00
|
|
|
return new SdkWrapper($this->sdk, $this->company);
|
2024-08-21 05:22:46 +02:00
|
|
|
}
|
2024-08-26 00:24:51 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* //@todo - refactor to a job
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2024-08-26 01:49:22 +02:00
|
|
|
public function syncFromQb()
|
2024-08-26 00:24:51 +02:00
|
|
|
{
|
|
|
|
//syncable_records.
|
2024-08-21 05:22:46 +02:00
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
foreach($this->entities as $key => $entity)
|
2024-08-26 00:24:51 +02:00
|
|
|
{
|
2024-08-26 01:49:22 +02:00
|
|
|
if(!$this->syncGate($key, 'pull'))
|
|
|
|
continue;
|
2024-08-26 00:24:51 +02:00
|
|
|
|
|
|
|
$records = $this->sdk()->fetchRecords($entity);
|
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
nlog($records);
|
|
|
|
|
|
|
|
$this->processEntitySync($key, $records);
|
2024-08-26 00:24:51 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
private function syncGate(string $entity, string $direction): bool
|
|
|
|
{
|
|
|
|
return (bool) $this->settings[$entity]['sync'] && in_array($this->settings[$entity]['direction'], [$direction,'bidirectional']);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function updateGate(string $entity): bool
|
|
|
|
{
|
|
|
|
return (bool) $this->settings[$entity]['sync'] && $this->settings[$entity]['update_record'];
|
|
|
|
}
|
|
|
|
|
|
|
|
private function harvestQbEntityName(string $entity): string
|
|
|
|
{
|
|
|
|
return $this->entities[$entity];
|
|
|
|
}
|
|
|
|
|
2024-08-26 00:24:51 +02:00
|
|
|
private function processEntitySync(string $entity, $records)
|
2024-08-21 05:22:46 +02:00
|
|
|
{
|
2024-08-26 01:49:22 +02:00
|
|
|
nlog($entity);
|
|
|
|
nlog($records);
|
2024-08-26 00:24:51 +02:00
|
|
|
match($entity){
|
|
|
|
'client' => $this->syncQbToNinjaClients($records),
|
|
|
|
// 'vendor' => $this->syncQbToNinjaClients($records),
|
|
|
|
// 'invoice' => $this->syncInvoices($records),
|
|
|
|
// 'quote' => $this->syncInvoices($records),
|
|
|
|
// 'purchase_order' => $this->syncInvoices($records),
|
|
|
|
// 'payment' => $this->syncPayment($records),
|
|
|
|
// 'product' => $this->syncItem($records),
|
2024-08-26 01:49:22 +02:00
|
|
|
default => false,
|
2024-08-26 00:24:51 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private function syncQbToNinjaClients(array $records)
|
|
|
|
{
|
2024-08-26 01:49:22 +02:00
|
|
|
nlog("qb => ninja");
|
|
|
|
|
|
|
|
$client_transformer = new ClientTransformer();
|
|
|
|
|
2024-08-26 00:24:51 +02:00
|
|
|
foreach($records as $record)
|
|
|
|
{
|
2024-08-26 01:49:22 +02:00
|
|
|
$ninja_client_data = $client_transformer->qbToNinja($record);
|
2024-08-26 00:24:51 +02:00
|
|
|
|
|
|
|
if($client = $this->findClient($ninja_client_data))
|
|
|
|
{
|
|
|
|
$client->fill($ninja_client_data[0]);
|
2024-08-26 01:49:22 +02:00
|
|
|
$client->saveQuietly();
|
|
|
|
|
|
|
|
$contact = $client->contacts()->where('email', $ninja_client_data[1]['email'])->first();
|
|
|
|
|
|
|
|
if(!$contact)
|
|
|
|
{
|
|
|
|
$contact = ClientContactFactory::create($this->company->id, $this->company->owner()->id);
|
|
|
|
$contact->client_id = $client->id;
|
|
|
|
$contact->send_email = true;
|
|
|
|
$contact->is_primary = true;
|
|
|
|
$contact->fill($ninja_client_data[1]);
|
|
|
|
$contact->saveQuietly();
|
|
|
|
}
|
|
|
|
elseif($this->updateGate('client')){
|
|
|
|
$contact->fill($ninja_client_data[1]);
|
|
|
|
$contact->saveQuietly();
|
|
|
|
}
|
|
|
|
|
2024-08-26 00:24:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
private function findClient(array $qb_data) :?Client
|
2024-08-26 00:24:51 +02:00
|
|
|
{
|
|
|
|
$client = $qb_data[0];
|
|
|
|
$contact = $qb_data[1];
|
|
|
|
$client_meta = $qb_data[2];
|
|
|
|
|
2024-08-26 01:49:22 +02:00
|
|
|
nlog($qb_data);
|
|
|
|
|
2024-08-26 00:24:51 +02:00
|
|
|
$search = Client::query()
|
|
|
|
->withTrashed()
|
2024-08-26 01:49:22 +02:00
|
|
|
->where('company_id', $this->company->id)
|
2024-08-26 00:24:51 +02:00
|
|
|
->where(function ($q) use ($client, $client_meta, $contact){
|
|
|
|
|
|
|
|
$q->where('client_hash', $client_meta['client_hash'])
|
|
|
|
->orWhere('id_number', $client['id_number'])
|
|
|
|
->orWhereHas('contacts', function ($q) use ($contact){
|
|
|
|
$q->where('email', $contact['email']);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if($search->count() == 0) {
|
|
|
|
//new client
|
|
|
|
$client = ClientFactory::create($this->company->id, $this->company->owner()->id);
|
|
|
|
$client->client_hash = $client_meta['client_hash'];
|
|
|
|
$client->settings = $client_meta['settings'];
|
|
|
|
|
|
|
|
return $client;
|
|
|
|
}
|
|
|
|
elseif($search->count() == 1) {
|
2024-08-26 01:49:22 +02:00
|
|
|
return $this->settings['client']['update_record'] ? $search->first() : null;
|
2024-08-26 00:24:51 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
//potentially multiple matching clients?
|
|
|
|
}
|
2024-08-21 05:22:46 +02:00
|
|
|
}
|
|
|
|
}
|