Invoice Sync

This commit is contained in:
David Bomba 2024-09-24 12:46:16 +10:00
parent 6f3e56a2a2
commit 8fadba5545
4 changed files with 104 additions and 26 deletions

View File

@ -53,10 +53,10 @@ class QuickbooksImport implements ShouldQueue
'product' => 'Item', 'product' => 'Item',
'client' => 'Customer', 'client' => 'Customer',
'invoice' => 'Invoice', 'invoice' => 'Invoice',
'sales' => 'SalesReceipt',
// 'quote' => 'Estimate', // 'quote' => 'Estimate',
// 'purchase_order' => 'PurchaseOrder', // 'purchase_order' => 'PurchaseOrder',
// 'payment' => 'Payment', // 'payment' => 'Payment',
'sales' => 'SalesReceipt',
// 'vendor' => 'Vendor', // 'vendor' => 'Vendor',
// 'expense' => 'Purchase', // 'expense' => 'Purchase',
]; ];

View File

@ -11,33 +11,39 @@
namespace App\Services\Quickbooks\Models; namespace App\Services\Quickbooks\Models;
use Carbon\Carbon;
use App\Models\Invoice; use App\Models\Invoice;
use App\DataMapper\InvoiceSync;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Interfaces\SyncInterface; use App\Interfaces\SyncInterface;
use App\Repositories\InvoiceRepository;
use App\Services\Quickbooks\QuickbooksService; use App\Services\Quickbooks\QuickbooksService;
use App\Services\Quickbooks\Transformers\InvoiceTransformer; use App\Services\Quickbooks\Transformers\InvoiceTransformer;
use App\Services\Quickbooks\Transformers\PaymentTransformer; use App\Services\Quickbooks\Transformers\PaymentTransformer;
class QbInvoice implements SyncInterface class QbInvoice implements SyncInterface
{ {
protected InvoiceTransformer $transformer; protected InvoiceTransformer $invoice_transformer;
protected InvoiceRepository $invoice_repository;
public function __construct(public QuickbooksService $service) public function __construct(public QuickbooksService $service)
{ {
$this->transformer = new InvoiceTransformer($this->service->company); $this->invoice_transformer = new InvoiceTransformer($this->service->company);
$this->invoice_repository = new InvoiceRepository();
} }
public function find(int $id): mixed public function find(string $id): mixed
{ {
return $this->service->sdk->FindById('Invoice', $id); return $this->service->sdk->FindById('Invoice', $id);
} }
public function syncToNinja(array $records): void public function syncToNinja(array $records): void
{ {
foreach ($records as $record) { foreach ($records as $record) {
$ninja_invoice_data = $this->transformer->qbToNinja($record); $ninja_invoice_data = $this->invoice_transformer->qbToNinja($record);
$payment_ids = $ninja_invoice_data['payment_ids'] ?? []; $payment_ids = $ninja_invoice_data['payment_ids'] ?? [];
@ -49,7 +55,11 @@ class QbInvoice implements SyncInterface
unset($ninja_invoice_data['payment_ids']); unset($ninja_invoice_data['payment_ids']);
if ($invoice = $this->findInvoice($ninja_invoice_data)) { 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->fill($ninja_invoice_data);
$invoice->saveQuietly(); $invoice->saveQuietly();
@ -95,17 +105,38 @@ class QbInvoice implements SyncInterface
} }
private function processQbToNinjaInvoiceUpdate(array $ninja_invoice_data, Invoice $invoice): void
{
$current_ninja_invoice_balance = $invoice->balance;
$qb_invoice_balance = $ninja_invoice_data['balance'];
private function findInvoice(array $ninja_invoice_data): ?Invoice if(floatval($current_ninja_invoice_balance) == floatval($qb_invoice_balance))
{
nlog('Invoice balance is the same, skipping update of line items');
unset($ninja_invoice_data['line_items']);
$invoice->fill($ninja_invoice_data);
$invoice->saveQuietly();
}
else{
nlog('Invoice balance is different, updating line items');
$this->invoice_repository->save($ninja_invoice_data, $invoice);
}
}
private function findInvoice(string $id, ?string $client_id = null): ?Invoice
{ {
$search = Invoice::query() $search = Invoice::query()
->withTrashed() ->withTrashed()
->where('company_id', $this->service->company->id) ->where('company_id', $this->service->company->id)
->where('sync->qb_id', $ninja_invoice_data['id']); ->where('sync->qb_id', $id);
if($search->count() == 0) { if($search->count() == 0 && $client_id) {
$invoice = InvoiceFactory::create($this->service->company->id, $this->service->company->owner()->id); $invoice = InvoiceFactory::create($this->service->company->id, $this->service->company->owner()->id);
$invoice->client_id = $ninja_invoice_data['client_id']; $invoice->client_id = $client_id;
$sync = new InvoiceSync();
$sync->qb_id = $id;
$invoice->sync = $sync;
return $invoice; return $invoice;
} elseif($search->count() == 1) { } elseif($search->count() == 1) {
@ -116,4 +147,25 @@ class QbInvoice implements SyncInterface
} }
public function sync(string $id, string $last_updated): void
{
$qb_record = $this->find($id);
if($this->service->updateGate('invoice') && $invoice = $this->findInvoice($id))
{
//logic here to determine if we should update the record
if(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);
}
// }
}
}
} }

View File

@ -90,11 +90,14 @@ class QuickbooksService
private function checkToken(): self private function checkToken(): self
{ {
nlog($this->company->quickbooks->accessTokenExpiresAt);
if($this->company->quickbooks->accessTokenKey > time()) nlog(time());
if($this->company->quickbooks->accessTokenExpiresAt > time())
return $this; return $this;
if($this->company->quickbooks->accessTokenExpiresAt < time() && $this->try_refresh){ if($this->company->quickbooks->accessTokenExpiresAt < time() && $this->try_refresh){
nlog('Refreshing token');
$this->sdk()->refreshToken($this->company->quickbooks->refresh_token); $this->sdk()->refreshToken($this->company->quickbooks->refresh_token);
$this->company = $this->company->fresh(); $this->company = $this->company->fresh();
$this->try_refresh = false; $this->try_refresh = false;
@ -145,7 +148,10 @@ class QuickbooksService
*/ */
public function updateGate(string $entity): bool public function updateGate(string $entity): bool
{ {
return (bool) $this->service->settings->{$entity}->sync && $this->service->settings->{$entity}->update_record; nlog($this->settings->{$entity}->sync);
nlog($this->settings->{$entity}->update_record);
return $this->settings->{$entity}->sync && $this->settings->{$entity}->update_record;
} }
/** /**

View File

@ -88,18 +88,38 @@ class InvoiceTransformer extends BaseTransformer
foreach($qb_items as $qb_item) foreach($qb_items as $qb_item)
{ {
$item = new InvoiceItem;
$item->product_key = data_get($qb_item, 'SalesItemLineDetail.ItemRef.name', ''); if(data_get($qb_item, 'DetailType.value') == 'SalesItemLineDetail')
$item->notes = data_get($qb_item,'Description', ''); {
$item->quantity = data_get($qb_item,'SalesItemLineDetail.Qty', 0); $item = new InvoiceItem;
$item->cost = data_get($qb_item, 'SalesItemLineDetail.UnitPrice', 0); $item->product_key = data_get($qb_item, 'SalesItemLineDetail.ItemRef.name', '');
$item->discount = data_get($item,'DiscountRate', data_get($qb_item,'DiscountAmount', 0)); $item->notes = data_get($qb_item,'Description', '');
$item->is_amount_discount = data_get($qb_item,'DiscountAmount', 0) > 0 ? true : false; $item->quantity = data_get($qb_item,'SalesItemLineDetail.Qty', 0);
$item->type_id = stripos(data_get($qb_item, 'ItemAccountRef.name') ?? '', 'Service') !== false ? '2' : '1'; $item->cost = data_get($qb_item, 'SalesItemLineDetail.UnitPrice', 0);
$item->tax_id = data_get($qb_item, 'TaxCodeRef.value', '') == 'NON' ? Product::PRODUCT_TYPE_EXEMPT : $item->type_id; $item->discount = data_get($item,'DiscountRate', data_get($qb_item,'DiscountAmount', 0));
$item->tax_rate1 = data_get($qb_item, 'TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent', 0); $item->is_amount_discount = data_get($qb_item,'DiscountAmount', 0) > 0 ? true : false;
$item->tax_name1 = $item->tax_rate1 > 0 ? "Sales Tax" : ""; $item->type_id = stripos(data_get($qb_item, 'ItemAccountRef.name') ?? '', 'Service') !== false ? '2' : '1';
$items[] = (object)$item; $item->tax_id = data_get($qb_item, 'TaxCodeRef.value', '') == 'NON' ? Product::PRODUCT_TYPE_EXEMPT : $item->type_id;
$item->tax_rate1 = data_get($qb_item, 'TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent', 0);
$item->tax_name1 = $item->tax_rate1 > 0 ? "Sales Tax" : "";
$items[] = (object)$item;
}
if(data_get($qb_item, 'DetailType.value') == 'DiscountLineDetail')
{
$item = new InvoiceItem();
$item->product_key = ctrans('texts.discount');
$item->notes = ctrans('texts.discount');
$item->quantity = 1;
$item->cost = data_get($qb_item, 'Amount', 0) * -1;
$item->discount = 0;
$item->is_amount_discount = true;
$item->type_id = '1';
$item->tax_id = Product::PRODUCT_TYPE_PHYSICAL;
$items[] = (object)$item;
}
} }
nlog($items); nlog($items);