mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 03:07:01 +00:00
Padding out Quickbooks integration
This commit is contained in:
57
app/DataMapper/QuickbooksPushEvents.php
Normal file
57
app/DataMapper/QuickbooksPushEvents.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataMapper;
|
||||
|
||||
/**
|
||||
* QuickbooksPushEvents.
|
||||
*
|
||||
* Stores push event configuration for QuickBooks integration.
|
||||
* This class provides a clean separation of push event settings.
|
||||
*/
|
||||
class QuickbooksPushEvents
|
||||
{
|
||||
/**
|
||||
* Push when a new client is created.
|
||||
*/
|
||||
public bool $push_on_new_client = false;
|
||||
|
||||
/**
|
||||
* Push when an existing client is updated.
|
||||
*/
|
||||
public bool $push_on_updated_client = false;
|
||||
|
||||
/**
|
||||
* Push when an invoice status matches one of these values.
|
||||
*
|
||||
* Valid values: 'draft', 'sent', 'paid', 'deleted'
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public array $push_invoice_statuses = [];
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->push_on_new_client = $attributes['push_on_new_client'] ?? false;
|
||||
$this->push_on_updated_client = $attributes['push_on_updated_client'] ?? false;
|
||||
$this->push_invoice_statuses = $attributes['push_invoice_statuses'] ?? [];
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'push_on_new_client' => $this->push_on_new_client,
|
||||
'push_on_updated_client' => $this->push_on_updated_client,
|
||||
'push_invoice_statuses' => $this->push_invoice_statuses,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@ class QuickbooksSync
|
||||
|
||||
public string $default_expense_account = '';
|
||||
|
||||
public QuickbooksPushEvents $push_events;
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->client = new QuickbooksSyncMap($attributes['client'] ?? []);
|
||||
@@ -52,6 +54,7 @@ class QuickbooksSync
|
||||
$this->expense = new QuickbooksSyncMap($attributes['expense'] ?? []);
|
||||
$this->default_income_account = $attributes['default_income_account'] ?? '';
|
||||
$this->default_expense_account = $attributes['default_expense_account'] ?? '';
|
||||
$this->push_events = new QuickbooksPushEvents($attributes['push_events'] ?? []);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
@@ -68,6 +71,7 @@ class QuickbooksSync
|
||||
'expense' => $this->expense->toArray(),
|
||||
'default_income_account' => $this->default_income_account,
|
||||
'default_expense_account' => $this->default_expense_account,
|
||||
'push_events' => $this->push_events->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,11 @@ class QuickbooksSyncMap
|
||||
{
|
||||
public SyncDirection $direction = SyncDirection::BIDIRECTIONAL;
|
||||
|
||||
// Push event settings (for PUSH direction)
|
||||
public bool $push_on_create = false; // Push when entity is created (e.g., new client)
|
||||
public bool $push_on_update = false; // Push when entity is updated (e.g., updated client)
|
||||
public array $push_on_statuses = []; // Push when entity status matches (e.g., invoice statuses: ['draft', 'sent', 'paid', 'deleted'])
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->direction = isset($attributes['direction'])
|
||||
? SyncDirection::from($attributes['direction'])
|
||||
: SyncDirection::BIDIRECTIONAL;
|
||||
|
||||
$this->push_on_create = $attributes['push_on_create'] ?? false;
|
||||
$this->push_on_update = $attributes['push_on_update'] ?? false;
|
||||
$this->push_on_statuses = $attributes['push_on_statuses'] ?? [];
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
@@ -46,9 +37,6 @@ class QuickbooksSyncMap
|
||||
|
||||
return [
|
||||
'direction' => $directionValue,
|
||||
'push_on_create' => $this->push_on_create,
|
||||
'push_on_update' => $this->push_on_update,
|
||||
'push_on_statuses' => $this->push_on_statuses,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
176
app/Jobs/Quickbooks/PushToQuickbooks.php
Normal file
176
app/Jobs/Quickbooks/PushToQuickbooks.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Quickbooks;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Company;
|
||||
use App\Services\Quickbooks\QuickbooksService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Unified job to push entities to QuickBooks.
|
||||
*
|
||||
* This job handles pushing different entity types (clients, invoices, etc.) to QuickBooks.
|
||||
* It is dispatched from model observers when:
|
||||
* - QuickBooks is configured
|
||||
* - Push events are enabled for the entity/action
|
||||
* - Sync direction allows push
|
||||
*/
|
||||
class PushToQuickbooks implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $entity_type Entity type: 'client', 'invoice', etc.
|
||||
* @param int $entity_id The ID of the entity to push
|
||||
* @param int $company_id The company ID
|
||||
* @param string $db The database name
|
||||
* @param string $action Action type: 'create', 'update', 'status'
|
||||
* @param string|null $status Optional status for status-based pushes (e.g., invoice status: 'draft', 'sent', 'paid', 'deleted')
|
||||
*/
|
||||
public function __construct(
|
||||
private string $entity_type,
|
||||
private int $entity_id,
|
||||
private int $company_id,
|
||||
private string $db,
|
||||
private string $action,
|
||||
private ?string $status = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
$company = Company::find($this->company_id);
|
||||
|
||||
if (!$company) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the entity based on type
|
||||
$entity = $this->resolveEntity($this->entity_type, $this->entity_id);
|
||||
|
||||
if (!$entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Double-check push is still enabled (settings might have changed)
|
||||
if (!$this->shouldPush($company, $this->entity_type, $this->action, $this->status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qbService = new QuickbooksService($company);
|
||||
|
||||
// Dispatch to appropriate handler based on entity type
|
||||
match($this->entity_type) {
|
||||
'client' => $this->pushClient($qbService, $entity),
|
||||
'invoice' => $this->pushInvoice($qbService, $entity),
|
||||
default => nlog("QuickBooks: Unsupported entity type: {$this->entity_type}"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the entity model based on type.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @param int $entity_id
|
||||
* @return Client|Invoice|null
|
||||
*/
|
||||
private function resolveEntity(string $entity_type, int $entity_id): Client|Invoice|null
|
||||
{
|
||||
return match($entity_type) {
|
||||
'client' => Client::find($entity_id),
|
||||
'invoice' => Invoice::find($entity_id),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if push should still occur (settings might have changed since job was queued).
|
||||
*
|
||||
* @param Company $company
|
||||
* @param string $entity_type
|
||||
* @param string $action
|
||||
* @param string|null $status
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldPush(Company $company, string $entity_type, string $action, ?string $status): bool
|
||||
{
|
||||
return $company->shouldPushToQuickbooks($entity_type, $action, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a client to QuickBooks.
|
||||
*
|
||||
* @param QuickbooksService $qbService
|
||||
* @param Client $client
|
||||
* @return void
|
||||
*/
|
||||
private function pushClient(QuickbooksService $qbService, Client $client): void
|
||||
{
|
||||
// TODO: Implement actual push logic
|
||||
// $qbService->client->push($client, $this->action);
|
||||
|
||||
nlog("QuickBooks: Pushing client {$client->id} to QuickBooks ({$this->action})");
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an invoice to QuickBooks.
|
||||
*
|
||||
* @param QuickbooksService $qbService
|
||||
* @param Invoice $invoice
|
||||
* @return void
|
||||
*/
|
||||
private function pushInvoice(QuickbooksService $qbService, Invoice $invoice): void
|
||||
{
|
||||
// Use syncToForeign to push the invoice
|
||||
$qbService->invoice->syncToForeign([$invoice]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map invoice status_id and is_deleted to status string.
|
||||
*
|
||||
* @param int $statusId
|
||||
* @param bool $isDeleted
|
||||
* @return string
|
||||
*/
|
||||
private function mapInvoiceStatusToString(int $statusId, bool $isDeleted): string
|
||||
{
|
||||
if ($isDeleted) {
|
||||
return 'deleted';
|
||||
}
|
||||
|
||||
return match($statusId) {
|
||||
\App\Models\Invoice::STATUS_DRAFT => 'draft',
|
||||
\App\Models\Invoice::STATUS_SENT => 'sent',
|
||||
\App\Models\Invoice::STATUS_PAID => 'paid',
|
||||
default => 'unknown',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1089,7 +1089,6 @@ class Company extends BaseModel
|
||||
|
||||
// Cache the detailed check for this request lifecycle
|
||||
// This prevents re-checking if called multiple times in the same request
|
||||
// Note: once() caches per closure, so we need separate closures for different entity/action combinations
|
||||
return once(function () use ($entity, $action, $status) {
|
||||
// Check if QuickBooks is actually configured (has token)
|
||||
if (!$this->quickbooks->isConfigured()) {
|
||||
@@ -1109,13 +1108,22 @@ class Company extends BaseModel
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check action-specific settings
|
||||
// Get push events from settings
|
||||
$pushEvents = $this->quickbooks->settings->push_events;
|
||||
|
||||
// Check action-specific settings from QuickbooksPushEvents
|
||||
return match($action) {
|
||||
'create' => $entitySettings->push_on_create ?? false,
|
||||
'update' => $entitySettings->push_on_update ?? false,
|
||||
'status' => $status && in_array($status, $entitySettings->push_on_statuses ?? []),
|
||||
'create' => match($entity) {
|
||||
'client' => $pushEvents->push_on_new_client ?? false,
|
||||
default => false, // Other entities can be added here
|
||||
},
|
||||
'update' => match($entity) {
|
||||
'client' => $pushEvents->push_on_updated_client ?? false,
|
||||
default => false, // Other entities can be added here
|
||||
},
|
||||
'status' => $status && in_array($status, $pushEvents->push_invoice_statuses ?? []),
|
||||
default => false,
|
||||
};
|
||||
}, $cacheKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ class ClientObserver
|
||||
|
||||
// QuickBooks push - efficient check in observer (zero overhead if not configured)
|
||||
if ($client->company->shouldPushToQuickbooks('client', 'create')) {
|
||||
\App\Jobs\Quickbooks\PushClientToQuickbooks::dispatch(
|
||||
\App\Jobs\Quickbooks\PushToQuickbooks::dispatch(
|
||||
'client',
|
||||
$client->id,
|
||||
$client->company->id,
|
||||
$client->company->db,
|
||||
@@ -128,7 +129,8 @@ class ClientObserver
|
||||
|
||||
// QuickBooks push - efficient check in observer (zero overhead if not configured)
|
||||
if ($client->company->shouldPushToQuickbooks('client', 'update')) {
|
||||
\App\Jobs\Quickbooks\PushClientToQuickbooks::dispatch(
|
||||
\App\Jobs\Quickbooks\PushToQuickbooks::dispatch(
|
||||
'client',
|
||||
$client->id,
|
||||
$client->company->id,
|
||||
$client->company->db,
|
||||
|
||||
@@ -36,13 +36,18 @@ class InvoiceObserver
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company, 'client')->delay(0);
|
||||
}
|
||||
|
||||
// QuickBooks push - efficient check in observer (zero overhead if not configured)
|
||||
if ($invoice->company->shouldPushToQuickbooks('invoice', 'create')) {
|
||||
\App\Jobs\Quickbooks\PushInvoiceToQuickbooks::dispatch(
|
||||
// QuickBooks push - check if invoice status matches push_invoice_statuses
|
||||
// Map invoice status to string for status-based push check
|
||||
$invoiceStatus = $this->mapInvoiceStatusToString($invoice->status_id, $invoice->is_deleted);
|
||||
|
||||
if ($invoice->company->shouldPushToQuickbooks('invoice', 'status', $invoiceStatus)) {
|
||||
\App\Jobs\Quickbooks\PushToQuickbooks::dispatch(
|
||||
'invoice',
|
||||
$invoice->id,
|
||||
$invoice->company->id,
|
||||
$invoice->company->db,
|
||||
'create'
|
||||
'create',
|
||||
$invoiceStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,19 +79,18 @@ class InvoiceObserver
|
||||
WebhookHandler::dispatch($event, $invoice, $invoice->company, 'client')->delay(0);
|
||||
}
|
||||
|
||||
// QuickBooks push - check push_on_update OR push_on_statuses
|
||||
// QuickBooks push - check if invoice status matches push_invoice_statuses
|
||||
// Map invoice status to string for status-based push check
|
||||
$invoiceStatus = $this->mapInvoiceStatusToString($invoice->status_id, $invoice->is_deleted);
|
||||
|
||||
$shouldPush = $invoice->company->shouldPushToQuickbooks('invoice', 'update') ||
|
||||
$invoice->company->shouldPushToQuickbooks('invoice', 'status', $invoiceStatus);
|
||||
|
||||
if ($shouldPush) {
|
||||
\App\Jobs\Quickbooks\PushInvoiceToQuickbooks::dispatch(
|
||||
if ($invoice->company->shouldPushToQuickbooks('invoice', 'status', $invoiceStatus)) {
|
||||
\App\Jobs\Quickbooks\PushToQuickbooks::dispatch(
|
||||
'invoice',
|
||||
$invoice->id,
|
||||
$invoice->company->id,
|
||||
$invoice->company->db,
|
||||
'update'
|
||||
'update',
|
||||
$invoiceStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,53 @@ class QbInvoice implements SyncInterface
|
||||
|
||||
public function syncToForeign(array $records): void
|
||||
{
|
||||
foreach ($records as $invoice) {
|
||||
if (!$invoice instanceof Invoice) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if sync direction allows push
|
||||
if (!$this->service->syncable('invoice', \App\Enum\SyncDirection::PUSH)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Transform invoice to QuickBooks format
|
||||
$qb_invoice_data = $this->invoice_transformer->ninjaToQb($invoice, $this->service);
|
||||
|
||||
// If updating, fetch SyncToken using existing find() method
|
||||
if (isset($invoice->sync->qb_id) && !empty($invoice->sync->qb_id)) {
|
||||
$existing_qb_invoice = $this->find($invoice->sync->qb_id);
|
||||
if ($existing_qb_invoice) {
|
||||
$qb_invoice_data['SyncToken'] = $existing_qb_invoice->SyncToken ?? '0';
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update invoice in QuickBooks
|
||||
$qb_invoice = \QuickBooksOnline\API\Facades\Invoice::create($qb_invoice_data);
|
||||
|
||||
if (isset($invoice->sync->qb_id) && !empty($invoice->sync->qb_id)) {
|
||||
// Update existing invoice
|
||||
$result = $this->service->sdk->Update($qb_invoice);
|
||||
nlog("QuickBooks: Updated invoice {$invoice->id} (QB ID: {$invoice->sync->qb_id})");
|
||||
} else {
|
||||
// Create new invoice
|
||||
$result = $this->service->sdk->Add($qb_invoice);
|
||||
|
||||
// Store QB ID in invoice sync
|
||||
$sync = new InvoiceSync();
|
||||
$sync->qb_id = data_get($result, 'Id') ?? data_get($result, 'Id.value');
|
||||
$invoice->sync = $sync;
|
||||
$invoice->saveQuietly();
|
||||
|
||||
nlog("QuickBooks: Created invoice {$invoice->id} (QB ID: {$sync->qb_id})");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog("QuickBooks: Error pushing invoice {$invoice->id} to QuickBooks: {$e->getMessage()}");
|
||||
// Continue with next invoice instead of failing completely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function qbInvoiceUpdate(array $ninja_invoice_data, Invoice $invoice): void
|
||||
|
||||
@@ -29,8 +29,165 @@ class InvoiceTransformer extends BaseTransformer
|
||||
return $this->transform($qb_data);
|
||||
}
|
||||
|
||||
public function ninjaToQb()
|
||||
public function ninjaToQb(Invoice $invoice, \App\Services\Quickbooks\QuickbooksService $qb_service): array
|
||||
{
|
||||
// Get client's QuickBooks ID
|
||||
$client_qb_id = $invoice->client->sync->qb_id ?? null;
|
||||
|
||||
// If client doesn't have QB ID, create it first
|
||||
if (!$client_qb_id) {
|
||||
$client_qb_id = $this->createClientInQuickbooks($invoice->client, $qb_service);
|
||||
}
|
||||
|
||||
// Build line items
|
||||
$line_items = [];
|
||||
$line_num = 1;
|
||||
|
||||
foreach ($invoice->line_items as $line_item) {
|
||||
// Get product's QuickBooks ID if it exists
|
||||
$product = \App\Models\Product::where('company_id', $this->company->id)
|
||||
->where('product_key', $line_item->product_key)
|
||||
->first();
|
||||
|
||||
if (!$product || !isset($product->sync->qb_id)) {
|
||||
// If product doesn't exist in QB, we'll need to create it or use a default item
|
||||
// For now, skip items without QB product mapping
|
||||
continue;
|
||||
}
|
||||
|
||||
$tax_code = 'TAX';
|
||||
if (isset($line_item->tax_id)) {
|
||||
// Check if tax exempt (similar to test pattern)
|
||||
if (in_array($line_item->tax_id, [5, 8])) {
|
||||
$tax_code = 'NON';
|
||||
}
|
||||
}
|
||||
|
||||
$line_items[] = [
|
||||
'LineNum' => $line_num,
|
||||
'DetailType' => 'SalesItemLineDetail',
|
||||
'SalesItemLineDetail' => [
|
||||
'ItemRef' => [
|
||||
'value' => $product->sync->qb_id,
|
||||
],
|
||||
'Qty' => $line_item->quantity ?? 1,
|
||||
'UnitPrice' => $line_item->cost ?? 0,
|
||||
'TaxCodeRef' => [
|
||||
'value' => $tax_code,
|
||||
],
|
||||
],
|
||||
'Description' => $line_item->notes ?? '',
|
||||
'Amount' => $line_item->line_total ?? ($line_item->cost * ($line_item->quantity ?? 1)),
|
||||
];
|
||||
|
||||
$line_num++;
|
||||
}
|
||||
|
||||
// Get primary contact email
|
||||
$primary_contact = $invoice->client->contacts()->orderBy('is_primary', 'desc')->first();
|
||||
$email = $primary_contact?->email ?? $invoice->client->contacts()->first()?->email ?? '';
|
||||
|
||||
// Build invoice data
|
||||
$invoice_data = [
|
||||
'Line' => $line_items,
|
||||
'CustomerRef' => [
|
||||
'value' => $client_qb_id,
|
||||
],
|
||||
'BillEmail' => [
|
||||
'Address' => $email,
|
||||
],
|
||||
'TxnDate' => $invoice->date,
|
||||
'DueDate' => $invoice->due_date,
|
||||
'TotalAmt' => $invoice->amount,
|
||||
'DocNumber' => $invoice->number,
|
||||
'ApplyTaxAfterDiscount' => true,
|
||||
'PrintStatus' => 'NeedToPrint',
|
||||
'EmailStatus' => 'NotSet',
|
||||
'GlobalTaxCalculation' => 'TaxExcluded',
|
||||
];
|
||||
|
||||
// Add optional fields
|
||||
if ($invoice->public_notes) {
|
||||
$invoice_data['CustomerMemo'] = [
|
||||
'value' => $invoice->public_notes,
|
||||
];
|
||||
}
|
||||
|
||||
if ($invoice->private_notes) {
|
||||
$invoice_data['PrivateNote'] = $invoice->private_notes;
|
||||
}
|
||||
|
||||
if ($invoice->po_number) {
|
||||
$invoice_data['PONumber'] = $invoice->po_number;
|
||||
}
|
||||
|
||||
// If invoice already has a QB ID, include it for updates
|
||||
// Note: SyncToken will be fetched in QbInvoice::syncToForeign using the existing find() method
|
||||
if (isset($invoice->sync->qb_id) && !empty($invoice->sync->qb_id)) {
|
||||
$invoice_data['Id'] = $invoice->sync->qb_id;
|
||||
}
|
||||
|
||||
return $invoice_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client in QuickBooks if it doesn't exist.
|
||||
*
|
||||
* @param \App\Models\Client $client
|
||||
* @param \App\Services\Quickbooks\QuickbooksService $qb_service
|
||||
* @return string The QuickBooks customer ID
|
||||
*/
|
||||
private function createClientInQuickbooks(\App\Models\Client $client, \App\Services\Quickbooks\QuickbooksService $qb_service): string
|
||||
{
|
||||
$primary_contact = $client->contacts()->orderBy('is_primary', 'desc')->first();
|
||||
|
||||
$customer_data = [
|
||||
'DisplayName' => $client->present()->name(),
|
||||
'PrimaryEmailAddr' => [
|
||||
'Address' => $primary_contact?->email ?? '',
|
||||
],
|
||||
'PrimaryPhone' => [
|
||||
'FreeFormNumber' => $primary_contact?->phone ?? '',
|
||||
],
|
||||
'CompanyName' => $client->present()->name(),
|
||||
'BillAddr' => [
|
||||
'Line1' => $client->address1 ?? '',
|
||||
'City' => $client->city ?? '',
|
||||
'CountrySubDivisionCode' => $client->state ?? '',
|
||||
'PostalCode' => $client->postal_code ?? '',
|
||||
'Country' => $client->country?->iso_3166_3 ?? '',
|
||||
],
|
||||
'ShipAddr' => [
|
||||
'Line1' => $client->shipping_address1 ?? '',
|
||||
'City' => $client->shipping_city ?? '',
|
||||
'CountrySubDivisionCode' => $client->shipping_state ?? '',
|
||||
'PostalCode' => $client->shipping_postal_code ?? '',
|
||||
'Country' => $client->shipping_country?->iso_3166_3 ?? '',
|
||||
],
|
||||
'GivenName' => $primary_contact?->first_name ?? '',
|
||||
'FamilyName' => $primary_contact?->last_name ?? '',
|
||||
'PrintOnCheckName' => $client->present()->primary_contact_name(),
|
||||
'Notes' => $client->public_notes ?? '',
|
||||
'BusinessNumber' => $client->id_number ?? '',
|
||||
'Active' => $client->deleted_at ? false : true,
|
||||
'V4IDPseudonym' => $client->client_hash ?? \Illuminate\Support\Str::random(32),
|
||||
'WebAddr' => $client->website ?? '',
|
||||
];
|
||||
|
||||
$customer = \QuickBooksOnline\API\Facades\Customer::create($customer_data);
|
||||
$resulting_customer = $qb_service->sdk->Add($customer);
|
||||
|
||||
$qb_id = data_get($resulting_customer, 'Id') ?? data_get($resulting_customer, 'Id.value');
|
||||
|
||||
// Store QB ID in client sync
|
||||
$sync = new \App\DataMapper\ClientSync();
|
||||
$sync->qb_id = $qb_id;
|
||||
$client->sync = $sync;
|
||||
$client->saveQuietly();
|
||||
|
||||
nlog("QuickBooks: Auto-created client {$client->id} in QuickBooks (QB ID: {$qb_id})");
|
||||
|
||||
return $qb_id;
|
||||
}
|
||||
|
||||
public function transform($qb_data)
|
||||
|
||||
Reference in New Issue
Block a user