mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
df914450e2
50
app/Casts/QuickbooksSettingsCast.php
Normal file
50
app/Casts/QuickbooksSettingsCast.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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);
|
||||
|
||||
if(!is_array($data))
|
||||
return null;
|
||||
|
||||
$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,
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
59
app/DataMapper/QuickbooksSettings.php
Normal file
59
app/DataMapper/QuickbooksSettings.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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\DataMapper;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use App\Casts\QuickbooksSettingsCast;
|
||||
|
||||
/**
|
||||
* QuickbooksSettings.
|
||||
*/
|
||||
class QuickbooksSettings implements Castable
|
||||
{
|
||||
public string $accessTokenKey;
|
||||
|
||||
public string $refresh_token;
|
||||
|
||||
public string $realmID;
|
||||
|
||||
public int $accessTokenExpiresAt;
|
||||
|
||||
public int $refreshTokenExpiresAt;
|
||||
|
||||
/**
|
||||
* entity client,invoice,quote,purchase_order,vendor,payment
|
||||
* sync true/false
|
||||
* update_record true/false
|
||||
* direction push/pull/birectional
|
||||
* */
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -99,7 +99,7 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
} elseif (array_key_exists('internalTransactionId', $transaction)) {
|
||||
$transactionId = $transaction["internalTransactionId"];
|
||||
} else {
|
||||
nlog(`Invalid Input for nordigen transaction transformer: ` . $transaction);
|
||||
nlog('Invalid Input for nordigen transaction transformer: ' . $transaction);
|
||||
throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer');
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Http\Requests\Quickbooks\AuthQuickbooksRequest;
|
||||
use App\Services\Import\Quickbooks\QuickbooksService;
|
||||
use App\Services\Quickbooks\QuickbooksService;
|
||||
|
||||
class ImportQuickbooksController extends BaseController
|
||||
{
|
||||
|
@ -93,6 +93,12 @@ class StoreInvoiceRequest extends Request
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key)) {
|
||||
usleep(200000);
|
||||
}
|
||||
|
||||
\Illuminate\Support\Facades\Cache::put($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key,1);
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://clientninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Import\Transformer\Quickbooks;
|
||||
|
||||
use App\Import\Transformer\Quickbooks\CommonTrait;
|
||||
use App\Import\Transformer\BaseTransformer;
|
||||
use App\Models\Client as Model;
|
||||
use App\Models\ClientContact;
|
||||
use App\Import\ImportException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class ClientTransformer.
|
||||
*/
|
||||
class ClientTransformer extends BaseTransformer
|
||||
{
|
||||
use CommonTrait {
|
||||
transform as preTransform;
|
||||
}
|
||||
|
||||
private $fillable = [
|
||||
'name' => 'CompanyName',
|
||||
'phone' => 'PrimaryPhone.FreeFormNumber',
|
||||
'country_id' => 'BillAddr.Country',
|
||||
'state' => 'BillAddr.CountrySubDivisionCode',
|
||||
'address1' => 'BillAddr.Line1',
|
||||
'city' => 'BillAddr.City',
|
||||
'postal_code' => 'BillAddr.PostalCode',
|
||||
'shipping_country_id' => 'ShipAddr.Country',
|
||||
'shipping_state' => 'ShipAddr.CountrySubDivisionCode',
|
||||
'shipping_address1' => 'ShipAddr.Line1',
|
||||
'shipping_city' => 'ShipAddr.City',
|
||||
'shipping_postal_code' => 'ShipAddr.PostalCode',
|
||||
'public_notes' => 'Notes'
|
||||
];
|
||||
|
||||
public function __construct($company)
|
||||
{
|
||||
parent::__construct($company);
|
||||
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a Customer array into a ClientContact model.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array|bool
|
||||
*/
|
||||
public function transform($data)
|
||||
{
|
||||
$transformed_data = [];
|
||||
// Assuming 'customer_name' is equivalent to 'CompanyName'
|
||||
if (isset($data['CompanyName']) && $this->hasClient($data['CompanyName'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transformed_data = $this->preTransform($data);
|
||||
$transformed_data['contacts'][0] = $this->getContacts($data)->toArray() + ['company_id' => $this->company->id, 'user_id' => $this->company->owner()->id ];
|
||||
|
||||
return $transformed_data;
|
||||
}
|
||||
|
||||
protected function getContacts($data)
|
||||
{
|
||||
return (new ClientContact())->fill([
|
||||
'first_name' => $this->getString($data, 'GivenName'),
|
||||
'last_name' => $this->getString($data, 'FamilyName'),
|
||||
'phone' => $this->getString($data, 'PrimaryPhone.FreeFormNumber'),
|
||||
'email' => $this->getString($data, 'PrimaryEmailAddr.Address'),
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->company->owner()->id,
|
||||
'send_email' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function getShipAddrCountry($data, $field)
|
||||
{
|
||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||
}
|
||||
|
||||
public function getBillAddrCountry($data, $field)
|
||||
{
|
||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Import\Transformer\Quickbooks;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait CommonTrait
|
||||
{
|
||||
protected $model;
|
||||
|
||||
public function getString($data, $field)
|
||||
{
|
||||
return Arr::get($data, $field);
|
||||
}
|
||||
|
||||
public function getCreateTime($data, $field = null)
|
||||
{
|
||||
return $this->parseDateOrNull($data, 'MetaData.CreateTime');
|
||||
}
|
||||
|
||||
public function getLastUpdatedTime($data, $field = null)
|
||||
{
|
||||
return $this->parseDateOrNull($data, 'MetaData.LastUpdatedTime');
|
||||
}
|
||||
|
||||
public function transform($data)
|
||||
{
|
||||
$transformed = [];
|
||||
|
||||
foreach ($this->fillable as $key => $field) {
|
||||
$transformed[$key] = is_null((($v = $this->getString($data, $field)))) ? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field) : $this->getString($data, $field));
|
||||
}
|
||||
|
||||
return $this->model->fillable(array_keys($this->fillable))->fill($transformed)->toArray() + ['company_id' => $this->company->id ] ;
|
||||
}
|
||||
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Import\Transformer\Quickbooks;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Import\ImportException;
|
||||
use App\Models\Invoice as Model;
|
||||
use App\Import\Transformer\BaseTransformer;
|
||||
use App\Import\Transformer\Quickbooks\CommonTrait;
|
||||
use App\Import\Transformer\Quickbooks\ClientTransformer;
|
||||
|
||||
/**
|
||||
* Class InvoiceTransformer.
|
||||
*/
|
||||
class InvoiceTransformer extends BaseTransformer
|
||||
{
|
||||
use CommonTrait {
|
||||
transform as preTransform;
|
||||
}
|
||||
|
||||
private $fillable = [
|
||||
'amount' => "TotalAmt",
|
||||
'line_items' => "Line",
|
||||
'due_date' => "DueDate",
|
||||
'partial' => "Deposit",
|
||||
'balance' => "Balance",
|
||||
'private_notes' => "CustomerMemo",
|
||||
'public_notes' => "CustomerMemo",
|
||||
'number' => "DocNumber",
|
||||
'created_at' => "CreateTime",
|
||||
'updated_at' => "LastUpdatedTime",
|
||||
'payments' => 'LinkedTxn',
|
||||
'status_id' => 'InvoiceStatus',
|
||||
];
|
||||
|
||||
public function __construct($company)
|
||||
{
|
||||
parent::__construct($company);
|
||||
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
public function getInvoiceStatus($data)
|
||||
{
|
||||
return Invoice::STATUS_SENT;
|
||||
}
|
||||
|
||||
public function transform($data)
|
||||
{
|
||||
return $this->preTransform($data) + $this->getInvoiceClient($data);
|
||||
}
|
||||
|
||||
public function getTotalAmt($data)
|
||||
{
|
||||
return (float) $this->getString($data, 'TotalAmt');
|
||||
}
|
||||
|
||||
public function getLine($data)
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'description' => $this->getString($item, 'Description'),
|
||||
'product_key' => $this->getString($item, 'Description'),
|
||||
'quantity' => (int) $this->getString($item, 'SalesItemLineDetail.Qty'),
|
||||
'unit_price' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
'line_total' => (float) $this->getString($item, 'Amount'),
|
||||
'cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
'product_cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
'tax_amount' => (float) $this->getString($item, 'TxnTaxDetail.TotalTax'),
|
||||
];
|
||||
}, array_filter($this->getString($data, 'Line'), function ($item) {
|
||||
return $this->getString($item, 'DetailType') !== 'SubTotalLineDetail';
|
||||
}));
|
||||
}
|
||||
|
||||
public function getInvoiceClient($data, $field = null)
|
||||
{
|
||||
/**
|
||||
* "CustomerRef": {
|
||||
"value": "23",
|
||||
"name": ""Barnett Design
|
||||
},
|
||||
"CustomerMemo": {
|
||||
"value": "Thank you for your business and have a great day!"
|
||||
},
|
||||
"BillAddr": {
|
||||
"Id": "58",
|
||||
"Line1": "Shara Barnett",
|
||||
"Line2": "Barnett Design",
|
||||
"Line3": "19 Main St.",
|
||||
"Line4": "Middlefield, CA 94303",
|
||||
"Lat": "37.4530553",
|
||||
"Long": "-122.1178261"
|
||||
},
|
||||
"ShipAddr": {
|
||||
"Id": "24",
|
||||
"Line1": "19 Main St.",
|
||||
"City": "Middlefield",
|
||||
"CountrySubDivisionCode": "CA",
|
||||
"PostalCode": "94303",
|
||||
"Lat": "37.445013",
|
||||
"Long": "-122.1391443"
|
||||
},"BillEmail": {
|
||||
"Address": "Design@intuit.com"
|
||||
},
|
||||
[
|
||||
'name' => 'CompanyName',
|
||||
'phone' => 'PrimaryPhone.FreeFormNumber',
|
||||
'country_id' => 'BillAddr.Country',
|
||||
'state' => 'BillAddr.CountrySubDivisionCode',
|
||||
'address1' => 'BillAddr.Line1',
|
||||
'city' => 'BillAddr.City',
|
||||
'postal_code' => 'BillAddr.PostalCode',
|
||||
'shipping_country_id' => 'ShipAddr.Country',
|
||||
'shipping_state' => 'ShipAddr.CountrySubDivisionCode',
|
||||
'shipping_address1' => 'ShipAddr.Line1',
|
||||
'shipping_city' => 'ShipAddr.City',
|
||||
'shipping_postal_code' => 'ShipAddr.PostalCode',
|
||||
'public_notes' => 'Notes'
|
||||
];
|
||||
|
||||
*/
|
||||
$bill_address = (object) $this->getString($data, 'BillAddr');
|
||||
$ship_address = $this->getString($data, 'ShipAddr');
|
||||
$customer = explode(" ", $this->getString($data, 'CustomerRef.name'));
|
||||
$customer = ['GivenName' => $customer[0], 'FamilyName' => $customer[1]];
|
||||
$has_company = property_exists($bill_address, 'Line4');
|
||||
$address = $has_company ? $bill_address->Line4 : $bill_address->Line3;
|
||||
$address_1 = substr($address, 0, stripos($address, ','));
|
||||
$address = array_filter([$address_1] + (explode(' ', substr($address, stripos($address, ",") + 1))));
|
||||
$client_id = null;
|
||||
$client =
|
||||
[
|
||||
"CompanyName" => $has_company ? $bill_address->Line2 : $bill_address->Line1,
|
||||
"BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address, 3, 'N/A')) + ['Line1' => $has_company ? $bill_address->Line3 : $bill_address->Line2 ],
|
||||
"ShipAddr" => $ship_address
|
||||
] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]];
|
||||
if($this->hasClient($client['CompanyName'])) {
|
||||
$client_id = $this->getClient($client['CompanyName'], $this->getString($client, 'PrimaryEmailAddr.Address'));
|
||||
}
|
||||
|
||||
|
||||
return ['client' => (new ClientTransformer($this->company))->transform($client), 'client_id' => $client_id ];
|
||||
}
|
||||
|
||||
public function getDueDate($data)
|
||||
{
|
||||
return $this->parseDateOrNull($data, 'DueDate');
|
||||
}
|
||||
|
||||
public function getDeposit($data)
|
||||
{
|
||||
return (float) $this->getString($data, 'Deposit');
|
||||
}
|
||||
|
||||
public function getBalance($data)
|
||||
{
|
||||
return (float) $this->getString($data, 'Balance');
|
||||
}
|
||||
|
||||
public function getCustomerMemo($data)
|
||||
{
|
||||
return $this->getString($data, 'CustomerMemo.value');
|
||||
}
|
||||
|
||||
public function getDocNumber($data, $field = null)
|
||||
{
|
||||
return sprintf(
|
||||
"%s-%s",
|
||||
$this->getString($data, 'DocNumber'),
|
||||
$this->getString($data, 'Id.value')
|
||||
);
|
||||
}
|
||||
|
||||
public function getLinkedTxn($data)
|
||||
{
|
||||
$payments = $this->getString($data, 'LinkedTxn');
|
||||
if(empty($payments)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [[
|
||||
'amount' => $this->getTotalAmt($data),
|
||||
'date' => $this->parseDateOrNull($data, 'TxnDate')
|
||||
]];
|
||||
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://Paymentninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Import\Transformer\Quickbooks;
|
||||
|
||||
use App\Import\Transformer\Quickbooks\CommonTrait;
|
||||
use App\Import\Transformer\BaseTransformer;
|
||||
use App\Models\Payment as Model;
|
||||
use App\Import\ImportException;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Models\Invoice;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class PaymentTransformer.
|
||||
*/
|
||||
class PaymentTransformer extends BaseTransformer
|
||||
{
|
||||
use CommonTrait;
|
||||
|
||||
protected $fillable = [
|
||||
'number' => "PaymentRefNum",
|
||||
'amount' => "TotalAmt",
|
||||
"client_id" => "CustomerRef",
|
||||
"currency_id" => "CurrencyRef",
|
||||
'date' => "TxnDate",
|
||||
"invoices" => "Line",
|
||||
'private_notes' => "PrivateNote",
|
||||
'created_at' => "CreateTime",
|
||||
'updated_at' => "LastUpdatedTime"
|
||||
];
|
||||
|
||||
public function __construct($company)
|
||||
{
|
||||
parent::__construct($company);
|
||||
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
public function getTotalAmt($data, $field = null)
|
||||
{
|
||||
return (float) $this->getString($data, $field);
|
||||
}
|
||||
|
||||
public function getTxnDate($data, $field = null)
|
||||
{
|
||||
return $this->parseDateOrNull($data, $field);
|
||||
}
|
||||
|
||||
public function getCustomerRef($data, $field = null)
|
||||
{
|
||||
return $this->getClient($this->getString($data, 'CustomerRef.name'), null);
|
||||
}
|
||||
|
||||
public function getCurrencyRef($data, $field = null)
|
||||
{
|
||||
return $this->getCurrencyByCode($data['CurrencyRef'], 'value');
|
||||
}
|
||||
|
||||
public function getLine($data, $field = null)
|
||||
{
|
||||
$invoices = [];
|
||||
$invoice = $this->getString($data, 'Line.LinkedTxn.TxnType');
|
||||
if(is_null($invoice) || $invoice !== 'Invoice') {
|
||||
return $invoices;
|
||||
}
|
||||
if(is_null(($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value'))))) {
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
return [[
|
||||
'amount' => (float) $this->getString($data, 'Line.Amount'),
|
||||
'invoice_id' => $invoice_id
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $invoice_number
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getInvoiceId($invoice_number)
|
||||
{
|
||||
$invoice = Invoice::query()->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false)
|
||||
->where(
|
||||
"number",
|
||||
"LIKE",
|
||||
"%-$invoice_number%",
|
||||
)
|
||||
->first();
|
||||
|
||||
return $invoice ? $invoice->id : null;
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://Productninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Import\Transformer\Quickbooks;
|
||||
|
||||
use App\Import\Transformer\Quickbooks\CommonTrait;
|
||||
use App\Import\Transformer\BaseTransformer;
|
||||
use App\Models\Product as Model;
|
||||
use App\Import\ImportException;
|
||||
|
||||
/**
|
||||
* Class ProductTransformer.
|
||||
*/
|
||||
class ProductTransformer extends BaseTransformer
|
||||
{
|
||||
use CommonTrait;
|
||||
|
||||
protected $fillable = [
|
||||
'product_key' => 'Name',
|
||||
'notes' => 'Description',
|
||||
'cost' => 'PurchaseCost',
|
||||
'price' => 'UnitPrice',
|
||||
'quantity' => 'QtyOnHand',
|
||||
'in_stock_quantity' => 'QtyOnHand',
|
||||
'created_at' => 'CreateTime',
|
||||
'updated_at' => 'LastUpdatedTime',
|
||||
];
|
||||
|
||||
|
||||
public function __construct($company)
|
||||
{
|
||||
parent::__construct($company);
|
||||
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
public function getQtyOnHand($data, $field = null)
|
||||
{
|
||||
return (int) $this->getString($data, $field);
|
||||
}
|
||||
|
||||
public function getPurchaseCost($data, $field = null)
|
||||
{
|
||||
return (float) $this->getString($data, $field);
|
||||
}
|
||||
|
||||
|
||||
public function getUnitPrice($data, $field = null)
|
||||
{
|
||||
return (float) $this->getString($data, $field);
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -118,7 +119,7 @@ use Laracasts\Presenter\PresentableTrait;
|
||||
* @property string|null $smtp_port
|
||||
* @property string|null $smtp_encryption
|
||||
* @property string|null $smtp_local_domain
|
||||
* @property object|null $quickbooks
|
||||
* @property \App\DataMapper\QuickbooksSettings|null $quickbooks
|
||||
* @property boolean $smtp_verify_peer
|
||||
* @property-read \App\Models\Account $account
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
||||
@ -392,7 +393,7 @@ class Company extends BaseModel
|
||||
'smtp_username' => 'encrypted',
|
||||
'smtp_password' => 'encrypted',
|
||||
'e_invoice' => 'object',
|
||||
'quickbooks' => 'object',
|
||||
'quickbooks' => QuickbooksSettings::class,
|
||||
];
|
||||
|
||||
protected $with = [];
|
||||
|
@ -30,6 +30,7 @@ use League\CommonMark\CommonMarkConverter;
|
||||
* @property string|null $custom_value4
|
||||
* @property string|null $product_key
|
||||
* @property string|null $notes
|
||||
* @property string|null $hash
|
||||
* @property float $cost
|
||||
* @property float $price
|
||||
* @property float $quantity
|
||||
|
@ -81,7 +81,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
if (Ninja::isSelfHost()) {
|
||||
return Limit::none();
|
||||
} else {
|
||||
return Limit::perMinute(25)->by($request->ip());
|
||||
return Limit::perMinute(10)->by($request->ip());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -154,7 +154,6 @@ class PaymentRepository extends BaseRepository
|
||||
if ($invoice) {
|
||||
|
||||
//25-06-2023
|
||||
|
||||
$paymentable = new Paymentable();
|
||||
$paymentable->payment_id = $payment->id;
|
||||
$paymentable->paymentable_id = $invoice->id;
|
||||
|
@ -109,6 +109,8 @@ class InvoiceService
|
||||
|
||||
/**
|
||||
* Apply a payment amount to an invoice.
|
||||
*
|
||||
* *** does not create a paymentable ****
|
||||
* @param Payment $payment The Payment
|
||||
* @param float $payment_amount The Payment amount
|
||||
* @return InvoiceService Parent class object
|
||||
|
@ -103,7 +103,6 @@ class MarkPaid extends AbstractService
|
||||
$this->invoice
|
||||
->service()
|
||||
->applyNumber()
|
||||
// ->deletePdf()
|
||||
->save();
|
||||
|
||||
$payment->ledger()
|
||||
|
308
app/Services/Quickbooks/Jobs/QuickbooksSync.php
Normal file
308
app/Services/Quickbooks/Jobs/QuickbooksSync.php
Normal file
@ -0,0 +1,308 @@
|
||||
<?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\Services\Quickbooks\Jobs;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\ProductFactory;
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\DataMapper\QuickbooksSettings;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use App\Services\Quickbooks\QuickbooksService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use App\Services\Quickbooks\Transformers\ClientTransformer;
|
||||
use App\Services\Quickbooks\Transformers\InvoiceTransformer;
|
||||
use App\Services\Quickbooks\Transformers\PaymentTransformer;
|
||||
use App\Services\Quickbooks\Transformers\ProductTransformer;
|
||||
|
||||
class QuickbooksSync implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
private array $entities = [
|
||||
'product' => 'Item',
|
||||
'client' => 'Customer',
|
||||
'invoice' => 'Invoice',
|
||||
'quote' => 'Estimate',
|
||||
'purchase_order' => 'PurchaseOrder',
|
||||
'payment' => 'Payment',
|
||||
];
|
||||
|
||||
private QuickbooksService $qbs;
|
||||
|
||||
private ?array $settings;
|
||||
|
||||
private Company $company;
|
||||
|
||||
public function __construct(public int $company_id, public string $db)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
$this->company = Company::find($this->company_id);
|
||||
$this->qbs = new QuickbooksService($this->company);
|
||||
$this->settings = $this->company->quickbooks->settings;
|
||||
|
||||
nlog("here we go!");
|
||||
foreach($this->entities as $key => $entity) {
|
||||
if(!$this->syncGate($key, 'pull')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$records = $this->qbs->sdk()->fetchRecords($entity);
|
||||
|
||||
$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)
|
||||
{
|
||||
match($entity){
|
||||
// 'client' => $this->syncQbToNinjaClients($records),
|
||||
'product' => $this->syncQbToNinjaProducts($records),
|
||||
// 'invoice' => $this->syncQbToNinjaInvoices($records),
|
||||
// 'vendor' => $this->syncQbToNinjaClients($records),
|
||||
// 'quote' => $this->syncInvoices($records),
|
||||
// 'purchase_order' => $this->syncInvoices($records),
|
||||
// 'payment' => $this->syncPayment($records),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
private function syncQbToNinjaInvoices($records): void
|
||||
{
|
||||
$invoice_transformer = new InvoiceTransformer($this->company);
|
||||
|
||||
foreach($records as $record)
|
||||
{
|
||||
$ninja_invoice_data = $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))
|
||||
{
|
||||
$invoice->fill($ninja_invoice_data);
|
||||
$invoice->saveQuietly();
|
||||
|
||||
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();
|
||||
|
||||
foreach($payment_ids as $payment_id)
|
||||
{
|
||||
|
||||
$payment = $this->qbs->sdk->FindById('Payment', $payment_id);
|
||||
$payment_transformer = new PaymentTransformer($this->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'];
|
||||
$paymentable->save();
|
||||
|
||||
$invoice->service()->applyPayment($ninja_payment, $transformed['applied']);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$ninja_invoice_data = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function findInvoice(array $ninja_invoice_data): ?Invoice
|
||||
{
|
||||
$search = Invoice::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('number', $ninja_invoice_data['number']);
|
||||
|
||||
if($search->count() == 0) {
|
||||
//new invoice
|
||||
$invoice = InvoiceFactory::create($this->company->id, $this->company->owner()->id);
|
||||
$invoice->client_id = $ninja_invoice_data['client_id'];
|
||||
|
||||
return $invoice;
|
||||
} elseif($search->count() == 1) {
|
||||
return $this->settings['invoice']['update_record'] ? $search->first() : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private function syncQbToNinjaClients(array $records): void
|
||||
{
|
||||
|
||||
$client_transformer = new ClientTransformer($this->company);
|
||||
|
||||
foreach($records as $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 syncQbToNinjaProducts($records): void
|
||||
{
|
||||
$product_transformer = new ProductTransformer($this->company);
|
||||
|
||||
foreach($records as $record)
|
||||
{
|
||||
$ninja_data = $product_transformer->qbToNinja($record);
|
||||
|
||||
if($product = $this->findProduct($ninja_data['hash']))
|
||||
{
|
||||
$product->fill($ninja_data);
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findClient(array $qb_data) :?Client
|
||||
{
|
||||
$client = $qb_data[0];
|
||||
$contact = $qb_data[1];
|
||||
$client_meta = $qb_data[2];
|
||||
|
||||
$search = Client::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where(function ($q) use ($client, $client_meta, $contact){
|
||||
|
||||
$q->where('client_hash', $client_meta['client_hash'])
|
||||
->orWhere('id_number', $client['id_number'])
|
||||
->orWhereHas('contacts', function ($q) use ($contact){
|
||||
$q->where('email', $contact['email']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if($search->count() == 0) {
|
||||
//new client
|
||||
$client = ClientFactory::create($this->company->id, $this->company->owner()->id);
|
||||
$client->client_hash = $client_meta['client_hash'];
|
||||
$client->settings = $client_meta['settings'];
|
||||
|
||||
return $client;
|
||||
}
|
||||
elseif($search->count() == 1) {
|
||||
return $this->settings['client']['update_record'] ? $search->first() : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findProduct(string $key): ?Product
|
||||
{
|
||||
$search = Product::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('hash', $key);
|
||||
|
||||
if($search->count() == 0) {
|
||||
//new product
|
||||
$product = ProductFactory::create($this->company->id, $this->company->owner()->id);
|
||||
$product->hash = $key;
|
||||
|
||||
return $product;
|
||||
} elseif($search->count() == 1) {
|
||||
return $this->settings['product']['update_record'] ? $search->first() : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [new WithoutOverlapping("qbs-{$this->company_id}-{$this->db}")];
|
||||
}
|
||||
|
||||
public function failed($exception)
|
||||
{
|
||||
nlog("QuickbooksSync failed => ".$exception->getMessage());
|
||||
config(['queue.failed.driver' => null]);
|
||||
|
||||
}
|
||||
}
|
@ -9,11 +9,23 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Import\Quickbooks;
|
||||
namespace App\Services\Quickbooks;
|
||||
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\ProductFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Services\Quickbooks\Jobs\QuickbooksSync;
|
||||
use QuickBooksOnline\API\Core\CoreConstants;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
use App\Services\Quickbooks\Transformers\ClientTransformer;
|
||||
use App\Services\Quickbooks\Transformers\InvoiceTransformer;
|
||||
use App\Services\Quickbooks\Transformers\PaymentTransformer;
|
||||
use App\Services\Quickbooks\Transformers\ProductTransformer;
|
||||
|
||||
// quickbooks_realm_id
|
||||
// quickbooks_refresh_token
|
||||
@ -64,14 +76,19 @@ class QuickbooksService
|
||||
] : [];
|
||||
}
|
||||
|
||||
public function getSdk(): DataService
|
||||
{
|
||||
return $this->sdk;
|
||||
}
|
||||
|
||||
public function sdk(): SdkWrapper
|
||||
{
|
||||
return new SdkWrapper($this->sdk, $this->company);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* //@todo - refactor to a job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function syncFromQb()
|
||||
{
|
||||
QuickbooksSync::dispatch($this->company);
|
||||
}
|
||||
|
||||
}
|
@ -9,8 +9,9 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Import\Quickbooks;
|
||||
namespace App\Services\Quickbooks;
|
||||
|
||||
use App\DataMapper\QuickbooksSettings;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Company;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
@ -20,7 +21,7 @@ class SdkWrapper
|
||||
{
|
||||
public const MAXRESULTS = 10000;
|
||||
|
||||
private $entities = ['Customer','Invoice','Payment','Item'];
|
||||
private $entities = ['Customer','Invoice','Item'];
|
||||
|
||||
private OAuth2AccessToken $token;
|
||||
|
||||
@ -55,9 +56,6 @@ class SdkWrapper
|
||||
|
||||
public function company()
|
||||
{
|
||||
nlog("getting company info");
|
||||
// nlog($this->sdk->getAccessToken());
|
||||
|
||||
return $this->sdk->getCompanyInfo();
|
||||
}
|
||||
/*
|
||||
@ -85,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'),
|
||||
@ -124,8 +122,6 @@ class SdkWrapper
|
||||
*/
|
||||
public function setAccessToken(OAuth2AccessToken $token): self
|
||||
{
|
||||
// $this->sdk = $this->sdk->updateOAuth2Token($token);
|
||||
|
||||
$this->token = $token;
|
||||
|
||||
return $this;
|
||||
@ -138,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!!
|
||||
@ -159,12 +155,17 @@ class SdkWrapper
|
||||
return (int)$this->sdk->Query("select count(*) from $entity");
|
||||
}
|
||||
|
||||
private function queryData(string $query, int $start = 1, $limit = 100): array
|
||||
private function queryData(string $query, int $start = 1, $limit = 1000): array
|
||||
{
|
||||
return (array) $this->sdk->Query($query, $start, $limit);
|
||||
}
|
||||
|
||||
public function fetchRecords(string $entity, int $max = 1000): array
|
||||
public function fetchById(string $entity, $id)
|
||||
{
|
||||
return $this->sdk->FindById($entity, $id);
|
||||
}
|
||||
|
||||
public function fetchRecords(string $entity, int $max = 100000): array
|
||||
{
|
||||
|
||||
if(!in_array($entity, $this->entities)) {
|
||||
@ -173,7 +174,7 @@ class SdkWrapper
|
||||
|
||||
$records = [];
|
||||
$start = 0;
|
||||
$limit = 100;
|
||||
$limit = 1000;
|
||||
try {
|
||||
$total = $this->totalRecords($entity);
|
||||
$total = min($max, $total);
|
||||
@ -197,6 +198,7 @@ class SdkWrapper
|
||||
nlog("Fetch Quickbooks API Error: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
nlog($records);
|
||||
return $records;
|
||||
}
|
||||
}
|
74
app/Services/Quickbooks/Transformers/BaseTransformer.php
Normal file
74
app/Services/Quickbooks/Transformers/BaseTransformer.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://clientninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Quickbooks\Transformers;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
|
||||
/**
|
||||
* Class BaseTransformer.
|
||||
*/
|
||||
class BaseTransformer
|
||||
{
|
||||
|
||||
public function __construct(public Company $company)
|
||||
{
|
||||
}
|
||||
|
||||
public function resolveCountry(string $iso_3_code): string
|
||||
{
|
||||
/** @var \App\Models\Country $country */
|
||||
$country = app('countries')->first(function ($c) use ($iso_3_code){
|
||||
|
||||
/** @var \App\Models\Country $c */
|
||||
return $c->iso_3166_3 == $iso_3_code;
|
||||
});
|
||||
|
||||
return $country ? (string) $country->id : '840';
|
||||
}
|
||||
|
||||
public function resolveCurrency(string $currency_code): string
|
||||
{
|
||||
|
||||
/** @var \App\Models\Currency $currency */
|
||||
$currency = app('currencies')->first(function($c) use ($currency_code){
|
||||
|
||||
/** @var \App\Models\Currency $c */
|
||||
return $c->code == $currency_code;
|
||||
});
|
||||
|
||||
return $currency ? (string) $currency->id : '1';
|
||||
}
|
||||
|
||||
public function getShipAddrCountry($data, $field)
|
||||
{
|
||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||
}
|
||||
|
||||
public function getBillAddrCountry($data, $field)
|
||||
{
|
||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||
}
|
||||
|
||||
public function getClientId($customer_reference_id): ?int
|
||||
{
|
||||
$client = Client::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('id_number', $customer_reference_id)
|
||||
->first();
|
||||
|
||||
return $client ? $client->id : null;
|
||||
}
|
||||
|
||||
}
|
70
app/Services/Quickbooks/Transformers/ClientTransformer.php
Normal file
70
app/Services/Quickbooks/Transformers/ClientTransformer.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://clientninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Quickbooks\Transformers;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
|
||||
/**
|
||||
* Class ClientTransformer.
|
||||
*/
|
||||
class ClientTransformer extends BaseTransformer
|
||||
{
|
||||
|
||||
public function qbToNinja(mixed $qb_data)
|
||||
{
|
||||
return $this->transform($qb_data);
|
||||
}
|
||||
|
||||
public function ninjaToQb()
|
||||
{
|
||||
}
|
||||
|
||||
public function transform(mixed $data): array
|
||||
{
|
||||
|
||||
$contact = [
|
||||
'first_name' => data_get($data, 'GivenName'),
|
||||
'last_name' => data_get($data, 'FamilyName'),
|
||||
'phone' => data_get($data, 'PrimaryPhone.FreeFormNumber'),
|
||||
'email' => data_get($data, 'PrimaryEmailAddr.Address'),
|
||||
];
|
||||
|
||||
$client = [
|
||||
'name' => data_get($data,'CompanyName', ''),
|
||||
'address1' => data_get($data, 'BillAddr.Line1', ''),
|
||||
'address2' => data_get($data, 'BillAddr.Line2', ''),
|
||||
'city' => data_get($data, 'BillAddr.City', ''),
|
||||
'country_id' => $this->resolveCountry(data_get($data, 'BillAddr.Country', '')),
|
||||
'state' => data_get($data, 'BillAddr.CountrySubDivisionCode', ''),
|
||||
'postal_code' => data_get($data, 'BillAddr.PostalCode', ''),
|
||||
'shipping_address1' => data_get($data, 'ShipAddr.Line1', ''),
|
||||
'shipping_address2' => data_get($data, 'ShipAddr.Line2', ''),
|
||||
'shipping_city' => data_get($data, 'ShipAddr.City', ''),
|
||||
'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.value', ''),
|
||||
];
|
||||
|
||||
$settings = ClientSettings::defaults();
|
||||
$settings->currency_id = (string) $this->resolveCurrency(data_get($data, 'CurrencyRef.value'));
|
||||
|
||||
$new_client_merge = [
|
||||
'client_hash' => data_get($data, 'V4IDPseudonym', \Illuminate\Support\Str::random(32)),
|
||||
'settings' => $settings,
|
||||
];
|
||||
|
||||
return [$client, $contact, $new_client_merge];
|
||||
}
|
||||
|
||||
}
|
256
app/Services/Quickbooks/Transformers/InvoiceTransformer.php
Normal file
256
app/Services/Quickbooks/Transformers/InvoiceTransformer.php
Normal file
@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://clientninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Quickbooks\Transformers;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
|
||||
/**
|
||||
* Class InvoiceTransformer.
|
||||
*/
|
||||
class InvoiceTransformer extends BaseTransformer
|
||||
{
|
||||
|
||||
public function qbToNinja(mixed $qb_data)
|
||||
{
|
||||
return $this->transform($qb_data);
|
||||
}
|
||||
|
||||
public function ninjaToQb()
|
||||
{
|
||||
}
|
||||
|
||||
public function transform($qb_data)
|
||||
{
|
||||
$client_id = $this->getClientId(data_get($qb_data, 'CustomerRef.value', null));
|
||||
|
||||
return $client_id ? [
|
||||
'client_id' => $client_id,
|
||||
'number' => data_get($qb_data, 'DocNumber', false),
|
||||
'date' => data_get($qb_data, 'TxnDate', now()->format('Y-m-d')),
|
||||
'private_notes' => data_get($qb_data, 'PrivateNote', ''),
|
||||
'public_notes' => data_get($qb_data, 'CustomerMemo.value', false),
|
||||
'due_date' => data_get($qb_data, 'DueDate', null),
|
||||
'po_number' => data_get($qb_data, 'PONumber', ""),
|
||||
'partial' => data_get($qb_data, 'Deposit', 0),
|
||||
'line_items' => $this->getLineItems(data_get($qb_data, 'Line', [])),
|
||||
'payment_ids' => $this->getPayments($qb_data),
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'tax_rate1' => $rate = data_get($qb_data,'TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent', 0),
|
||||
'tax_name1' => $rate > 0 ? "Sales Tax" : "",
|
||||
] : false;
|
||||
}
|
||||
|
||||
private function getPayments(mixed $qb_data)
|
||||
{
|
||||
$payments = [];
|
||||
|
||||
nlog("get payments");
|
||||
|
||||
$qb_payments = data_get($qb_data, 'LinkedTxn', false);
|
||||
|
||||
nlog($qb_payments);
|
||||
|
||||
if(!$qb_payments) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if(!is_array($qb_payments) && data_get($qb_payments, 'TxnType', false) == 'Payment'){
|
||||
nlog([data_get($qb_payments, 'TxnId.value', false)]);
|
||||
return [data_get($qb_payments, 'TxnId.value', false)];
|
||||
}
|
||||
|
||||
|
||||
foreach($qb_payments as $payment)
|
||||
{
|
||||
if(data_get($payment, 'TxnType', false) == 'Payment')
|
||||
{
|
||||
$payments[] = data_get($payment, 'TxnId.value', false);
|
||||
}
|
||||
}
|
||||
|
||||
return $payments;
|
||||
|
||||
}
|
||||
|
||||
private function getLineItems(mixed $qb_items)
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach($qb_items as $qb_item)
|
||||
{
|
||||
$item = new InvoiceItem;
|
||||
$item->product_key = data_get($qb_item, 'SalesItemLineDetail.ItemRef.name', '');
|
||||
$item->notes = data_get($qb_item,'Description', '');
|
||||
$item->quantity = data_get($qb_item,'SalesItemLineDetail.Qty', 0);
|
||||
$item->cost = data_get($qb_item, 'SalesItemLineDetail.UnitPrice', 0);
|
||||
$item->discount = data_get($item,'DiscountRate', data_get($qb_item,'DiscountAmount', 0));
|
||||
$item->is_amount_discount = data_get($qb_item,'DiscountAmount', 0) > 0 ? true : false;
|
||||
$item->type_id = stripos(data_get($qb_item, 'ItemAccountRef.name'), 'Service') !== false ? '2' : '1';
|
||||
$item->tax_id = data_get($qb_item, 'TaxCodeRef.value', '') == 'NON' ? Product::PRODUCT_TYPE_EXEMPT : $item->type_id;
|
||||
$item->tax_rate1 = data_get($qb_item,'TaxLineDetail.TaxRateRef.TaxPercent', 0);
|
||||
$item->tax_name1 = $item->tax_rate1 > 0 ? "Sales Tax" : "";
|
||||
$items[] = (object)$item;
|
||||
}
|
||||
|
||||
nlog($items);
|
||||
|
||||
return $items;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// public function getTotalAmt($data)
|
||||
// {
|
||||
// return (float) $this->getString($data, 'TotalAmt');
|
||||
// }
|
||||
|
||||
// public function getLine($data)
|
||||
// {
|
||||
// return array_map(function ($item) {
|
||||
// return [
|
||||
// 'description' => $this->getString($item, 'Description'),
|
||||
// 'product_key' => $this->getString($item, 'Description'),
|
||||
// 'quantity' => (int) $this->getString($item, 'SalesItemLineDetail.Qty'),
|
||||
// 'unit_price' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
// 'line_total' => (float) $this->getString($item, 'Amount'),
|
||||
// 'cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
// 'product_cost' => (float) $this->getString($item, 'SalesItemLineDetail.UnitPrice'),
|
||||
// 'tax_amount' => (float) $this->getString($item, 'TxnTaxDetail.TotalTax'),
|
||||
// ];
|
||||
// }, array_filter($this->getString($data, 'Line'), function ($item) {
|
||||
// return $this->getString($item, 'DetailType') !== 'SubTotalLineDetail';
|
||||
// }));
|
||||
// }
|
||||
|
||||
// public function getInvoiceClient($data, $field = null)
|
||||
// {
|
||||
// /**
|
||||
// * "CustomerRef": {
|
||||
// "value": "23",
|
||||
// "name": ""Barnett Design
|
||||
// },
|
||||
// "CustomerMemo": {
|
||||
// "value": "Thank you for your business and have a great day!"
|
||||
// },
|
||||
// "BillAddr": {
|
||||
// "Id": "58",
|
||||
// "Line1": "Shara Barnett",
|
||||
// "Line2": "Barnett Design",
|
||||
// "Line3": "19 Main St.",
|
||||
// "Line4": "Middlefield, CA 94303",
|
||||
// "Lat": "37.4530553",
|
||||
// "Long": "-122.1178261"
|
||||
// },
|
||||
// "ShipAddr": {
|
||||
// "Id": "24",
|
||||
// "Line1": "19 Main St.",
|
||||
// "City": "Middlefield",
|
||||
// "CountrySubDivisionCode": "CA",
|
||||
// "PostalCode": "94303",
|
||||
// "Lat": "37.445013",
|
||||
// "Long": "-122.1391443"
|
||||
// },"BillEmail": {
|
||||
// "Address": "Design@intuit.com"
|
||||
// },
|
||||
// [
|
||||
// 'name' => 'CompanyName',
|
||||
// 'phone' => 'PrimaryPhone.FreeFormNumber',
|
||||
// 'country_id' => 'BillAddr.Country',
|
||||
// 'state' => 'BillAddr.CountrySubDivisionCode',
|
||||
// 'address1' => 'BillAddr.Line1',
|
||||
// 'city' => 'BillAddr.City',
|
||||
// 'postal_code' => 'BillAddr.PostalCode',
|
||||
// 'shipping_country_id' => 'ShipAddr.Country',
|
||||
// 'shipping_state' => 'ShipAddr.CountrySubDivisionCode',
|
||||
// 'shipping_address1' => 'ShipAddr.Line1',
|
||||
// 'shipping_city' => 'ShipAddr.City',
|
||||
// 'shipping_postal_code' => 'ShipAddr.PostalCode',
|
||||
// 'public_notes' => 'Notes'
|
||||
// ];
|
||||
|
||||
// */
|
||||
// $bill_address = (object) $this->getString($data, 'BillAddr');
|
||||
// $ship_address = $this->getString($data, 'ShipAddr');
|
||||
// $customer = explode(" ", $this->getString($data, 'CustomerRef.name'));
|
||||
// $customer = ['GivenName' => $customer[0], 'FamilyName' => $customer[1]];
|
||||
// $has_company = property_exists($bill_address, 'Line4');
|
||||
// $address = $has_company ? $bill_address->Line4 : $bill_address->Line3;
|
||||
// $address_1 = substr($address, 0, stripos($address, ','));
|
||||
// $address = array_filter([$address_1] + (explode(' ', substr($address, stripos($address, ",") + 1))));
|
||||
// $client_id = null;
|
||||
// $client =
|
||||
// [
|
||||
// "CompanyName" => $has_company ? $bill_address->Line2 : $bill_address->Line1,
|
||||
// "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address, 3, 'N/A')) + ['Line1' => $has_company ? $bill_address->Line3 : $bill_address->Line2 ],
|
||||
// "ShipAddr" => $ship_address
|
||||
// ] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]];
|
||||
// if($this->hasClient($client['CompanyName'])) {
|
||||
// $client_id = $this->getClient($client['CompanyName'], $this->getString($client, 'PrimaryEmailAddr.Address'));
|
||||
// }
|
||||
|
||||
|
||||
// return ['client' => (new ClientTransformer($this->company))->transform($client), 'client_id' => $client_id ];
|
||||
// }
|
||||
|
||||
// public function getDueDate($data)
|
||||
// {
|
||||
// return $this->parseDateOrNull($data, 'DueDate');
|
||||
// }
|
||||
|
||||
// public function getDeposit($data)
|
||||
// {
|
||||
// return (float) $this->getString($data, 'Deposit');
|
||||
// }
|
||||
|
||||
// public function getBalance($data)
|
||||
// {
|
||||
// return (float) $this->getString($data, 'Balance');
|
||||
// }
|
||||
|
||||
// public function getCustomerMemo($data)
|
||||
// {
|
||||
// return $this->getString($data, 'CustomerMemo.value');
|
||||
// }
|
||||
|
||||
// public function getDocNumber($data, $field = null)
|
||||
// {
|
||||
// return sprintf(
|
||||
// "%s-%s",
|
||||
// $this->getString($data, 'DocNumber'),
|
||||
// $this->getString($data, 'Id.value')
|
||||
// );
|
||||
// }
|
||||
|
||||
// public function getLinkedTxn($data)
|
||||
// {
|
||||
// $payments = $this->getString($data, 'LinkedTxn');
|
||||
// if(empty($payments)) {
|
||||
// return [];
|
||||
// }
|
||||
|
||||
// return [[
|
||||
// 'amount' => $this->getTotalAmt($data),
|
||||
// 'date' => $this->parseDateOrNull($data, 'TxnDate')
|
||||
// ]];
|
||||
|
||||
// }
|
||||
}
|
85
app/Services/Quickbooks/Transformers/PaymentTransformer.php
Normal file
85
app/Services/Quickbooks/Transformers/PaymentTransformer.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://Paymentninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Quickbooks\Transformers;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Payment;
|
||||
use App\Factory\PaymentFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class PaymentTransformer.
|
||||
*/
|
||||
class PaymentTransformer extends BaseTransformer
|
||||
{
|
||||
|
||||
public function qbToNinja(mixed $qb_data)
|
||||
{
|
||||
return $this->transform($qb_data);
|
||||
}
|
||||
|
||||
public function ninjaToQb()
|
||||
{
|
||||
}
|
||||
|
||||
public function transform(mixed $qb_data)
|
||||
{
|
||||
|
||||
return [
|
||||
'date' => data_get($qb_data, 'TxnDate', now()->format('Y-m-d')),
|
||||
'amount' => floatval(data_get($qb_data, 'TotalAmt', 0)),
|
||||
'applied' => data_get($qb_data, 'TotalAmt', 0) - data_get($qb_data, 'UnappliedAmt', 0),
|
||||
'number' => data_get($qb_data, 'DocNumber', null),
|
||||
'private_notes' => data_get($qb_data, 'PrivateNote', null),
|
||||
'currency_id' => (string) $this->resolveCurrency(data_get($qb_data, 'CurrencyRef.value')),
|
||||
'client_id' => $this->getClientId(data_get($qb_data, 'CustomerRef.value', null)),
|
||||
];
|
||||
}
|
||||
|
||||
public function buildPayment($qb_data): ?Payment
|
||||
{
|
||||
$ninja_payment_data = $this->transform($qb_data);
|
||||
|
||||
if($ninja_payment_data['client_id'])
|
||||
{
|
||||
$payment = PaymentFactory::create($this->company->id, $this->company->owner()->id,$ninja_payment_data['client_id']);
|
||||
$payment->amount = $ninja_payment_data['amount'];
|
||||
$payment->applied = $ninja_payment_data['applied'];
|
||||
$payment->status_id = 4;
|
||||
$payment->fill($ninja_payment_data);
|
||||
|
||||
$payment->client->service()->updatePaidToDate($payment->amount);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getLine($data, $field = null)
|
||||
{
|
||||
$invoices = [];
|
||||
$invoice = $this->getString($data, 'Line.LinkedTxn.TxnType');
|
||||
if(is_null($invoice) || $invoice !== 'Invoice') {
|
||||
return $invoices;
|
||||
}
|
||||
if(is_null(($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value'))))) {
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
return [[
|
||||
'amount' => (float) $this->getString($data, 'Line.Amount'),
|
||||
'invoice_id' => $invoice_id
|
||||
]];
|
||||
}
|
||||
|
||||
}
|
46
app/Services/Quickbooks/Transformers/ProductTransformer.php
Normal file
46
app/Services/Quickbooks/Transformers/ProductTransformer.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://clientninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Quickbooks\Transformers;
|
||||
|
||||
/**
|
||||
* Class ProductTransformer.
|
||||
*/
|
||||
class ProductTransformer extends BaseTransformer
|
||||
{
|
||||
|
||||
public function qbToNinja(mixed $qb_data)
|
||||
{
|
||||
return $this->transform($qb_data);
|
||||
}
|
||||
|
||||
public function ninjaToQb()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function transform(mixed $data): array
|
||||
{
|
||||
nlog(data_get($data, 'Id', null));
|
||||
|
||||
return [
|
||||
'hash' => data_get($data, 'Id.value', null),
|
||||
'product_key' => data_get($data, 'Name', data_get($data, 'FullyQualifiedName','')),
|
||||
'notes' => data_get($data, 'Description', ''),
|
||||
'cost' => data_get($data, 'PurchaseCost', 0),
|
||||
'price' => data_get($data, 'UnitPrice', 0),
|
||||
'in_stock_quantity' => data_get($data, 'QtyOnHand', 0),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -122,15 +122,11 @@ return [
|
||||
'secret' => env('CHORUS_SECRET', false),
|
||||
],
|
||||
'quickbooks' => [
|
||||
// 'auth_mode' => 'oauth2',
|
||||
'client_id' => env('QUICKBOOKS_CLIENT_ID', false),
|
||||
'client_secret' => env('QUICKBOOKS_CLIENT_SECRET', false),
|
||||
// 'ClientID' => env('QUICKBOOKS_CLIENT_ID', false),
|
||||
// 'ClientSecret' => env('QUICKBOOKS_CLIENT_SECRET', false),
|
||||
// TODO use env('QUICKBOOKS_REDIRECT_URI') or route()/ url()
|
||||
// 'RedirectURI' => url("/quickbooks/authorized"),
|
||||
// 'scope' => "com.intuit.quickbooks.accounting",
|
||||
// 'baseUrl' => ucfirst(env('APP_URL'))
|
||||
'debug' => env('APP_DEBUG',false)
|
||||
],
|
||||
];
|
||||
'quickbooks_webhook' => [
|
||||
'verifier_token' => env('QUICKBOOKS_VERIFIER_TOKEN', false),
|
||||
],
|
||||
];
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table){
|
||||
$table->string('hash')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
109
public/build/assets/app-234e3402.js
vendored
Normal file
109
public/build/assets/app-234e3402.js
vendored
Normal file
File diff suppressed because one or more lines are too long
109
public/build/assets/app-e0713224.js
vendored
109
public/build/assets/app-e0713224.js
vendored
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-e0713224.js",
|
||||
"file": "assets/app-234e3402.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace Tests\Feature\Http\Controllers;
|
||||
|
||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
|
||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDK;
|
||||
use App\Services\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
use App\Services\Quickbooks\Service as QuickbooksService;
|
||||
use App\Services\Quickbooks\SdkWrapper as QuickbooksSDK;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
|
@ -30,110 +30,12 @@ class QuickbooksTest extends TestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->markTestSkipped("NO BUENO");
|
||||
$this->withoutMiddleware(ThrottleRequests::class);
|
||||
config(['database.default' => config('ninja.db.default')]);
|
||||
$this->makeTestData();
|
||||
//
|
||||
$this->withoutExceptionHandling();
|
||||
Auth::setUser($this->user);
|
||||
|
||||
$this->markTestSkipped('no bueno');
|
||||
|
||||
}
|
||||
|
||||
public function testImportCallsGetDataOnceForClient()
|
||||
public function testCustomerSync()
|
||||
{
|
||||
$data = (json_decode(file_get_contents(base_path('tests/Feature/Import/customers.json')), true))['Customer'];
|
||||
$hash = Str::random(32);
|
||||
Cache::put($hash.'-client', base64_encode(json_encode($data)), 360);
|
||||
|
||||
$quickbooks = Mockery::mock(Quickbooks::class, [[
|
||||
'hash' => $hash,
|
||||
'column_map' => ['client' => ['mapping' => []]],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'quickbooks',
|
||||
], $this->company ])->makePartial();
|
||||
$quickbooks->shouldReceive('getData')
|
||||
->once()
|
||||
->with('client')
|
||||
->andReturn($data);
|
||||
|
||||
// Mocking the dependencies used within the client method
|
||||
|
||||
$quickbooks->import('client');
|
||||
|
||||
$this->assertArrayHasKey('clients', $quickbooks->entity_count);
|
||||
$this->assertGreaterThan(0, $quickbooks->entity_count['clients']);
|
||||
|
||||
$base_transformer = new BaseTransformer($this->company);
|
||||
$this->assertTrue($base_transformer->hasClient('Sonnenschein Family Store'));
|
||||
$contact = $base_transformer->getClient('Amy\'s Bird Sanctuary', '');
|
||||
$contact = Client::where('name', 'Amy\'s Bird Sanctuary')->first();
|
||||
$this->assertEquals('(650) 555-3311', $contact->phone);
|
||||
$this->assertEquals('Birds@Intuit.com', $contact->contacts()->first()->email);
|
||||
}
|
||||
|
||||
public function testImportCallsGetDataOnceForProducts()
|
||||
{
|
||||
$data = (json_decode(file_get_contents(base_path('tests/Feature/Import/items.json')), true))['Item'];
|
||||
$hash = Str::random(32);
|
||||
Cache::put($hash.'-item', base64_encode(json_encode($data)), 360);
|
||||
|
||||
$quickbooks = Mockery::mock(Quickbooks::class, [[
|
||||
'hash' => $hash,
|
||||
'column_map' => ['item' => ['mapping' => []]],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'quickbooks',
|
||||
], $this->company ])->makePartial();
|
||||
$quickbooks->shouldReceive('getData')
|
||||
->once()
|
||||
->with('product')
|
||||
->andReturn($data);
|
||||
|
||||
// Mocking the dependencies used within the client method
|
||||
|
||||
$quickbooks->import('product');
|
||||
|
||||
$this->assertArrayHasKey('products', $quickbooks->entity_count);
|
||||
$this->assertGreaterThan(0, $quickbooks->entity_count['products']);
|
||||
|
||||
$base_transformer = new BaseTransformer($this->company);
|
||||
$this->assertTrue($base_transformer->hasProduct('Gardening'));
|
||||
$product = Product::where('product_key', 'Pest Control')->first();
|
||||
$this->assertGreaterThanOrEqual(35, $product->price);
|
||||
$this->assertLessThanOrEqual(0, $product->quantity);
|
||||
}
|
||||
|
||||
public function testImportCallsGetDataOnceForInvoices()
|
||||
{
|
||||
$data = (json_decode(file_get_contents(base_path('tests/Feature/Import/invoices.json')), true))['Invoice'];
|
||||
$hash = Str::random(32);
|
||||
Cache::put($hash.'-invoice', base64_encode(json_encode($data)), 360);
|
||||
$quickbooks = Mockery::mock(Quickbooks::class, [[
|
||||
'hash' => $hash,
|
||||
'column_map' => ['invoice' => ['mapping' => []]],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'quickbooks',
|
||||
], $this->company ])->makePartial();
|
||||
$quickbooks->shouldReceive('getData')
|
||||
->once()
|
||||
->with('invoice')
|
||||
->andReturn($data);
|
||||
$quickbooks->import('invoice');
|
||||
$this->assertArrayHasKey('invoices', $quickbooks->entity_count);
|
||||
$this->assertGreaterThan(0, $quickbooks->entity_count['invoices']);
|
||||
$base_transformer = new BaseTransformer($this->company);
|
||||
$this->assertTrue($base_transformer->hasInvoice(1007));
|
||||
$invoice = Invoice::where('number', 1012)->first();
|
||||
$data = collect($data)->where('DocNumber', '1012')->first();
|
||||
$this->assertGreaterThanOrEqual($data['TotalAmt'], $invoice->amount);
|
||||
$this->assertEquals(count($data['Line']) - 1, count((array)$invoice->line_items));
|
||||
}
|
||||
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
$data = (json_decode(file_get_contents(base_path('tests/Feature/Import/Quickbooks/customers.json')), false));
|
||||
}
|
||||
}
|
||||
|
11675
tests/Feature/Import/Quickbooks/customer.json
Normal file
11675
tests/Feature/Import/Quickbooks/customer.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ class QuickbooksIngestTest extends TestCase
|
||||
parent::setUp();
|
||||
|
||||
config(['database.default' => config('ninja.db.default')]);
|
||||
$this->markTestSkipped('no bueno');
|
||||
$this->makeTestData();
|
||||
$this->withoutExceptionHandling();
|
||||
Auth::setUser($this->user);
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace Tests\Integration\Services\Import\Quickbooks;
|
||||
|
||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
|
||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDK;
|
||||
use App\Services\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
use App\Services\Quickbooks\Service as QuickbooksService;
|
||||
use App\Services\Quickbooks\SdkWrapper as QuickbooksSDK;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Tests\TestCase;
|
||||
|
@ -7,8 +7,8 @@ namespace Tests\Unit\Services\Import\Quickbooks;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface;
|
||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbookSDK;
|
||||
use App\Services\Quickbooks\Contracts\SdkInterface;
|
||||
use App\Services\Quickbooks\SdkWrapper as QuickbookSDK;
|
||||
|
||||
class SdkWrapperTest extends TestCase
|
||||
{
|
||||
|
@ -5,8 +5,8 @@ namespace Tests\Unit\Services\Import\Quickbooks;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
|
||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
use App\Services\Quickbooks\Service as QuickbooksService;
|
||||
use App\Services\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
|
||||
class ServiceTest extends TestCase
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user