mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Refactor QB
This commit is contained in:
parent
b43c9ee59b
commit
8f82b27e50
47
app/Casts/QuickbooksSettingsCast.php
Normal file
47
app/Casts/QuickbooksSettingsCast.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\DataMapper\QuickbooksSettings;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
class QuickbooksSettingsCast implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes)
|
||||
{
|
||||
$data = json_decode($value, true);
|
||||
|
||||
$qb = new QuickbooksSettings();
|
||||
$qb->accessTokenKey = $data['accessTokenKey'];
|
||||
$qb->refresh_token = $data['refresh_token'];
|
||||
$qb->realmID = $data['realmID'];
|
||||
$qb->accessTokenExpiresAt = $data['accessTokenExpiresAt'];
|
||||
$qb->refreshTokenExpiresAt = $data['refreshTokenExpiresAt'];
|
||||
$qb->settings = $data['settings'] ?? [];
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return [
|
||||
$key => json_encode([
|
||||
'accessTokenKey' => $value->accessTokenKey,
|
||||
'refresh_token' => $value->refresh_token,
|
||||
'realmID' => $value->realmID,
|
||||
'accessTokenExpiresAt' => $value->accessTokenExpiresAt,
|
||||
'refreshTokenExpiresAt' => $value->refreshTokenExpiresAt,
|
||||
'settings' => $value->settings,
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
@ -11,10 +11,13 @@
|
||||
|
||||
namespace App\DataMapper;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use App\Casts\QuickbooksSettingsCast;
|
||||
|
||||
/**
|
||||
* QuickbooksSettings.
|
||||
*/
|
||||
class QuickbooksSettings
|
||||
class QuickbooksSettings implements Castable
|
||||
{
|
||||
public string $accessTokenKey;
|
||||
|
||||
@ -30,7 +33,27 @@ class QuickbooksSettings
|
||||
* entity client,invoice,quote,purchase_order,vendor,payment
|
||||
* sync true/false
|
||||
* update_record true/false
|
||||
* direction push/pull/birection
|
||||
* direction push/pull/birectional
|
||||
* */
|
||||
public array $settings = [];
|
||||
public array $settings = [
|
||||
'client' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'invoice' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'quote' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array<string, mixed> $arguments
|
||||
*/
|
||||
public static function castUsing(array $arguments): string
|
||||
{
|
||||
return QuickbooksSettingsCast::class;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace App\Models;
|
||||
|
||||
use App\Casts\EncryptedCast;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\DataMapper\QuickbooksSettings;
|
||||
use App\Models\Presenters\CompanyPresenter;
|
||||
use App\Services\Company\CompanyService;
|
||||
use App\Services\Notification\NotificationService;
|
||||
@ -392,7 +393,7 @@ class Company extends BaseModel
|
||||
'smtp_username' => 'encrypted',
|
||||
'smtp_password' => 'encrypted',
|
||||
'e_invoice' => 'object',
|
||||
'quickbooks' => 'object',
|
||||
'quickbooks' => QuickbooksSettings::class,
|
||||
];
|
||||
|
||||
protected $with = [];
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Services\Import\Quickbooks;
|
||||
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
@ -36,7 +37,7 @@ class QuickbooksService
|
||||
|
||||
private bool $testMode = true;
|
||||
|
||||
private array $settings = [];
|
||||
private mixed $settings;
|
||||
|
||||
public function __construct(private Company $company)
|
||||
{
|
||||
@ -89,24 +90,44 @@ class QuickbooksService
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sync()
|
||||
public function syncFromQb()
|
||||
{
|
||||
//syncable_records.
|
||||
|
||||
foreach($this->entities as $entity)
|
||||
foreach($this->entities as $key => $entity)
|
||||
{
|
||||
if(!$this->syncGate($key, 'pull'))
|
||||
continue;
|
||||
|
||||
$records = $this->sdk()->fetchRecords($entity);
|
||||
|
||||
$this->processEntitySync($entity, $records);
|
||||
|
||||
nlog($records);
|
||||
|
||||
$this->processEntitySync($key, $records);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
private function processEntitySync(string $entity, $records)
|
||||
{
|
||||
nlog($entity);
|
||||
nlog($records);
|
||||
match($entity){
|
||||
'client' => $this->syncQbToNinjaClients($records),
|
||||
// 'vendor' => $this->syncQbToNinjaClients($records),
|
||||
@ -115,32 +136,57 @@ class QuickbooksService
|
||||
// 'purchase_order' => $this->syncInvoices($records),
|
||||
// 'payment' => $this->syncPayment($records),
|
||||
// 'product' => $this->syncItem($records),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
private function syncQbToNinjaClients(array $records)
|
||||
{
|
||||
nlog("qb => ninja");
|
||||
|
||||
$client_transformer = new ClientTransformer();
|
||||
|
||||
foreach($records as $record)
|
||||
{
|
||||
$ninja_client_data = new ClientTransformer($record);
|
||||
$ninja_client_data = $client_transformer->qbToNinja($record);
|
||||
|
||||
if($client = $this->findClient($ninja_client_data))
|
||||
{
|
||||
$client->fill($ninja_client_data[0]);
|
||||
$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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function findClient(array $qb_data)
|
||||
private function findClient(array $qb_data) :?Client
|
||||
{
|
||||
$client = $qb_data[0];
|
||||
$contact = $qb_data[1];
|
||||
$client_meta = $qb_data[2];
|
||||
|
||||
nlog($qb_data);
|
||||
|
||||
$search = Client::query()
|
||||
->withTrashed()
|
||||
->where('company', $this->company->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->where(function ($q) use ($client, $client_meta, $contact){
|
||||
|
||||
$q->where('client_hash', $client_meta['client_hash'])
|
||||
@ -160,7 +206,7 @@ class QuickbooksService
|
||||
return $client;
|
||||
}
|
||||
elseif($search->count() == 1) {
|
||||
// ? sync / update
|
||||
return $this->settings['client']['update_record'] ? $search->first() : null;
|
||||
}
|
||||
else {
|
||||
//potentially multiple matching clients?
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Services\Import\Quickbooks;
|
||||
|
||||
use App\DataMapper\QuickbooksSettings;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Company;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
@ -82,10 +83,10 @@ class SdkWrapper
|
||||
/**
|
||||
* Set Stored NinjaAccessToken
|
||||
*
|
||||
* @param mixed $token_object
|
||||
* @param QuickbooksSettings $token_object
|
||||
* @return self
|
||||
*/
|
||||
public function setNinjaAccessToken(mixed $token_object): self
|
||||
public function setNinjaAccessToken(QuickbooksSettings $token_object): self
|
||||
{
|
||||
$token = new OAuth2AccessToken(
|
||||
config('services.quickbooks.client_id'),
|
||||
@ -133,7 +134,7 @@ class SdkWrapper
|
||||
|
||||
public function saveOAuthToken(OAuth2AccessToken $token): void
|
||||
{
|
||||
$obj = new \stdClass();
|
||||
$obj = $this->company->quickbooks ?? new QuickbooksSettings();
|
||||
$obj->accessTokenKey = $token->getAccessToken();
|
||||
$obj->refresh_token = $token->getRefreshToken();
|
||||
$obj->accessTokenExpiresAt = Carbon::createFromFormat('Y/m/d H:i:s', $token->getAccessTokenExpiresAt())->timestamp; //@phpstan-ignore-line - QB phpdoc wrong types!!
|
||||
|
@ -20,12 +20,21 @@ use App\DataMapper\ClientSettings;
|
||||
class ClientTransformer
|
||||
{
|
||||
|
||||
public function __invoke($qb_data): array
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function qbToNinja(mixed $qb_data)
|
||||
{
|
||||
return $this->transform($qb_data);
|
||||
}
|
||||
|
||||
public function transform($data): array
|
||||
public function ninjaToQb()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function transform(mixed $data): array
|
||||
{
|
||||
|
||||
$contact = [
|
||||
@ -33,7 +42,7 @@ class ClientTransformer
|
||||
'last_name' => data_get($data, 'FamilyName'),
|
||||
'phone' => data_get($data, 'PrimaryPhone.FreeFormNumber'),
|
||||
'email' => data_get($data, 'PrimaryEmailAddr.Address'),
|
||||
];
|
||||
];
|
||||
|
||||
$client = [
|
||||
'name' => data_get($data,'CompanyName', ''),
|
||||
@ -49,7 +58,7 @@ class ClientTransformer
|
||||
'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', ''),
|
||||
'id_number' => data_get($data, 'Id.value', ''),
|
||||
];
|
||||
|
||||
$settings = ClientSettings::defaults();
|
||||
@ -65,16 +74,20 @@ class ClientTransformer
|
||||
|
||||
private function resolveCountry(string $iso_3_code)
|
||||
{
|
||||
return (string) app('countries')->first(function ($c) use ($iso_3_code){
|
||||
$country = app('countries')->first(function ($c) use ($iso_3_code){
|
||||
return $c->iso_3166_3 == $iso_3_code;
|
||||
})->id ?? 840;
|
||||
});
|
||||
|
||||
return $country ? (string) $country->id : '840';
|
||||
}
|
||||
|
||||
private function resolveCurrency(string $currency_code)
|
||||
{
|
||||
return (string) app('currencies')->first(function($c) use ($currency_code){
|
||||
$currency = app('currencies')->first(function($c) use ($currency_code){
|
||||
return $c->code == $currency_code;
|
||||
}) ?? 'USD';
|
||||
});
|
||||
|
||||
return $currency ? (string) $currency->id : '1';
|
||||
}
|
||||
|
||||
public function getShipAddrCountry($data, $field)
|
Loading…
Reference in New Issue
Block a user