Quickbooks settings

This commit is contained in:
David Bomba 2024-08-26 08:24:51 +10:00
parent 7745d6db17
commit b43c9ee59b
5 changed files with 189 additions and 47 deletions

View File

@ -0,0 +1,36 @@
<?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\DataMapper;
/**
* QuickbooksSettings.
*/
class QuickbooksSettings
{
public string $accessTokenKey;
public string $refresh_token;
public string $realmID;
public int $accessTokenExpiresAt;
public int $refreshTokenExpiresAt;
/**
* entity client,invoice,quote,purchase_order,vendor,payment
* sync true/false
* update_record true/false
* direction push/pull/birection
* */
public array $settings = [];
}

View File

@ -118,7 +118,7 @@ use Laracasts\Presenter\PresentableTrait;
* @property string|null $smtp_port * @property string|null $smtp_port
* @property string|null $smtp_encryption * @property string|null $smtp_encryption
* @property string|null $smtp_local_domain * @property string|null $smtp_local_domain
* @property object|null $quickbooks * @property \App\DataMapper\QuickbooksSettings|null $quickbooks
* @property boolean $smtp_verify_peer * @property boolean $smtp_verify_peer
* @property-read \App\Models\Account $account * @property-read \App\Models\Account $account
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities

View File

@ -11,9 +11,12 @@
namespace App\Services\Import\Quickbooks; namespace App\Services\Import\Quickbooks;
use App\Factory\ClientFactory;
use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use QuickBooksOnline\API\Core\CoreConstants; use QuickBooksOnline\API\Core\CoreConstants;
use QuickBooksOnline\API\DataService\DataService; use QuickBooksOnline\API\DataService\DataService;
use App\Services\Import\Quickbooks\Transformers\ClientTransformer;
// quickbooks_realm_id // quickbooks_realm_id
// quickbooks_refresh_token // quickbooks_refresh_token
@ -21,12 +24,24 @@ use QuickBooksOnline\API\DataService\DataService;
class QuickbooksService class QuickbooksService
{ {
public DataService $sdk; public DataService $sdk;
private $entities = [
'client' => 'Customer',
'invoice' => 'Invoice',
'quote' => 'Estimate',
'purchase_order' => 'PurchaseOrder',
'payment' => 'Payment',
'product' => 'Item',
];
private bool $testMode = true; private bool $testMode = true;
private array $settings = [];
public function __construct(private Company $company) public function __construct(private Company $company)
{ {
$this->init(); $this->init();
$this->settings = $this->company->quickbooks->settings;
} }
private function init(): self private function init(): self
@ -64,14 +79,91 @@ class QuickbooksService
] : []; ] : [];
} }
public function getSdk(): DataService
{
return $this->sdk;
}
public function sdk(): SdkWrapper public function sdk(): SdkWrapper
{ {
return new SdkWrapper($this->sdk, $this->company); return new SdkWrapper($this->sdk, $this->company);
} }
/**
* //@todo - refactor to a job
*
* @return void
*/
public function sync()
{
//syncable_records.
foreach($this->entities as $entity)
{
$records = $this->sdk()->fetchRecords($entity);
$this->processEntitySync($entity, $records);
}
}
private function processEntitySync(string $entity, $records)
{
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),
};
}
private function syncQbToNinjaClients(array $records)
{
foreach($records as $record)
{
$ninja_client_data = new ClientTransformer($record);
if($client = $this->findClient($ninja_client_data))
{
$client->fill($ninja_client_data[0]);
}
}
}
private function findClient(array $qb_data)
{
$client = $qb_data[0];
$contact = $qb_data[1];
$client_meta = $qb_data[2];
$search = Client::query()
->withTrashed()
->where('company', $this->company->id)
->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) {
// ? sync / update
}
else {
//potentially multiple matching clients?
}
}
} }

View File

