Refactor for QB sync

This commit is contained in:
David Bomba 2024-09-24 14:15:11 +10:00
parent 8fadba5545
commit cb7c87e053
8 changed files with 143 additions and 104 deletions

View File

@ -11,32 +11,20 @@
namespace App\DataMapper;
enum SyncDirection: string
{
case PUSH = 'push';
case PULL = 'pull';
case BIDIRECTIONAL = 'bidirectional';
}
use App\Enum\SyncDirection;
/**
* QuickbooksSyncMap.
*/
class QuickbooksSyncMap
{
public bool $sync = true;
public bool $update_record = true;
public SyncDirection $direction = SyncDirection::BIDIRECTIONAL;
public function __construct(array $attributes = [])
{
$this->sync = $attributes['sync'] ?? true;
$this->update_record = $attributes['update_record'] ?? true;
$this->direction = isset($attributes['direction'])
? SyncDirection::from($attributes['direction'])
: SyncDirection::BIDIRECTIONAL;
}
}

View File

@ -0,0 +1,19 @@
<?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\Enum;
enum SyncDirection: string
{
case PUSH = 'push'; // only creates and updates records created by Invoice Ninja.
case PULL = 'pull'; // creates and updates record from QB.
case BIDIRECTIONAL = 'bidirectional'; // creates and updates records created by Invoice Ninja and from QB.
}

View File

