From cb7c87e053608c5dadefdb8cc1cd2c934cd76af5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 24 Sep 2024 14:15:11 +1000 Subject: [PATCH] Refactor for QB sync --- app/DataMapper/QuickbooksSyncMap.php | 16 +- app/Enum/SyncDirection.php | 19 ++ app/Http/Controllers/SearchController.php | 2 +- .../Quickbooks/Jobs/QuickbooksImport.php | 12 +- app/Services/Quickbooks/Models/QbClient.php | 5 +- app/Services/Quickbooks/Models/QbInvoice.php | 164 +++++++++++------- app/Services/Quickbooks/Models/QbProduct.php | 5 +- app/Services/Quickbooks/QuickbooksService.php | 24 +-- 8 files changed, 143 insertions(+), 104 deletions(-) create mode 100644 app/Enum/SyncDirection.php diff --git a/app/DataMapper/QuickbooksSyncMap.php b/app/DataMapper/QuickbooksSyncMap.php index 40250b61f103..800b2dfea633 100644 --- a/app/DataMapper/QuickbooksSyncMap.php +++ b/app/DataMapper/QuickbooksSyncMap.php @@ -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 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; } } - diff --git a/app/Enum/SyncDirection.php b/app/Enum/SyncDirection.php new file mode 100644 index 000000000000..537f32b9633b --- /dev/null +++ b/app/Enum/SyncDirection.php @@ -0,0 +1,19 @@ +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) { diff --git a/app/Services/Quickbooks/Jobs/QuickbooksImport.php b/app/Services/Quickbooks/Jobs/QuickbooksImport.php index 105c5e6ad898..16506102a3fa 100644 --- a/app/Services/Quickbooks/Jobs/QuickbooksImport.php +++ b/app/Services/Quickbooks/Jobs/QuickbooksImport.php @@ -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; diff --git a/app/Services/Quickbooks/Models/QbClient.php b/app/Services/Quickbooks/Models/QbClient.php index cf7b773622c7..c7c896150e3d 100644 --- a/app/Services/Quickbooks/Models/QbClient.php +++ b/app/Services/Quickbooks/Models/QbClient.php @@ -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; diff --git a/app/Services/Quickbooks/Models/QbInvoice.php b/app/Services/Quickbooks/Models/QbInvoice.php index 0cf18aba807a..e1181f86824a 100644 --- a/app/Services/Quickbooks/Models/QbInvoice.php +++ b/app/Services/Quickbooks/Models/QbInvoice.php @@ -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); + } + } } diff --git a/app/Services/Quickbooks/Models/QbProduct.php b/app/Services/Quickbooks/Models/QbProduct.php index f9fb2a64418c..6a9da37f5457 100644 --- a/app/Services/Quickbooks/Models/QbProduct.php +++ b/app/Services/Quickbooks/Models/QbProduct.php @@ -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)) diff --git a/app/Services/Quickbooks/QuickbooksService.php b/app/Services/Quickbooks/QuickbooksService.php index 9f3b2e1befc9..87a100ac1807 100644 --- a/app/Services/Quickbooks/QuickbooksService.php +++ b/app/Services/Quickbooks/QuickbooksService.php @@ -139,31 +139,17 @@ class QuickbooksService { return $this->sdk->FindById($entity, $id); } - + /** - * 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; } }