@ -154,12 +154,17 @@ class SdkWrapper
return (int)$this->sdk->Query("select count(*) from $entity"); return (int)$this->sdk->Query("select count(*) from $entity");
} }
private function queryData(string $query, int $start = 1, $limit = 100): array private function queryData(string $query, int $start = 1, $limit = 1000): array
{ {
return (array) $this->sdk->Query($query, $start, $limit); return (array) $this->sdk->Query($query, $start, $limit);
} }
public function fetchRecords(string $entity, int $max = 1000): array public function fetchById(string $entity, $id)
{
return $this->sdk->FindById($entity, $id);
}
public function fetchRecords(string $entity, int $max = 100000): array
{ {
if(!in_array($entity, $this->entities)) { if(!in_array($entity, $this->entities)) {
@ -168,7 +173,7 @@ class SdkWrapper
$records = []; $records = [];
$start = 0; $start = 0;
$limit = 100; $limit = 1000;
try { try {
$total = $this->totalRecords($entity); $total = $this->totalRecords($entity);
$total = min($max, $total); $total = min($max, $total);

View File

@ -12,61 +12,70 @@
namespace App\Services\Import\Quickbooks\Transformers; namespace App\Services\Import\Quickbooks\Transformers;
use App\DataMapper\ClientSettings;
/** /**
* Class ClientTransformer. * Class ClientTransformer.
*/ */
class ClientTransformer class ClientTransformer
{ {
private $fillable = [
'name' => 'CompanyName',
'phone' => 'PrimaryPhone.FreeFormNumber',
'country_id' => 'BillAddr.Country',
'state' => 'BillAddr.CountrySubDivisionCode',
'address1' => 'BillAddr.Line1',
'city' => 'BillAddr.City',
'postal_code' => 'BillAddr.PostalCode',
'shipping_country_id' => 'ShipAddr.Country',
'shipping_state' => 'ShipAddr.CountrySubDivisionCode',
'shipping_address1' => 'ShipAddr.Line1',
'shipping_city' => 'ShipAddr.City',
'shipping_postal_code' => 'ShipAddr.PostalCode',
'public_notes' => 'Notes'
];
public function __invoke($qb_data) public function __invoke($qb_data): array
{ {
return $this->transform($qb_data); return $this->transform($qb_data);
} }
public function transform($data): array
public function transform($data)
{ {
$transformed_data = [];
// Assuming 'customer_name' is equivalent to 'CompanyName'
if (isset($data['CompanyName']) && $this->hasClient($data['CompanyName'])) {
return false;
}
$transformed_data = $this->preTransform($data); $contact = [
$transformed_data['contacts'][0] = $this->getContacts($data)->toArray() + ['company_id' => $this->company->id, 'user_id' => $this->company->owner()->id ]; 'first_name' => data_get($data, 'GivenName'),
'last_name' => data_get($data, 'FamilyName'),
'phone' => data_get($data, 'PrimaryPhone.FreeFormNumber'),
'email' => data_get($data, 'PrimaryEmailAddr.Address'),
];
return $transformed_data; $client = [
'name' => data_get($data,'CompanyName', ''),
'address1' => data_get($data, 'BillAddr.Line1', ''),
'address2' => data_get($data, 'BillAddr.Line2', ''),
'city' => data_get($data, 'BillAddr.City', ''),
'country_id' => $this->resolveCountry(data_get($data, 'BillAddr.Country', '')),
'state' => data_get($data, 'BillAddr.CountrySubDivisionCode', ''),
'postal_code' => data_get($data, 'BillAddr.PostalCode', ''),
'shipping_address1' => data_get($data, 'ShipAddr.Line1', ''),
'shipping_address2' => data_get($data, 'ShipAddr.Line2', ''),
'shipping_city' => data_get($data, 'ShipAddr.City', ''),
'shipping_country_id' => $this->resolveCountry(data_get($data, 'ShipAddr.Country', '')),
'shipping_state' => data_get($data, 'ShipAddr.CountrySubDivisionCode', ''),
'shipping_postal_code' => data_get($data, 'BillAddr.PostalCode', ''),
'id_number' => data_get($data, 'Id', ''),
];
$settings = ClientSettings::defaults();
$settings->currency_id = (string) $this->resolveCurrency(data_get($data, 'CurrencyRef.value'));
$new_client_merge = [
'client_hash' => data_get($data, 'V4IDPseudonym', \Illuminate\Support\Str::random(32)),
'settings' => $settings,
];
return [$client, $contact, $new_client_merge];
} }
protected function getContacts($data) private function resolveCountry(string $iso_3_code)
{ {
return (new ClientContact())->fill([ return (string) app('countries')->first(function ($c) use ($iso_3_code){
'first_name' => $this->getString($data, 'GivenName'), return $c->iso_3166_3 == $iso_3_code;
'last_name' => $this->getString($data, 'FamilyName'), })->id ?? 840;
'phone' => $this->getString($data, 'PrimaryPhone.FreeFormNumber'),
'email' => $this->getString($data, 'PrimaryEmailAddr.Address'),
'company_id' => $this->company->id,
'user_id' => $this->company->owner()->id,
'send_email' => true,
]);
} }
private function resolveCurrency(string $currency_code)
{
return (string) app('currencies')->first(function($c) use ($currency_code){
return $c->code == $currency_code;
}) ?? 'USD';
}
public function getShipAddrCountry($data, $field) public function getShipAddrCountry($data, $field)
{ {