@ -28,7 +28,7 @@ class SearchController extends Controller
public function __invoke(GenericSearchRequest $request)
{
if(Ninja::isHosted() && $request->has('search') && $request->input('search') !== '') {
if(config('scount.driver') == 'elastic' && $request->has('search') && $request->input('search') !== '') {
try{
return $this->search($request->input('search', ''));
} catch(\Exception $e) {

View File

@ -84,7 +84,7 @@ class QuickbooksImport implements ShouldQueue
foreach($this->entities as $key => $entity) {
if(!$this->qbs->syncGate($key, 'pull')) {
if(!$this->qbs->syncable($key, \App\Enum\SyncDirection::PULL)) {
nlog('skipping ' . $key);
continue;
}
@ -153,7 +153,7 @@ class QuickbooksImport implements ShouldQueue
$contact->fill($ninja_data[1]);
$contact->saveQuietly();
}
elseif($this->qbs->updateGate('vendor')){
elseif($this->qbs->syncable('vendor', (\App\Enum\SyncDirection::PULL)->value)){
$contact->fill($ninja_data[1]);
$contact->saveQuietly();
}
@ -194,7 +194,7 @@ class QuickbooksImport implements ShouldQueue
return ExpenseFactory::create($this->company->id, $this->company->owner()->id);
}
elseif($search->count() == 1) {
return $this->settings->expense->update_record ? $search->first() : null;
return $this->service->syncable('expense', \App\Enum\SyncDirection::PULL) ? $search->first() : null;
}
return null;
@ -224,7 +224,8 @@ class QuickbooksImport implements ShouldQueue
return VendorFactory::create($this->company->id, $this->company->owner()->id);
}
elseif($search->count() == 1) {
return $this->settings->vendor->update_record ? $search->first() : null;
return $this->service->syncable('vendor', \App\Enum\SyncDirection::PULL) ? $search->first() : null;
}
return null;
@ -258,7 +259,8 @@ class QuickbooksImport implements ShouldQueue
return $client;
}
elseif($search->count() == 1) {
return $this->settings->client->update_record ? $search->first() : null;
return $this->service->syncable('client', \App\Enum\SyncDirection::PULL) ? $search->first() : null;
}
return null;

View File

@ -69,7 +69,7 @@ class QbClient implements SyncInterface
$contact->fill($ninja_data[1]);
$contact->saveQuietly();
}
elseif($this->service->updateGate('client')){
elseif($this->service->syncable('client', \App\Enum\SyncDirection::PULL)){
$contact->fill($ninja_data[1]);
$contact->saveQuietly();
}
@ -101,7 +101,8 @@ class QbClient implements SyncInterface
return $client;
} elseif ($search->count() == 1) {
return $this->service->settings->client->update_record ? $search->first() : null;
return $this->service->syncable('client', \App\Enum\SyncDirection::PULL) ? $search->first() : null;
}
return null;

View File

@ -43,58 +43,7 @@ class QbInvoice implements SyncInterface
foreach ($records as $record) {
$ninja_invoice_data = $this->invoice_transformer->qbToNinja($record);
$payment_ids = $ninja_invoice_data['payment_ids'] ?? [];
$client_id = $ninja_invoice_data['client_id'] ?? null;
if (is_null($client_id)) {
continue;
}
unset($ninja_invoice_data['payment_ids']);
if ($invoice = $this->findInvoice($ninja_invoice_data['id'], $ninja_invoice_data['client_id'])) {
if($invoice->id)
$this->processQbToNinjaInvoiceUpdate($ninja_invoice_data, $invoice);
$invoice->fill($ninja_invoice_data);
$invoice->saveQuietly();
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->createInvitations()->save();
foreach ($payment_ids as $payment_id) {
$payment = $this->service->sdk->FindById('Payment', $payment_id);
$payment_transformer = new PaymentTransformer($this->service->company);
$transformed = $payment_transformer->qbToNinja($payment);
$ninja_payment = $payment_transformer->buildPayment($payment);
$ninja_payment->service()->applyNumber()->save();
$paymentable = new \App\Models\Paymentable();
$paymentable->payment_id = $ninja_payment->id;
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $transformed['applied'] + $ninja_payment->credits->sum('amount');
$paymentable->created_at = $ninja_payment->date; //@phpstan-ignore-line
$paymentable->save();
$invoice->service()->applyPayment($ninja_payment, $paymentable->amount);
}
if ($record instanceof IPPSalesReceipt) {
$invoice->service()->markPaid()->save();
}
}
$ninja_invoice_data = false;
$this->syncNinjaInvoice($record);
}
@ -105,7 +54,7 @@ class QbInvoice implements SyncInterface
}
private function processQbToNinjaInvoiceUpdate(array $ninja_invoice_data, Invoice $invoice): void
private function qbInvoiceUpdate(array $ninja_invoice_data, Invoice $invoice): void
{
$current_ninja_invoice_balance = $invoice->balance;
$qb_invoice_balance = $ninja_invoice_data['balance'];
@ -130,7 +79,7 @@ class QbInvoice implements SyncInterface
->where('company_id', $this->service->company->id)
->where('sync->qb_id', $id);
if($search->count() == 0 && $client_id) {
if($search->count() == 0) {
$invoice = InvoiceFactory::create($this->service->company->id, $this->service->company->owner()->id);
$invoice->client_id = $client_id;
@ -140,32 +89,125 @@ class QbInvoice implements SyncInterface
return $invoice;
} elseif($search->count() == 1) {
return $this->service->settings->invoice->update_record ? $search->first() : null;
return $this->service->syncable('invoice', \App\Enum\SyncDirection::PULL) ? $search->first() : null;
}
return null;
}
public function sync(string $id, string $last_updated): void
public function sync($id, string $last_updated): void
{
$qb_record = $this->find($id);
if($this->service->updateGate('invoice') && $invoice = $this->findInvoice($id))
if($this->service->syncable('invoice', \App\Enum\SyncDirection::PULL))
{
//logic here to determine if we should update the record
if(Carbon::parse($last_updated)->gt(Carbon::parse($invoice->updated_at)))
$invoice = $this->findInvoice($id);
if(data_get($qb_record, 'TxnStatus') === 'Voided')
{
$this->delete($id);
return;
}
if(!$invoice->id){
$this->syncNinjaInvoice($qb_record);
}
elseif(Carbon::parse($last_updated)->gt(Carbon::parse($invoice->updated_at)))
{
$ninja_invoice_data = $this->invoice_transformer->qbToNinja($qb_record);
$this->invoice_repository->save($ninja_invoice_data, $invoice);
}
// }
}
}
/**
* syncNinjaInvoice
*
* @param $record
* @return void
*/
public function syncNinjaInvoice($record): void
{
$ninja_invoice_data = $this->invoice_transformer->qbToNinja($record);
$payment_ids = $ninja_invoice_data['payment_ids'] ?? [];
$client_id = $ninja_invoice_data['client_id'] ?? null;
if (is_null($client_id)) {
return;
}
unset($ninja_invoice_data['payment_ids']);
if ($invoice = $this->findInvoice($ninja_invoice_data['id'], $ninja_invoice_data['client_id'])) {
if ($invoice->id) {
$this->qbInvoiceUpdate($ninja_invoice_data, $invoice);
}
//new invoice scaffold
$invoice->fill($ninja_invoice_data);
$invoice->saveQuietly();
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->applyNumber()->createInvitations()->save();
foreach ($payment_ids as $payment_id) {
$payment = $this->service->sdk->FindById('Payment', $payment_id);
$payment_transformer = new PaymentTransformer($this->service->company);
$transformed = $payment_transformer->qbToNinja($payment);
$ninja_payment = $payment_transformer->buildPayment($payment);
$ninja_payment->service()->applyNumber()->save();
$paymentable = new \App\Models\Paymentable();
$paymentable->payment_id = $ninja_payment->id;
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $transformed['applied'] + $ninja_payment->credits->sum('amount');
$paymentable->created_at = $ninja_payment->date; //@phpstan-ignore-line
$paymentable->save();
$invoice->service()->applyPayment($ninja_payment, $paymentable->amount);
}
if ($record instanceof IPPSalesReceipt) {
$invoice->service()->markPaid()->save();
}
}
$ninja_invoice_data = false;
}
/**
* Deletes the invoice from Ninja and sets the sync to null
*
* @param string $id
* @return void
*/
public function delete($id): void
{
$qb_record = $this->find($id);
if($this->service->syncable('invoice', \App\Enum\SyncDirection::PULL) && $invoice = $this->findInvoice($id))
{
$invoice->sync = null;
$invoice->saveQuietly();
$this->invoice_repository->delete($invoice);
}
}
}

View File

@ -73,7 +73,8 @@ class QbProduct implements SyncInterface
return $product;
} elseif($search->count() == 1) {
return $this->service->settings->product->update_record ? $search->first() : null;
return $this->service->syncable('product', \App\Enum\SyncDirection::PULL) ? $search->first() : null;
}
return null;
@ -84,7 +85,7 @@ class QbProduct implements SyncInterface
{
$qb_record = $this->find($id);
if($this->service->updateGate('product') && $ninja_record = $this->findProduct($id))
if($this->service->syncable('product', \App\Enum\SyncDirection::PULL) && $ninja_record = $this->findProduct($id))
{
if(Carbon::parse($last_updated) > Carbon::parse($ninja_record->updated_at))

View File

@ -141,29 +141,15 @@ class QuickbooksService
}
/**
* Updates the gate for a given entity
* Flag to determine if a sync is allowed in either direction
*
* @param string $entity
* @param mixed $direction
* @return bool
*/
public function updateGate(string $entity): bool
public function syncable(string $entity, \App\Enum\SyncDirection $direction): bool
{
nlog($this->settings->{$entity}->sync);
nlog($this->settings->{$entity}->update_record);
return $this->settings->{$entity}->sync && $this->settings->{$entity}->update_record;
}
/**
* Determines whether a sync is allowed based on the settings
*
* @param string $entity
* @param string $direction
* @return bool
*/
public function syncGate(string $entity, string $direction): bool
{
return (bool) $this->settings->{$entity}->sync && in_array($this->settings->{$entity}->direction->value, [$direction, 'bidirectional']);
return $this->settings->{$entity}->direction === $direction || $this->settings->{$entity}->direction === \App\Enum\SyncDirection::BIDIRECTIONAL;
}
}