mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 03:07:01 +00:00
Transform company
This commit is contained in:
@@ -20,15 +20,23 @@ use App\Services\Quickbooks\QuickbooksService;
|
|||||||
|
|
||||||
class ImportQuickbooksController extends BaseController
|
class ImportQuickbooksController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* authorizeQuickbooks
|
||||||
*
|
*
|
||||||
|
* Starts the Quickbooks authorization process.
|
||||||
|
*
|
||||||
|
* @param mixed $request
|
||||||
|
* @param string $token
|
||||||
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
|
public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
|
||||||
{
|
{
|
||||||
|
|
||||||
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
|
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
|
||||||
|
|
||||||
$company = $request->getCompany();
|
$company = $request->getCompany();
|
||||||
|
|
||||||
$qb = new QuickbooksService($company);
|
$qb = new QuickbooksService($company);
|
||||||
|
|
||||||
$authorizationUrl = $qb->sdk()->getAuthorizationUrl();
|
$authorizationUrl = $qb->sdk()->getAuthorizationUrl();
|
||||||
@@ -39,18 +47,45 @@ class ImportQuickbooksController extends BaseController
|
|||||||
|
|
||||||
return redirect()->to($authorizationUrl);
|
return redirect()->to($authorizationUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onAuthorized
|
||||||
|
*
|
||||||
|
* Handles the callback from Quickbooks after authorization.
|
||||||
|
*
|
||||||
|
* @param AuthorizedQuickbooksRequest $request
|
||||||
|
* @return RedirectResponse
|
||||||
|
*/
|
||||||
public function onAuthorized(AuthorizedQuickbooksRequest $request)
|
public function onAuthorized(AuthorizedQuickbooksRequest $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
nlog($request->all());
|
||||||
|
|
||||||
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
|
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
|
||||||
$company = $request->getCompany();
|
$company = $request->getCompany();
|
||||||
|
|
||||||
$qb = new QuickbooksService($company);
|
$qb = new QuickbooksService($company);
|
||||||
|
|
||||||
$realm = $request->query('realmId');
|
$realm = $request->query('realmId');
|
||||||
|
|
||||||
|
nlog($realm);
|
||||||
|
|
||||||
$access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm);
|
$access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm);
|
||||||
|
|
||||||
|
nlog($access_token_object);
|
||||||
|
|
||||||
$qb->sdk()->saveOAuthToken($access_token_object);
|
$qb->sdk()->saveOAuthToken($access_token_object);
|
||||||
|
|
||||||
|
// Refresh the service to initialize SDK with the new access token
|
||||||
|
$qb->refresh();
|
||||||
|
|
||||||
|
$companyInfo = $qb->sdk()->company();
|
||||||
|
|
||||||
|
$company->quickbooks->companyName = $companyInfo->CompanyName;
|
||||||
|
$company->save();
|
||||||
|
|
||||||
|
nlog($companyInfo);
|
||||||
|
|
||||||
return redirect(config('ninja.react_url'));
|
return redirect(config('ninja.react_url'));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
use App\Libraries\MultiDB;
|
use App\Libraries\MultiDB;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
use App\Services\Quickbooks\QuickbooksService;
|
||||||
|
use App\Http\Requests\Quickbooks\SyncQuickbooksRequest;
|
||||||
use App\Http\Requests\Quickbooks\ConfigQuickbooksRequest;
|
use App\Http\Requests\Quickbooks\ConfigQuickbooksRequest;
|
||||||
use App\Http\Requests\Quickbooks\DisconnectQuickbooksRequest;
|
use App\Http\Requests\Quickbooks\DisconnectQuickbooksRequest;
|
||||||
use App\Http\Requests\Quickbooks\SyncQuickbooksRequest;
|
|
||||||
|
|
||||||
class QuickbooksController extends BaseController
|
class QuickbooksController extends BaseController
|
||||||
{
|
{
|
||||||
@@ -53,7 +54,9 @@ class QuickbooksController extends BaseController
|
|||||||
$company = $user->company();
|
$company = $user->company();
|
||||||
|
|
||||||
$qb = new QuickbooksService($company);
|
$qb = new QuickbooksService($company);
|
||||||
$qb->sdk()->revokeAccessToken();
|
$rs = $qb->sdk()->revokeAccessToken();
|
||||||
|
|
||||||
|
nlog($rs);
|
||||||
|
|
||||||
$company->quickbooks = null;
|
$company->quickbooks = null;
|
||||||
$company->save();
|
$company->save();
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class QuickbooksService
|
|||||||
'ClientSecret' => config('services.quickbooks.client_secret'),
|
'ClientSecret' => config('services.quickbooks.client_secret'),
|
||||||
'auth_mode' => 'oauth2',
|
'auth_mode' => 'oauth2',
|
||||||
'scope' => "com.intuit.quickbooks.accounting",
|
'scope' => "com.intuit.quickbooks.accounting",
|
||||||
'RedirectURI' => $this->testMode ? 'https://grok.romulus.com.au/quickbooks/authorized' : 'https://invoicing.co/quickbooks/authorized',
|
'RedirectURI' => $this->testMode ? 'https://qb.romulus.com.au/quickbooks/authorized' : 'https://invoicing.co/quickbooks/authorized',
|
||||||
'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL,
|
'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -134,6 +134,24 @@ class QuickbooksService
|
|||||||
// return $this;
|
// return $this;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the service after OAuth token has been updated.
|
||||||
|
* This reloads the company from the database and reinitializes the SDK
|
||||||
|
* with the new access token.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function refresh(): self
|
||||||
|
{
|
||||||
|
// Reload company from database to get fresh token data
|
||||||
|
$this->company = $this->company->fresh();
|
||||||
|
|
||||||
|
// Reinitialize the SDK with the updated token
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
private function checkToken(): self
|
private function checkToken(): self
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ class SdkWrapper
|
|||||||
return $this->accessToken()->getRefreshToken();
|
return $this->accessToken()->getRefreshToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function revokeAccessToken()
|
||||||
|
{
|
||||||
|
return $this->sdk->getOAuth2LoginHelper()->revokeToken($this->accessToken()->getAccessToken());
|
||||||
|
}
|
||||||
|
|
||||||
public function company()
|
public function company()
|
||||||
{
|
{
|
||||||
return $this->sdk->getCompanyInfo();
|
return $this->sdk->getCompanyInfo();
|
||||||
|
|||||||
@@ -50,6 +50,21 @@ class BaseTransformer
|
|||||||
return $currency ? (string) $currency->id : $this->company->settings->currency_id;
|
return $currency ? (string) $currency->id : $this->company->settings->currency_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function resolveTimezone(?string $timezone_name): string
|
||||||
|
{
|
||||||
|
if (empty($timezone_name)) {
|
||||||
|
return (string) $this->company->settings->timezone_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \App\Models\Timezone $timezone */
|
||||||
|
$timezone = app('timezones')->first(function ($t) use ($timezone_name) {
|
||||||
|
/** @var \App\Models\Timezone $t */
|
||||||
|
return $t->name === $timezone_name;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $timezone ? (string) $timezone->id : (string) $this->company->settings->timezone_id;
|
||||||
|
}
|
||||||
|
|
||||||
public function getShipAddrCountry($data, $field)
|
public function getShipAddrCountry($data, $field)
|
||||||
{
|
{
|
||||||
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
return is_null(($c = $this->getString($data, $field))) ? null : $this->getCountryId($c);
|
||||||
|
|||||||
99
app/Services/Quickbooks/Transformers/CompanyTransformer.php
Normal file
99
app/Services/Quickbooks/Transformers/CompanyTransformer.php
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?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\Services\Quickbooks\Transformers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms QuickBooks IPPCompanyInfo into Invoice Ninja company data.
|
||||||
|
*
|
||||||
|
* QB fields: CompanyName, LegalName, CompanyAddr, LegalAddr, CustomerCommunicationAddr,
|
||||||
|
* Email, CustomerCommunicationEmailAddr, PrimaryPhone, WebAddr, CompanyURL,
|
||||||
|
* Country, DefaultTimeZone.
|
||||||
|
*/
|
||||||
|
class CompanyTransformer extends BaseTransformer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform QuickBooks company info to Ninja structure.
|
||||||
|
*
|
||||||
|
* @param mixed $qb_data QuickBooksOnline\API\Data\IPPCompanyInfo (or array)
|
||||||
|
* @return array{quickbooks: array, settings: array}
|
||||||
|
*/
|
||||||
|
public function qbToNinja(mixed $qb_data): array
|
||||||
|
{
|
||||||
|
return $this->transform($qb_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ninjaToQb(): void
|
||||||
|
{
|
||||||
|
// Reserved for Ninja → QB sync when needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data IPPCompanyInfo object or array
|
||||||
|
* @return array{quickbooks: array<string, mixed>, settings: array<string, mixed>}
|
||||||
|
*/
|
||||||
|
public function transform(mixed $data): array
|
||||||
|
{
|
||||||
|
$addr = $this->pickAddress($data);
|
||||||
|
$country_raw = data_get($addr, 'Country') ?? data_get($addr, 'CountryCode') ?? data_get($data, 'Country');
|
||||||
|
$country_id = $this->resolveCountry($country_raw);
|
||||||
|
|
||||||
|
$quickbooks = [
|
||||||
|
'companyName' => data_get($data, 'CompanyName', '') ?: data_get($data, 'LegalName', ''),
|
||||||
|
];
|
||||||
|
|
||||||
|
$settings = [
|
||||||
|
'address1' => data_get($addr, 'Line1', ''),
|
||||||
|
'address2' => data_get($addr, 'Line2', ''),
|
||||||
|
'city' => data_get($addr, 'City', ''),
|
||||||
|
'state' => data_get($addr, 'CountrySubDivisionCode', ''),
|
||||||
|
'postal_code' => data_get($addr, 'PostalCode', ''),
|
||||||
|
'country_id' => $country_id,
|
||||||
|
'phone' => $this->pickPhone($data),
|
||||||
|
'email' => $this->pickEmail($data),
|
||||||
|
'website' => data_get($data, 'WebAddr', '') ?: data_get($data, 'CompanyURL', ''),
|
||||||
|
'timezone_id' => $this->resolveTimezone(data_get($data, 'DefaultTimeZone')),
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'quickbooks' => $quickbooks,
|
||||||
|
'settings' => $settings,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer CompanyAddr, then LegalAddr, then CustomerCommunicationAddr.
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
* @return object|array|null
|
||||||
|
*/
|
||||||
|
private function pickAddress(mixed $data)
|
||||||
|
{
|
||||||
|
$addr = data_get($data, 'CompanyAddr') ?? data_get($data, 'LegalAddr') ?? data_get($data, 'CustomerCommunicationAddr');
|
||||||
|
|
||||||
|
return is_object($addr) ? $addr : (is_array($addr) ? $addr : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pickPhone(mixed $data): string
|
||||||
|
{
|
||||||
|
$phone = data_get($data, 'PrimaryPhone.FreeFormNumber');
|
||||||
|
|
||||||
|
return is_string($phone) ? $phone : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pickEmail(mixed $data): string
|
||||||
|
{
|
||||||
|
$email = data_get($data, 'Email.Address') ?? data_get($data, 'CustomerCommunicationEmailAddr.Address') ?? data_get($data, 'CompanyEmailAddr');
|
||||||
|
|
||||||
|
return is_string($email) ? $email : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,6 +152,7 @@ return [
|
|||||||
'quickbooks_webhook' => [
|
'quickbooks_webhook' => [
|
||||||
'verifier_token' => env('QUICKBOOKS_VERIFIER_TOKEN', false),
|
'verifier_token' => env('QUICKBOOKS_VERIFIER_TOKEN', false),
|
||||||
],
|
],
|
||||||
|
|
||||||
'verifactu' => [
|
'verifactu' => [
|
||||||
'sender_nif' => env('VERIFACTU_SENDER_NIF', ''),
|
'sender_nif' => env('VERIFACTU_SENDER_NIF', ''),
|
||||||
'certificate' => env('VERIFACTU_CERTIFICATE', ''),
|
'certificate' => env('VERIFACTU_CERTIFICATE', ''),
|
||||||
@@ -159,6 +160,14 @@ return [
|
|||||||
'sender_name' => env('VERIFACTU_SENDER_NAME', 'CERTIFICADO FISICA PRUEBAS'),
|
'sender_name' => env('VERIFACTU_SENDER_NAME', 'CERTIFICADO FISICA PRUEBAS'),
|
||||||
'test_mode' => env('VERIFACTU_TEST_MODE', false),
|
'test_mode' => env('VERIFACTU_TEST_MODE', false),
|
||||||
],
|
],
|
||||||
|
'quickbooks' => [
|
||||||
|
'client_id' => env('QUICKBOOKS_CLIENT_ID', false),
|
||||||
|
'client_secret' => env('QUICKBOOKS_CLIENT_SECRET', false),
|
||||||
|
'redirect' => env('QUICKBOOKS_REDIRECT_URI'),
|
||||||
|
'test_redirect' => env('QUICKBOOKS_TEST_REDIRECT_URI'),
|
||||||
|
'env' => env('QUICKBOOKS_ENV', 'sandbox'),
|
||||||
|
'debug' => env('APP_DEBUG',false)
|
||||||
|
],
|
||||||
'cloudflare' => [
|
'cloudflare' => [
|
||||||
'zone_id' => env('CLOUDFLARE_SAAS_ZONE_ID', false),
|
'zone_id' => env('CLOUDFLARE_SAAS_ZONE_ID', false),
|
||||||
'api_token' => env('CLOUDFLARE_SAAS_API_TOKEN', false),
|
'api_token' => env('CLOUDFLARE_SAAS_API_TOKEN', false),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ use App\Http\Controllers\SystemLogController;
|
|||||||
use App\Http\Controllers\TwoFactorController;
|
use App\Http\Controllers\TwoFactorController;
|
||||||
use App\Http\Controllers\Auth\LoginController;
|
use App\Http\Controllers\Auth\LoginController;
|
||||||
use App\Http\Controllers\ImportJsonController;
|
use App\Http\Controllers\ImportJsonController;
|
||||||
|
use App\Http\Controllers\QuickbooksController;
|
||||||
use App\Http\Controllers\SelfUpdateController;
|
use App\Http\Controllers\SelfUpdateController;
|
||||||
use App\Http\Controllers\TaskStatusController;
|
use App\Http\Controllers\TaskStatusController;
|
||||||
use App\Http\Controllers\Bank\YodleeController;
|
use App\Http\Controllers\Bank\YodleeController;
|
||||||
@@ -336,8 +337,9 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local
|
|||||||
Route::get('quote/{invitation_key}/download', [QuoteController::class, 'downloadPdf'])->name('quotes.downloadPdf');
|
Route::get('quote/{invitation_key}/download', [QuoteController::class, 'downloadPdf'])->name('quotes.downloadPdf');
|
||||||
Route::get('quote/{invitation_key}/download_e_quote', [QuoteController::class, 'downloadEQuote'])->name('quotes.downloadEQuote');
|
Route::get('quote/{invitation_key}/download_e_quote', [QuoteController::class, 'downloadEQuote'])->name('quotes.downloadEQuote');
|
||||||
|
|
||||||
Route::post('quickbooks/sync', [ImportQuickbooksController::class, 'sync'])->name('quickbooks.sync');
|
Route::post('quickbooks/sync', [QuickbooksController::class, 'sync'])->name('quickbooks.sync');
|
||||||
Route::post('quickbooks/configuration', [ImportQuickbooksController::class, 'configuration'])->name('quickbooks.configuration');
|
Route::post('quickbooks/configuration', [QuickbooksController::class, 'configuration'])->name('quickbooks.configuration');
|
||||||
|
Route::post('quickbooks/disconnect', [QuickbooksController::class, 'disconnect'])->name('quickbooks.disconnect');
|
||||||
|
|
||||||
Route::resource('recurring_expenses', RecurringExpenseController::class);
|
Route::resource('recurring_expenses', RecurringExpenseController::class);
|
||||||
Route::post('recurring_expenses/bulk', [RecurringExpenseController::class, 'bulk'])->name('recurring_expenses.bulk');
|
Route::post('recurring_expenses/bulk', [RecurringExpenseController::class, 'bulk'])->name('recurring_expenses.bulk');
|
||||||
|
|||||||
Reference in New Issue
Block a user